Unnamed repository; edit this file 'description' to name the repository.
41 files changed, 2454 insertions, 1182 deletions
@@ -9,80 +9,92 @@ fimg = { git = "https://git.bendn.org/fimg" } pattypan = { git = "https://git.bendn.org/pattypan" } implicit-fn = "0.1.0" ropey = "1.6.1" -softbuffer = "0.4.6" -swash = "0.2.5" -winit = "0.31.0-beta.2" +softbuffer = { version = "0.4.8", default-features = false, features = ["wayland", "x11"] } +swash = "0.2.10" +winit = { version = "0.31.0-beta.2", default-features = false, features = ["wayland", "x11"] } -tree-sitter = "0.25.0" -memchr = "2.7.5" -lower = "0.2.0" +tree-sitter = "0.26.12" +memchr = "2.8.3" +lower = "0.2.1" amap = "0.1.4" run_times = "0.1.0" array_chunks = "1.0.0" rust-fsm = { git = "https://git.bendn.org/rust-fsm", features = ["diagram"] } -clipp = "0.1.0" -regex = { version = "1.11.3", features = ["unstable", "use_std"] } -tree-house = { version = "0.3.0", features = ["fixtures"] } +clipp = "0.1.1" +regex = { version = "1.13.1", features = ["unstable", "use_std"] } +tree-house = { version = "0.4.0", features = ["fixtures"] } # for tree house grammar loading helix-loader = { git = "https://git.bendn.org/helix" } helix-core = { git = "https://git.bendn.org/helix" } -lsp-types = { git = "https://git.bendn.org/helix", package = "helix-lsp-types", features = [ - "proposed", -] } +# lsp-types = { git = "https://git.bendn.org/helix", package = "helix-lsp-types", features = [ + # "proposed", +# ] } -lsp-server = { git = "https://git.bendn.org/rust-analyzer" } -rust-analyzer = { git = "https://git.bendn.org/rust-analyzer" } + +lsp-server = { version = "0.7.9" } +# rust-analyzer = { git = "https://git.bendn.org/rust-analyzer" } # lsp-server = { path = "../rust-analyzer/lib/lsp-server" } -# rust-analyzer = { path = "../rust-analyzer/crates/rust-analyzer" } +rust-analyzer = { path = "../rust-analyzer/crates/rust-analyzer" } -serde_json = "1.0.145" -serde = { version = "1.0.228", features = ["unstable"] } -serde_derive = "1.0.228" -log = "0.4.28" +serde_json = "1.0.151" +serde = { version = "1.0.229", features = ["unstable"] } +serde_derive = "1.0.229" +log = "0.4.33" crossbeam = { version = "0.8.4", features = ["nightly", "crossbeam-channel"] } -test-log = "0.2.18" -env_logger = "0.11.8" -url = "2.5.7" -tokio = { version = "1.47.1", features = ["rt-multi-thread", "sync", "time"] } +test-log = "0.2.21" +env_logger = "0.11.11" +url = "2.5.8" +tokio = { version = "1.53.1", features = ["rt-multi-thread", "sync", "time"] } regex-cursor = "0.1.5" -papaya = "0.2.3" +papaya = "0.2.4" markdown = "1.0.0" -itertools = "0.14.0" -pin-project = "1.1.10" +itertools = "0.15.0" +pin-project = "1.1.13" replace_with = "0.1.8" nucleo = "0.5.0" -tokio-util = { version = "0.7.17", features = ["rt"] } +tokio-util = { version = "0.7.19", features = ["rt"] } scopeguard = "1.2.0" -arc-swap = "1.7.1" -atools = "0.1.10" +arc-swap = "1.9.2" +atools = "0.1.13" swizzle = "0.1.0" walkdir = "2.5.0" -niri = { package = "niri-ipc", version = "25.11.0" } -libc = "0.2.180" -rustc-hash = "=2.1.1" -bendy = { version = "0.6.1", features = ["serde"] } -git2 = "0.20.4" +libc = "0.2.189" +rustc-hash = "2.1.3" +git2 = "0.21.0" imara-diff = "0.2.0" vecto = "0.1.1" rangemap = { version = "1.7.1", features = ["const_fn", "nightly", "serde1"] } itern = "0.1.1" -kitty-rc = { version = "0.4.2", git = "https://github.com/bend-n/kitty-rc-rs" } + smol_str = "0.3.6" -futures = "0.3.32" -rootcause = { version = "0.12.1", features = ["compat-anyhow1"] } +futures = "0.3.33" +rootcause = { version = "0.13.0", features = ["compat-anyhow1"] } ttools = { git = "https://git.bendn.org/ttools" } # ttools = { path = "../ttools/" } ftools = { git = "https://git.bendn.org/ftools" } json_value_merge = "2.0.1" annotate-snippets = "0.12.16" -toml = "1.1.2" +toml = "1.1.4" +bendncode = { git = "https://git.bendn.org/bendncode" } +rfd = "0.17.2" +lsp-types = { version = "0.11.0", package = "gen-lsp-types", features = ["url"] } +rayon = "1.12.0" +tracing-subscriber = "0.3.23" +crossbeam-channel = "0.5.16" + +[target.'cfg(unix)'.dependencies] +kitty-rc = { version = "0.4.2", git = "https://github.com/bend-n/kitty-rc-rs" } +niri = { package = "niri-ipc", version = "25.11.0" } + [profile.dev.package] rust-analyzer.opt-level = 3 fimg.opt-level = 3 nucleo.opt-level = 3 tree-house.debug-assertions = false + + [profile.release.package.tree-house] debug-assertions = false @@ -102,4 +114,6 @@ incremental = false [patch.crates-io] smol_str = { git = "https://git.bendn.org/rust-analyzer" } ttools = { git = "https://git.bendn.org/ttools" } +# lsp-server = { path = "../rust-analyzer/lib/lsp-server" } # winit = { git = "https://github.com/rust-windowing/winit" } + @@ -1,3 +1,9 @@ -# gracilaria +<h1><svg xmlns="http://www.w3.org/2000/svg" style="height: 1em; vertical-align: middle; display: inline-block" viewBox="0 0 121.876 121.876"><circle cx="60.938" cy="60.938" r="60.938" style="fill:#0a3d81;fill-rule:evenodd;stroke-width:7.14375;stroke-linecap:round;stroke-dasharray:none;paint-order:markers stroke fill;fill-opacity:1"/><path fill="none" d="M64.282 145.583c.55.043 1.1.085 3.91 2.96 2.812 2.874 7.884 8.58 12.98 12.87 5.096 4.292 10.215 7.166 13.366 5.39 3.152-1.775 4.335-8.2 8.33-8.348s10.8 5.981 17.268 5.03c6.467-.951 12.597-8.982 18.261-8.961 5.665.021 10.864 8.095 16.063 16.169" style="fill:#0f0;fill-opacity:0;fill-rule:evenodd;stroke:#e0605c;stroke-width:15.734;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" transform="translate(-54.874 -94.9)"/><path d="m88.944 95.828-.75-15.062-14.587-3.837 12.67-8.184-3.97-14.552 13.42 6.884 10.617-10.714.752 15.064 14.585 3.834-12.67 8.185 3.97 14.552-13.42-6.885z" style="fill:#ffb30c;fill-opacity:1;fill-rule:evenodd;stroke:#0cff48;stroke-width:0;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"/></svg> gracilaria</h1> -readme for testing cgit +lsp-enabled gui text editor / ide + + + +### installation steps + +`cargo build -r` diff --git a/dist/gracilaria.svg b/dist/gracilaria.svg new file mode 100644 index 0000000..a38781f --- /dev/null +++ b/dist/gracilaria.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="460.634" height="460.634" viewBox="0 0 121.876 121.876"><path d="M18.21 209.008h159.257v153.159H18.21z" style="fill:#0a3d81;fill-opacity:1;fill-rule:evenodd;stroke:#0cff48;stroke-width:0;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" transform="translate(-35.069 -216.54)"/><path fill="none" d="M64.282 145.583c.55.043 1.1.085 3.91 2.96 2.812 2.874 7.884 8.58 12.98 12.87 5.096 4.292 10.215 7.166 13.366 5.39 3.152-1.775 4.335-8.2 8.33-8.348s10.8 5.981 17.268 5.03c6.467-.951 12.597-8.982 18.261-8.961 5.665.021 10.864 8.095 16.063 16.169" style="fill:#0f0;fill-opacity:0;fill-rule:evenodd;stroke:#e0605c;stroke-width:15.734;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" transform="translate(-54.874 -94.9)"/><path d="m133.155 245.006-4.046-2.166-3.288 3.203-.148-4.588-4.418-1.246 3.9-2.421-1.13-4.449 4.047 2.166 3.287-3.203.148 4.588 4.418 1.246-3.9 2.421z" style="fill:#ffb30c;fill-opacity:1;fill-rule:evenodd;stroke:#0cff48;stroke-width:0;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" transform="rotate(58.987 890.717 143.189)scale(3.28598)"/></svg>
\ No newline at end of file diff --git a/dist/image.png b/dist/image.png Binary files differnew file mode 100644 index 0000000..e60b96b --- /dev/null +++ b/dist/image.png diff --git a/languages.toml b/languages.toml index ea92bab..61ed0fc 100644 --- a/languages.toml +++ b/languages.toml @@ -287,6 +287,8 @@ typing.triggerChars = ".=<>{(+" assist.preferSelf = true checkOnSave = true diagnostics.enable = true +diagnostics.experimental.enable = true +diagnostics.styleLints.enable = true semanticHighlighting.punctuation.separate.macroBang = true semanticHighlighting.punctuation.specialization.enable = true semanticHighlighting.punctuation.enable = true @@ -1,7 +1,8 @@ use dsb::Cell; use dsb::cell::Style; use itertools::Itertools; -use lsp_types::{CodeAction, CodeActionKind}; +use lsp_types::CodeActionKind; +use rust_analyzer::lsp::ext::CodeAction; #[derive(Debug, Clone)] pub enum N<T> { @@ -165,14 +166,15 @@ fn write(x: &CodeAction, c: usize, selected: bool, to: &mut Vec<Cell>) { vec![Cell { style: Style::new(FG, bg), ..Default::default() }; c]; let t = match &x.kind { - Some(x) if x == &CodeActionKind::QUICKFIX => '', - Some(x) - if x == &CodeActionKind::REFACTOR - || x == &CodeActionKind::REFACTOR_EXTRACT - || x == &CodeActionKind::REFACTOR_INLINE - || x == &CodeActionKind::REFACTOR_REWRITE => - '', - Some(x) if x == &CodeActionKind::SOURCE => '', + Some(CodeActionKind::QuickFix) => '', + Some( + CodeActionKind::Refactor + | CodeActionKind::RefactorExtract + | CodeActionKind::RefactorInline + | CodeActionKind::RefactorRewrite + | CodeActionKind::RefactorMove, + ) => '', + Some(CodeActionKind::Source) => '', _ => '', /* ☭ */ }; into[0].style.fg = col!("#E5C07B"); @@ -1,9 +1,11 @@ use std::iter::{chain, repeat}; +use std::sync::Arc; use dsb::Cell; use dsb::cell::Style; -use lsp_types::WorkDoneProgress; +use crate::killring::KillRM; +use crate::lsp::communication::WorkDoneProgress; use crate::lsp::{Client, Rq}; use crate::rnd::simplify_path; use crate::sym::Symbols; @@ -23,7 +25,7 @@ impl Bar { fname: &str, state: &super::State, _t: &TextArea, - lsp: Option<&Client>, + lsp: Option<Arc<Client>>, ) { let fname = simplify_path(fname); let row = &mut into[oy * w..oy * w + w]; @@ -128,6 +130,19 @@ impl Bar { } }); } + State::KillRing(KillRM { tedit, .. }) => { + "filter: " + .chars() + .zip(repeat(Style::BOLD | Style::ITALIC)) + .chain(s(&tedit.rope.to_string())) + .zip(row) + .for_each(|((x, z), y)| { + *y = Cell { + letter: Some(x), + style: Style { flags: z, ..y.style }, + } + }); + } State::RequestBoolean(x) => { x.prompt() .chars() @@ -164,6 +179,12 @@ impl Bar { x.letter = Some(c); }); } + State::GoToL(x) => format!("list of {}", x.name()) + .chars() + .zip(row) + .for_each(|(c, x)| { + x.letter = Some(c); + }), State::Save => unreachable!(), _ => {} } diff --git a/src/commands.rs b/src/commands.rs index 1273cfe..b020c22 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,7 +11,7 @@ use lsp_types::*; use rootcause::{bail, report}; use rust_analyzer::lsp::ext::*; -use crate::FG; +use crate::edi::lsp_mn::LSPM; use crate::edi::{Editor, lsp}; use crate::gotolist::{At, GoToList}; use crate::lsp::{PathURI, Rq, tdpp}; @@ -19,6 +19,7 @@ use crate::menu::charc; use crate::menu::generic::{CorA, GenericMenu, MenuData}; use crate::sym::GoTo; use crate::text::{Bookmark, RopeExt, SortTedits, TextArea, col, color_}; +use crate::{FG, KillRing}; macro_rules! repl { ($x:ty, $($with:tt)+) => { @@ -115,6 +116,10 @@ commands!( @ Incoming: "callers-of", /// Functions this function calls @ Outgoing: "calling", + /// loads up the kill ring + | KillRing: "killring", + /// Reloads the file from disk. + | Reload: "reload", // /// View child modules // @ ViewChildModules: "child-modules", /// GoTo line, @@ -129,6 +134,7 @@ commands!( pub enum Cmds {} impl MenuData for Cmds { + const NAME: &'static str = "commands"; const HEIGHT: usize = 30; type Data = (); type Element<'a> = Cmd; @@ -222,6 +228,8 @@ impl Editor { &mut self, z: Cmd, w: Arc<dyn winit::window::Window>, + kr: &mut KillRing, + lsp_mn: &mut LSPM, ) -> rootcause::Result<()> { match z { Cmd::GoTo(Some(x)) => @@ -271,7 +279,19 @@ impl Editor { ..default() }); } - z if z.needs_lsp() => return self.handle_lsp_command(z, w), + Cmd::KillRing => { + self.state = crate::edi::st::State::KillRing( + crate::killring::KillRM { + data: kr.clone(), + tedit: default(), + selection: 0, + vo: 0, + }, + ); + } + Cmd::Reload => self.reload(), + z if z.needs_lsp() => + return self.handle_lsp_command(z, w, lsp_mn), x => unimplemented!("{x:?}"), } @@ -281,6 +301,7 @@ impl Editor { &mut self, z: Cmd, w: Arc<dyn winit::window::Window>, + lsp_mn: &mut LSPM, ) -> rootcause::Result<()> { let Some((l, o)) = lsp!(self + p) else { bail!("no lsp"); @@ -291,7 +312,7 @@ impl Editor { .text .to_l_position(*self.text.cursor.first()) .unwrap(); - let mut x = l.request_immediate::<rust_analyzer::lsp::ext::MoveItem>(&MoveItemParams { + let mut x = l.request_immediate::<rust_analyzer::lsp::ext::MoveItemRequest>(&MoveItemParams { direction: if let Cmd::RAMoveIU = z { MoveItemDirection::Up } else { MoveItemDirection::Down }, text_document: o.tid(), range: Range { start : r, end : r}, @@ -303,11 +324,11 @@ impl Editor { } } Cmd::RARestart => { - _ = l.request::<ReloadWorkspace>(&())?.0; + _ = l.request::<ReloadWorkspaceRequest>(&())?.0; } Cmd::RAParent => { - let Some(GotoDefinitionResponse::Link([ref x])) = - l.request_immediate::<ParentModule>( + let Some(DefinitionResponse::DefinitionLinkList([ref x])) = + l.request_immediate::<ParentModuleRequest>( &TextDocumentPositionParams { text_document: o.tid(), position: self @@ -320,11 +341,11 @@ impl Editor { self.bar.last_action = "no such parent".into(); return Ok(()); }; - self.go(x, w)?; + self.go(x, w, lsp_mn)?; } Cmd::RAJoinLines => { - let teds = - l.request_immediate::<JoinLines>(&JoinLinesParams { + let teds = l.request_immediate::<JoinLinesRequest>( + &JoinLinesParams { ranges: self .text .cursor @@ -336,13 +357,14 @@ impl Editor { }) .collect(), text_document: o.tid(), - })?; + }, + )?; self.text .apply_tedits(&mut { teds }) .map_err(|_| report!("couldnt apply edits"))?; } Cmd::RADocs => { - let u = l.request_immediate::<ExternalDocs>( + let u = l.request_immediate::<ExternalDocsRequest>( &TextDocumentPositionParams { position: self .text @@ -384,18 +406,19 @@ impl Editor { .unwrap(); } Cmd::RARebuildProcMacros => { - _ = l.request::<RebuildProcMacros>(&())?; + _ = l.request::<RebuildProcMacrosRequest>(&())?; } - Cmd::RACancelFlycheck => l.notify::<CancelFlycheck>(&())?, + Cmd::RACancelFlycheck => + l.notify::<CancelFlycheckNotification>(&())?, Cmd::RAOpenCargoToml => { - let Some(GotoDefinitionResponse::Scalar(x)) = - &l.request_immediate::<OpenCargoToml>( + let Some(DefinitionResponse::Definition(x)) = + &l.request_immediate::<OpenCargoTomlRequest>( &OpenCargoTomlParams { text_document: o.tid() }, )? else { bail!("wtf?"); }; - self.go(x, w)?; + self.go(x, w, lsp_mn)?; } Cmd::RARunnables => { let p = self.text.to_l_position(*self.text.cursor.first()); @@ -415,7 +438,8 @@ impl Editor { ..default() }), Cmd::Incoming => { - let x = l.runtime.spawn(l.callers(tdpp!(self))); + let l2 = l.clone(); + let x = l.runtime.spawn(l2.callers(tdpp!(self))); self.state = crate::edi::st::State::GoToL(GoToList { data: ( vec![], @@ -425,7 +449,8 @@ impl Editor { }); } Cmd::Outgoing => { - let x = l.runtime.spawn(l.calling(tdpp!(self))); + let l2 = l.clone(); + let x = l.runtime.spawn(l2.calling(tdpp!(self))); self.state = crate::edi::st::State::GoToL(GoToList { data: ( vec![], diff --git a/src/complete.rs b/src/complete.rs index 81c9c2f..3ab13d3 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -63,8 +63,8 @@ fn filter_c<'a>( ) -> impl Iterator<Item = &'a CompletionItem> { let x = &completion.r; let y = match x { - CompletionResponse::Array(x) => x, - CompletionResponse::List(x) => &x.items, + CompletionResponse::CompletionItemList(x) => x, + CompletionResponse::CompletionList(x) => &x.items, }; filter(y.iter(), f) } @@ -82,7 +82,9 @@ pub fn s(completion: &Complete, c: usize, f: &str) -> Vec<Cell> { }); out -} +}pub const fn i<T:Copy>(x: T) -> usize { + (unsafe { *<*const _>::from(&x).cast::<u32>() }) as usize + } #[implicit_fn::implicit_fn] fn r( x: &CompletionItem, @@ -96,34 +98,36 @@ fn r( let ds = Style::new(FG, bg); let d: Cell = Cell { letter: None, style: ds }; let mut b = vec![d; c]; + const MAP: [([u8; 3], [u8; 3], &str); 26] = { ( amap::amap! { - const { CompletionItemKind::TEXT.0 as usize } => ("#9a9b9a", " "), - const { CompletionItemKind::METHOD.0 as usize } | const { CompletionItemKind::FUNCTION.0 as usize } => ("#FFD173", "λ "), - const { CompletionItemKind::CONSTRUCTOR.0 as usize } => ("#FFAD66", "->"), - const { CompletionItemKind::FIELD.0 as usize } => ("#E06C75", "x."), - const { CompletionItemKind::VARIABLE.0 as usize } => ("#E06C75", "x "), - const { CompletionItemKind::MODULE.0 as usize } => ("#D5FF80", "::"), - const { CompletionItemKind::PROPERTY.0 as usize } => ("#e6e1cf", "x."), - const { CompletionItemKind::VALUE.0 as usize } => ("#DFBFFF", "4 "), - const { CompletionItemKind::ENUM.0 as usize } => ("#73b9ff", "u"), - const { CompletionItemKind::ENUM_MEMBER.0 as usize } => ("#73b9ff", ":"), - const { CompletionItemKind::SNIPPET.0 as usize } => ("#9a9b9a", "! "), - const { CompletionItemKind::INTERFACE.0 as usize } => ("#E5C07B", "t "), - const { CompletionItemKind::REFERENCE.0 as usize } => ("#9a9b9a", "& "), - const { CompletionItemKind::CONSTANT.0 as usize } => ("#DFBFFF", "N "), - const { CompletionItemKind::STRUCT.0 as usize } => ("#73D0FF", "X{"), - const { CompletionItemKind::OPERATOR.0 as usize } => ("#F29E74", "+ "), - const { CompletionItemKind::TYPE_PARAMETER.0 as usize } => ("#9a9b9a", "T "), - const { CompletionItemKind::KEYWORD.0 as usize } => ("#FFAD66", "as"), + const { i(CompletionItemKind::Text) } => ("#9a9b9a", " "), + const { i(CompletionItemKind::Method) } | const { i(CompletionItemKind::Function) } => ("#FFD173", "λ "), + const { i(CompletionItemKind::Constructor) } => ("#FFAD66", "->"), + const { i(CompletionItemKind::Field) } => ("#E06C75", "x."), + const { i(CompletionItemKind::Variable) } => ("#E06C75", "x "), + const { i(CompletionItemKind::Module) } => ("#D5FF80", "::"), + const { i(CompletionItemKind::Property) } => ("#e6e1cf", "x."), + const { i(CompletionItemKind::Value) } => ("#DFBFFF", "4 "), + const { i(CompletionItemKind::Enum) } => ("#73b9ff", "u"), + const { i(CompletionItemKind::EnumMember) } => ("#73b9ff", ":"), + const { i(CompletionItemKind::Snippet) } => ("#9a9b9a", "! "), + const { i(CompletionItemKind::Interface) } => ("#E5C07B", "t "), + const { i(CompletionItemKind::Reference) } => ("#9a9b9a", "& "), + const { i(CompletionItemKind::Constant) } => ("#DFBFFF", "N "), + const { i(CompletionItemKind::Struct) } => ("#73D0FF", "X{"), + const { i(CompletionItemKind::Operator) } => ("#F29E74", "+ "), + const { i(CompletionItemKind::TypeParameter) } => ("#9a9b9a", "T "), + const { i(CompletionItemKind::Keyword) } => ("#FFAD66", "as"), + _ => ("#9a9b9a", " ") }).map(const |(x, y)| (set_a(color_(x), 0.5), color_(x), y), ) }; let (bgt, col, ty) = - MAP[x.kind.unwrap_or(CompletionItemKind(25)).0 as usize]; + MAP[i(x.kind.unwrap_or(CompletionItemKind::Text))]; b.iter_mut().zip(ty.chars()).for_each(|(x, c)| { *x = (Style::new(col, bgt) | Style::BOLD).basic(c) }); @@ -159,20 +163,30 @@ fn r( })) .for_each(|(a, b)| *a = b); } + let s = if let Some(ref ft) = x.filter_text + && &x.label != ft + // l = &x + // ft = x + && let Some(e) = x.label.find(ft) + { + -(e as i32) + } else { + 0 + }; i.iter_mut() .zip( - x.label.chars().map(|x| ds.basic(x)).zip(0..).chain( + x.label.chars().map(|x| ds.basic(x)).zip(s..).chain( label_details .map(|x| { Style { bg, fg: color_("#858685"), ..default() } .basic(x) }) - .zip(repeat(u32::MAX)), + .zip(repeat(i32::MAX)), ), ) .for_each(|(a, (b, i))| { *a = b; - if indices.contains(&i) { + if indices.contains(&(i as u32)) { a.style |= (Style::BOLD, color_("#ffcc66")); } }); @@ -224,16 +238,16 @@ impl Editor { let sel = x.sel(&crate::filter(&self.text)); let sel = lsp.resolve(sel.clone()).unwrap(); let CompletionItem { - text_edit: Some(CompletionTextEdit::Edit(ed)), + text_edit: Some(CompletionItemTextEdit::TextEdit(ed)), additional_text_edits, insert_text_format, .. } = sel.clone() else { - panic!() + return; }; match insert_text_format { - Some(InsertTextFormat::SNIPPET) => { + Some(InsertTextFormat::Snippet) => { self.text.apply_snippet(&ed).unwrap(); } _ => { @@ -1,24 +1,21 @@ use std::collections::HashMap; use std::ffi::OsString; use std::fmt::Debug; -use std::io::BufReader; +use std::io::ErrorKind; use std::mem::take; use std::path::{Path, PathBuf}; -use std::process::Stdio; use std::sync::Arc; use std::time::SystemTime; use Default::default; use ftools::Bind; -use helix_core::syntax::config::FileType; -use log::info; -use lsp_server::Connection; use lsp_types::*; use regex::Regex; use rootcause::report; use ropey::Rope; use rust_fsm::StateMachine; use tokio::sync::oneshot::Sender; +use tree_house::Language; use winit::keyboard::NamedKey; use winit::window::Window; @@ -26,6 +23,7 @@ mod input_handlers; pub use input_handlers::handle2; mod lsp_impl; +pub mod lsp_mn; mod ra; pub mod st; mod wsedit; @@ -35,6 +33,7 @@ use st::*; use crate::bar::Bar; use crate::commands::Cmds; +use crate::edi::lsp_mn::LSPM; use crate::error::WDebug; use crate::gotolist::{At, GoTo}; use crate::hov::{self, HOV_HEIGHT, Hovr, Hovring, Rendered}; @@ -49,8 +48,8 @@ use crate::text::cursor::{Ronge, ceach}; use crate::text::hist::{ClickHistory, Hist}; use crate::text::{LOADER, Mapping, RopeExt, SortTedits, TextArea}; use crate::{ - BoolRequest, CDo, CompletionAction, CompletionState, alt, ctrl, - filter, hash, shift, sym, trm, + BoolRequest, CDo, CompletionAction, CompletionState, Freq, KillRing, + alt, ctrl, filter, hash, shift, sym, }; impl Debug for Editor { @@ -61,13 +60,18 @@ impl Debug for Editor { .field("origin", &self.origin) .field("state", &self.state.name()) .field("bar", &self.bar) - .field("workspace", &self.workspace) .field("hist", &self.hist) .field("mtime", &self.mtime) .finish() } } - +#[derive( + Default, serde_derive::Serialize, serde_derive::Deserialize, Debug, +)] +pub struct Edithin { + pub files: HashMap<PathBuf, Edithin>, + pub mtime: Option<std::time::SystemTime>, +} #[derive(Default, serde_derive::Serialize, serde_derive::Deserialize)] pub struct Editor { pub files: HashMap<PathBuf, Editor>, @@ -77,14 +81,11 @@ pub struct Editor { pub state: State, #[serde(skip)] pub bar: Bar, - pub workspace: Option<PathBuf>, + // pub workspace: Option<PathBuf>, + #[serde(skip)] pub git_dir: Option<PathBuf>, #[serde(skip)] - pub lsp: Option<( - &'static Client, - std::thread::JoinHandle<()>, - Option<Sender<Arc<dyn Window>>>, - )>, + pub lsp: Option<(Arc<Client>, Option<Sender<Arc<dyn Window>>>)>, // #[serde(skip)] pub requests: Requests, #[serde(skip)] @@ -98,9 +99,13 @@ pub struct Editor { // pub git_diff: // Option<std::rc::Rc<std::cell::RefCell<imara_diff::Diff>>>, } + macro_rules! lsp { + (&$self:ident) => { + $self.lsp.as_ref().map(|(x,..)| x) + }; ($self:ident) => { - $self.lsp.as_ref().map(|(x, ..)| *x) + $self.lsp.as_ref().map(|(x, ..)| x.clone()) }; ($self:ident + p) => { $crate::edi::lsp!($self).zip($self.origin.as_deref()) @@ -112,6 +117,13 @@ macro_rules! lsp { return $($else)?; }; }; + (let $lsp:ident = $self:ident $(else $else:expr)?) => { + let Some($lsp) = + $crate::edi::lsp!($self) + else { + return $($else)?; + }; + }; } pub(crate) use lsp; macro_rules! inlay { @@ -171,170 +183,237 @@ macro_rules! change { } pub(crate) use change; -fn rooter( +fn rooter_( x: &Path, mut search: impl FnMut(OsString) -> bool + Clone, -) -> Option<PathBuf> { - for f in std::fs::read_dir(&x).ok()?.filter_map(Result::ok) { + whilst: impl for<'a> FnMut(&&'a Path) -> bool + Clone, + found: &mut Vec<PathBuf>, +) { + let Some(d) = std::fs::read_dir(&x).ok() else { return }; + for f in d.filter_map(Result::ok) { if search(f.file_name()) { - return Some(f.path().with_file_name("").to_path_buf()); + found.push(f.path().with_file_name("").to_path_buf()); } } - x.parent().and_then(rooter.rbind(search)) + x.parent() + .filter(whilst.clone()) + .map(|x| rooter_(x, search, whilst, found)); } -impl Editor { - pub fn new() -> rootcause::Result<Self> { - let mut me = Self::default(); +fn rooter( + x: &Path, + search: impl FnMut(OsString) -> bool + Clone, - let o = std::env::args() + whilst: impl for<'a> FnMut(&&'a Path) -> bool + Clone, +) -> Option<PathBuf> { + let mut v = vec![]; + rooter_(x, search, whilst, &mut v); + v.try_remove(v.len().checked_sub(1)?) +} + +impl Editor { + pub fn new( + lsp_mn: &mut LSPM, + ) -> rootcause::Result<(Self, Freq, KillRing)> { + let mut o = std::env::args() .nth(1) .and_then(|x| PathBuf::try_from(x).ok()) - .and_then(|x| x.canonicalize().ok()); + .and_then(|x| { + x.canonicalize() + .inspect_err(|_| { + _ = std::fs::File::create(&x); + }) + .ok() + .or_else(|| x.canonicalize().ok()) + .or(Some(x)) + }) + .or_else(|| { + rfd::FileDialog::new() + .set_can_create_directories(true) + .set_title("pick dir to open") + .pick_folder() + }); + if let Some(x) = &o { + match std::fs::read_to_string(x) + .map(|x| x.replace("\r\n", "\n")) + { + Ok(_) => {} + Err(e) + if e.kind() == ErrorKind::IsADirectory + && let h = hash(&x) + && let cf = cfgdir().join(format!("{h:x}")) + && let at = cf.join(SSTORE) + && at.exists() => + { + let x = std::fs::read(at)?; + let x = bendncode::from_bytes::<Edithin>(&x)?; + o = Some( + x.files + .iter() + .max_by_key(|(_, x)| x.mtime) + .map(ttools::fst) + .expect("file") + .clone(), + ); + } + Err(e) if e.kind() == ErrorKind::IsADirectory => + o = rfd::FileDialog::new() + .set_directory(x) + .set_can_create_directories(true) + .set_title("pick file in project to open") + .pick_file(), + Err(e) => { + eprintln!("path could not be opened: {e}"); + std::process::exit(5); + } + } + }; + Editor::new_for(o, lsp_mn) + } - if let Some(x) = std::env::args().nth(1) { - me.text.insert(&std::fs::read_to_string(x)?); + pub fn upto(&self) -> impl std::ops::Fn(&&Path) -> bool + Clone { + let upto = self + .git_dir + .clone() + .or(std::env::current_dir().ok()) + .unwrap_or(PathBuf::from("/home/")); + move |x: &&Path| x.starts_with(&upto) + } + + pub fn root_for(&self, o: &Path, l: Language) -> Option<PathBuf> { + let l = LOADER.language(l).config(); + + o.parent() + .and_then(|x| rooter(&x, |f| l.roots.is_match(f), self.upto())) + .or(std::env::current_dir().ok()) + .and_then(|x| x.canonicalize().ok()) + } + pub fn vsc(&self) -> Option<serde_json::Value> { + self.origin + .as_ref() + .and_then(|x| x.parent()) + .and_then(|x| rooter(&x, |x| x == ".vscode", self.upto())) + .map(|x| (x.clone(), x.join(".vscode").join("settings.json"))) + .filter(|x| x.1.exists()) + .and_then(|(ws, x)| vsc_settings::load(&x, &ws).ok()) + } + pub fn load_lsp(&mut self, lsp_mn: &mut LSPM) { + self.language = self + .origin + .as_deref() + .and_then(|o| LOADER.language_for_filename(o)); + let ws = self + .origin + .as_deref() + .zip(self.language) + .and_then(|(p, l)| self.root_for(p, l)); + let l = ws + .as_ref() + .zip(self.language) + .and_then(|(w, ln)| lsp_mn.load(w, ln, self.vsc())); + self.lsp = l; + } + pub fn new_for( + o: Option<PathBuf>, + lsp_mn: &mut LSPM, + ) -> rootcause::Result<(Self, Freq, KillRing)> { + let mut me = Self::default(); + if let Some(o) = o.as_deref() + && let o = std::fs::read_to_string(o) + .map(|x| x.replace("\r\n", "\n")) + .unwrap() + { + me.text.insert(&o); me.text.cursor = default(); - }; + } let n = o.as_deref().and_then(|o| LOADER.language_for_filename(o)); me.language = n; - let l = - n.map(|n| LOADER.languages().nth(n.idx()).unwrap().1.config()); - me.workspace = o - .as_ref() - .and_then(|x| x.parent()) + let l = n.map(|n| LOADER.language(n).config()); + me.git_dir = o + .as_deref() .and_then(|x| { - l.and_then(|l| rooter(&x, |f| l.roots.is_match(f))) + x.ancestors() + .find(|x| x.join(".git").exists()) + .map(PathBuf::from) }) - .or(std::env::current_dir().ok()) .and_then(|x| x.canonicalize().ok()); - let vsc = o - .as_ref() - .and_then(|x| x.parent()) - .and_then(|x| rooter(&x, |x| x == ".vscode")) - .map(|x| (x.clone(), x.join(".vscode").join("settings.json"))) - .filter(|x| x.1.exists()) - .and_then(|(ws, x)| (vsc_settings::load(&x, &ws)).ok()); + let ws = o.as_deref().zip(n).and_then(|(p, l)| me.root_for(p, l)); let mut loaded_state = false; - if let Some(ws) = me.workspace.as_deref() + let mut freq = default(); + let mut kr = default(); + if let Some(ws) = ws.as_deref() && let h = hash(&ws) - && let at = cfgdir().join(format!("{h:x}")).join(STORE) + && let cf = cfgdir().join(format!("{h:x}")) + && let at = cf.join(SSTORE) && at.exists() { + println!("loaded "); let x = std::fs::read(at)?; - let x = bendy::serde::from_bytes::<Editor>(&x)?; + let x = bendncode::from_bytes::<Editor>(&x)?; + let gd = me.git_dir.take(); me = x; + me.git_dir = gd; loaded_state = true; - assert!(me.workspace.is_some()); + // assert!(me.workspace.is_some()); + if let at = cf.join(FSTORE) + && at.exists() + { + let x = std::fs::read(at)?; + freq = bendncode::from_bytes::<Freq>(&x)?; + } + if let at = cf.join(KSTORE) + && at.exists() + { + let x = std::fs::read(at)?; + kr = bendncode::from_bytes::<KillRing>(&x)?; + } } + println!("set git dir {:?}", &me.git_dir); me.language = n; - me.git_dir = me - .workspace - .as_deref() - .and_then(|x| rooter(&x, |x| x == ".git")) - .and_then(|x| x.canonicalize().ok()); me.origin = o; - me.tree = me.workspace.as_ref().map(|x| { - walkdir::WalkDir::new(x) - .into_iter() - .flatten() - .filter(|x| { - let x = x.path(); - l.is_some_and(|l| { - l.file_types.iter().any(|y| match y { - FileType::Extension(e) => - x.extension().is_some_and(|x| x == &**e), - FileType::Glob(glob) => - glob.compile_matcher().is_match(x), - }) - }) - }) - .map(|x| x.path().to_owned()) - .collect::<Vec<_>>() - }); - let l = me.workspace.as_ref().zip(l).map(|(workspace, l)| { - let (Connection { sender, receiver }, conf) = if l.language_id - == "rust" - { - super let (_jh, a) = ra::ra(workspace.clone()); - ( - a, - ( - &LOADER.language_server_configs()["rust-analyzer"], - &l.language_servers[0], - ), - ) - } else { - let (mut c, conf) = l - .language_servers - .iter() - .find_map(|l| { - let lc = LOADER - .language_server_configs() - .get(&l.name)?; - std::process::Command::new(&lc.command) - .args(&lc.args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .ok() - .zip(Some((lc, l))) - }) - .ok_or(report!( - "no lsp for this language; install one of {:?}", - l.language_servers - )) - .unwrap(); - super let (x, _iot) = - Connection::stdio( - BufReader::new(c.stdout.take().unwrap()), - c.stdin.take().unwrap(), - ); - (x, conf) - }; - info!("spawned {conf:?}"); - let (c, t2, changed) = crate::lsp::run( - (sender, receiver), - WorkspaceFolder { - uri: Url::from_file_path(&workspace).unwrap(), - name: workspace - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(), - }, - vsc, - conf, - ) - .unwrap(); - (&*Box::leak(Box::new(c)), (t2), Some(changed)) - }); + // me.tree = ws.as_ref().map(|x| { + // walkdir::WalkDir::new(x) + // .into_iter() + // .flatten() + // .filter(|x| { + // let x = x.path(); + // l.is_some_and(|l| { + // l.file_types.iter().any(|y| match y { + // FileType::Extension(e) => + // x.extension().is_some_and(|x| x == &**e), + // FileType::Glob(glob) => + // glob.compile_matcher().is_match(x), + // }) + // }) + // }) + // .map(|x| x.path().to_owned()) + // .collect::<Vec<_>>() + // }); + let g = me.git_dir.clone(); if let Some(o) = me.origin.clone() && loaded_state { - let w = me.workspace.clone(); - let la = me.language; - let t = me.tree.clone(); - assert!(me.files.len() != 0); - me.open_or_restore(&o, l, la, None, w)?; - me.git_dir = g; - me.tree = t; + if me.files.len() != 0 { + log::error!( + "too many files? {:?}", + me.files.keys().collect::<Vec<_>>() + ); + } + me.open_or_restore(&o, g, None, lsp_mn)?; me.language = n; } else { - me.lsp = l; + me.load_lsp(lsp_mn); me.hist.lc = me.text.cursor.clone(); me.hist.last = me.text.changes.clone(); - if let Some(((c, ..), origin)) = - me.lsp.as_ref().zip(me.origin.as_deref()) - { + if let Some((c, origin)) = lsp!(me + p) { c.open( &origin, - std::fs::read_to_string(&origin)?, + std::fs::read_to_string(&origin) + .map(|x| x.replace("\r\n", "\n"))?, me.language.unwrap(), )?; c.rq_semantic_tokens( @@ -345,7 +424,7 @@ impl Editor { } me.git_dir = g; me.mtime = Self::modify(me.origin.as_deref()); - + me.tree(); // me.hist.last = me.text.clone(); // me.lsp.as_ref().zip(me.origin.as_deref()).map( // |((x, ..), origin)| { @@ -358,7 +437,7 @@ impl Editor { // }, // ); } - Ok(me) + Ok((me, freq, kr)) } #[must_use = "please apply this"] @@ -385,7 +464,8 @@ impl Editor { self.bar.last_action = "saved".into(); lsp!(self + p).map(|(l, o)| { if let Ok(fut) = l.format(o) - && let Ok(Some(v)) = l.runtime.block_on(fut) + && let Ok(Ok(Some(v))) = + l.by(fut, tokio::time::Duration::from_millis(500)) && let Err(x) = self.text.apply_tedits_adjusting(&mut { v }) { @@ -472,6 +552,30 @@ impl Editor { inlay!(self); } + pub fn paste_m<T: AsRef<str>>( + &mut self, + r: impl Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator, + ) { + // let bounds = unsafe { &*META.splits }; + // let pieces = bounds.windows(2).map(|w| unsafe { + // std::str::from_utf8_unchecked(&r.as_bytes()[w[0]..w[1]]) + // }); + if r.len() == self.text.cursor.iter().len() { + for (piece, cursor) in + r.rev().zip(0..self.text.cursor.iter().count()) + { + let c = self.text.cursor.iter().nth(cursor).unwrap(); + self.text.insert_at(*c, piece.as_ref()).unwrap(); + } + } else { + let new = r + .fold(String::default(), |acc, x| acc + x.as_ref() + "\n"); + + // vscode behaviour: insane? + self.text.insert(&new); + eprintln!("hrmst"); + } + } pub fn paste(&mut self) { self.hist.push_if_changed(&mut self.text); let r = clipp::paste(); @@ -480,19 +584,7 @@ impl Editor { let pieces = bounds.windows(2).map(|w| unsafe { std::str::from_utf8_unchecked(&r.as_bytes()[w[0]..w[1]]) }); - if unsafe { META.count } == self.text.cursor.iter().len() { - for (piece, cursor) in - pieces.zip(0..self.text.cursor.iter().count()) - { - let c = self.text.cursor.iter().nth(cursor).unwrap(); - self.text.insert_at(*c, piece).unwrap(); - } - } else { - let new = pieces.intersperse("\n").collect::<String>(); - // vscode behaviour: insane? - self.text.insert(&new); - eprintln!("hrmst"); - } + self.paste_m(pieces); } else { self.text.insert(&clipp::paste()); } @@ -504,6 +596,7 @@ impl Editor { &mut self, x: &Path, w: Arc<dyn Window>, + lsp_mn: &mut LSPM, ) -> rootcause::Result<()> { let x = x.canonicalize()?.to_path_buf(); if Some(&*x) == self.origin.as_deref() { @@ -511,91 +604,99 @@ impl Editor { return Ok(()); } let r = self.text.r; - let ws = self.workspace.clone(); - let git_dir = self.workspace.clone(); - let tree = self.tree.clone(); - let lsp = self.lsp.take(); - let l = self.language; + // let ws = self.workspace.clone(); + let git_dir = self.git_dir.clone(); + // let lsp = self.lsp.take(); let mut me = take(self); let f = take(&mut me.files); if let Some(x) = me.origin.clone() { - lsp.as_ref().map(|l| l.0.close(&x)); + lsp!(me).as_ref().map(|l| l.close(&x)); self.files.insert(x, me); self.files.extend(f); // assert!(f.len() == 0); } - self.open_or_restore(&x, lsp, l, Some(w), ws)?; + self.open_or_restore(&x, git_dir, Some(w), lsp_mn)?; self.text.r = r; - self.tree = tree; - self.git_dir = git_dir; // maybe it should change? you know. sometimes? Ok(()) } + fn restore( + &mut self, + git_dir: Option<PathBuf>, + // lsp: Option<(Arc<Client>, Option<Sender<Arc<dyn Window>>>)>, + // l: Option<helix_core::Language>, + with: Editor, + lsp_mn: &mut LSPM, + ) -> rootcause::Result<()> { + let f = take(&mut self.files); + + *self = with; + assert!(self.files.len() == 0); + self.files = f; + self.bar.last_action = "restored".into(); + if self.mtime != Self::modify(self.origin.as_deref()) { + self.hist.push_if_changed(&mut self.text); + self.text.rope = Rope::from_str( + &std::fs::read_to_string( + self.origin + .as_ref() + .ok_or(report!("origin missing"))?, + ) + .map(|x| x.replace("\r\n", "\n")) + .unwrap(), + ); + + self.text.cursor.first_mut().position = self + .text + .cursor + .first() + .position + .min(self.text.rope.len_chars()); + self.mtime = Self::modify(self.origin.as_deref()); + self.bar.last_action = "restored -> reloaded".into(); + take(&mut self.requests); + self.hist.push(&mut self.text) + } + self.git_dir = git_dir; + self.load_lsp(lsp_mn); + + if let Some((x, origin)) = lsp!(self + p) { + x.open( + &origin, + self.text.rope.to_string(), + self.language.unwrap(), + )?; + } + Ok(()) + } fn open_or_restore( &mut self, x: &Path, - lsp: Option<( - &'static Client, - std::thread::JoinHandle<()>, - Option<Sender<Arc<dyn Window>>>, - )>, - l: Option<helix_core::Language>, + git_dir: Option<PathBuf>, + // lsp: Option<(Arc<Client>, Option<Sender<Arc<dyn Window>>>)>, + // l: Option<helix_core::Language>, w: Option<Arc<dyn Window>>, - ws: Option<PathBuf>, + lsp_mn: &mut LSPM, + // ws: Option<PathBuf>, ) -> rootcause::Result<()> { if let Some(x) = self.files.remove(x) { - let f = take(&mut self.files); - - *self = x; - assert!(self.files.len() == 0); - self.files = f; - self.bar.last_action = "restored".into(); - if self.mtime != Self::modify(self.origin.as_deref()) { - self.hist.push_if_changed(&mut self.text); - self.text.rope = Rope::from_str( - &std::fs::read_to_string( - self.origin - .as_ref() - .ok_or(report!("origin missing"))?, - ) - .unwrap(), - ); - - self.text.cursor.first_mut().position = self - .text - .cursor - .first() - .position - .min(self.text.rope.len_chars()); - self.mtime = Self::modify(self.origin.as_deref()); - self.bar.last_action = "restored -> reloaded".into(); - take(&mut self.requests); - self.hist.push(&mut self.text) - } - self.lsp = lsp; - self.language = l; - if let Some((x, origin)) = lsp!(self + p) { - x.open( - &origin, - self.text.rope.to_string(), - self.language.unwrap(), - )?; - } + self.restore(git_dir, x, lsp_mn)? } else { - self.workspace = ws; + // self.workspace = ws; self.origin = Some(x.to_path_buf()); - let new = std::fs::read_to_string(&x)?; + let new = std::fs::read_to_string(&x) + .map(|x| x.replace("\r\n", "\n"))?; take(&mut self.text); self.text.insert(&new); take(&mut self.text.changes); self.text.cursor.just(0, &self.text.rope); self.bar.last_action = "open".into(); self.mtime = Self::modify(self.origin.as_deref()); - self.lsp = lsp; - self.language = l; + self.git_dir = git_dir; + self.load_lsp(lsp_mn); if let Some((ls, origin)) = lsp!(self + p) { take(&mut self.requests); @@ -607,9 +708,30 @@ impl Editor { .peel()?; } } + + self.tree(); self.set_title(w); Ok(()) } + pub fn tree(&mut self) { + fn is_hidden(entry: &walkdir::DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.starts_with(".")) + .unwrap_or(false) + } + self.tree = self.git_dir.as_ref().map(|x| { + walkdir::WalkDir::new(x) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .flatten() + .filter(|x| !x.path().starts_with(".")) + .filter(|x| !x.path().is_dir()) + .map(|x| x.path().to_owned()) + .collect::<Vec<_>>() + }); + } pub fn set_title(&self, w: Option<Arc<dyn Window>>) { if let Some(x) = w && let Some(t) = self.title() @@ -618,22 +740,26 @@ impl Editor { } } pub fn title(&self) -> Option<String> { - [self.workspace.as_deref(), self.origin.as_deref()] + [self.git_dir.as_deref(), self.origin.as_deref()] .try_map(|x| { x.and_then(Path::file_name).and_then(|x| x.to_str()) }) .map(|[wo, or]| format!("gracilaria - {wo} - {or}")) } - pub fn store(&mut self) -> rootcause::Result<()> { - let ws = self.workspace.clone(); + pub fn store( + &mut self, + fq: &Freq, + kr: &KillRing, + ) -> rootcause::Result<()> { + // let ws = self.workspace.clone(); let tree = self.tree.clone(); let mtime = self.mtime.clone(); let origin = self.origin.clone(); - if let Some(w) = self.workspace.clone() { + if let Some(w) = self.git_dir.clone() { let mut me = take(self); - self.workspace = ws; + // self.workspace = ws; self.tree = tree; self.mtime = mtime; self.origin = origin; @@ -647,9 +773,13 @@ impl Editor { let cfgdir = cfgdir(); let p = cfgdir.join(format!("{hash:x}")); std::fs::create_dir_all(&p)?; - let b = bendy::serde::to_bytes(&self).unwrap(); - bendy::serde::from_bytes::<Editor>(&b)?; - std::fs::write(p.join(STORE), &b)?; + let b = bendncode::to_bytes(&self)?; + bendncode::from_bytes::<Editor>(&b)?; + std::fs::write(p.join(SSTORE), &b)?; + let b = bendncode::to_bytes(fq)?; + std::fs::write(p.join(FSTORE), &b)?; + let b = bendncode::to_bytes(kr)?; + std::fs::write(p.join(KSTORE), &b)?; } Ok(()) } @@ -658,10 +788,11 @@ impl Editor { &mut self, g: impl Into<GoTo<'_>>, w: Arc<dyn Window>, + lsp_mn: &mut LSPM, ) -> rootcause::Result<()> { let g = g.into(); let f = g.path.canonicalize()?; - self.open(&f, w.clone())?; + self.open(&f, w.clone(), lsp_mn)?; match g.at { At::R(r) => { @@ -697,17 +828,11 @@ fn cfgdir() -> PathBuf { std::env::var("HOME") .map(PathBuf::from) .map(|x| x.join(".config")) + .or(std::env::var("AppData").map(PathBuf::from)) }) .unwrap_or("/tmp/".into()) .join("gracilaria") } -const STORE: &str = "state.torrent"; -#[track_caller] -#[allow(dead_code)] -fn rtt<T: serde::Serialize + serde::Deserialize<'static>>( - x: &T, -) -> Result<T, bendy::serde::Error> { - bendy::serde::from_bytes::<T>( - bendy::serde::to_bytes(x).unwrap().leak(), - ) -} +const SSTORE: &str = "state.bendn"; +const FSTORE: &str = "freq.bendn"; +const KSTORE: &str = "killring.bendn"; diff --git a/src/edi/input_handlers.rs b/src/edi/input_handlers.rs index 6457787..aa9e6a3 100644 --- a/src/edi/input_handlers.rs +++ b/src/edi/input_handlers.rs @@ -8,7 +8,7 @@ use crate::edi::*; pub fn handle2<'a>( key: &'a Key, text: &mut TextArea, - l: Option<(&Client, &Path)>, + l: Option<(Arc<Client>, &Path)>, ) -> Option<&'a str> { use Key::*; @@ -58,3 +58,13 @@ pub fn handle2<'a>( }; None } +impl Editor { + #[track_caller] + pub fn transition(&mut self, a: Action) -> Option<Do> { + self.state + .consume(a) + .inspect_err(|e| log::error!("transition failed: {e}")) + .ok() + .flatten() + } +} diff --git a/src/edi/input_handlers/click.rs b/src/edi/input_handlers/click.rs index d687c60..82ddd2b 100644 --- a/src/edi/input_handlers/click.rs +++ b/src/edi/input_handlers/click.rs @@ -11,15 +11,23 @@ impl Editor { bt: MouseButton, cursor_position: (usize, usize), w: Arc<dyn Window>, + lsp_mn: &mut LSPM, ) { - let text = &mut self.text; _ = self .requests .complete .consume(CompletionAction::Click) - .unwrap(); - match self.state.consume(Action::M(bt)).unwrap() { - Some(Do::ClickedHover | Do::MoveCursor) => { + .inspect_err(|x| log::error!("transition comact {x:?}")); + + let Some(mut o) = self.transition(Action::M(bt)) else { return }; + if let Do::Reinsert = o { + let Some(o2) = self.transition(Action::M(bt)) else { return }; + o = o2; + }; + + let text = &mut self.text; + match o { + Do::MoveCursor => { text.cursor.just( text.mapped_index_at(cursor_position), &text.rope, @@ -36,13 +44,13 @@ impl Editor { text.cursor.first().setc(&text.rope); self.refresh_document_highlights(); } - Some(Do::NavForward) => self.nav_forward(), - Some(Do::NavBack) => self.nav_back(), - Some(Do::ExtendSelectionToMouse) => { + Do::NavForward => self.nav_forward(), + Do::NavBack => self.nav_back(), + Do::ExtendSelectionToMouse => { let p = text.mapped_index_at(cursor_position); text.cursor.first_mut().extend_selection_to(p, &text.rope); } - Some(Do::StartSelection) => { + Do::StartSelection => { let p = text.mapped_index_at(cursor_position); let x = *text.cursor.first(); @@ -50,37 +58,51 @@ impl Editor { text.cursor.first_mut().extend_selection_to(p, &text.rope); self.hist.lc = text.cursor.clone(); } - Some(Do::GoToDefinition(tdpp)) => { + Do::GoToDefinition(tdpp) => { dbg!(&tdpp); - if let Some(x) = self.requests.def.result.clone().or_else(|| { - tdpp.zip(lsp!(self)).and_then(|(tdpp, lsp)| { - lsp.request_immediate::<lsp_request!("textDocument/definition")>( - &GotoDefinitionParams { + if let Some(x) = self.requests.def.result.clone() + && let Err(e) = self.go(&x, w.clone(), lsp_mn) + { + log::error!("gtd: {e}"); + } else if let Some(x) = { + tdpp.zip(lsp!(self)).and_then(|(tdpp, lsp)| { + lsp.request_immediate::< lsp_request!("textDocument/definition")>( + &DefinitionParams { text_document_position_params: tdpp, work_done_progress_params: default(), partial_result_params: default(), }, ).ok() }).flatten().and_then(|x| match &x { - GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()), + DefinitionResponse::Definition(Definition::Location(x)) => Some(x.clone()), + DefinitionResponse::Definition(Definition::LocationList([x])) => Some(x.clone()), + // DefinitionResponse::DefinitionLinkList([x, ..]) => Some(x.clone()), _ => None, }) - }) - && let Err(e) = self.go(&x, w.clone()) + } && let Err(e) = self.go(&x, w.clone(), lsp_mn) { log::error!("gtd: {e}"); } } - Some(Do::InsertCursorAtMouse) => { - text.cursor.add( - text.mapped_index_at(cursor_position), - &text.rope, - ); - self.hist.lc = text.cursor.clone(); - self.chist.push(text.primary_cursor()); - text.cursor.first().setc(&text.rope); + Do::InsertCursorAtMouse => { + let p = text.mapped_index_at(cursor_position); + + let v = (text.cursor.inner.len() != 1) + .then(|| { + text.cursor + .inner + .extract_if(.., |x| *x == p) + .next() + }) + .flatten(); + if let None = v { + text.cursor.add(p, &text.rope); + self.hist.lc = text.cursor.clone(); + self.chist.push(text.primary_cursor()); + text.cursor.first().setc(&text.rope); + } } - None => {} + _ => unreachable!(), } } diff --git a/src/edi/input_handlers/cursor.rs b/src/edi/input_handlers/cursor.rs index 092bc5d..8887045 100644 --- a/src/edi/input_handlers/cursor.rs +++ b/src/edi/input_handlers/cursor.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use Default::default; use implicit_fn::implicit_fn; -use lsp_types::request::*; use lsp_types::*; use rust_fsm::StateMachine; use tokio::task::spawn_blocking; use tokio_util::task::AbortOnDropHandle as DropH; +use url::Url; use winit::window::Window; enum Set<T> { To(T), @@ -17,6 +17,7 @@ enum Set<T> { use crate::edi::*; use crate::hov::{DiagnosticHovr, Hoverable, Hovring}; use crate::lsp::BehaviourAfter; +use crate::lsp::communication::send; use crate::rnd::CellBuffer; impl Editor { #[implicit_fn] @@ -26,7 +27,7 @@ impl Editor { w: Arc<dyn Window>, c: usize, ) { - match self.state.consume(Action::C(cursor_position)).unwrap() { + match self.transition(Action::C(cursor_position)) { Some(Do::ExtendSelectionToMouse) => { let p = self.text.mapped_index_at(cursor_position); self.text @@ -53,9 +54,7 @@ impl Editor { && !e.is_whitespace() => 'out: { match self - .state - .consume(Action::HOnSomething(cursor_position)) - .unwrap() + .transition(Action::HOnSomething(cursor_position)) { Some(Do::SetHovering) => { let State::Hovering(l) = &mut self.state else { @@ -109,8 +108,10 @@ impl Editor { // let l = &mut self.requests.hovering.result; } Some(Do::Hover) => { - self.state.consume(Action::HOnNothing).unwrap(); - w.request_redraw(); + match self.state.consume(Action::HOnNothing).unwrap() { + Some(st::Do::R(false)) => {} + _ => w.request_redraw(), + } } None => {} x => unreachable!("{x:?}"), @@ -221,12 +222,15 @@ impl Editor { .is_none_or(|&(_, x)| x != cursor_position) { let handle = lsp.runtime.spawn( - lsp.request::<lsp_request!("textDocument/definition")>( - &GotoDefinitionParams { - text_document_position_params: tdpp.clone(), - work_done_progress_params: default(), - partial_result_params: default(), - }, + send!( + lsp.request, + "textDocument/definition", + _ { + text_document_position_params: tdpp + .clone(), + work_done_progress_params: default(), + partial_result_params: default(), + }, ) .unwrap() .0, @@ -267,44 +271,42 @@ impl Editor { let tdp = tdpp.clone(); let l = self.language; let window = w.clone(); + let x = lsp.request_::<HoverRequest, { BehaviourAfter::Nil }>( + &HoverParams { + text_document_position_params: tdp.clone(), + work_done_progress_params: default(), + }, + ); let handle: tokio::task::JoinHandle<Result<Option<Hovr>, _>> = lsp.runtime.spawn(async move { - let Some(x) = lsp - .request_::<HoverRequest, { BehaviourAfter::Nil }>( - &HoverParams { - text_document_position_params: tdp.clone(), - work_done_progress_params: default(), - }, - )? - .0 - .await? - else { + let Some(x) = x?.0.await? else { return Ok(None::<Hovr>); }; let (width, cells) = spawn_blocking(move || { + #[allow(deprecated)] let mut x = match &x.contents { - lsp_types::HoverContents::Scalar( - marked_string, - ) => match marked_string { - MarkedString::LanguageString(x) => - Cow::Borrowed(&*x.value), - MarkedString::String(x) => Cow::Borrowed(&**x), - }, - lsp_types::HoverContents::Array( - marked_strings, - ) => Cow::Owned( - marked_strings - .iter() - .map(|x| match x { - MarkedString::LanguageString(x) => - &*x.value, - MarkedString::String(x) => &*x, - }) - .collect::<String>(), - ), - lsp_types::HoverContents::Markup( - markup_content, - ) => Cow::Borrowed(&*markup_content.value), + Contents::MarkedString(marked_string) => + match marked_string { + MarkedString::MarkedStringWithLanguage( + x, + ) => Cow::Borrowed(&*x.value), + MarkedString::String(x) => + Cow::Borrowed(&**x), + }, + Contents::MarkedStringList(marked_strings) => + Cow::Owned( + marked_strings + .iter() + .map(|x| match x { + MarkedString::MarkedStringWithLanguage( + x, + ) => &*x.value, + MarkedString::String(x) => &*x, + }) + .collect::<String>(), + ), + Contents::MarkupContent(markup_content) => + Cow::Borrowed(&*markup_content.value), }; if l.is_some_and(|l| { matches!( @@ -358,13 +360,11 @@ impl Editor { }); let diags = self.find_diags(cursor_position, &w); println!("requesting hover"); - self.state - .consume(Action::SetHovering( - diags.map(|of| Hovring { of, ..default() }), - handle, - (cursor_position, tdpp), - )) - .unwrap(); + self.transition(Action::SetHovering( + diags.map(|of| Hovring { of, ..default() }), + handle, + (cursor_position, tdpp), + )); // self.requests.hovering.request = // (DropH::new(handle), cursor_position).into(); // requests.hovering.result = None; diff --git a/src/edi/input_handlers/keyboard.rs b/src/edi/input_handlers/keyboard.rs index f61a338..08376aa 100644 --- a/src/edi/input_handlers/keyboard.rs +++ b/src/edi/input_handlers/keyboard.rs @@ -5,11 +5,9 @@ use std::sync::Arc; use Default::default; use lsp_server::ResponseError; -use lsp_types::request::*; use lsp_types::*; use regex::Regex; use ropey::Rope; -use rust_analyzer::lsp::ext::OnTypeFormatting; use rust_fsm::StateMachine; use tokio_util::task::AbortOnDropHandle as DropH; use ttools::{IteratorOfTuples, IteratorOfTuplesWithF, hrf}; @@ -19,6 +17,7 @@ use winit::window::Window; use crate::Freq; use crate::edi::*; +use crate::lsp::{Require, acceptable_duration}; impl Editor { pub fn keyboard( @@ -26,27 +25,31 @@ impl Editor { event: KeyEvent, window: &mut Arc<dyn Window>, freq: &mut Freq, + kr: &mut KillRing, + lsp_mn: &mut LSPM, ) -> ControlFlow<()> { - let mut o: Option<Do> = self - .state - .consume(Action::K(event.logical_key.clone())) - .unwrap(); + let Some(mut o) = + self.transition(Action::K(event.logical_key.clone())) + else { + return ControlFlow::Continue(()); + }; + if let Do::Reinsert = o { + let Some(o2) = + self.transition(Action::K(event.logical_key.clone())) + else { + return ControlFlow::Continue(()); + }; + dbg!(&o2); + o = o2; + }; match o { - Some(Do::Reinsert) => - o = self - .state - .consume(Action::K(event.logical_key.clone())) - .unwrap(), - _ => {} - } - match o { - Some(Do::Escape) => { + Do::Escape => { take(&mut self.requests.complete); take(&mut self.requests.sig_help); self.text.cursor.alone(); } - Some(Do::Comment(p)) => { + Do::Comment(p) => { ceach!(self.text.cursor, |cursor| { Some( if let Some(x) = cursor.sel @@ -62,29 +65,46 @@ impl Editor { self.text.cursor.clear_selections(); change!(self, window.clone()); } - Some(Do::SpawnTerminal) => { - if let Err(e) = trm::toggle( - self.workspace + #[cfg(target_family = "unix")] + Do::SpawnTerminal => { + if let Err(e) = crate::trm::toggle( + &lsp!(self) .as_deref() - .unwrap_or(Path::new("/home/os/")), + .map_or(Path::new("/home/os/").into(), |x| { + x.workspace.uri.to_file_path().unwrap() + }), ) { log::error!("opening terminal failed {e}"); } } - Some(Do::MatchingBrace) => + #[cfg(not(target_family = "unix"))] + Do::SpawnTerminal => { + unimplemented!() + } + Do::MatchingBrace => if let Some((l, f)) = lsp!(self + p) { l.matching_brace(f, &mut self.text) }, - Some(Do::DeleteBracketPair) => self.delete_bracket_pair(), - Some(Do::Symbols) => + Do::DeleteBracketPair => self.delete_bracket_pair(), + Do::Symbols if let Some((lsp, o)) = lsp!(self + p) - && let Ok(syms) = lsp.workspace_symbols("".into()) - { - let mut q = Rq::new(lsp.runtime.spawn( - syms.map(Anonymize::anonymize).map(|x| { - x.map(|x| x.map(SymbolsList::Workspace)) - }), - )); + && let Ok(syms) = lsp.workspace_symbols("".into()) => + { + let mut q = Rq::new( + lsp.runtime.spawn(syms.map(Anonymize::anonymize).map( + |x| x.map(|x| x.map(SymbolsList::Workspace)), + )), + ); + q.result = Some(Symbols::new( + self.tree.as_deref().unwrap(), + self.text.bookmarks.clone(), + o.into(), + )); + self.state = State::Symbols(q); + } + Do::Symbols => + if let Some(o) = &self.origin { + let mut q = Rq::default(); q.result = Some(Symbols::new( self.tree.as_deref().unwrap(), self.text.bookmarks.clone(), @@ -92,7 +112,7 @@ impl Editor { )); self.state = State::Symbols(q); }, - Some(Do::SwitchType) => + Do::SwitchType => if let Some((lsp, p)) = lsp!(self + p) { let State::Symbols(Rq { result: Some(x), request }) = &mut self.state @@ -113,7 +133,7 @@ impl Editor { )); } }, - Some(Do::ProcessCommand(mut x, z)) => + Do::ProcessCommand(mut x, z) => match Cmds::complete_or_accept(&z) { crate::menu::generic::CorA::Complete => { x.tedit.rope = @@ -122,14 +142,17 @@ impl Editor { self.state = State::Command(x); } crate::menu::generic::CorA::Accept => { - if let Err(e) = - self.handle_command(z, window.clone()) - { + if let Err(e) = self.handle_command( + z, + window.clone(), + kr, + lsp_mn, + ) { self.bar.last_action = format!("{e}"); } } }, - Some(Do::CmdTyped) => { + Do::CmdTyped => { let State::Command(x) = &self.state else { unreachable!() }; @@ -139,13 +162,11 @@ impl Editor { self.text.scroll_to_ln_centering(x as _); } } - Some(Do::SymbolsHandleKey) => { - if let Some(lsp) = lsp!(self) { - let State::Symbols(Rq { result: Some(x), request }) = + Do::SymbolsHandleKey => { + if let Some(lsp) = lsp!(self) + && let State::Symbols(Rq { result: Some(x), request }) = &mut self.state - else { - unreachable!() - }; + { let ptedit = x.tedit.rope.clone(); if handle2( &event.logical_key, @@ -183,7 +204,7 @@ impl Editor { } } } - Some(Do::SymbolsSelectNext) => { + Do::SymbolsSelectNext => { let State::Symbols(Rq { result: Some(x), .. }) = &mut self.state else { @@ -203,7 +224,7 @@ impl Editor { } } } - Some(Do::SymbolsSelectPrev) => { + Do::SymbolsSelectPrev => { let State::Symbols(Rq { result: Some(x), .. }) = &mut self.state else { @@ -223,15 +244,15 @@ impl Editor { } } } - Some(Do::SymbolsSelect(x)) => + Do::SymbolsSelect(x) => if let Some(Ok(x)) = x.sel(Some(freq)) - && let Err(e) = self.go(x.at, window.clone()) + && let Err(e) = self.go(x.at, window.clone(), lsp_mn) { log::error!("alas! {e}") }, - Some(Do::RenameSymbol(to)) => self.rename_symbol(to), - Some(Do::CodeAction) => self.request_code_actions(), - Some(Do::CASelectLeft) => { + Do::RenameSymbol(to) => self.rename_symbol(to), + Do::CodeAction => self.request_code_actions(), + Do::CASelectLeft => { let State::CodeAction(Rq { result: Some(c), .. }) = &mut self.state else { @@ -239,7 +260,7 @@ impl Editor { }; c.left(); } - Some(Do::CASelectRight) => 'out: { + Do::CASelectRight => 'out: { let Some(lsp) = lsp!(self) else { unreachable!() }; let State::CodeAction(Rq { result: Some(c), .. }) = &mut self.state @@ -252,15 +273,15 @@ impl Editor { self.hist.lc = self.text.cursor.clone(); self.hist.test_push(&mut self.text); let act = lsp - .request_immediate::<CodeActionResolveRequest>(&act) + .request_immediate::<rust_analyzer::lsp::ext::CodeActionResolveRequest>(&act) .unwrap(); if let Some(x) = act.edit - && let Err(e) = self.apply_wsedit(x) + && let Err(e) = self.apply_swsedit(x) { log::error!("{e}"); } } - Some(Do::CASelectNext) => { + Do::CASelectNext => { let State::CodeAction(Rq { result: Some(c), .. }) = &mut self.state else { @@ -268,7 +289,7 @@ impl Editor { }; c.down(); } - Some(Do::CASelectPrev) => { + Do::CASelectPrev => { let State::CodeAction(Rq { result: Some(c), .. }) = &mut self.state else { @@ -276,7 +297,7 @@ impl Editor { }; c.up(); } - Some(Do::GoToMatch) + Do::GoToMatch if let Some(x) = &self.requests.document_highlights.result => 'out: { @@ -295,8 +316,10 @@ impl Editor { })) .max_by_key(|x| x.0.start) else { - self.bar.last_action = - "couldnt get symbol here".into(); + self.requests.document_highlights.result = None; + self.refresh_document_highlights(); + // self.bar.last_action = + // "couldnt get symbol here".into(); break 'out; }; if self.text.cursor.inner.len() == 1 @@ -314,37 +337,35 @@ impl Editor { } } } - Some(Do::GoToMatch) => + Do::GoToMatch => if self.requests.document_highlights.request.is_none() { self.refresh_document_highlights(); }, - Some(Do::NavBack) => self.nav_back(), - Some(Do::NavForward) => self.nav_forward(), - Some( - Do::Reinsert - | Do::GoToDefinition(_) - | Do::MoveCursor - | Do::ExtendSelectionToMouse - | Do::Hover - | Do::InsertCursorAtMouse - | Do::SetHovering - | Do::ClickedHover, - ) => panic!(), - Some(Do::Save) => match &self.origin { + Do::NavBack => self.nav_back(), + Do::NavForward => self.nav_forward(), + + Do::Reinsert + | Do::GoToDefinition(_) + | Do::MoveCursor + | Do::ExtendSelectionToMouse + | Do::Hover + | Do::InsertCursorAtMouse + | Do::SetHovering => panic!(), + Do::Save => match &self.origin { Some(_) => { - self.state.consume(Action::Saved).unwrap(); + self.transition(Action::Saved); self.save(); } None => { - self.state.consume(Action::RequireFilename).unwrap(); + self.transition(Action::RequireFilename); } }, - Some(Do::SaveTo(x)) => { + Do::SaveTo(x) => { self.origin = Some(PathBuf::try_from(x).unwrap()); self.save(); } - Some(Do::Edit) => self.handle_edit(event), - Some(Do::Undo) => { + Do::Edit => self.handle_edit(event), + Do::Undo => { self.hist.test_push(&mut self.text); if let Err(e) = self.hist.undo(&mut self.text) { eprintln!("undo failed: {e}"); @@ -353,14 +374,14 @@ impl Editor { change!(self, window.clone()); } - Some(Do::Redo) => { + Do::Redo => { self.hist.test_push(&mut self.text); self.hist.redo(&mut self.text).unwrap(); self.bar.last_action = "redid".to_string(); change!(self, window.clone()); } - Some(Do::Quit) => return ControlFlow::Break(()), - Some(Do::SetCursor(x)) => { + Do::Quit => return ControlFlow::Break(()), + Do::SetCursor(x) => { self.text.cursor.each(|c| { let Some(r) = c.sel else { return }; match x { @@ -370,7 +391,7 @@ impl Editor { }); self.text.cursor.clear_selections(); } - Some(Do::StartSelection) => { + Do::StartSelection => { let Key::Named(y) = event.logical_key else { panic!() }; // let mut z = vec![]; self.text.cursor.each(|x| { @@ -385,7 +406,7 @@ impl Editor { }); // *self.state.sel() = z; } - Some(Do::UpdateSelection) => { + Do::UpdateSelection => { let Key::Named(y) = event.logical_key else { panic!() }; self.text.cursor.each(|x| { x.extend_selection( @@ -400,9 +421,10 @@ impl Editor { self.text.scroll_to_cursor(); inlay!(self); } - Some(Do::Insert(c)) => { + Do::Insert(c) => { // self.text.cursor.inner.clear(); self.hist.push_if_changed(&mut self.text); + self.kill(kr); ceach!(self.text.cursor, |cursor| { let Some(r) = cursor.sel else { return }; _ = self.text.remove(r.into()); @@ -413,30 +435,29 @@ impl Editor { self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } - Some(Do::Delete) => { + Do::Delete => { self.hist.push_if_changed(&mut self.text); + self.kill(kr); ceach!(self.text.cursor, |cursor| { let Some(r) = cursor.sel else { return }; _ = self.text.remove(r.into()); }); self.text.cursor.clear_selections(); - self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } - Some(Do::Copy) => { + Do::Copy => { self.hist.push_if_changed(&mut self.text); unsafe { take(&mut META) }; let mut clip = String::new(); - self.text.cursor.each_ref(|x| { - if let Some(x) = x.sel { - unsafe { - META.count += 1; - META.splits.push(clip.len()); - } - clip.extend(self.text.rope.slice(x).chars()); + self.kill(kr); + for sel in self.text.cursor.sels(&self.text) { + unsafe { + META.count += 1; + META.splits.push(clip.len()); } - }); + clip.extend(sel.chars()); + } unsafe { META.splits.push(clip.len()); META.hash = hash(&clip) @@ -446,19 +467,18 @@ impl Editor { self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } - Some(Do::Cut) => { + Do::Cut => { self.hist.push_if_changed(&mut self.text); unsafe { take(&mut META) }; let mut clip = String::new(); - self.text.cursor.each_ref(|x| { - if let Some(x) = x.sel { - unsafe { - META.count += 1; - META.splits.push(clip.len()); - } - clip.extend(self.text.rope.slice(x).chars()); + self.kill(kr); + for sel in self.text.cursor.sels(&self.text) { + unsafe { + META.count += 1; + META.splits.push(clip.len()); } - }); + clip.extend(sel.chars()); + } unsafe { META.splits.push(clip.len()); META.hash = hash(&clip) @@ -473,8 +493,9 @@ impl Editor { self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } - Some(Do::PasteOver) => { + Do::PasteOver => { self.hist.push_if_changed(&mut self.text); + self.kill(kr); ceach!(self.text.cursor, |cursor| { let Some(r) = cursor.sel else { return }; _ = self.text.remove(r.into()); @@ -483,11 +504,11 @@ impl Editor { self.paste(); // self.hist.push_if_changed(&mut self.text); } - Some(Do::Paste) => self.paste(), - Some(Do::OpenFile(x)) => { - _ = self.open(Path::new(&x), window.clone()); + Do::Paste => self.paste(), + Do::OpenFile(x) => { + _ = self.open(Path::new(&x), window.clone(), lsp_mn); } - Some(Do::StartSearch(x)) => { + Do::StartSearch(x) => { let s = Regex::new(&x).unwrap(); let n = s .find_iter(&self.text.rope.to_string()) @@ -510,7 +531,7 @@ impl Editor { self.bar.last_action = "no matches".into() }); } - Some(Do::SearchChanged) => { + Do::SearchChanged => { let (re, index, _) = self.state.search(); let s = self.text.rope.to_string(); let m = re.find_iter(&s).nth(*index).unwrap(); @@ -521,48 +542,41 @@ impl Editor { self.text.scroll_to_cursor_centering(); inlay!(self); } - Some(Do::Boolean(BoolRequest::ReloadFile, true)) => { - self.hist.push_if_changed(&mut self.text); - self.text.rope = Rope::from_str( - &std::fs::read_to_string( - self.origin.as_ref().unwrap(), - ) - .unwrap(), - ); - - self.text.cursor.first_mut().position = self - .text + Do::Boolean(BoolRequest::ReloadFile, true) => self.reload(), + Do::Boolean(BoolRequest::ReloadFile, false) => {} + Do::InsertCursor(dir) => { + self.text .cursor - .first() - .position - .min(self.text.rope.len_chars()); - self.mtime = Self::modify(self.origin.as_deref()); - self.bar.last_action = "reloaded".into(); - self.hist.push(&mut self.text) - } - Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {} - Some(Do::InsertCursor(dir)) => { - let (x, y) = match dir { - Direction::Above => self.text.cursor.min(), - Direction::Below => self.text.cursor.max(), - } - .cursor(&self.text.rope); - let y = match dir { - Direction::Above => y - 1, - Direction::Below => y + 1, - }; - let position = self.text.line_to_char(y); - self.text.cursor.add(position + x, &self.text.rope); + .inner + .iter() + .filter_map(|cursor| { + let (x, y) = cursor.cursor(&self.text.rope); + let y = match dir { + Direction::Above => y.checked_sub(1)?, + Direction::Below => y + 1, + }; + let position = self.text.line_to_char(y) + x; + Some(position) + }) + .filter(|&p| self.text.cursor.iter().all(|x| x != p)) + .collect::<Vec<_>>() + .into_iter() + .for_each(|x| { + self.text.cursor.add(x, &self.text.rope); + }); } - Some(Do::Run(x)) => + #[cfg(target_family = "unix")] + Do::Run(x) => if let Some((l, ws)) = - lsp!(self).zip(self.workspace.as_deref()) + lsp!(self).zip(self.git_dir.as_deref()) { l.runtime .block_on(crate::runnables::run(x, ws)) .unwrap(); }, - Some(Do::GoToImplementations) => { + #[cfg(not(target_family = "unix"))] + Do::Run(_) => {} + Do::GoToImplementations => { let State::GoToL(x) = &mut self.state else { unreachable!() }; @@ -574,13 +588,13 @@ impl Editor { ))); } } - Some(Do::GTLSelect(x)) => + Do::GTLSelect(x) => if let Some(Ok((g, _))) = x.sel(None) - && let Err(e) = self.go(g, window.clone()) + && let Err(e) = self.go(g, window.clone(), lsp_mn) { eprintln!("go-to-list select fail: {e}"); }, - Some(Do::GT) => { + Do::GT => { let State::GoToL(x) = &mut self.state else { unreachable!() }; @@ -593,7 +607,28 @@ impl Editor { // self.text.vo = self.text.char_to_line(x.start); } } - None => {} + // Do::KillRing => { + // self.state = State::KillRing(KillRM { + // data: kr.clone(), + // tedit: default(), + // selection: 0, + // vo: 0, + // }); + // } + Do::KillRMHandleKey => { + if let State::KillRing(x) = &mut self.state { + handle2( + &event.logical_key, + &mut x.tedit, + lsp!(self + p), + ); + } + } + Do::Revive(x) => + if let Some(Ok(x)) = x.sel(Some(freq)) { + self.paste_m(x.iter()) + }, + Do::R(_) => {} } ControlFlow::Continue(()) } @@ -628,25 +663,21 @@ impl Editor { || t.iter().any(|y| y == x)) && self.text.cursor.inner.len() == 1 && change!(just self).is_some() - && let Ok(Some(mut x)) = l - .request_immediate::<OnTypeFormatting>( + && let Ok(Ok(Some(mut x))) = l + .request_by::<rust_analyzer::lsp::ext::DocumentOnTypeFormattingRequest>( &DocumentOnTypeFormattingParams { - text_document_position: - TextDocumentPositionParams { - text_document: p.tid(), - position: self - .text - .to_l_position( - *self.text.cursor.first(), - ) - .unwrap(), - }, + text_document: p.tid(), + position: self + .text + .to_l_position(*self.text.cursor.first()) + .unwrap(), ch: x.into(), options: FormattingOptions { tab_size: 4, ..default() }, }, + acceptable_duration(), ) { x.sort_tedits(); @@ -699,7 +730,11 @@ impl Editor { .requests .complete .consume(CompletionAction::K(event.logical_key.as_ref())) - .unwrap() + .inspect_err(|e| { + log::error!("failure: {e}"); + }) + .ok() + .flatten() { Some(CDo::Request(ctx)) => { if let Ok(fut) = lsp.request_complete( @@ -776,15 +811,16 @@ impl Editor { let x = lsp .request_immediate::<lsp_request!("textDocument/rename")>( &RenameParams { - text_document_position: TextDocumentPositionParams { - text_document: f.tid(), - position: self - .text - .to_l_position( - self.text.cursor.first().position, - ) - .unwrap(), - }, + text_document_position_params: + TextDocumentPositionParams { + text_document: f.tid(), + position: self + .text + .to_l_position( + self.text.cursor.first().position, + ) + .unwrap(), + }, new_name, work_done_progress_params: default(), }, @@ -814,8 +850,11 @@ impl Editor { pub fn request_code_actions(&mut self) { lsp!(let lsp, f = self); + if lsp.caps().code_action_provider.is_none() { + return; + } let r = lsp - .request::<lsp_request!("textDocument/codeAction")>( + .request::<rust_analyzer::lsp::ext::CodeActionRequest>( &CodeActionParams { text_document: f.tid(), range: self @@ -826,7 +865,7 @@ impl Editor { ) .unwrap(), context: CodeActionContext { - trigger_kind: Some(CodeActionTriggerKind::INVOKED), + trigger_kind: Some(CodeActionTriggerKind::Invoked), // diagnostics: if let Some((lsp, p)) = lsp!() && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.requests.diagnostics.get(&uri, &lsp.requests.diagnostics.guard()) { dbg!(diag.iter().filter(|x| { // self.text.l_range(x.range).unwrap().contains(&self.text.cursor) // }).cloned().collect()) } else { vec![] }, @@ -854,4 +893,35 @@ impl Editor { .request(lsp.runtime.spawn(fut)); } } + pub fn reload(&mut self) { + self.hist.push_if_changed(&mut self.text); + self.text.rope = Rope::from_str( + &std::fs::read_to_string(self.origin.as_ref().unwrap()) + .map(|x| x.replace("\r\n", "\n")) + .unwrap(), + ); + + self.text.cursor.first_mut().position = self + .text + .cursor + .first() + .position + .min(self.text.rope.len_chars()); + self.mtime = Self::modify(self.origin.as_deref()); + self.bar.last_action = "reloaded".into(); + self.hist.push(&mut self.text) + } + pub fn kill(&mut self, kr: &mut KillRing) { + if let x = self + .text + .cursor + .sels(&self.text) + .map(String::from) + .collect::<Box<[_]>>() + && !x.is_empty() + { + println!("kill {x:?}"); + kr.push(x); + } + } } diff --git a/src/edi/lsp_impl.rs b/src/edi/lsp_impl.rs index d0e506e..6026a3d 100644 --- a/src/edi/lsp_impl.rs +++ b/src/edi/lsp_impl.rs @@ -2,16 +2,16 @@ use std::iter::repeat; use Default::default; use lsp_server::Request as LRq; -use lsp_types::request::*; use lsp_types::*; +use rust_analyzer::lsp::ext::CodeAction; use serde::{Deserialize, Serialize}; use ttools::{Tupl, With}; use crate::complete::Complete; +use crate::edi::lsp; use crate::edi::st::*; use crate::hov::{Hoverable, Hovring}; use crate::lsp::{RequestError, Rq}; -use crate::runnables::Runnables; use crate::sym::GoTo; use crate::{CompletionState, act, sig, sym}; @@ -32,14 +32,15 @@ pub struct Requests { (), RequestError<SignatureHelpRequest>, >, // vo, lines - // #[serde(serialize_with = "serialize_tokens")] - // #[serde(deserialize_with = "deserialize_tokens")] - #[serde(skip)] + #[serde(serialize_with = "serialize_tokens")] + #[serde(deserialize_with = "deserialize_tokens")] + #[serde(default)] + // #[serde(skip)] pub semantic_tokens: Rq< Box<[SemanticToken]>, Box<[SemanticToken]>, (), - RequestError<SemanticTokensFullRequest>, + RequestError<lsp_request!("textDocument/semanticTokens/full")>, >, pub diag: Rq< String, @@ -47,18 +48,18 @@ pub struct Requests { (), RequestError<DocumentDiagnosticRequest>, >, - #[serde(skip)] + #[serde(default)] pub inlay: Rq< Vec<InlayHint>, Vec<InlayHint>, (), RequestError<lsp_request!("textDocument/inlayHint")>, >, - pub def: Rq< + pub def: crate::RqS< LocationLink, - Option<GotoDefinitionResponse>, + lsp_request!("textDocument/definition"), (usize, usize), - RequestError<lsp_request!("textDocument/definition")>, + // RequestError<lsp_request!("textDocument/definition")>, >, #[serde(skip)] pub document_symbols: Rq< @@ -70,9 +71,138 @@ pub struct Requests { #[serde(skip)] pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>, } +use serde::ser::SerializeSeq; + +fn untokenr<'de, D>( + deserializer: D, +) -> Result<Vec<SemanticToken>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let data = Vec::<u32>::deserialize(deserializer)?; + let chunks = data.chunks_exact(5); + + if !chunks.remainder().is_empty() { + return Result::Err(serde::de::Error::custom( + "Length is not divisible by 5", + )); + } + + Result::Ok( + chunks + .map(|chunk| SemanticToken { + delta_line: chunk[0], + delta_start: chunk[1], + length: chunk[2], + token_type: chunk[3], + token_modifiers_bitset: chunk[4], + }) + .collect(), + ) +} + +fn tokenr<S>( + tokens: &[SemanticToken], + serializer: S, +) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, +{ + let mut seq = serializer.serialize_seq(Some(tokens.len() * 5))?; + for token in tokens.iter() { + seq.serialize_element(&token.delta_line)?; + seq.serialize_element(&token.delta_start)?; + seq.serialize_element(&token.length)?; + seq.serialize_element(&token.token_type)?; + seq.serialize_element(&token.token_modifiers_bitset)?; + } + seq.end() +} + +fn deserialize_tokens_opt<'de, D>( + deserializer: D, +) -> Result<Option<Vec<SemanticToken>>, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(transparent)] + struct Wrapper { + #[serde(deserialize_with = "untokenr")] + tokens: Vec<SemanticToken>, + } + + Ok(Option::<Wrapper>::deserialize(deserializer)? + .map(|wrapper| wrapper.tokens)) +} + +fn serialize_tokens_opt<S>( + data: Option<&[SemanticToken]>, + serializer: S, +) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, +{ + #[derive(Serialize)] + #[serde(transparent)] + struct Wrapper<'a> { + #[serde(serialize_with = "tokenr")] + tokens: &'a [SemanticToken], + } + + let opt = data.as_ref().map(|t| Wrapper { tokens: t }); + + opt.serialize(serializer) +} +pub fn deserialize_tokens<'de, D: serde::Deserializer<'de>>( + ser: D, +) -> Result< + Rq< + Box<[SemanticToken]>, + Box<[SemanticToken]>, + (), + RequestError<lsp_request!("textDocument/semanticTokens/full")>, + >, + D::Error, +> { + { + #[derive(Deserialize)] + #[serde(transparent)] + struct Wrapper { + #[serde(deserialize_with = "untokenr")] + tokens: Vec<SemanticToken>, + } + + Ok(Option::<Wrapper>::deserialize(ser)? + .map(|wrapper| wrapper.tokens)) + } + .map(|x| Rq { result: x.map(Into::into), request: None }) +} +pub fn serialize_tokens<S: serde::Serializer>( + s: &Rq< + Box<[SemanticToken]>, + Box<[SemanticToken]>, + (), + RequestError<lsp_request!("textDocument/semanticTokens/full")>, + >, + ser: S, +) -> Result<S::Ok, S::Error> { + { + let data: &Option<Vec<SemanticToken>> = + &s.result.clone().map(|x| x.to_vec()); + #[derive(Serialize)] + #[serde(transparent)] + struct Wrapper { + #[serde(serialize_with = "tokenr")] + tokens: Vec<SemanticToken>, + } + let opt = data.as_ref().map(|t| Wrapper { tokens: t.to_vec() }); + opt.serialize(ser) + } +} impl crate::edi::Editor { pub fn poll(&mut self) { - let Some((l, ..)) = self.lsp else { return }; + lsp!(let l = self); for rq in l.req_rx.try_iter() { match rq { LRq { method: "workspace/diagnostic/refresh", .. } => { @@ -120,14 +250,7 @@ impl crate::edi::Editor { } State::CodeAction(x) => { if x.poll(|x, _| { - let lems: Vec<CodeAction> = x - .ok()?? - .into_iter() - .map(|x| match x { - CodeActionOrCommand::CodeAction(x) => x, - _ => panic!("alas we dont like these"), - }) - .collect(); + let lems = x.ok()??; if lems.is_empty() { self.bar.last_action = "no code actions available".into(); @@ -142,9 +265,10 @@ impl crate::edi::Editor { self.state = State::Default; } } + #[cfg(target_family = "unix")] State::Runnables(x) => { x.poll(|x, ((), old)| { - Some(Runnables { + Some(crate::runnables::Runnables { data: x.ok()?, ..old.unwrap_or_default() }) @@ -188,18 +312,13 @@ impl crate::edi::Editor { x.ok().map(|x| { x.and_then(|x| try { z.data.0 = match x { - GotoDefinitionResponse::Scalar( - location, + ImplementationResponse::Definition( + Definition::Location(location), ) => vec![(GoTo::from( location, ),None)], - GotoDefinitionResponse::Array( - locations, - ) => locations - .into_iter() - .map(GoTo::from).zip(repeat(None)) - .collect(), - GotoDefinitionResponse::Link( + + ImplementationResponse::DefinitionLinkList( location_links, ) => location_links .into_iter() @@ -212,6 +331,7 @@ impl crate::edi::Editor { ) }).zip(repeat(None)) .collect(), + ImplementationResponse::Definition(Definition::LocationList(x)) => {unimplemented!()}, }; }); }) @@ -251,7 +371,10 @@ impl crate::edi::Editor { } self.requests.def.poll(|x, _| { x.ok().flatten().and_then(|x| match &x { - GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()), + // DefinitionResponse::Definition(x) => Some(x.clone()), + DefinitionResponse::DefinitionLinkList([x, ..]) => + Some(x.clone()), + // DefinitionRequest::Definition([x, ..]) => Some(x.clone()), _ => None, }) }); @@ -273,8 +396,8 @@ impl crate::edi::Editor { self.requests.git_diff.poll(|x, _| x.ok()); self.requests.document_symbols.poll(|x, _| { x.ok().flatten().map(|x| match x { - DocumentSymbolResponse::Flat(_) => None, - DocumentSymbolResponse::Nested(x) => Some(x), + DocumentSymbolResponse::SymbolInformationList(_) => None, + DocumentSymbolResponse::DocumentSymbolList(x) => Some(x), }) }); } diff --git a/src/edi/lsp_mn.rs b/src/edi/lsp_mn.rs new file mode 100644 index 0000000..45cfd24 --- /dev/null +++ b/src/edi/lsp_mn.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; +use std::io::BufReader; +use std::path::PathBuf; +use std::process::Stdio; +use std::sync::Arc; + +use lsp_server::Connection; +use lsp_types::*; +use tokio::sync::oneshot::Sender; +use tree_house::Language; +use url::Url; +use winit::window::Window; + +pub struct LSPM { + pub four: HashMap<(Language, PathBuf), LoadedLSP>, +} + +pub struct LoadedLSP { + pub c: Arc<Client>, + pub iot: Option<IoThreads>, + pub comms: std::thread::JoinHandle<()>, +} +impl LoadedLSP { + pub fn status(&self) { + assert!(!self.comms.is_finished()); + } +} +mod stdio; +impl LSPM { + pub fn load( + &mut self, + workspace: &PathBuf, + l: Language, + vsc: Option<serde_json::Value>, + ) -> Option<(Arc<Client>, Option<Sender<Arc<dyn Window + 'static>>>)> + { + let ra_conn_used = self + .four + .values() + .any(|x| x.c.lsp_data.1.name == "rust-analyzer"); + let e = match self.four.entry((l, workspace.to_owned())) { + std::collections::hash_map::Entry::Occupied(x) => { + let x = x.get(); + return Some((x.c.clone(), None)); + } + std::collections::hash_map::Entry::Vacant(e) => e, + }; + let (l, w) = load(workspace, l, vsc, ra_conn_used)?; + let v = e.insert(l); + Some((v.c.clone(), Some(w))) + } +} +use crate::edi::lsp_mn::stdio::IoThreads; +use crate::lsp::Client; +pub fn load( + workspace: &PathBuf, + l: Language, + vsc: Option<serde_json::Value>, + ra_conn_used: bool, +) -> Option<(LoadedLSP, Sender<Arc<dyn Window>>)> { + let l = super::LOADER.language(l).config(); + let use_builtin_ra = l.language_id == "rust" && !ra_conn_used; + let (Connection { sender, receiver }, conf, iot) = if use_builtin_ra { + let (_jh, a) = super::ra::ra(workspace.clone()); + ( + a, + ( + &super::LOADER.language_server_configs()["rust-analyzer"], + &l.language_servers[0], + ), + None, + ) + } else { + let (mut c, conf) = l + .language_servers + .iter() + .find_map(|l| { + let lc = super::LOADER + .language_server_configs() + .get(&l.name)?; + std::process::Command::new(&lc.command) + .args(&lc.args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .ok() + .zip(Some((lc, l))) + }) + .ok_or_else(|| { + log::error!( + "no lsp for this language; install one of {:?}", + l.language_servers + ) + }) + .ok()?; + + let (x, iot) = stdio::transport( + BufReader::new(c.stdout.take().unwrap()), + c.stdin.take().unwrap(), + ); + (x, conf, Some(iot)) + }; + let (c, t2, changed) = crate::lsp::run( + (sender, receiver), + WorkspaceFolder { + uri: Url::from_file_path(&workspace).unwrap(), + name: workspace + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(), + }, + vsc.and_then(|x| x.as_object()?.get(&conf.1.name).cloned()), + conf, + ) + .unwrap(); + Some((LoadedLSP { iot, c: Arc::new(c), comms: t2 }, changed)) +} diff --git a/src/edi/lsp_mn/stdio.rs b/src/edi/lsp_mn/stdio.rs new file mode 100644 index 0000000..fa5a480 --- /dev/null +++ b/src/edi/lsp_mn/stdio.rs @@ -0,0 +1,75 @@ +use std::io::{self, Read, Stdin, Stdout, Write, stdin, stdout}; +use std::thread; + +use crossbeam_channel::{Receiver, Sender, bounded, unbounded}; +use log::trace; +use lsp_server::{Connection, Message}; +/// Creates an LSP connection via stdio. +pub fn transport( + mut read_from: impl Read + std::io::BufRead + Sync + Send + 'static, + mut write_to: impl Write + Sync + Send + 'static, +) -> (Connection, IoThreads) { + let (writer_sender, writer_receiver) = unbounded::<Message>(); + let writer = thread::Builder::new() + .name("send to lsp".to_owned()) + .spawn(move || { + loop { + let it = writer_receiver.recv().unwrap(); + trace!("sent message {it:#?}"); + let result = it.write(&mut write_to).unwrap(); + result + } + }) + .unwrap(); + let (reader_sender, reader_receiver) = bounded::<Message>(0); + let reader: thread::JoinHandle<Result<(), io::Error>> = thread::Builder::new() + .name("read from lsp".to_owned()) + .spawn(move || { + while let Some(msg) = Message::read(&mut read_from)? { + let is_exit = matches!(&msg, Message::Notification(n) if n.method == "exit"); + trace!("received message {msg:#?}"); + if let Err(e) = reader_sender.send(msg) { + return Err(io::Error::other(e)); + } + + if is_exit { + break; + } + } + Ok(()) + }) + .unwrap(); + let threads = IoThreads { reader, writer }; + ( + Connection { sender: writer_sender, receiver: reader_receiver }, + threads, + ) +} + +// Creates an IoThreads +pub(crate) fn make_io_threads( + reader: thread::JoinHandle<io::Result<()>>, + writer: thread::JoinHandle<io::Result<()>>, +) -> IoThreads { + IoThreads { reader, writer } +} + +pub struct IoThreads { + pub reader: thread::JoinHandle<io::Result<()>>, + pub writer: thread::JoinHandle<io::Result<()>>, +} + +impl IoThreads { + pub fn join(self) -> io::Result<()> { + match self.reader.join() { + Ok(r) => r?, + Err(err) => std::panic::panic_any(err), + } + match self.writer.join() { + Ok(r) => r, + Err(err) => { + std::panic::panic_any(err); + } + } + } +} diff --git a/src/edi/ra.rs b/src/edi/ra.rs index ef0c7c8..bed6931 100644 --- a/src/edi/ra.rs +++ b/src/edi/ra.rs @@ -31,7 +31,14 @@ pub fn ra( println!("RA panic @ {}", info.location().unwrap()); } })); - rust_analyzer::bin::run_server(b).into_rootcause() + + rayon::ThreadPoolBuilder::new() + .thread_name(|ix| format!("RayonWorker{}", ix)) + .build_global() + .unwrap(); + + rust_analyzer::session::run_session(b, None, None) + .into_rootcause() }) .unwrap(); (jh, a) diff --git a/src/edi/st.rs b/src/edi/st.rs index 3fe6b31..40be312 100644 --- a/src/edi/st.rs +++ b/src/edi/st.rs @@ -1,10 +1,9 @@ #![allow(dead_code, unused, unexpected_cfgs)] use Default::default; use NamedKey::*; -use lsp_types::request::HoverRequest; use lsp_types::*; use regex::Regex; -use rust_analyzer::lsp::ext::Runnable; +use rust_analyzer::lsp::ext::{CodeAction, Runnable}; use winit::event::MouseButton; use winit::keyboard::{Key, NamedKey, SmolStr}; @@ -35,6 +34,11 @@ pub enum LR { Left, Right, } + +#[cfg(target_family = "unix")] +type Run = crate::runnables::Runnables; +#[cfg(not(target_family = "unix"))] +type Run = (); rust_fsm::state_machine! { #[derive(Debug)] pub(crate) State => #[derive(Debug)] pub(crate) Action => #[derive(Debug)] pub(crate) Do @@ -78,9 +82,15 @@ Default => { K(_) => _ [Edit], M(_) => _, }, +// Matches(x) => { + // K(Key::Character("d") if ctrl()) => _ [GoToMatch], + // C(_) => Default [Reinsert], + // K(_) => Default [Reinsert], + // K(_) => Default [Reinsert], +// }, Hovered => { HOnSomething(((usize, usize)) => pos) => Hovering(Rq<Hovring, Option<Hovr>, ((usize, usize), TextDocumentPositionParams), RequestError<HoverRequest>> => default()) [SetHovering], - HOnNothing => Default, + HOnNothing => Default [R(bool => false)], }, Hovering(x) => { // now hovering over something else, cancel existing hover @@ -93,46 +103,10 @@ Hovering(x) => { C(_) => _ [Hover], MovedOut => Default, Scrolled => Default, - // reconsidering - K(Key::Character("s") if ctrl()) => Save [Save], - K(Key::Character("q") if ctrl()) => Dead [Quit], - K(Key::Character("v") if ctrl()) => Default [Paste], - K(Key::Character("z") if ctrl()) => Default [Undo], - K(Key::Character("d") if ctrl()) => Default [GoToMatch], - K(Key::Character("y") if ctrl()) => Default [Redo], - K(Key::Character("f") if ctrl()) => Procure((default(), InputRequest::Search)), - K(Key::Character("o") if ctrl()) => Procure((default(), InputRequest::OpenFile)), - K(Key::Character("c") if ctrl()) => Default , - K(Key::Character("l") if ctrl()) => Default [Symbols], - K(Key::Character(".") if ctrl()) => Default [CodeAction], - K(Key::Character("0") if ctrl()) => Default [MatchingBrace], - K(Key::Character("`") if ctrl()) => Default [SpawnTerminal], - K(Key::Character("/") if ctrl()) => Default [Comment(State => State::Default)], - K(Key::Character("p") if ctrl()) => Command(Commands => default()), - K(Key::Named(Backspace) if alt()) => Default [DeleteBracketPair], - K(Key::Named(F1)) => Procure((default(), InputRequest::RenameSymbol)), - K(Key::Named(F10)) => GoToL(GoToList => default()) [GoToImplementations], - K(Key::Named(k @ (ArrowUp | ArrowDown)) if alt()) => Default [InsertCursor(Direction => { - if k == ArrowUp {Direction::Above} else { Direction::Below } - })], - K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection [StartSelection], - M(MouseButton::Left if shift()) => Selection [StartSelection], - M(MouseButton::Left if alt()) => Default [InsertCursorAtMouse], - M(MouseButton::Left if ctrl()) => Default [GoToDefinition(Option<TextDocumentPositionParams> => None)], - M(MouseButton::Left) => Default [MoveCursor], - K(Key::Character("=") if ctrl()) => Default [NavForward], - K(Key::Character("-") if ctrl()) => Default [NavBack], - M(MouseButton::Back) => Default [NavBack], - M(MouseButton::Forward) => _ [NavForward], - C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection [StartSelection], - Changed => RequestBoolean(BoolRequest => BoolRequest::ReloadFile), - K(Key::Named(Escape)) => Default [Escape], - K(_) => Default [Edit], - // duplicate zone - + K(_) => Default [Reinsert], M(MouseButton::Left if ctrl()) => Default [GoToDefinition(x.request.map(|x| x.1.1).or(x.result.and_then(|x| x.of.iter().find_map(|x| x.tdpp()))))], - M(_) => _ [ClickedHover], + M(_) => Default [Reinsert], }, Command(_) => K(Key::Named(Escape)) => Default, Command(t) => K(Key::Named(Enter) if let Some(Ok(x)) = t.sel(None)) => Default [ProcessCommand((Commands, crate::commands::Cmd) => (t, x))], @@ -144,21 +118,32 @@ Command(mut t) => K(k) => Command({ if let Some(_) = handle2(&k, &mut t.tedit, N }; t }) [CmdTyped], Command(t) => C(_) => _, Command(t) => K(_) => _, +#[cfg(target_family = "unix")] Runnables(_x) => { + #[cfg(target_family = "unix")] K(Key::Named(Escape)) => Default, }, -Runnables(RqS::<crate::runnables::Runnables, rust_analyzer::lsp::ext::Runnables> => Rq { result: Some(mut x), request }) => { +#[cfg(target_family = "unix")] +Runnables(RqS::<Run, rust_analyzer::lsp::ext::RunnablesRequest> => Rq { result: Some(mut x), request }) => { + #[cfg(target_family = "unix")] K(Key::Named(Tab) if shift()) => Runnables({ x.next(); Rq { result: Some(x), request }}), + #[cfg(target_family = "unix")] K(Key::Named(ArrowDown)) => Runnables({ x.next(); Rq { result: Some(x), request }}), + #[cfg(target_family = "unix")] K(Key::Named(ArrowUp | Tab)) => Runnables({ x.back(); Rq { result: Some(x), request }}), + #[cfg(target_family = "unix")] K(Key::Named(Enter) if let Some(Ok(x_)) = x.clone().sel(None)) => Default [Run(Runnable => x_.clone())], + #[cfg(target_family = "unix")] K(k) => Runnables({ if let Some(_) = handle2(&k, &mut x.tedit, None) { x.selection = 0; x.vo = 0; }; Rq { result: Some(x), request } }), }, +#[cfg(target_family = "unix")] Runnables(_x) => { + #[cfg(target_family = "unix")] C(_) => _, + #[cfg(target_family = "unix")] M(_) => _, }, @@ -186,6 +171,20 @@ Symbols(Rq::<Symbols, Option<SymbolsList>, (), AQErr> => _rq) => { C(_) => _, M(_) => _, }, + + +KillRing(crate::killring::KillRM => x) => { + K(Key::Named(Tab) if shift()) => KillRing({ let mut x = x; x.next(); x }), + K(Key::Named(ArrowDown)) => KillRing({ let mut x = x; x.next(); x }), + K(Key::Named(ArrowUp | Tab)) => KillRing({ let mut x = x; x.back(); x }), + K(Key::Named(Enter)) => Default [Revive(crate::killring::KillRM => x)], + K(Key::Named(Escape)) => Default, + K(_) => _ [KillRMHandleKey], + // K(_) => _ [GTLHandleKey], + C(_) => _, + M(_) => _, +}, + CodeAction(Rq { result : Some(_x), request }) => { K(Key::Named(Tab) if shift()) => _ [CASelectPrev], K(Key::Named(ArrowDown | Tab)) => _ [CASelectNext], @@ -193,7 +192,7 @@ CodeAction(Rq { result : Some(_x), request }) => { K(Key::Named(Enter | ArrowRight)) => _ [CASelectRight], K(Key::Named(ArrowLeft)) => _ [CASelectLeft], }, -CodeAction(RqS<act::CodeActions, lsp_request!("textDocument/codeAction")> => rq) => { +CodeAction(RqS<act::CodeActions, rust_analyzer::lsp::ext::CodeActionRequest> => rq) => { K(Key::Named(Escape)) => Default, C(_) => _, M(_) => _, diff --git a/src/edi/wsedit.rs b/src/edi/wsedit.rs index eb11a8b..55e0827 100644 --- a/src/edi/wsedit.rs +++ b/src/edi/wsedit.rs @@ -5,6 +5,10 @@ use lsp_types::*; use rootcause::prelude::{IteratorExt, ResultExt}; use rootcause::report; use ropey::Rope; +use rust_analyzer::lsp::ext::{ + SnippetDocumentChangeOperation, SnippetTextDocumentEdit, + SnippetWorkspaceEdit, +}; use super::*; use crate::error::WDebug; @@ -52,23 +56,221 @@ impl Editor { ) -> rootcause::Result<()> { self.apply_tds( &text_document + .text_document_identifier .uri .to_file_path() .map_err(|_| report!("sad"))?, edits, - TextArea::apply_snippet_tedit, - TextArea::apply_snippet_tedit_raw, + |t, e| match e { + Edit::AnnotatedTextEdit(AnnotatedTextEdit { + text_edit, + .. + }) + | Edit::TextEdit(text_edit) => + Ok(drop(t.apply(text_edit)?)), + Edit::SnippetTextEdit(SnippetTextEdit { + range, + snippet, + annotation_id, + }) => { + let x2 = rust_analyzer::lsp::ext::SnippetTextEdit { + range: *range, + new_text: snippet.value.clone(), + insert_text_format: None, + annotation_id: annotation_id.clone(), + }; + t.apply_snippet_tedit(&x2) + } + }, + |e, t| match e { + Edit::AnnotatedTextEdit(AnnotatedTextEdit { + text_edit, + .. + }) + | Edit::TextEdit(text_edit) => + TextArea::apply_raw(text_edit, t), + Edit::SnippetTextEdit(SnippetTextEdit { + range, + snippet, + annotation_id, + }) => { + let x2 = rust_analyzer::lsp::ext::SnippetTextEdit { + range: *range, + new_text: snippet.value.clone(), + insert_text_format: None, + annotation_id: annotation_id.clone(), + }; + TextArea::apply_snippet_tedit_raw(&x2, t) + } + }, + // TextArea::apply_snippet_tedit, + // TextArea::apply_snippet_tedit_raw, ) } - fn apply_dco( + fn apply_sdc( &mut self, - op: DocumentChangeOperation, + op: SnippetDocumentChangeOperation, ) -> rootcause::Result<()> { match op.clone() { - DocumentChangeOperation::Edit(t) => self.apply_tde(t)?, - DocumentChangeOperation::Op(ResourceOp::Create( - CreateFile { uri, options, .. }, - )) => { + SnippetDocumentChangeOperation::Edit( + SnippetTextDocumentEdit { text_document, edits }, + ) => self.apply_tds( + &text_document + .text_document_identifier + .uri + .to_file_path() + .map_err(|_| report!("sad"))?, + edits, + TextArea::apply_snippet_tedit, + TextArea::apply_snippet_tedit_raw, + ), + SnippetDocumentChangeOperation::Change(dc) => + self.apply_dc(dc), + } + } + // pub fn apply_sdc( + // &mut self, + // x: SnippetDocumentChangeOperation, + // ) -> rootcause::Result<()> { + // match x { + // // self.apply_tde(x) + // DocumentChange::TextDocumentEdit(text_document_edit) => + // self.apply_tde(text_document_edit)?, + // DocumentChange::CreateFile(CreateFile { + // uri, + // options, + // .. + // }) => { + // let f = uri + // .to_file_path() + // .map_err(|()| report!("not path"))?; + // let mut opts = OpenOptions::new(); + // opts.write(true) + // // .create(true) + // .create(matches!( + // options, + // Some(CreateFileOptions { + // ignore_if_exists: Some(true) | None, + // .. + // }) | None + // )) + // .create_new(matches!( + // options, + // Some(CreateFileOptions { + // ignore_if_exists: Some(false), + // .. + // }) + // )) + // .truncate(true); + // match opts.open(&f) { + // Ok(_f) => {} + // Err(e) + // if let Some(CreateFileOptions { + // overwrite: Some(true), + // .. + // }) = options + // && e.kind() + // == std::io::ErrorKind::AlreadyExists => {} + // Err(e) => + // Err(e) + // .context_custom::<WDebug, _>(f) + // .context_custom::<WDebug, _>((opts, options))?, + // }; + // } + // DocumentChange::RenameFile(RenameFile { + // old_uri, + // new_uri, + // options, + // annotation_id, + // }) => { + // let old_f = old_uri + // .to_file_path() + // .map_err(|()| report!("not path"))?; + // let new_f = new_uri + // .to_file_path() + // .map_err(|()| report!("not path"))?; + + // // FIXME: overwrite + // if let Some(p) = new_f.parent() + // && !p.exists() + // { + // std::fs::create_dir_all(p)?; + // } + // match std::fs::rename(&old_f, &new_f) { + // Err(e) + // if e.kind() + // == std::io::ErrorKind::IsADirectory => + // { + // do yeet report!("unhandled case: dir rename"); + // } + // Err(e) + // if let Some(RenameFileOptions { + // ignore_if_exists: Some(true), + // .. + // }) = options + // && e.kind() + // == std::io::ErrorKind::AlreadyExists => {} + // Ok(_) => {} + // Err(e) => Err(e).context_with(|| { + // format!("renaming {old_f:?} to {new_f:?}") + // })?, + // } + // if let Some(f) = self.files.get_mut(&old_f) + // // .or(Some(self).filter(|x| x.origin == Some(old_f))) + // { + // f.bar.last_action = + // annotation_id.unwrap_or("renamed".into()); + // f.origin = Some(new_f); + // f.mtime = Editor::modify(f.origin.as_deref()); + // } else if self.origin == Some(old_f) { + // self.bar.last_action = + // annotation_id.unwrap_or("renamed".into()); + // self.origin = Some(new_f); + // self.mtime = Editor::modify(self.origin.as_deref()); + // } + // } + // DocumentChange::DeleteFile(DeleteFile { + // uri, + // options, + // .. + // }) => { + // let f = uri + // .to_file_path() + // .map_err(|()| report!("not path"))?; + // match std::fs::remove_file(&f) { + // Ok(()) => (), + // Err(e) + // if e.kind() + // == std::io::ErrorKind::IsADirectory => + // { + // std::fs::remove_dir_all(f)?; + // } + // Err(e) + // if let Some(DeleteFileOptions { + // ignore_if_not_exists: Some(true), + // .. + // }) = options + // && e.kind() + // == std::io::ErrorKind::NotFound => {} + // Err(e) => do yeet e, + // } + // } + // }; + // Ok(()) + // } + pub fn apply_dc( + &mut self, + x: DocumentChange, + ) -> rootcause::Result<()> { + match x { + // self.apply_tde(x) + DocumentChange::TextDocumentEdit(text_document_edit) => + self.apply_tde(text_document_edit)?, + DocumentChange::CreateFile(CreateFile { + uri, + options, + .. + }) => { let f = uri .to_file_path() .map_err(|()| report!("not path"))?; @@ -105,9 +307,12 @@ impl Editor { .context_custom::<WDebug, _>((opts, options))?, }; } - DocumentChangeOperation::Op(ResourceOp::Rename( - RenameFile { old_uri, new_uri, options, annotation_id }, - )) => { + DocumentChange::RenameFile(RenameFile { + old_uri, + new_uri, + options, + annotation_id, + }) => { let old_f = old_uri .to_file_path() .map_err(|()| report!("not path"))?; @@ -154,31 +359,11 @@ impl Editor { self.mtime = Editor::modify(self.origin.as_deref()); } } - DocumentChangeOperation::Op(ResourceOp::Delete( - DeleteFile { - uri, - options: - Some(DeleteFileOptions { - recursive: Some(true), // ??? seems redundant - ignore_if_not_exists, - .. - }), - }, - )) => { - let f = uri - .to_file_path() - .map_err(|()| report!("not path"))?; - match std::fs::remove_dir_all(f) { - Ok(()) => (), - Err(e) - if e.kind() == std::io::ErrorKind::NotFound - && ignore_if_not_exists == Some(true) => {} - Err(e) => do yeet e, - } - } - DocumentChangeOperation::Op(ResourceOp::Delete( - DeleteFile { uri, options }, - )) => { + DocumentChange::DeleteFile(DeleteFile { + uri, + options, + .. + }) => { let f = uri .to_file_path() .map_err(|()| report!("not path"))?; @@ -200,8 +385,41 @@ impl Editor { Err(e) => do yeet e, } } + }; + Ok(()) + } + pub fn apply_swsedit( + &mut self, + x: SnippetWorkspaceEdit, + ) -> rootcause::Result<()> { + match x { + SnippetWorkspaceEdit { document_changes: Some(x), .. } => x + .into_iter() + .map(|x| self.apply_sdc(x)) + .collect_reports() + .context("couldnt apply one or more wsedits")?, + SnippetWorkspaceEdit { changes: Some(x), .. } => x + .into_iter() + .map(|(p, e)| { + self.apply_tds( + &p.to_file_path().map_err(|_| { + report!("evil path") + .context_custom::<WDebug, _>(e.clone()) + })?, + e.clone(), + |x, y| x.apply(y).map(drop), + TextArea::apply_raw, + ) + .context_custom::<WDebug, _>(e) + }) + .collect_reports() + .context("couldnt apply one or more fs operations")?, + x => + do yeet report!("strange workspace edit") + .context_custom::<WDebug, _>(x), } - + change!(self); + self.hist.record(&self.text); Ok(()) } #[must_use] @@ -210,27 +428,11 @@ impl Editor { x: WorkspaceEdit, ) -> rootcause::Result<()> { match x { - WorkspaceEdit { - document_changes: Some(DocumentChanges::Edits(x)), - .. - } => x + WorkspaceEdit { document_changes: Some(x), .. } => x .into_iter() - .map(|x| self.apply_tde(x)) + .map(|x| self.apply_dc(x)) .collect_reports() .context("couldnt apply one or more wsedits")?, - WorkspaceEdit { - document_changes: Some(DocumentChanges::Operations(x)), - .. - } => x - .clone() - .into_iter() - .map(|op: DocumentChangeOperation| { - self.apply_dco(op.clone()) - .context_custom::<WDebug, _>(op) - }) - .collect_reports() - .context("couldnt apply one or more fs operations") - .context_custom::<WDebug, _>(x)?, WorkspaceEdit { changes: Some(x), .. } => x .into_iter() .map(|(p, e)| { diff --git a/src/gotolist.rs b/src/gotolist.rs index 2e7b304..1999446 100644 --- a/src/gotolist.rs +++ b/src/gotolist.rs @@ -4,10 +4,10 @@ use std::path::Path; use dsb::Cell; use dsb::cell::Style; -use lsp_types::request::GotoImplementation; use lsp_types::{ - CallHierarchyIncomingCall, CallHierarchyOutgoingCall, Location, - LocationLink, Range, + CallHierarchyIncomingCall, CallHierarchyOutgoingCall, Definition, + DefinitionResponse, ImplementationRequest, Location, LocationLink, + LocationUriOnly, Range, WorkspaceSymbolLocation, }; use crate::FG; @@ -20,8 +20,8 @@ pub type GoToList = GenericMenu<GTL>; pub enum GTL {} #[derive(Debug)] pub enum O { - Impl(RqS<(), GotoImplementation>), - References(RqS<(), lsp_types::request::References>), + Impl(RqS<(), ImplementationRequest>), + References(RqS<(), lsp_types::ReferencesRequest>), Incoming( Rq<(), Vec<CallHierarchyIncomingCall>, (), rootcause::Report>, ), @@ -38,6 +38,7 @@ impl<'a> Key<'a> for (GoTo<'a>, Option<&'a str>) { } impl MenuData for GTL { + const NAME: &'static str = "!"; type Data = (Vec<(GoTo<'static>, Option<String>)>, Option<O>); type Element<'a> = (GoTo<'a>, Option<&'a str>); @@ -117,6 +118,22 @@ impl<'a> Hash for GoTo<'a> { self.path.hash(state); } } +impl From<&LocationUriOnly> for GoTo<'static> { + fn from(LocationUriOnly { uri }: &LocationUriOnly) -> Self { + Self { + path: Cow::Owned(uri.to_file_path().unwrap()), + at: At::P(0), + } + } +} +impl<'a> From<&'a WorkspaceSymbolLocation> for GoTo<'static> { + fn from(value: &'a WorkspaceSymbolLocation) -> Self { + match value { + WorkspaceSymbolLocation::Location(location) => location.into(), + WorkspaceSymbolLocation::LocationUriOnly(x) => x.into(), + } + } +} impl From<Location> for GoTo<'static> { fn from(Location { uri, range }: Location) -> Self { Self { @@ -134,6 +151,16 @@ impl From<&Location> for GoTo<'static> { } } +impl From<&Definition> for GoTo<'static> { + fn from(x: &Definition) -> Self { + match x { + Definition::LocationList([x]) | Definition::Location(x) => + x.into(), + _ => panic!(), + } + } +} + impl From<&LocationLink> for GoTo<'static> { fn from( LocationLink { target_uri, target_range, .. }: &LocationLink, @@ -1,6 +1,5 @@ use std::fmt::Debug; use std::iter::{empty, once, repeat_n}; -use std::os::fd::AsFd; use std::pin::pin; use std::str::FromStr; use std::sync::Arc; @@ -310,7 +309,7 @@ fn t() { let (w, h) = (400, 8000); let (c, r) = dsb::fit(&crate::FONT, ppem, lh, (w, h)); - let cells = markdown2(c, &p(include_str!("vec.md")).unwrap()); + let cells = markdown2(c, &p(include_str!("vec.md")).unwrap(), None); dbg!(l(&p(include_str!("vec.md")).unwrap())); dbg!(cells.len() / c); dbg!(w, h); @@ -399,7 +398,13 @@ impl DiagnosticHovr { simplify_path(&dawg.replace('\n', "\r\n").replace("⸬", ":")) .bytes() { - t.rx(b, std::fs::File::open("/dev/null").unwrap().as_fd()); + t.rx( + b, + #[cfg(target_family = "unix")] + std::os::fd::AsFd::as_fd( + &std::fs::File::open("/dev/null").unwrap(), + ), + ); } let y_lim = t .cells @@ -491,12 +496,15 @@ pub fn to_snippet<'a: 'c, 'b: 'c, 'c>( ) -> annotate_snippets::Group<'c> { use annotate_snippets::*; match severity { - Some(DiagnosticSeverity::WARNING) => Level::WARNING, - Some(DiagnosticSeverity::INFORMATION) => Level::INFO, - Some(DiagnosticSeverity::HINT) => Level::HELP, + Some(DiagnosticSeverity::Warning) => Level::WARNING, + Some(DiagnosticSeverity::Information) => Level::INFO, + Some(DiagnosticSeverity::Hint) => Level::HELP, _ => Level::ERROR, } - .primary_title(message) + .primary_title(match message { + lsp_types::Message::String(x) => x, + lsp_types::Message::MarkupContent(_) => unimplemented!(), + }) .element(Snippet::source(t).annotation( AnnotationKind::Primary.span(t.l_range(*range).unwrap()), )) diff --git a/src/killring.rs b/src/killring.rs new file mode 100644 index 0000000..5f6438d --- /dev/null +++ b/src/killring.rs @@ -0,0 +1,58 @@ +use std::path::Path; + +use dsb::Cell; +use dsb::cell::Style; + +use crate::menu::Key; +use crate::menu::generic::{GenericMenu, MenuData}; +use crate::text::{col, color_}; +use crate::{FG, KillRing}; +impl<'a> Key<'a> for &'a [String] { + fn key(&self) -> impl Into<std::borrow::Cow<'a, str>> { + self.join("") + } +} +pub enum KillR {} +impl MenuData for KillR { + const NAME: &'static str = "symbols"; + type Data = KillRing; + + type Element<'a> = &'a [String]; + + fn gn(d: &Self::Data) -> impl Iterator<Item = Self::Element<'_>> { + println!("x{d:?}"); + d.iter() + .inspect(|x| { + dbg!(&x); + }) + .map(|x| &**x) + } + + fn r( + _: &Self::Data, + x: Self::Element<'_>, + _: &Path, + c: usize, + selected: bool, + indices: &[u32], + to: &mut Vec<Cell>, + ) { + let bg = if selected { col!("#262d3b") } else { col!("#1c212b") }; + let ds: Style = Style::new(FG, bg); + let mut b = vec![Cell { style: ds, letter: None }; c]; + x.join("").chars().zip(&mut b).zip(1..).for_each( + |((x, y), i)| { + y.letter = Some(x); + if indices.contains(&i) { + y.style |= (Style::BOLD, color_("#ffcc66")); + } + }, + ); + to.extend(b); + } + + fn hash<'b>(x: &'b Self::Element<'_>) -> Option<impl std::hash::Hash> { + Some(x) + } +} +pub type KillRM = GenericMenu<KillR>; @@ -10,15 +10,14 @@ use helix_core::syntax::config::{ LanguageServerConfiguration, LanguageServerFeatures, }; use json_value_merge::Merge; +use log::info; use lsp_server::Message; -use lsp_types::notification::*; -use lsp_types::request::*; use lsp_types::*; use rootcause::report; use tokio::sync::oneshot; use winit::window::Window; mod client; -mod communication; +pub(crate) mod communication; pub use client::*; mod init_opts; mod rq; @@ -44,12 +43,14 @@ pub fn run( let (not_tx, not_rx) = unbounded(); let (_req_tx, _req_rx) = unbounded(); let (window_tx, window_rx) = oneshot::channel::<Arc<dyn Window>>(); + let mut rt = tokio::runtime::Builder::new_multi_thread(); + #[cfg(target_family = "unix")] + rt.enable_io(); let mut c = Client { - tx, + tx: Tx(tx), progress: Box::leak(Box::new(papaya::HashMap::new())), - runtime: tokio::runtime::Builder::new_multi_thread() + runtime: rt .enable_time() - .enable_io() .worker_threads(3) .thread_name("lsp runtime") .build()?, @@ -60,24 +61,31 @@ pub fn run( req_rx: _req_rx, not_rx, lsp_data: data, + workspace: workspace.clone(), }; let mut opts = init_opts::get( workspace, &data.0.config, data.1.name == "rust-analyzer", ); + if let Some(v) = vscode_conf { match opts.initialization_options { Some(ref mut x) => x.merge(&v), None => opts.initialization_options = Some(v), } }; - _ = c.request::<Initialize>(&opts)?; + info!("spawned {}", serde_json::to_string_pretty(&opts).unwrap()); + _ = c.request::<InitializeRequest>(&opts)?; let x = rx .iter() .find_map(|x| { serde_json::from_value::<InitializeResult>( - x.response()?.result?, + match x { + Message::Notification(_) | Message::Request(_) => None, + Message::Response(response) => Some(response), + }? + .result?, ) .ok() }) @@ -88,10 +96,8 @@ pub fn run( // Some(PositionEncodingKind::UTF8) // ); c.initialized = Some(x); - c.notify::<lsp_types::notification::Initialized>( - &InitializedParams {}, - )?; - c.notify::<SetTrace>(&SetTraceParams { + c.notify::<lsp_types::InitializedNotification>(&InitializedParams {})?; + c.notify::<SetTraceNotification>(&SetTraceParams { value: lsp_types::TraceValue::Verbose, })?; let progress = c.progress; diff --git a/src/lsp/client.rs b/src/lsp/client.rs index 9bc85b5..ddbe7c4 100644 --- a/src/lsp/client.rs +++ b/src/lsp/client.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; @@ -13,24 +14,39 @@ use log::debug; use lsp_server::{ Message, Notification as N, Request as LRq, Response as Re, }; -use lsp_types::notification::*; -use lsp_types::request::*; use lsp_types::*; use rootcause::option_ext::OptionExt; use rust_analyzer::lsp::ext::*; use tokio::sync::oneshot; use ttools::*; +use url::Url; use crate::lsp::BehaviourAfter::{self, *}; -use crate::lsp::{RequestError, Require, Requiring, Rq}; +use crate::lsp::communication::{WorkDoneProgress, send}; +use crate::lsp::{RequestError, Require, Requiring, Rq, RqSendError}; use crate::text::cursor::ceach; use crate::text::{LOADER, RopeExt, SortTedits, TextArea}; +#[derive(Debug, Clone)] +pub struct Tx(pub Sender<Message>); +impl std::ops::Deref for Client { + type Target = Tx; + fn deref(&self) -> &Self::Target { + &self.tx + } +} +impl std::ops::Deref for Tx { + type Target = Sender<Message>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Debug)] pub struct Client { pub runtime: tokio::runtime::Runtime, - pub tx: Sender<Message>, + pub tx: Tx, pub id: AtomicI32, pub initialized: Option<InitializeResult>, // pub pending: HashMap<i32, oneshot::Sender<Re>>, @@ -39,7 +55,7 @@ pub struct Client { ProgressToken, Option<(WorkDoneProgress, WorkDoneProgressBegin)>, >, - pub diagnostics: &'static papaya::HashMap<Url, Vec<Diagnostic>>, + pub diagnostics: &'static papaya::HashMap<url::Url, Vec<Diagnostic>>, #[allow(dead_code)] // TODO: handle notifications from the server pub not_rx: Receiver<N>, @@ -49,11 +65,17 @@ pub struct Client { &'static LanguageServerConfiguration, &'static LanguageServerFeatures, ), + pub workspace: WorkspaceFolder, } impl Drop for Client { fn drop(&mut self) { - panic!("please dont"); + _ = self.notify::<ExitNotification>(&()); + println!( + "dropped lsp({}) @ {}", + self.lsp_data.1.name, self.workspace.uri + ); + // panic!("please dont"); } } @@ -68,22 +90,25 @@ impl Client { l: helix_core::Language, ) -> Result<(), SendError<Message>> { let l = LOADER.language(l).config(); - self.notify::<DidOpenTextDocument>(&DidOpenTextDocumentParams { - text_document: TextDocumentItem { - uri: url::Url::from_file_path(f).unwrap(), - language_id: l - .language_server_language_id - .clone() - .unwrap_or(l.language_id.clone()), - version: 0, - text, + self.notify::<DidOpenTextDocumentNotification>( + &DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: url::Url::from_file_path(f).unwrap(), + language_id: l + .language_server_language_id + .clone() + .unwrap_or(l.language_id.clone()) + .into(), + version: 0, + text, + }, }, - }) + ) } pub fn close(&self, f: &Path) -> Result<(), SendError<Message>> { - self.notify::<DidCloseTextDocument>(&DidCloseTextDocumentParams { - text_document: f.tid(), - }) + self.notify::<DidCloseTextDocumentNotification>( + &DidCloseTextDocumentParams { text_document: f.tid() }, + ) } pub fn edit( &self, @@ -91,17 +116,16 @@ impl Client { text: String, ) -> Result<(), SendError<Message>> { static V: AtomicI32 = AtomicI32::new(0); - self.notify::<lsp_types::notification::DidChangeTextDocument>( + self.notify::<lsp_types::DidChangeTextDocumentNotification>( &DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier { - uri: url::Url::from_file_path(f).unwrap(), + text_document_identifier: f.tid(), version: V.fetch_add(1, Relaxed), }, - content_changes: vec![TextDocumentContentChangeEvent { - range: None, - range_length: None, - text, - }], + content_changes: vec![ + (TextDocumentContentChangeWholeDocument { text }) + .into(), + ], }, ) } @@ -109,8 +133,13 @@ impl Client { pub fn resolve( &self, x: CompletionItem, - ) -> Result<CompletionItem, RequestError<ResolveCompletionItem>> { - self.request_immediate::<ResolveCompletionItem>(&x) + ) -> Result< + CompletionItem, + RequestError<lsp_request!("completionItem/resolve")>, + > { + self.request_immediate::<lsp_request!("completionItem/resolve")>( + &x, + ) } pub fn request_complete<'me>( @@ -123,18 +152,15 @@ impl Client { impl Future< Output = Result< Option<CompletionResponse>, - RequestError<Completion>, + RequestError<lsp_request!("textDocument/completion")>, >, - > + use<'me>, + > + use<>, > { self.caps().completion_provider.require()?; let (rx, _) = self - .request_::<Completion, { Redraw }>(&CompletionParams { - text_document_position: TextDocumentPositionParams { - text_document: f.tid(), - position: Position { line: y as _, character: x as _ }, - }, + .request_::<lsp_request!("textDocument/completion"), { Redraw }>(&CompletionParams { + text_document_position_params: TextDocumentPositionParams::new(f.tid(), Position { line: y as _, character: x as _ }), work_done_progress_params: default(), partial_result_params: default(), context: Some(c), @@ -154,7 +180,7 @@ impl Client { Option<SignatureHelp>, RequestError<SignatureHelpRequest>, >, - > + use<'me>, + > + use<>, > { self.caps().signature_help_provider.require()?; Ok(self @@ -246,7 +272,7 @@ impl Client { .diagnostic_provider .as_ref()? { - DiagnosticServerCapabilities::RegistrationOptions( + DiagnosticProvider::DiagnosticRegistrationOptions( x, ) => x.diagnostic_options.identifier.clone()?, _ => None?, @@ -271,8 +297,8 @@ impl Client { }; // dbg!(&x); match x.clone() { - DocumentDiagnosticReportResult::Report( - DocumentDiagnosticReport::Full( + // DocumentDiagnosticReport::DocumentDiagnosticReport( + DocumentDiagnosticReport::RelatedFullDocumentDiagnosticReport( RelatedFullDocumentDiagnosticReport { related_documents, full_document_diagnostic_report: @@ -281,7 +307,7 @@ impl Client { result_id, }, }, - ), + ) => { let l = self.diagnostics.guard(); self.diagnostics.insert(f.tid().uri, items, &l); @@ -289,14 +315,14 @@ impl Client { related_documents.into_iter().flatten() { match rel { - DocumentDiagnosticReportKind::Full( + RelatedDocument::FullDocumentDiagnosticReport( FullDocumentDiagnosticReport { items, .. }, ) => { self.diagnostics.insert(uri, items, &l); } - DocumentDiagnosticReportKind::Unchanged(_) => { + RelatedDocument::UnchangedDocumentDiagnosticReport(_) => { } } } @@ -318,24 +344,23 @@ impl Client { Vec<DocumentHighlight>, RequestError<DocumentHighlightRequest>, >, - > + use<'me>, + > + use<>, > { self.caps().document_highlight_provider.require()?; - let p = DocumentHighlightParams { + Ok(send!(self.request_::<{Redraw}>, "textDocument/documentHighlight", _ { text_document_position_params: TextDocumentPositionParams { text_document: f.tid(), position: cursor, }, work_done_progress_params: default(), partial_result_params: default(), - }; - Ok(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, + &self, p: &Path, ) -> Requiring< "document_symbol", @@ -347,8 +372,7 @@ impl Client { > + use<>, > { self.caps().document_symbol_provider.require()?; - Ok(self.request_::<lsp_request!("textDocument/documentSymbol"), { Redraw }>( - &DocumentSymbolParams { + Ok(send!(self.request_::<{ Redraw }>, "textDocument/documentSymbol", _ { text_document: p.tid(), work_done_progress_params: default(), partial_result_params: default(), @@ -358,27 +382,29 @@ impl Client { .0) } pub fn workspace_symbols( - &'static self, + &self, f: String, ) -> Requiring< "workspace_symbol", impl Future< Output = Result< Option<WorkspaceSymbolResponse>, - RequestError<lsp_request!("workspace/symbol")>, + RequestError< + rust_analyzer::lsp::ext::WorkspaceSymbolRequest, + >, >, - >, + > + use<>, > { self.caps().workspace_symbol_provider.require()?; Ok(self - .request_::<lsp_request!("workspace/symbol"), { Redraw }>( - &lsp_types::WorkspaceSymbolParams { + .request_::<rust_analyzer::lsp::ext::WorkspaceSymbolRequest, { Redraw }>( + &rust_analyzer::lsp::ext::WorkspaceSymbolParams { query: f, search_scope: Some( - lsp_types::WorkspaceSymbolSearchScope::Workspace, + WorkspaceSymbolSearchScope::Workspace, ), search_kind: Some( - lsp_types::WorkspaceSymbolSearchKind::AllSymbols, + WorkspaceSymbolSearchKind::AllSymbols, ), ..Default::default() }, @@ -391,36 +417,38 @@ impl Client { &self, f: &Path, x: Vec<Position>, - ) -> Result<Vec<Option<Position>>, RequestError<MatchingBrace>> { - self.request_immediate::<MatchingBrace>(&MatchingBraceParams { - text_document: f.tid(), - positions: x, - }) + ) -> Result<Vec<[Position;2]>, RequestError<MatchingBraceRequest>> { + self.request_immediate::<MatchingBraceRequest>( + &MatchingBraceParams { text_document: f.tid(), positions: x }, + ) } - pub fn matching_brace<'a>( - &'static self, - f: &Path, - t: &'a mut TextArea, - ) { + pub fn matching_brace<'a>(&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).unwrap(); - } + // if let Some(p) = p { + c.position = t.rope.l_position(p[1]).unwrap(); + // } } } } pub fn legend(&self) -> Option<&SemanticTokensLegend> { - match &self.caps(){ - ServerCapabilities {semantic_tokens_provider:Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{legend,..})), ..}=> {Some(legend)},_ => None, + match &self.caps() { + ServerCapabilities { + semantic_tokens_provider: + Some(SemanticTokensProvider::SemanticTokensOptions( + SemanticTokensOptions { legend, .. }, + )), + .. + } => Some(legend), + _ => None, } } pub fn inlay( - &'static self, + &self, f: &Path, t: &TextArea, ) -> Requiring< @@ -433,7 +461,7 @@ impl Client { > + use<>, > { self.caps().inlay_hint_provider.require()?; - Ok(self.request_::<lsp_request!("textDocument/inlayHint"), { Redraw }>(&InlayHintParams { + Ok(send!(self.request_::<{ Redraw }>, "textDocument/inlayHint", _ { work_done_progress_params: default(), text_document: f.tid(), range: t.to_l_range(lower::saturating::math!{ @@ -456,48 +484,49 @@ impl Client { // } } pub fn format( - &'static self, + &self, f: &Path, ) -> Requiring< "document_formatting", impl Future< Output = Result< Option<Vec<TextEdit>>, - RequestError<Formatting>, + RequestError<lsp_request!("textDocument/formatting")>, >, >, > { self.caps().document_formatting_provider.require()?; - Ok(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) + Ok(send!(self.request, "textDocument/formatting", + _ { + 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, + &self, to: &mut Rq< Box<[SemanticToken]>, Box<[SemanticToken]>, (), - RequestError<SemanticTokensFullRequest>, + RequestError<lsp_request!("textDocument/semanticTokens/full")>, >, f: &Path, ) -> Requiring< "semantic_tokens", - Result<(), RequestError<SemanticTokensFullRequest>>, + Result< + (), + RequestError<lsp_request!("textDocument/semanticTokens/full")>, + >, > { self.caps().semantic_tokens_provider.require()?; debug!("requested semantic tokens"); @@ -506,25 +535,23 @@ impl Client { // return Ok(()); // }; Ok(try bikeshed Result<(), RequestError<_>> { - let (rx, _) = self.request::<SemanticTokensFullRequest>( - &SemanticTokensParams { - work_done_progress_params: default(), - partial_result_params: default(), - text_document: f.tid(), - }, - )?; + let (rx, _) = send!(self.request, "textDocument/semanticTokens/full", _ { + 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?.ok_or(RequestError::Rx(std::marker::PhantomData))?; 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) + // let r = match y { + // SemanticTokensResult::Partial(_) => + // panic!("i told the lsp i dont support this"), + // SemanticTokensResult::Tokens(x) => + // x.data.into_boxed_slice(), + // }; + Ok(y.data.into_boxed_slice()) }); to.request(x); }) @@ -537,15 +564,17 @@ impl Client { ) -> rootcause::Result<()> { ceach!(t.cursor, |c| try bikeshed rootcause::Result<()> { let r = self - .request_immediate::<OnEnter>( + .request_by::<OnEnterRequest>( &TextDocumentPositionParams { text_document: f.tid(), position: t.to_l_position(*c).unwrap(), }, + acceptable_duration(), ); match r { - Ok(None) | Err(_) => t.enter(), - Ok(Some(mut r)) => { + Ok(Ok(None)) | Err(_) | Ok(Err(_)) => { println!("hmm") ;t.enter() }, + Ok(Ok(Some(mut r))) => { + println!("applying"); r.sort_tedits(); for f in r { t.apply_snippet_tedit(&f)?; @@ -556,15 +585,16 @@ impl Client { Ok(()) } pub fn runnables( - &'static self, + &self, t: &Path, c: Option<Position>, ) -> Result< - impl Future<Output = Result<Vec<Runnable>, RequestError<Runnables>>> - + use<>, - SendError<Message>, + impl Future< + Output = Result<Vec<Runnable>, RequestError<RunnablesRequest>>, + > + use<>, + RqSendError<RunnablesRequest>, > { - self.request::<Runnables>(&RunnablesParams { + self.request::<RunnablesRequest>(&RunnablesParams { text_document: t.tid(), position: c, }) @@ -572,19 +602,19 @@ impl Client { } pub fn _child_modules( - &'static self, + &self, p: Position, t: &Path, ) -> Result< impl Future< Output = Result< - <ChildModules as Request>::Result, - RequestError<ChildModules>, + <ChildModulesRequest as Request>::Result, + RequestError<ChildModulesRequest>, >, >, - SendError<Message>, + RqSendError<ChildModulesRequest>, > { - self.request::<ChildModules>(&TextDocumentPositionParams { + self.request::<ChildModulesRequest>(&TextDocumentPositionParams { position: p, text_document: t.tid(), }) @@ -596,17 +626,19 @@ impl Client { ) -> Result< impl Future< Output = Result< - <GotoImplementation as Request>::Result, - RequestError<GotoImplementation>, + <lsp_request!("textDocument/implementation") as Request>::Result, + RequestError<lsp_request!("textDocument/implementation")>, >, - >, - SendError<Message>, - > { - self.request::<GotoImplementation>(&GotoImplementationParams { - text_document_position_params: tdpp, - work_done_progress_params: default(), - partial_result_params: default(), - }) + > + use<>, + RqSendError<lsp_request!("textDocument/implementation")>, + >{ + send!(self.request, "textDocument/implementation", + _ { + text_document_position_params: tdpp, + work_done_progress_params: default(), + partial_result_params: default(), + }, + ) .map(fst) } @@ -617,17 +649,19 @@ impl Client { impl Future< Output = Result< Option<Vec<Location>>, - RequestError<References>, + RequestError<lsp_request!("textDocument/references")>, >, - >, - SendError<Message>, + > + use<>, + RqSendError<lsp_request!("textDocument/references")>, > { - self.request::<References>(&ReferenceParams { - text_document_position: tdpp, - work_done_progress_params: default(), - partial_result_params: default(), - context: ReferenceContext { include_declaration: false }, - }) + send!(self.request, "textDocument/references", + _ { + text_document_position_params: tdpp, + work_done_progress_params: default(), + partial_result_params: default(), + context: ReferenceContext { include_declaration: false }, + }, + ) .map(fst) } @@ -646,24 +680,28 @@ impl Client { impl Future< Output = Result< Option<CallHierarchyItem>, - RequestError<CallHierarchyPrepare>, + RequestError< + lsp_request!("textDocument/prepareCallHierarchy"), + >, >, - >, - SendError<Message>, + > + use<>, + RqSendError<lsp_request!("textDocument/prepareCallHierarchy")>, > { - self.request::<CallHierarchyPrepare>(&CallHierarchyPrepareParams { - text_document_position_params: at, - work_done_progress_params: default(), - }) + send!(self.request, "textDocument/prepareCallHierarchy", + _ { + text_document_position_params: at, + work_done_progress_params: default(), + } + ) .map(fst) .map(|x| x.map(|x| x.map(|x| x.and_then(|mut x| x.try_remove(0))))) } pub async fn callers( - &self, + self: Arc<Self>, at: TextDocumentPositionParams, ) -> rootcause::Result<Vec<CallHierarchyIncomingCall>> { let calls = self - .request::<CallHierarchyIncomingCalls>( + .request::<CallHierarchyIncomingCallsRequest>( &CallHierarchyIncomingCallsParams { item: self .find_function(at)? @@ -679,11 +717,11 @@ impl Client { Ok(calls) } pub async fn calling( - &self, + self: Arc<Self>, at: TextDocumentPositionParams, ) -> rootcause::Result<Vec<CallHierarchyOutgoingCall>> { let calls = self - .request::<CallHierarchyOutgoingCalls>( + .request::<CallHierarchyOutgoingCallsRequest>( &CallHierarchyOutgoingCallsParams { item: self .find_function(at)? @@ -716,3 +754,7 @@ pub macro tdpp($e:expr) { position: $e.text.to_l_position(*$e.text.cursor.first()).unwrap(), } } + +pub fn acceptable_duration() -> tokio::time::Duration { + tokio::time::Duration::from_millis(50) +} diff --git a/src/lsp/communication.rs b/src/lsp/communication.rs index 7bc6b7c..efc44ee 100644 --- a/src/lsp/communication.rs +++ b/src/lsp/communication.rs @@ -7,21 +7,29 @@ use std::time::Instant; use crossbeam::channel::{Receiver, RecvError, SendError, Sender}; use log::{debug, error, trace}; use lsp_server::{ - ErrorCode, Message, Notification as N, Request as LRq, Response as Re, - ResponseError, + ErrorCode, ExtractError, Message, Notification as N, Request as LRq, + RequestId, Response as Re, ResponseError, }; -use lsp_types::notification::*; -use lsp_types::request::*; use lsp_types::*; +use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; +use tokio::time::error::Elapsed; +use url::Url; use winit::window::Window; +#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)] +#[serde(untagged)] +pub enum WorkDoneProgress { + Begin(WorkDoneProgressBegin), + Report(WorkDoneProgressReport), + End(WorkDoneProgressEnd), +} use crate::lsp::BehaviourAfter::{self, *}; -use crate::lsp::RequestError; +use crate::lsp::{RequestError, RqSendError}; pub fn handler( window_rx: oneshot::Receiver<Arc<dyn Window + 'static>>, progress: &papaya::HashMap< - NumberOrString, + ProgressToken, Option<(WorkDoneProgress, WorkDoneProgressBegin)>, >, _req_tx: Sender<LRq>, @@ -32,19 +40,20 @@ pub fn handler( ) { let mut map = HashMap::new(); let w = window_rx.blocking_recv().unwrap(); + println!("got w"); loop { crossbeam::select! { recv(req_rx) -> x => match x { Ok((.., BehaviourAfter::RedrawNow)) => w.request_redraw(), Ok((x, y, and)) => { debug!("received request {x}"); - assert!(map.insert(x, (y, Instant::now(), and)).is_none()); + assert!(map.insert(RequestId::from(x), (y, Instant::now(), and)).is_none()); } Err(RecvError) => return, }, recv(rx) -> x => match x { Ok(Message::Request(rq @ LRq { method: "window/workDoneProgress/create", .. })) => { - match rq.load::<WorkDoneProgressCreate>() { + match load_rq::<lsp_request!("window/workDoneProgress/create")>(rq) { Ok((_, x)) => { let g = progress.guard(); progress.insert(x.token, None, &g); @@ -65,13 +74,13 @@ pub fn handler( if let Some(e) = &x.error { if e.code == ErrorCode::RequestCanceled as i32 {} else if e.code == ErrorCode::ServerCancelled as i32 { - if let Some((s, _, t)) = map.remove(&x.id.i32()) { + if let Some((s, _, t)) = map.remove(&x.id) { log::info!("request {} cancelled", x.id); _ = s.send(x); if t == Redraw { w.request_redraw() } } } else { - if let Some((s, _, t)) = map.remove(&x.id.i32()) { + if let Some((s, _, t)) = map.remove(&x.id) { _ = s.send(x.clone()); if t == Redraw { w.request_redraw() } trace!("received error from lsp for response {x:?}"); @@ -80,7 +89,7 @@ pub fn handler( } } } - else if let Some((s, took, t)) = map.remove(&x.id.i32()) { + else if let Some((s, took, t)) = map.remove(&x.id) { log::debug!("request {} took {:?}", x.id, took.elapsed()); match s.send(x) { Ok(()) => {} @@ -98,7 +107,7 @@ pub fn handler( } Ok(Message::Notification(rq @ N { method: "textDocument/publishDiagnostics", .. })) => { debug!("got diagnostics"); - match rq.load::<PublishDiagnostics>() { + match load_n::<lsp_notification!("textDocument/publishDiagnostics")>(rq) { Ok(x) => { d.insert(x.uri, x.diagnostics, &d.guard()); w.request_redraw(); @@ -107,7 +116,8 @@ pub fn handler( } }, Ok(Message::Notification(x @ N { method: "$/progress", .. })) => { - let ProgressParams {token,value:ProgressParamsValue::WorkDone(x) } = x.load::<Progress>().unwrap(); + let ProgressParams {token,value:x } = load_n::<lsp_notification!("$/progress")>(x).unwrap(); + let Ok(x) = serde_json::from_value::<WorkDoneProgress>(x.clone()) else { error!("{x:?}"); continue }; match x.clone() { WorkDoneProgress::Begin(y) => { progress.update(token, move |_| Some((x.clone(), y.clone())), &progress.guard()); @@ -129,36 +139,95 @@ pub fn handler( } } } -impl super::Client { +#[derive(Debug)] +#[allow(dead_code)] +pub enum ExtractErr<T> { + /// The extracted message was of a different method than expected. + MethodMismatch(T), + /// Failed to deserialize the message. + JsonError { method: String, error: serde_json::Error }, +} +fn load_n<T: lsp_types::Notification>( + me: lsp_server::Notification, +) -> Result<T::Params, ExtractErr<lsp_server::Notification>> { + (T::METHOD.as_str() == me.method) + .ok_or(ExtractErr::MethodMismatch(me.clone()))?; + serde_json::from_value(me.params) + .map_err(|e| ExtractErr::JsonError { method: me.method, error: e }) +} +pub fn load_rq<P: lsp_types::Request>( + me: LRq, +) -> Result<(RequestId, P::Params), ExtractError<LRq>> { + if me.method != P::METHOD.to_string() { + return Err(ExtractError::MethodMismatch(me)); + } + match serde_json::from_value(me.params) { + Ok(params) => Ok((me.id, params)), + Err(error) => + Err(ExtractError::JsonError { method: me.method, error }), + } +} +impl super::Tx { pub fn notify<X: Notification>( &self, y: &X::Params, ) -> Result<(), SendError<Message>> { - self.tx.send(Message::Notification(N { + self.send(Message::Notification(N { method: X::METHOD.into(), params: serde_json::to_value(y).unwrap(), })) } pub fn cancel(&self, rid: i32) { - _ = self.notify::<Cancel>(&CancelParams { id: rid.into() }); + _ = self.notify::<CancelNotification>(&CancelParams { + id: rid.into(), + }); } +} +impl super::Client { pub fn request_immediate<'me, X: Request>( &'me self, y: &X::Params, ) -> Result<X::Result, RequestError<X>> { - self.runtime.block_on(self.request_::<X, { Nil }>(y)?.0) + let _guard = self.runtime.enter(); + self.runtime + .block_on(tokio::time::timeout( + tokio::time::Duration::from_secs(20), + self.request_::<X, { Nil }>(y)?.0, + )) + .unwrap() + } + pub fn request_by<'me, X: Request>( + &'me self, + y: &X::Params, + d: tokio::time::Duration, + ) -> Result<Result<X::Result, RequestError<X>>, Elapsed> { + let _guard = self.runtime.enter(); + self.runtime.block_on(tokio::time::timeout( + d, + match self.request_::<X, { Nil }>(y) { + Err(e) => return Ok(Err(e.into())), + Ok((x, _)) => x, + }, + )) } + pub fn by<T>( + &self, + x: impl Future<Output = T>, + d: tokio::time::Duration, + ) -> Result<T, Elapsed> { + let _guard = self.runtime.enter(); + self.runtime.block_on(tokio::time::timeout(d, x)) + } pub fn request<'me, X: Request>( &'me self, y: &X::Params, ) -> Result< ( - impl Future<Output = Result<X::Result, RequestError<X>>> - + use<'me, X>, + impl Future<Output = Result<X::Result, RequestError<X>>> + use<X>, i32, ), - SendError<Message>, + RqSendError<X>, > { self.request_::<X, { Redraw }>(y) } @@ -169,10 +238,10 @@ impl super::Client { ) -> Result< ( impl Future<Output = Result<X::Result, RequestError<X>>> - + use<'me, X, THEN>, + + use<X, THEN>, i32, ), - SendError<Message>, + RqSendError<X>, > { let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel); self.tx.send(Message::Request(LRq { @@ -187,10 +256,11 @@ impl super::Client { .send((id, tx, THEN)) .expect("oughtnt really fail"); } + let tx = self.tx.clone(); Ok(( async move { let g = scopeguard::guard((), |()| { - self.cancel(id); + tx.cancel(id); }); let mut x = rx.await?; @@ -234,3 +304,13 @@ impl super::Client { self.send_to.send((0, tx, BehaviourAfter::RedrawNow)) } } +macro_rules! send + { + ($c:ident.$call:ident$(::<$($arg:block),*>)?, $request:tt, _ { $($field:ident: $expr:expr),* $(,)? $(,..$base:expr)? } $(,)?) => {{ + type R = lsp_request!($request); + type P = <R as Request>::Params; + $c.$call::<R $($(,$arg)*)?>(&P { $($field: $expr),* }) + }}; +} + +pub(crate) use send; diff --git a/src/lsp/init_opts.rs b/src/lsp/init_opts.rs index 345e2a4..797ba17 100644 --- a/src/lsp/init_opts.rs +++ b/src/lsp/init_opts.rs @@ -6,13 +6,13 @@ pub fn get( conf: &Option<serde_json::Value>, is_ra: bool, ) -> InitializeParams { - let sym = Some(if is_ra { - SymbolKind::ALL.to_vec() - } else { - (1..=26).map(SymbolKind).collect() - }); + // let sym = Some(if is_ra { + // SymbolKind::ALL.to_vec() + // } else { + // (1..=26).map(SymbolKind).collect() + // }); InitializeParams { - process_id: Some(std::process::id()), + process_id: Some(std::process::id() as _), capabilities: ClientCapabilities { window: Some(WindowClientCapabilities { @@ -21,20 +21,18 @@ pub fn get( }), workspace: Some(WorkspaceClientCapabilities { symbol: Some(WorkspaceSymbolClientCapabilities { - symbol_kind: Some(SymbolKindCapability { - value_set: sym.clone(), + // symbol_kind: Some(ClientSymbolKindOptions { + // value_set: sym.clone(), + // }), + // tag_support: Some(ClientSymbolTagOptions { + // value_set: SymbolTag::ALL.to_vec(), + // }), + resolve_support: Some(ClientSymbolResolveOptions { + properties: vec!["location".into()], }), - tag_support: Some(TagSupport { - value_set: SymbolTag::ALL.to_vec(), - }), - resolve_support: Some( - WorkspaceSymbolResolveSupportCapability { - properties: vec!["location".into()], - }, - ), ..default() }), - diagnostic: Some(DiagnosticWorkspaceClientCapabilities { + diagnostics: Some(DiagnosticWorkspaceClientCapabilities { refresh_support: Some(true), }), inlay_hint: Some(InlayHintWorkspaceClientCapabilities { @@ -50,10 +48,12 @@ pub fn get( failure_handling: Some(FailureHandlingKind::Abort), normalizes_line_endings: Some(false), change_annotation_support: Some( - ChangeAnnotationWorkspaceEditClientCapabilities { + ChangeAnnotationsSupportOptions { groups_on_label: Some(false), }, ), + metadata_support: None, + snippet_edit_support: Some(true), }), ..default() }), @@ -64,47 +64,45 @@ pub fn get( }, ), document_highlight: Some(default()), - formatting: Some(DynamicRegistrationClientCapabilities { + formatting: Some(DocumentFormattingClientCapabilities { dynamic_registration: Some(false), }), inlay_hint: Some(InlayHintClientCapabilities { dynamic_registration: None, - resolve_support: Some( - InlayHintResolveClientCapabilities { - properties: vec![ - "textEdits".into(), - "tooltip".into(), - "label.tooltip".into(), - "label.command".into(), - ], - }, - ), + resolve_support: Some(ClientInlayHintResolveOptions { + properties: vec![ + "textEdits".into(), + "tooltip".into(), + "label.tooltip".into(), + "label.command".into(), + ], + }), }), document_symbol: Some(DocumentSymbolClientCapabilities { - tag_support: Some(TagSupport { - value_set: SymbolTag::ALL.to_vec(), - }), - symbol_kind: Some(SymbolKindCapability { - value_set: sym, - }), + // tag_support: Some(ClientSymbolTagSupport { + // value_set: SymbolTag::ALL.to_vec(), + // }), + // symbol_kind: Some(ClientSymbolKindOptions { + // value_set: sym, + // }), hierarchical_document_symbol_support: Some(true), ..default() }), - definition: Some(GotoCapability { + definition: Some(DefinitionClientCapabilities { link_support: Some(true), ..default() }), code_action: Some(CodeActionClientCapabilities { data_support: Some(true), resolve_support: Some( - CodeActionCapabilityResolveSupport { + ClientCodeActionResolveOptions { properties: vec!["edit".to_string()], }, ), code_action_literal_support: Some( - CodeActionLiteralSupport { + ClientCodeActionLiteralOptions { code_action_kind: - CodeActionKindLiteralSupport { + ClientCodeActionKindOptions { value_set: [ "", "Empty", @@ -123,7 +121,7 @@ pub fn get( "source", "source.organizeImports", ] - .map(String::from) + .map(Into::into) .into(), }, }, @@ -133,7 +131,7 @@ pub fn get( rename: Some(RenameClientCapabilities { prepare_support: Some(true), prepare_support_default_behavior: Some( - PrepareSupportDefaultBehavior::IDENTIFIER, + PrepareSupportDefaultBehavior::Identifier, ), honors_change_annotations: Some(false), ..default() @@ -148,36 +146,40 @@ pub fn get( diagnostic: Some(DiagnosticClientCapabilities { dynamic_registration: None, related_document_support: Some(true), + markup_message_support: None, + diagnostics_capabilities: default(), }), publish_diagnostics: Some( PublishDiagnosticsClientCapabilities { - related_information: Some(true), - code_description_support: Some(true), - data_support: Some(true), + // related_information: Some(true), + // code_description_support: Some(true), + // data_support: Some(true), ..default() }, ), signature_help: Some(SignatureHelpClientCapabilities { dynamic_registration: None, signature_information: Some( - SignatureInformationSettings { + ClientSignatureInformationOptions { documentation_format: Some(vec![ MarkupKind::Markdown, MarkupKind::PlainText, ]), parameter_information: Some( - ParameterInformationSettings { + ClientSignatureParameterInformationOptions { label_offset_support: Some(true), }, ), active_parameter_support: Some(true), + no_active_parameter_support: Some(true), + }, ), context_support: Some(false), }), completion: Some(CompletionClientCapabilities { dynamic_registration: Some(false), - completion_item: Some(CompletionItemCapability { + completion_item: Some(ClientCompletionItemOptions { snippet_support: Some(true), commit_characters_support: Some(true), documentation_format: Some(vec![ @@ -186,12 +188,12 @@ pub fn get( ]), deprecated_support: None, preselect_support: None, - tag_support: Some(TagSupport { - value_set: vec![CompletionItemTag::DEPRECATED], + tag_support: Some(CompletionItemTagOptions { + value_set: vec![CompletionItemTag::Deprecated], }), resolve_support: Some( - CompletionItemCapabilityResolveSupport { + ClientCompletionItemResolveOptions { properties: vec![ "additionalTextEdits".into(), "documentation".into(), @@ -200,60 +202,56 @@ pub fn get( ), insert_replace_support: Some(false), insert_text_mode_support: Some( - InsertTextModeSupport { - value_set: vec![InsertTextMode::AS_IS], + ClientCompletionItemInsertTextModeOptions { + value_set: vec![InsertTextMode::AsIs], }, ), label_details_support: Some(true), ..default() }), completion_item_kind: Some( - CompletionItemKindCapability { + ClientCompletionItemOptionsKind { 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, + 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::EnumMember, + CompletionItemKind::Constant, + CompletionItemKind::Struct, + CompletionItemKind::Event, + CompletionItemKind::Operator, + CompletionItemKind::TypeParameter, ]), - // value_set: Some(vec![CompletionItemKind::]), }, ), context_support: None, - insert_text_mode: Some(InsertTextMode::AS_IS), - completion_list: Some(CompletionListCapability { + insert_text_mode: Some(InsertTextMode::AsIs), + completion_list: Some(CompletionListCapabilities { item_defaults: None, + apply_kind_support: None, }), }), semantic_tokens: Some(SemanticTokensClientCapabilities { dynamic_registration: Some(false), - requests: SemanticTokensClientCapabilitiesRequests { - range: Some(true), - full: Some( - lsp_types::SemanticTokensFullOptions::Bool( - true, - ), - ), + requests: ClientSemanticTokensRequestOptions { + range: Some(true.into()), + full: Some(true.into()), }, token_modifiers: [ "associated", @@ -313,8 +311,10 @@ pub fn get( }), initialization_options: conf.clone(), trace: None, - workspace_folders: Some(vec![workspace]), - + workspace_folders_initialize_params: + WorkspaceFoldersInitializeParams { + workspace_folders: Some(vec![workspace].into()), + }, ..default() } } diff --git a/src/lsp/rq.rs b/src/lsp/rq.rs index 1d7ef09..50a6fd3 100644 --- a/src/lsp/rq.rs +++ b/src/lsp/rq.rs @@ -4,15 +4,18 @@ use std::marker::PhantomData; use crossbeam::channel::SendError; use lsp_server::{Message, Response as Re}; -use lsp_types::DiagnosticServerCancellationData; -use lsp_types::request::Request; +use lsp_types::{ + DiagnosticServerCancellationData, LspRequestMethod, MessageDirection, + Request, +}; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; use tokio::task; use tokio_util::task::AbortOnDropHandle; use crate::lsp::Void; - +#[derive(Serialize, Deserialize)] +pub struct RqSendError<X>(Message, PhantomData<X>); #[derive(Serialize, Deserialize)] pub enum RequestError<X> { Rx(PhantomData<X>), @@ -25,7 +28,10 @@ pub type AQErr = RequestError<LSPError>; impl Request for LSPError { type Params = (); type Result = (); - const METHOD: &'static str = "<unknown method>"; + const MESSAGE_DIRECTION: lsp_types::MessageDirection = + MessageDirection::ServerToClient; + const METHOD: LspRequestMethod<'static> = + LspRequestMethod::Custom("<unknown method>"); } #[derive(Debug)] pub struct LSPError {} @@ -50,16 +56,42 @@ impl<X> From<oneshot::error::RecvError> for RequestError<X> { Self::Rx(PhantomData) } } +impl<X: Request> std::error::Error for RqSendError<X> { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} impl<X: Request> std::error::Error for RequestError<X> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } +impl<X> From<SendError<Message>> for RqSendError<X> { + fn from(x: SendError<Message>) -> Self { + Self(x.into_inner(), PhantomData) + } +} impl<X> From<SendError<Message>> for RequestError<X> { fn from(x: SendError<Message>) -> Self { Self::Send(x.into_inner()) } } +impl<X> From<RqSendError<X>> for RequestError<X> { + fn from(x: RqSendError<X>) -> Self { + Self::Send(x.0) + } +} + +impl<X: Request> std::fmt::Display for RqSendError<X> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} failed; couldnt send {:?}", X::METHOD, self.0) + } +} +impl<X: Request> std::fmt::Debug for RqSendError<X> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} impl<X: Request> std::fmt::Display for RequestError<X> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -99,14 +131,15 @@ impl<const R: &'static str> Display for Unsupported<R> { write!(f, "request {} isnt supported by this LSP", R) } } -pub trait Peel<E> { - fn peel(self) -> Result<(), E>; +pub trait Peel<E, T> { + fn peel(self) -> Result<Option<T>, E>; } -impl<E, N> Peel<E> for Result<Result<(), E>, N> { - fn peel(self) -> Result<(), E> { +impl<E, N, T> Peel<E, T> for Result<Result<T, E>, N> { + fn peel(self) -> Result<Option<T>, E> { match self { Ok(Err(e)) => Err(e), - Ok(Ok(_)) | Err(_) => Ok(()), + Ok(Ok(x)) => Ok(Some(x)), + Err(_) => Ok(None), } } } @@ -130,11 +163,11 @@ impl<T: Clone, R, D, E> Clone for Rq<T, R, D, E> { } } #[derive(serde_derive::Serialize, serde_derive::Deserialize)] -pub struct Rq<T, R, D = (), E = RequestError<R>> { +pub struct Rq<Output, Res, Data = (), Error = LSPError> { #[serde(skip_serializing_if = "Option::is_none", default = "none")] - pub result: Option<T>, + pub result: Option<Output>, #[serde(skip, default = "none")] - pub request: Option<(AbortOnDropHandle<Result<R, E>>, D)>, + pub request: Option<(AbortOnDropHandle<Result<Res, Error>>, Data)>, } impl<T: Debug, R, D: Debug, E> Debug for Rq<T, R, D, E> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/lsp/vsc_settings.rs b/src/lsp/vsc_settings.rs index 570ebe3..1734c75 100644 --- a/src/lsp/vsc_settings.rs +++ b/src/lsp/vsc_settings.rs @@ -6,7 +6,7 @@ use rootcause::report; pub fn load(p: &Path, ws: &Path) -> rootcause::Result<serde_json::Value> { let std::process::Output {stdout, stderr,.. } = std::process::Command::new("jq").stdout(Stdio::piped()) - .args(["-M", "-c", "-r", r#"reduce to_entries[] as $x ({}; setpath(($x.key | split(".")); $x.value)) | ."rust-analyzer""#, &p.to_string_lossy()]).spawn()?.wait_with_output() + .args(["-M", "-c", "-r", r#"reduce to_entries[] as $x ({}; setpath(($x.key | split(".")); $x.value))"#, &p.to_string_lossy()]).spawn()?.wait_with_output() ?; let stdout = String::from_utf8(stdout)?.replace( "${workspaceFolder}", diff --git a/src/main.rs b/src/main.rs index 95d2c5e..9ac477a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature( -unsized_const_params, + const_convert, + unsized_const_params, exact_div, yeet_expr, const_array, @@ -45,13 +46,15 @@ mod edi; mod error; mod git; mod gotolist; +mod killring; mod meta; mod rnd; +#[cfg(target_family = "unix")] mod runnables; mod sym; +#[cfg(target_family = "unix")] mod trm; -use std::any::TypeId; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::mem::MaybeUninit; @@ -62,8 +65,10 @@ use Default::default; use NamedKey::*; use dsb::cell::Style; use dsb::{Cell, F}; +use env_logger::{Env, TimestampPrecision}; use fimg::Image; use libc::{atexit, signal}; +use log::LevelFilter; use lsp::Rq; use lsp_types::*; use rust_fsm::StateMachine; @@ -83,6 +88,7 @@ use winit::window::{ }; use crate::edi::Editor; +use crate::edi::lsp_mn::LSPM; use crate::edi::st::*; use crate::lsp::RqS; use crate::text::{TextArea, col, is_word}; @@ -100,10 +106,15 @@ fn main() { let _x = 4; // let x = HashMap::new(); unsafe { std::env::set_var("CARGO_UNSTABLE_RUSTC_UNICODE", "true") }; - env_logger::builder() - .filter_level(log::LevelFilter::Info) - .parse_env("GRA_LOG") - .init(); + env_logger::builder().filter_level(LevelFilter::Error).init(); + // .format_timestamp(Some(TimestampPrecision::Seconds)) + // .filter(Some("regex_cursor"), LevelFilter::Error) + // .filter(Some("calloop"), LevelFilter::Error) + // .filter(Some("tracing::span"), LevelFilter::Error) + // .parse_env(Env::new().default_filter_or("info")) + // .filter_level(log::LevelFilter::Info) + // .parse_env("G"RA_LOG") + // .init(); // lsp::x(); entry(EventLoop::new().unwrap()) } @@ -113,35 +124,55 @@ static mut CLICKING: bool = false; const BG: [u8; 3] = col!("#1f2430"); const FG: [u8; 3] = [204, 202, 194]; const BORDER: [u8; 3] = col!("#ffffff"); - +type KillRing = Vec<Box<[String]>>; +static mut __KR: MaybeUninit<KillRing> = MaybeUninit::uninit(); static mut __ED: MaybeUninit<Editor> = MaybeUninit::uninit(); +static mut __FREQ: MaybeUninit<Freq> = MaybeUninit::uninit(); +static mut __CLEAN: bool = false; extern "C" fn cleanup() { - unsafe { __ED.assume_init_mut().store().unwrap() }; + unsafe { + if __CLEAN == false { + __CLEAN = true; + + match __ED + .assume_init_read() + .store(__FREQ.assume_init_mut(), __KR.assume_init_mut()) + { + Ok(_) => {} + Err(e) => eprintln!("{e}"), + }; + } + } } extern "C" fn sigint(_: i32) { cleanup(); std::process::exit(12); } -type Freq = FxHashMap<TypeId, FxHashMap<u64, u16>>; +type FID = u8; +type Freq = FxHashMap<FID, FxHashMap<u64, u16>>; pub(crate) fn entry(event_loop: EventLoop) { + let mut lsp_mn = LSPM { four: default() }; + unsafe { - __ED.write(match Editor::new() { + let (ed, freq, kr) = match Editor::new(&mut lsp_mn) { Err(e) => { eprintln!("failure to launch: {e}"); return; } Ok(x) => x, - }) + }; + __ED.write(ed); + __FREQ.write(freq); + __KR.write(kr); }; assert_eq!(unsafe { atexit(cleanup) }, 0); unsafe { signal(libc::SIGINT, sigint as *const () as usize) }; let ed: &'static mut Editor = unsafe { __ED.assume_init_mut() }; - - let mut freq: Freq = default(); + let freq = unsafe { __FREQ.assume_init_mut() }; + let kr = unsafe { __KR.assume_init_mut() }; let ppem = 18.0; let ls = 20.0; // let ed = Box::leak(Box::new(ed)); - let mut fonts = dsb::Fonts::new( F::FontRef(*FONT, &[]), F::FontRef(*BFONT, &[]), @@ -161,9 +192,11 @@ pub(crate) fn entry(event_loop: EventLoop) { let app = winit_app::WinitAppBuilder::with_init( move |elwt| { let window = winit_app::make_window(elwt, |x| { + #[cfg(target_family = "unix")] + let x = x.with_platform_attributes( + Box::new(winit::platform::wayland::WindowAttributesWayland::default().with_name("com.bendn.gracilaria", "com.bendn.gracilaria")) + ).with_decorations(false); x.with_title("gracilaria") - .with_platform_attributes(Box::new(winit::platform::wayland::WindowAttributesWayland::default().with_name("com.bendn.gracilaria", "com.bendn.gracilaria"))) - .with_decorations(false) // .with_name("com.bendn.gracilaria", "") // .with_resize_increments(PhysicalSize::new(fw, fh)) .with_window_icon(Some( @@ -175,6 +208,7 @@ pub(crate) fn entry(event_loop: EventLoop) { .unwrap().into() )) }); + if let Some(x) = w.take() { x.send(window.clone()).unwrap(); } @@ -207,6 +241,15 @@ pub(crate) fn entry(event_loop: EventLoop) { .with_event_handler( move |(window, _context), surface, window_id, event, elwt| { elwt.set_control_flow(ControlFlow::Wait); + if let Some((.., c)) = &mut ed.lsp && let Some(c) = c.take() { + c.send(window.clone()).unwrap(); + } + + let lsp_mn = &mut lsp_mn; + for lem in &lsp_mn.four { + lem.1.status(); + // dbg!(lem.1); + } let (fw, fh) = dsb::dims(&FONT, ppem); let (c, r) = dsb::fit( &FONT, @@ -307,13 +350,13 @@ pub(crate) fn entry(event_loop: EventLoop) { if button == MouseButton::Left { unsafe { CLICKING = true }; } - ed.click(button, cursor_position, window.clone()); + ed.click(button, cursor_position, window.clone(), lsp_mn); window.request_redraw(); } WindowEvent::PointerButton { button: ButtonSource::Mouse(MouseButton::Left), .. - } => unsafe { CLICKING = false }, + } => unsafe { CLICKING = false }, WindowEvent::MouseWheel { device_id: _, delta: MouseScrollDelta::LineDelta(_, rows), @@ -322,6 +365,14 @@ pub(crate) fn entry(event_loop: EventLoop) { ed.scroll(rows); window.request_redraw(); } + WindowEvent::MouseWheel { + device_id: _, + delta: MouseScrollDelta::PixelDelta(PhysicalPosition { x:_, y }), + phase: _, + } => { + ed.scroll(y as f32 *5.0); + window.request_redraw(); + } WindowEvent::ModifiersChanged(modifiers) => { unsafe { MODIFIERS = modifiers.state() }; window.request_redraw(); @@ -342,7 +393,7 @@ pub(crate) fn entry(event_loop: EventLoop) { ) { return; } - if ed.keyboard(event, window,&mut freq).is_break() { + if ed.keyboard(event, window, freq, kr, lsp_mn).is_break() { elwt.exit(); } window.request_redraw(); @@ -466,11 +517,11 @@ rust_fsm::state_machine! { pub(crate) CompletionState => #[derive(Debug)] pub(crate) CompletionAction<'i> => pub(crate) CDo None => Click => None, None => K(Key<&'i str> => Key::Character(k @ ("." | ":"))) => Complete( - RqS<Complete, lsp_types::request::Completion, usize> => default() - ) [Request(CompletionContext => CompletionContext {trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character:Some(k.to_string()) })], - None => K(Key::Character(" ") if ctrl()) => Complete(default()) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], + RqS<Complete, lsp_types::CompletionRequest, usize> => default() + ) [Request(CompletionContext => CompletionContext {trigger_kind: CompletionTriggerKind::TriggerCharacter, trigger_character:Some(k.to_string()) })], + None => K(Key::Character(" ") 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) - && !matches!(x, "w"|"a"|"s"|"d" if alt())) => Complete(default()) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], + && !matches!(x, "w"|"a"|"s"|"d" if alt())) => Complete(default()) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::Invoked, trigger_character:None })], None => K(_) => _, // when @@ -488,7 +539,7 @@ rust_fsm::state_machine! { Complete(Rq { result: Some(x), .. }) => K(Key::Named(NamedKey::Enter)) => None [Finish(Complete => x)], - Complete(_x) => K(_) => _ [Request(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, trigger_character:None })], + Complete(_x) => K(_) => _ [Request(CompletionContext { trigger_kind: CompletionTriggerKind::TriggerForIncompleteCompletions, trigger_character:None })], } use complete::Complete; diff --git a/src/menu.rs b/src/menu.rs index 7186908..ca5cb47 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,5 +1,4 @@ pub mod generic; -use std::any::TypeId; use std::borrow::Cow; use std::cmp::Reverse; use std::sync::LazyLock; @@ -51,7 +50,7 @@ pub fn score<'a, T: Key<'a>, D: MenuData<Element<'a> = T> + 'static>( .map(move |y| { if let Some(f) = freq && filter == "" - && let Some(f) = f.get(&TypeId::of::<D>()) + && let Some(f) = f.get(&D::ID) { return ( f.get(&D::hashed(&y).unwrap()) diff --git a/src/menu/generic.rs b/src/menu/generic.rs index 51be7e7..da465c5 100644 --- a/src/menu/generic.rs +++ b/src/menu/generic.rs @@ -1,4 +1,3 @@ -use std::any::TypeId; use std::fmt::Debug; use std::hash::Hash; use std::path::Path; @@ -6,9 +5,9 @@ use std::path::Path; use Default::default; use dsb::Cell; -use crate::Freq; use crate::menu::{Key, back, filter, next, score}; use crate::text::TextArea; +use crate::{FID, Freq}; pub struct GenericMenu<T: MenuData> { pub data: T::Data, @@ -53,7 +52,25 @@ pub enum CorA { Complete, Accept, } -pub trait MenuData: Sized + 'static { + +pub trait ID { + const ID: FID; +} +impl ID for crate::commands::Cmds { + const ID: u8 = 0; +} +impl ID for crate::gotolist::GTL { + const ID: u8 = 1; +} +// RunB = 2 +impl ID for crate::sym::Symb { + const ID: u8 = 3; +} +impl ID for crate::killring::KillR { + const ID: u8 = 4; +} +pub trait MenuData: Sized + 'static + ID { + const NAME: &'static str; const HEIGHT: usize = 30; type Data; type Element<'a>: Key<'a>; @@ -127,6 +144,9 @@ impl<T: MenuData + 'static> GenericMenu<T> { // coz its bottom up back::<{ T::HEIGHT }>(n, &mut self.selection, &mut self.vo); } + pub fn name(&self) -> &'static str { + T::NAME + } pub fn sel( &self, @@ -144,7 +164,7 @@ impl<T: MenuData + 'static> GenericMenu<T> { "if calling with freq, please impl hash", ); - *fq.entry(TypeId::of::<T>()) + *fq.entry(T::ID) .or_default() .entry(x) .or_default() += 1; @@ -102,7 +102,27 @@ pub fn render( ), _ => None, }; - + let sym_sel = + if let State::Symbols(Rq { result: Some(menu), .. }) = + &ed.state + && let Some(Ok(UsedSI { + at: GoTo { at: At::R(x), .. }, + .. + })) = menu.sel(None) + { + Some(x) + } else { + None + }; + let gen_sel = if let State::GoToL(menu) = &ed.state + && let Some(Ok((GoTo { at: At::R(r), path }, _))) = + menu.sel(None) + && Some(&*path) == ed.origin.as_deref() + { + Some(r) + } else { + None + }; text.line_numbers( (c, r - 1), [67, 76, 87], @@ -110,22 +130,12 @@ pub fn render( (cells, (c, r)), (1, 0), |_text, mut f, y| { - if let State::GoToL(menu) = &ed.state - && let Some(Ok((GoTo { at: At::R(r), path }, _))) = - menu.sel(None) - && Some(&*path) == ed.origin.as_deref() - { + if let Some(r) = gen_sel { if (r.start.line..=r.end.line).contains(&(y as _)) { f.style.fg = col!("#FFCC66"); } } - if let State::Symbols(Rq { result: Some(menu), .. }) = - &ed.state - && let Some(Ok(UsedSI { - at: GoTo { at: At::R(x), .. }, - .. - })) = menu.sel(None) - { + if let Some(x) = sym_sel { if (x.start.line..=x.end.line).contains(&(y as _)) { f.style.fg = col!("#FFCC66"); } @@ -252,14 +262,23 @@ pub fn render( .flat_map(|diag| { let sev = diag .severity - .unwrap_or(DiagnosticSeverity::ERROR); + .unwrap_or(DiagnosticSeverity::Error); let sev_ = match sev { - DiagnosticSeverity::ERROR => EType::Error, - DiagnosticSeverity::WARNING => EType::Warning, - DiagnosticSeverity::HINT => EType::Hint, + DiagnosticSeverity::Error => EType::Error, + DiagnosticSeverity::Warning => EType::Warning, + DiagnosticSeverity::Hint => EType::Hint, _ => EType::Info, }; - once((diag.range, &*diag.message, sev_)).chain( + once(( + diag.range, + match &diag.message { + Message::String(x) => &**x, + Message::MarkupContent(x) => + unimplemented!(), + }, + sev_, + )) + .chain( diag.related_information .iter() .flatten() @@ -275,7 +294,7 @@ pub fn render( }) .for_each(|(mut r, m, sev)| { if let EType::Related(x) = sev - && x != DiagnosticSeverity::ERROR + && x != DiagnosticSeverity::Error { return; } @@ -291,7 +310,7 @@ pub fn render( | EType::Hint | EType::Info => col!("#9469242c"), EType::Related( - DiagnosticSeverity::ERROR, + DiagnosticSeverity::Error, ) => col!("#dfbfff26"), EType::Related(_) => col!("#ffad6625"), }); @@ -332,7 +351,7 @@ pub fn render( col!("#ff942f1b", "#f26462") } EType::Related( - DiagnosticSeverity::WARNING, + DiagnosticSeverity::Warning, ) => { col!("#dfbfff26", "#DFBFFF") } @@ -373,7 +392,7 @@ pub fn render( z, wt, ed.origin.as_deref(), - lsp!(ed).and_then(|x| x.legend()), + lsp!(&ed).and_then(|x| x.legend()), ed.language, ); @@ -385,7 +404,7 @@ pub fn render( ed.origin .as_ref() .map(|x| { - ed.workspace + ed.git_dir .as_ref() .and_then(|w| x.strip_prefix(w).ok()) .unwrap_or(&x) @@ -571,13 +590,13 @@ pub fn render( continue; } - let r = if let Some(r) = e.sticky_range { - let r = text.l_range(r).unwrap(); - text.char_to_line(r.start)..=text.char_to_line(r.end) - } else { - let x = text.char_to_line(p); - x..=x - }; + // let r = if let Some(r) = e.sticky_range { + // let r = text.l_range(r).unwrap(); + // text.char_to_line(r.start)..=text.char_to_line(r.end) + // } else { + let x = text.char_to_line(p); + let r = x..=x; + // }; let d = Cell { style: Style { @@ -592,7 +611,7 @@ pub fn render( // chain([Cell::default()], x.to_string().chars().map(Cell::basic)).chain([Cell::default()]).collect(); for (x, rel, mut cell) in text - .colored_lines(r, lsp!(ed).and_then(|x| x.legend())) + .colored_lines(r, lsp!(&ed).and_then(|x| x.legend())) { if rel == 0 { let rem = cells.len() % c; @@ -847,22 +866,28 @@ pub fn render( ); } State::Command(x) if x.should_render() => { - let ws = ed.workspace.as_deref().unwrap(); + let ws = ed.git_dir.as_deref().unwrap(); let c = x.cells(50, ws, None); drawb(&c, 50); } State::Symbols(Rq { result: Some(x), .. }) => { - let ws = ed.workspace.as_deref().unwrap(); + let ws = ed.git_dir.as_deref().unwrap(); let c = x.cells(50, ws, Some(freq)); drawb(&c, 50); } + #[cfg(target_family = "unix")] State::Runnables(Rq { result: Some(x), .. }) => { - let ws = ed.workspace.as_deref().unwrap(); + let ws = ed.git_dir.as_deref().unwrap(); let c = x.cells(50, ws, None); drawb(&c, 50); } State::GoToL(y) => { - let ws = ed.workspace.as_deref().unwrap(); + let ws = ed.git_dir.as_deref().unwrap(); + let c = y.cells(50, ws, None); + drawb(&c, 50); + } + State::KillRing(y) => { + let ws = ed.git_dir.as_deref().unwrap(); let c = y.cells(50, ws, None); drawb(&c, 50); } @@ -1088,7 +1113,13 @@ pub fn simplify_path(x: &str) -> String { static RUST_SRC: LazyLock<Regex> = LazyLock::new(|| { Regex::new(r".rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library").unwrap() }); - let x = x.replace(env!("HOME"), " "); + let x = x.replace( + #[cfg(target_family = "unix")] + env!("HOME"), + #[cfg(target_family = "windows")] + env!("HOMEPATH"), + " ", + ); [(&*RUST_SRC, " "), (&*DEP, " /$name"), (&*DEP2, " /$name")] .into_iter() .fold(x, |acc, (r, repl)| r.replace(&acc, repl).into_owned()) diff --git a/src/runnables.rs b/src/runnables.rs index ee3970d..e70aab9 100644 --- a/src/runnables.rs +++ b/src/runnables.rs @@ -16,6 +16,7 @@ use crate::trm; pub enum Runb {} impl MenuData for Runb { + const NAME: &'static str = "runnables"; type Data = Vec<Runnable>; type Element<'a> = &'a Runnable; @@ -190,3 +191,7 @@ pub fn run( } } } + +impl crate::menu::generic::ID for crate::runnables::Runb { + const ID: u8 = 2; +} @@ -3,8 +3,8 @@ use std::iter::repeat_n; use dsb::Cell; use dsb::cell::Style; use lsp_types::{ - MarkupContent, ParameterInformation, SignatureHelp, - SignatureInformation, + MarkupContent, ParameterInformation, ParameterInformationLabel, + SignatureHelp, SignatureInformation, }; use crate::FG; @@ -16,9 +16,14 @@ pub fn active( let y = &sig.signatures.get(sig.active_signature? as usize)?; Some(( y, - sig.active_parameter - .zip(y.parameters.as_ref()) - .and_then(|(i, x)| x.get(i as usize)), + sig.active_parameter.zip(y.parameters.as_ref()).and_then( + |(i, x)| { + x.get(match i { + lsp_types::ActiveParameter::Int(n) => n as usize, + lsp_types::ActiveParameter::Null => return None, + }) + }, + ), )) } @@ -32,8 +37,8 @@ pub fn sig( 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]) => + ParameterInformationLabel::String(_) => false, + ParameterInformationLabel::Tuple((a, b)) => (a..b).contains(&i), }) { a.style |= (Style::BOLD, color_("#ffcc66")); @@ -5,10 +5,12 @@ use std::path::{Path, PathBuf}; use Default::default; use dsb::Cell; use dsb::cell::Style; -use itern::Iter3; +use itern::{Iter3, Iter4}; use lsp_types::*; +use url::Url; use crate::FG; +use crate::complete::i; use crate::gotolist::At; pub use crate::gotolist::GoTo; use crate::menu::generic::{GenericMenu, MenuData}; @@ -17,6 +19,7 @@ use crate::rnd::simplify_path; use crate::text::{Bookmarks, col, color_, set_a}; pub enum Symb {} impl MenuData for Symb { + const NAME: &'static str = "symbols"; type Data = ( SymbolsList, Vec<SymbolInformation>, @@ -31,38 +34,48 @@ impl MenuData for Symb { (r, tree, bmks, _, origin): &Self::Data, ) -> impl Iterator<Item = Self::Element<'_>> { match r { - SymbolsList::Document(DocumentSymbolResponse::Flat(x)) => - Iter3::A(x.iter().map(UsedSI::from)), - SymbolsList::Document(DocumentSymbolResponse::Nested(x)) => - Iter3::B( - gen { - for bmk in &**bmks { - yield UsedSI { - name: &bmk.text, - kind: SymbolKind::BOOKMARK, - tags: None, - at: GoTo { - path: origin.into(), - at: At::P(bmk.position), - }, - right: None, - } + SymbolsList::Document( + DocumentSymbolResponse::SymbolInformationList(x), + ) => Iter4::A(x.iter().map(UsedSI::from)), + SymbolsList::Document( + DocumentSymbolResponse::DocumentSymbolList(x), + ) => Iter4::B( + gen { + for bmk in &**bmks { + yield UsedSI { + name: &bmk.text, + // kind: SymbolKind::Bookmark, + kind: SymbolKind::Class, // FIXME + tags: None, + at: GoTo { + path: origin.into(), + at: At::P(bmk.position), + }, + right: None, } } - .chain(x.iter().flat_map( - move |x| gen move { - let mut q = VecDeque::with_capacity(12); - q.push_back(x); - while let Some(x) = q.pop_front() { - q.extend(x.children.iter().flatten()); - yield (x, origin).into(); - } - }, - )), - ), - SymbolsList::Workspace(WorkspaceSymbolResponse::Flat(x)) => - Iter3::C(chain(tree, x.iter()).map(UsedSI::from)), - _ => unreachable!("please no"), + } + .chain(x.iter().flat_map( + move |x| gen move { + let mut q = VecDeque::with_capacity(12); + q.push_back(x); + while let Some(x) = q.pop_front() { + q.extend(x.children.iter().flatten()); + yield (x, origin).into(); + } + }, + )), + ), + SymbolsList::Workspace( + WorkspaceSymbolResponse::WorkspaceSymbolList(x), + ) => Iter4::C(chain( + tree.iter().map(UsedSI::from), + x.iter().map(UsedSI::from), + )), + SymbolsList::Workspace( + WorkspaceSymbolResponse::SymbolInformationList(x), + ) => Iter4::D(chain(tree, x.iter()).map(UsedSI::from)), + // _ => unreachable!("please no"), } } @@ -96,7 +109,7 @@ pub struct UsedSI<'a> { impl<'a> std::hash::Hash for UsedSI<'a> { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.name.hash(state); - self.kind.0.hash(state); + self.kind.hash(state); self.at.hash(state); self.right.hash(state); } @@ -104,11 +117,9 @@ impl<'a> std::hash::Hash for UsedSI<'a> { impl<'a> From<&'a SymbolInformation> for UsedSI<'a> { fn from( SymbolInformation { - name, - kind, - tags, + base_symbol_information: + BaseSymbolInformation { name, kind, tags, container_name }, location, - container_name, .. }: &'a SymbolInformation, ) -> Self { @@ -121,6 +132,24 @@ impl<'a> From<&'a SymbolInformation> for UsedSI<'a> { } } } +impl<'a> From<&'a WorkspaceSymbol> for UsedSI<'a> { + fn from( + WorkspaceSymbol { + location, + data: _, + base_symbol_information: + BaseSymbolInformation { name, kind, tags, container_name }, + }: &'a WorkspaceSymbol, + ) -> Self { + UsedSI { + name, + kind: *kind, + tags: tags.as_deref(), + at: location.into(), + right: container_name.as_deref(), + } + } +} impl<'a> From<(&'a DocumentSymbol, &'a PathBuf)> for UsedSI<'a> { fn from( ( @@ -159,7 +188,9 @@ pub enum SymbolsList { } impl Default for SymbolsList { fn default() -> Self { - Self::Workspace(WorkspaceSymbolResponse::Flat(vec![])) + Self::Workspace(WorkspaceSymbolResponse::WorkspaceSymbolList( + vec![], + )) } } @@ -168,8 +199,17 @@ impl GenericMenu<Symb> { let tree = tree .iter() .map(|x| SymbolInformation { - name: x.file_name().unwrap().to_str().unwrap().to_string(), - kind: SymbolKind::FILE, + base_symbol_information: BaseSymbolInformation { + name: x + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + kind: SymbolKind::File, + container_name: None, + tags: None, + }, location: Location { range: lsp_types::Range { end: default(), @@ -177,10 +217,9 @@ impl GenericMenu<Symb> { }, uri: Url::from_file_path(&x).unwrap(), }, - container_name: None, + #[allow(deprecated)] deprecated: None, - tags: None, }) .collect(); Self { data: (default(), tree, bmk, default(), orig), ..default() } @@ -210,35 +249,35 @@ fn r<'a>( const MAP: [([u8; 3], [u8; 3], &str); 85] = { ( amap::amap! { - const { SymbolKind::FILE.0 as usize } => ("#9a9b9a", " "), - const { SymbolKind::METHOD.0 as usize } | const { SymbolKind::FUNCTION.0 as usize } => ("#FFD173", "λ "), - const { SymbolKind::CONSTRUCTOR.0 as usize } => ("#FFAD66", "->"), - const { SymbolKind::FIELD.0 as usize } => ("#E06C75", "x."), - const { SymbolKind::VARIABLE.0 as usize } => ("#E06C75", "x "), - const { SymbolKind::MODULE.0 as usize } => ("#D5FF80", "::"), - const { SymbolKind::PROPERTY.0 as usize } => ("#e6e1cf", "x."), - // const { SymbolKind::VALUE.0 as usize } => ("#DFBFFF", "4 "), - const { SymbolKind::ENUM.0 as usize } => ("#73b9ff", "u"), - const { SymbolKind::ENUM_MEMBER.0 as usize } => ("#73b9ff", ":"), - // const { SymbolKind::SNIPPET.0 as usize } => ("#9a9b9a", "! "), - const { SymbolKind::INTERFACE.0 as usize } => ("#E5C07B", "t "), - // const { SymbolKind::REFERENCE.0 as usize } => ("#9a9b9a", "& "), - const { SymbolKind::CONSTANT.0 as usize } => ("#DFBFFF", "N "), - const { SymbolKind::STRUCT.0 as usize } => ("#73D0FF", "X{"), - const { SymbolKind::OPERATOR.0 as usize } => ("#F29E74", "+ "), - const { SymbolKind::TYPE_PARAMETER.0 as usize } => ("#9a9b9a", "T "), - // const { SymbolKind::KEYWORD.0 as usize } => ("#FFAD66", "as"), + const { i(SymbolKind::File) } => ("#9a9b9a", " "), + const { i(SymbolKind::Method) } | const { i(SymbolKind::Function) } => ("#FFD173", "λ "), + const { i(SymbolKind::Constructor) } => ("#FFAD66", "->"), + const { i(SymbolKind::Field) } => ("#E06C75", "x."), + const { i(SymbolKind::Variable) } => ("#E06C75", "x "), + const { i(SymbolKind::Module) } => ("#D5FF80", "::"), + const { i(SymbolKind::Property) } => ("#e6e1cf", "x."), + // const { SymbolKind::Val.0 as usize } => ("#DFBFFF", "4 "), + const { i(SymbolKind::Enum) } => ("#73b9ff", "u"), + const { i(SymbolKind::EnumMember) } => ("#73b9ff", ":"), + // const { SymbolKind::Snipp.0 as usize } => ("#9a9b9a", "! "), + const { i(SymbolKind::Interface) } => ("#E5C07B", "t "), + // const { SymbolKind::Referen.0 as usize } => ("#9a9b9a", "& "), + const { i(SymbolKind::Constant) } => ("#DFBFFF", "N "), + const { i(SymbolKind::Struct) } => ("#73D0FF", "X{"), + const { i(SymbolKind::Operator) } => ("#F29E74", "+ "), + const { i(SymbolKind::TypeParameter) } => ("#9a9b9a", "T "), + // const { SymbolKind::Keyword.0 as usize } => ("#FFAD66", "as"), - const { SymbolKind::MACRO.0 as usize } => ("#f28f74", "! "), - const { SymbolKind::PROC_MACRO.0 as usize } => ("#f28f74", "r!"), - const { SymbolKind::BOOKMARK.0 as usize } => ("#73D0FF", " "), + // const { SymbolKind::Macro.0 as usize } => ("#f28f74", "! "), + // const { SymbolKind::ProcMacro.0 as usize } => ("#f28f74", "r!"), + // const { SymbolKind::Bookmark.0 as usize } => ("#73D0FF", " "), _ => ("#9a9b9a", " ") }) .map( const |(x, y)| (set_a(color_(x), 0.5), color_(x), y), ) }; - let (bgt, col, ty) = MAP[x.kind.0 as usize]; + let (bgt, col, ty) = MAP[i(x.kind)]; b.iter_mut().zip(ty.chars()).for_each(|(x, c)| { *x = (Style::new(col, bgt) | Style::BOLD).basic(c) }); diff --git a/src/text.rs b/src/text.rs index 923a23d..59dd5ae 100644 --- a/src/text.rs +++ b/src/text.rs @@ -16,14 +16,14 @@ use helix_core::syntax::{HighlightEvent, Loader}; use implicit_fn::implicit_fn; use itertools::Itertools; use lsp_types::{ - DocumentSymbol, Location, SemanticTokensLegend, SnippetTextEdit, - TextEdit, + DocumentSymbol, Edit, Location, SemanticTokensLegend, TextEdit, }; pub use manipulations::Manip; use rootcause::option_ext::OptionExt; use rootcause::prelude::{IteratorExt, ResultExt}; use rootcause::report; use ropey::{Rope, RopeSlice}; +use rust_analyzer::lsp::ext::SnippetTextEdit; use serde::{Deserialize, Serialize}; use tree_house::Language; pub mod cursor; @@ -275,16 +275,14 @@ impl TextArea { self.changes .inner .push(Action::Inserted { at: c, insert: with.to_string() }); + let cc = with.chars().count(); let manip = |x| { - if x < c { - Manip::Unmoved(x) - } else { - Manip::Moved(x + with.chars().count()) - } + if x < c { Manip::Unmoved(x) } else { Manip::Moved(x + cc) } }; self.tabstops.as_mut().map(|x| x.manipulate(manip)); self.cursor.manipulate(manip); self.bookmarks.manipulate(manip); + for m in self .inlays .range(Marking::idx(c as _)..) @@ -297,7 +295,7 @@ impl TextArea { std::collections::btree_set::Entry::Vacant(_) => unreachable!(), }; - m.position += with.chars().count() as u32; + m.position += cc as u32; self.inlays.insert(m); } self.tokens.iter_mut().for_each(|d| d.manip(manip)); @@ -360,7 +358,7 @@ impl TextArea { text: &'b mut Rope, ) -> rootcause::Result<()> { match insert_text_format { - Some(lsp_types::InsertTextFormat::SNIPPET) => { + Some(lsp_types::InsertTextFormat::Snippet) => { let begin = text.l_position(range.start).ok_or_report()?; let end = text.l_position(range.end).ok_or_report()?; text.try_remove(begin..end)?; @@ -382,7 +380,7 @@ impl TextArea { SnippetTextEdit { range,new_text, insert_text_format, .. }: &'b SnippetTextEdit, ) -> rootcause::Result<()> { match insert_text_format { - Some(lsp_types::InsertTextFormat::SNIPPET) => self + Some(lsp_types::InsertTextFormat::Snippet) => self .apply_snippet(&TextEdit { range: range.clone(), new_text: new_text.clone(), @@ -525,13 +523,11 @@ impl TextArea { Some(x) => { self.cursor.one(Cursor::new(x.r().end, &self.rope)); } - None => { - self.cursor.one(Cursor::new( - x.last.clone().unwrap().r().end, - &self.rope, - )); + None if let Some(x) = &x.last => { + self.cursor.one(Cursor::new(x.r().end, &self.rope)); self.tabstops = None; } + None => self.tabstops = None, }, } } @@ -806,9 +802,9 @@ impl TextArea { .take(c) .zip(0..) { - if e.c() != '\n' { + if e.c() != '\n' && e.c() != '\r' { cells.get((x + self.ho, y)).unwrap().letter = - Some(e.c()); + Some(if e.c() == '\t' { ' ' } else { e.c() }); let s = &mut cells.get((x + self.ho, y)).unwrap().style; @@ -1353,7 +1349,7 @@ fn apply() { } "#, ); - + use lsp_types::*; t.apply_snippet(&TextEdit { range: lsp_types::Range { start: Position { line: 0, character: 8 }, @@ -1386,7 +1382,7 @@ fn apply2() { }", ); - use lsp_types::Range; + use lsp_types::*; let mut th = [ TextEdit { range: Range { @@ -1451,12 +1447,21 @@ fn apply2() { pub trait SortTedits { fn sort_tedits(&mut self); } +impl SortTedits for [Edit] { + fn sort_tedits(&mut self) { + self.as_mut().sort_by_key(|t| match t { + Edit::TextEdit(t) => Reverse(t.range.start), + Edit::AnnotatedTextEdit(t) => Reverse(t.text_edit.range.start), + Edit::SnippetTextEdit(t) => Reverse(t.range.start), + }); + } +} impl SortTedits for [TextEdit] { fn sort_tedits(&mut self) { self.as_mut().sort_by_key(|t| Reverse(t.range.start)); } } -impl SortTedits for [SnippetTextEdit] { +impl SortTedits for [rust_analyzer::lsp::ext::SnippetTextEdit] { fn sort_tedits(&mut self) { self.as_mut().sort_by_key(|t| Reverse(t.range.start)); } @@ -1464,13 +1469,13 @@ impl SortTedits for [SnippetTextEdit] { #[test] fn inlays() { - use lsp_types::{InlayHint, InlayHintLabel}; + use lsp_types::InlayHint; let mut t = TextArea::default(); _ = t.insert("let x = 4;"); t.set_inlay(&[InlayHint { - position: Position { line: 0, character: 4 }, - label: InlayHintLabel::String("u".into()), - kind: Some(lsp_types::InlayHintKind::TYPE), + position: lsp_types::Position { line: 0, character: 4 }, + label: lsp_types::Label::String("u".into()), + kind: Some(lsp_types::InlayHintKind::Type), text_edits: None, tooltip: None, padding_left: None, @@ -1486,7 +1491,10 @@ fn inlays() { Char('t', 2, 2), Char(' ', 3, 3), Fake( - &Marking { position: 4, data: Box::new([('u', None)]) }, + Cow::Owned(Marking { + position: 4, + data: vec![('u', None)].into_boxed_slice(), + }), 0, 4, 'u' diff --git a/src/text/cursor.rs b/src/text/cursor.rs index 32b33a7..077a750 100644 --- a/src/text/cursor.rs +++ b/src/text/cursor.rs @@ -93,8 +93,11 @@ pub fn caster<T, U>(x: impl FnMut(T) -> U) -> impl FnMut(T) -> U { } pub macro ceach($cursor: expr, $f:expr $( => $q:tt)?) { for i in (0..$cursor.inner.len()) { - let c = *$cursor.inner.get(i).expect("aw dangit"); - caster::<Cursor, _>($f)(c) $($q)?; + if let Some(&c) = $cursor.inner.get(i) { + caster::<Cursor, _>($f)(c) $($q)?; + } else { + log::error!("for some reason the number of cursors has changed."); + } } $cursor.coalesce(); } @@ -506,4 +509,14 @@ impl Cursors { // ) -> Vec<std::ops::Range<usize>> { // panic!(); // } + + pub fn sels<'a>( + &self, + text: &'a Rope, + ) -> std::iter::FilterMap< + impl Iterator<Item = Cursor> + ExactSizeIterator, + impl FnMut(Cursor) -> Option<RopeSlice<'a>>, + > { + self.iter().filter_map(|x| Some(text.get_slice(x.sel?)?)) + } } diff --git a/src/text/hist.rs b/src/text/hist.rs index 97a05c7..05a5bcb 100644 --- a/src/text/hist.rs +++ b/src/text/hist.rs @@ -197,7 +197,7 @@ impl Hist { let c = take(&mut x.changes); self.history.push(Diff(c, [take(&mut self.lc), x.cursor.clone()])); self.lc = x.cursor.clone(); - println!("push {}", self.history.last().unwrap()); + // println!("push {}", self.history.last().unwrap()); self.redo_history.clear(); take(&mut self.last); self.last_edit = Instant::now(); @@ -211,14 +211,21 @@ impl Hist { } pub fn undo(&mut self, t: &mut TextArea) -> ropey::Result<Option<()>> { self.push_if_changed(t); - self.undo_() + let r = self + .undo_() .map(|x| { let r = x.apply(t, false); self.lc = t.cursor.clone(); self.last = t.changes.clone(); r }) - .transpose() + .transpose(); + let l = t.len_chars(); + t.cursor + .inner + .iter_mut() + .for_each(|x| x.position = x.position.min(l)); + r } pub fn redo(&mut self, t: &mut TextArea) -> ropey::Result<Option<()>> { self.redo_() diff --git a/src/text/inlay.rs b/src/text/inlay.rs index 37b98cf..049aad7 100644 --- a/src/text/inlay.rs +++ b/src/text/inlay.rs @@ -1,5 +1,5 @@ use Default::default; -use lsp_types::{InlayHint, InlayHintLabel, Location}; +use lsp_types::{InlayHint, Label, Location}; use serde_derive::{Deserialize, Serialize}; use crate::text::{RopeExt, TextArea}; @@ -37,11 +37,11 @@ impl TextArea { pub fn set_inlay(&mut self, inlay: &[InlayHint]) { self.inlays = inlay .iter() - .map(|i| { + .filter_map(|i| { let mut label = match &i.label { - InlayHintLabel::String(x) => + Label::String(x) => x.chars().map(|x| (x, None)).collect::<Vec<_>>(), - InlayHintLabel::LabelParts(v) => v + Label::InlayHintLabelPartList(v) => v .iter() .flat_map(|x| { x.value @@ -56,8 +56,8 @@ impl TextArea { if i.padding_right == Some(true) { label.push((' ', None)); } - let position = self.l_position(i.position).unwrap() as _; - Marking { position, data: label.into() } + let position = self.l_position(i.position)? as _; + Some(Marking { position, data: label.into() }) }) .collect(); } diff --git a/src/text/semantic_tokens.rs b/src/text/semantic_tokens.rs index f7c414a..8b09851 100644 --- a/src/text/semantic_tokens.rs +++ b/src/text/semantic_tokens.rs @@ -14,8 +14,11 @@ use crate::text::manipulations::Manip; Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, )] pub struct TokenD { + #[serde(rename = "0")] pub range: (u32, u32), + #[serde(rename = "1")] pub ty: u32, + #[serde(rename = "2")] pub modifiers: u32, } impl TokenD { |