A simple CPU rendered GUI IDE experience.
bookmarks
| -rw-r--r-- | src/commands.rs | 100 | ||||
| -rw-r--r-- | src/edi.rs | 94 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/menu/generic.rs | 2 | ||||
| -rw-r--r-- | src/rnd.rs | 11 | ||||
| -rw-r--r-- | src/sym.rs | 49 | ||||
| -rw-r--r-- | src/text.rs | 11 | ||||
| -rw-r--r-- | src/text/bookmark.rs | 33 |
8 files changed, 213 insertions, 88 deletions
diff --git a/src/commands.rs b/src/commands.rs index 5b99581..a0b28f8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -16,7 +16,9 @@ use crate::edi::{Editor, lsp_m}; use crate::lsp::{PathURI, Rq}; use crate::menu::charc; use crate::menu::generic::{CorA, GenericMenu, MenuData}; -use crate::text::{RopeExt, SortTedits, col, color_}; +use crate::text::{ + Bookmark, RopeExt, SortTedits, TextArea, col, color_, +}; macro_rules! repl { ($x:ty, $($with:tt)+) => { @@ -25,7 +27,7 @@ macro_rules! repl { } macro_rules! commands { ($(#[doc = $d: literal] $t:tt $identifier: ident$(($($thing:ty),+))?: $c:literal),+ $(,)?) => { - #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(Clone, PartialEq, Eq, Debug)] pub enum Cmd { $(#[doc = $d] $identifier $( ( $(Option<$thing>,)+ ) )? @@ -35,7 +37,7 @@ macro_rules! commands { pub const ALL: [Cmd; { [$($c),+].len() }] = [$(Self::$identifier $( ( $(None::<$thing>,)+) )? ,)+]; - pub fn name(self) -> &'static str { + pub fn name(&self) -> &'static str { match self { $(Self::$identifier $( ( $(repl!($thing, _),)+ ) )? => $c,)+ } @@ -45,16 +47,42 @@ macro_rules! commands { // $(Self::$identifier => &[$($(wants::$thing as u8,)+)?],)+ // } // } - pub fn desc(self) -> &'static str { + pub fn desc(&self) -> &'static str { match self { $(Self::$identifier $( ( $(repl!($thing, _),)+ ) )? => $d,)+ } } - pub fn needs_lsp(self) -> bool { + pub fn needs_lsp(&self) -> bool { match self { $(Self::$identifier $( ( $(repl!($thing, _),)+ ) )? => stringify!($t) == "@",)+ } } + fn map(self, t: &TextArea) -> Result<Self, &'static str> { + match self { + $($( + Self::$identifier($( repl!($thing, None),)+) => { + if !t.rope.to_string().starts_with(Self::$identifier(None).name()) { return Ok(self) } + if let Some((_, x)) = t.to_string().split_once(" ") + // && x.chars().all(|x| x.is_numeric())s + && let Ok(n) = x.parse() + { + Ok(Self::$identifier(Some(n))) + } else { + Err(concat! ("supply ", $(stringify!($thing))+)) + } + })?)+ + _ => Ok(self), + } + } + fn cora(&self) -> CorA { + match self { + $($( + Self::$identifier($( repl!($thing, None),)+) => { + CorA::Complete + })?)+ + _ => CorA::Accept, + } + } } }; } @@ -71,13 +99,13 @@ commands!( @ RAJoinLines: "join-lines", /// gets list of runnables @ RARunnables: "runnables", - /// Open docs for type at cursor + /// Open docs for type at cursor @ RADocs: "open-docs", /// Rebuilds rust-analyzer proc macros @ RARebuildProcMacros: "rebuild-proc-macros", /// Cancels current running rust-analyzer check process @ RACancelFlycheck: "cancel-flycheck", - /// Opens Cargo.toml file for this workspace + /// Opens Cargo.toml file for this workspace @ RAOpenCargoToml: "open-cargo-toml", /// Runs the test at the cursor @ RARunTest: "run-test", @@ -85,6 +113,10 @@ commands!( // @ ViewChildModules: "child-modules", /// GoTo line, | GoTo(u32): "g", + /// Define bookmark + | DefineBookmark(String): "bookmark", + /// Remove bookmark + | RemoveBookmark: "remove-bookmark", ); pub enum Cmds {} @@ -104,24 +136,10 @@ impl MenuData for Cmds { m: &GenericMenu<Self>, x: Self::Element<'a>, ) -> Result<Self::Element<'a>, Self::E> { - if let Cmd::GoTo(_) = x { - if !m.tedit.to_string().starts_with(Cmd::GoTo(None).name()) { - return Ok(Cmd::GoTo(None)); - } - if let Some((_, x)) = m.tedit.to_string().split_once(" ") - // && x.chars().all(|x| x.is_numeric()) - && let Ok(n) = x.parse() - { - Ok(Cmd::GoTo(Some(n))) - } else { - Err("supply number") - } - } else { - Ok(x) - } + x.map(&m.tedit) } - fn complete_or_accept<'a>(x: Self::Element<'a>) -> CorA { - if let Cmd::GoTo(None) = x { CorA::Complete } else { CorA::Accept } + fn complete_or_accept<'a>(x: &Self::Element<'a>) -> CorA { + x.cora() } fn f(m: &GenericMenu<Self>) -> String { m.tedit @@ -203,16 +221,39 @@ impl Editor { self.text.cursor.just(x, &self.text.rope); self.text.scroll_to_cursor_centering(); }, - x if x.needs_lsp() => {} + Cmd::DefineBookmark(Some(x)) => + self.text.bookmarks.push(Bookmark { + position: *self.text.cursor.first(), + text: x.clone(), + }), + Cmd::RemoveBookmark => { + let y = self.text.y(*self.text.cursor.first()).unwrap(); + let r = self.text.line_to_char(y) + ..self.text.line_to_char(y + 1); + self.text + .bookmarks + .clone() + .iter() + .enumerate() + .filter(|(_, x)| r.contains(&x.position)) + .map(ttools::fst) + .rev() + .for_each(|x| drop(self.text.bookmarks.remove(x))); + } + z if z.needs_lsp() => return self.handle_lsp_command(z, w), x => unimplemented!("{x:?}"), } - if !z.needs_lsp() { - return Ok(()); - } + + Ok(()) + } + pub fn handle_lsp_command( + &mut self, + z: Cmd, + w: Arc<dyn winit::window::Window>, + ) -> rootcause::Result<()> { let Some((l, o)) = lsp_m!(self + p) else { bail!("no lsp"); }; - match z { Cmd::RAMoveIU | Cmd::RAMoveID => { let r = self @@ -333,7 +374,6 @@ impl Editor { } _ => unimplemented!(), } - Ok(()) } } @@ -13,7 +13,6 @@ use lsp_server::{Connection, Request as LRq, ResponseError}; use lsp_types::request::*; use lsp_types::*; use regex::Regex; -use rootcause::prelude::ResultExt; use rootcause::report; use ropey::Rope; use rust_analyzer::lsp::ext::OnTypeFormatting; @@ -467,7 +466,7 @@ impl Editor { x.poll(|x, (_, p)| { let Some(p) = p else { unreachable!() }; x.ok().flatten().map(|r| sym::Symbols { - data: (r, p.data.1, p.data.2), + data: (r, p.data.1, p.data.2, p.data.3), selection: 0, vo: 0, ..p @@ -1031,8 +1030,10 @@ impl Editor { }), ), ); - q.result = - Some(Symbols::new(self.tree.as_deref().unwrap())); + q.result = Some(Symbols::new( + self.tree.as_deref().unwrap(), + self.text.bookmarks.clone(), + )); self.state = State::Symbols(q); }, Some(Do::SwitchType) => @@ -1042,7 +1043,7 @@ impl Editor { else { unreachable!() }; - x.data.2 = sym::SymbolsType::Document; + x.data.3 = sym::SymbolsType::Document; let p = p.to_owned(); take(&mut x.data.0); *request = Some(( @@ -1056,7 +1057,7 @@ impl Editor { )); }, Some(Do::ProcessCommand(mut x, z)) => - match Cmds::complete_or_accept(z) { + match Cmds::complete_or_accept(&z) { crate::menu::generic::CorA::Complete => { x.tedit.rope = Rope::from_str(&format!("{} ", z.name())); @@ -1097,7 +1098,7 @@ impl Editor { .is_some() || ptedit != x.tedit.rope { - if x.data.2 == SymbolsType::Workspace { + if x.data.3 == SymbolsType::Workspace { *request = Some(( DropH::new( lsp.runtime.spawn( @@ -1136,6 +1137,8 @@ impl Editor { let x = self.text.l_range(x).unwrap(); self.text.vo = self.text.char_to_line(x.start); } + sym::GoTo::P(None, x) => + self.text.vo = self.text.char_to_line(x), _ => {} } } @@ -1153,45 +1156,58 @@ impl Editor { let x = self.text.l_range(x).unwrap(); self.text.vo = self.text.char_to_line(x.start); } + sym::GoTo::P(None, x) => + self.text.vo = self.text.char_to_line(x), _ => {} } } } - Some(Do::SymbolsSelect(x)) => { - if let Some(Ok(x)) = x.sel() - && let Err(e) = try bikeshed rootcause::Result<()> { - let r = match x.at { - sym::GoTo::Loc(x) => { - let x = x.clone(); - let f = x - .uri - .to_file_path() - .map_err(|()| { - report!("provided uri not path") + Some(Do::SymbolsSelect(x)) => 'out: { + { + if let Some(Ok(x)) = x.sel() + && let Err(e) = try bikeshed rootcause::Result<()> { + let r = match x.at { + sym::GoTo::Loc(x) => { + let x = x.clone(); + let f = x + .uri + .to_file_path() + .map_err(|()| { + report!( + "provided uri not path" + ) .context(x.uri) - })? - .canonicalize()?; - self.state = State::Default; - self.requests.complete = - CompletionState::None; - if Some(&f) != self.origin.as_ref() { - self.open(&f, window.clone())?; + })? + .canonicalize()?; + self.state = State::Default; + self.requests.complete = + CompletionState::None; + if Some(&f) != self.origin.as_ref() { + self.open(&f, window.clone())?; + } + x.range + } + sym::GoTo::P(_u, x) => { + self.text + .cursor + .just(x, &self.text.rope); + self.text.scroll_to_cursor_centering(); + break 'out; } - x.range + sym::GoTo::R(range) => range, + }; + let p = self.text.l_position(r.start).ok_or( + report!("provided range out of bound") + .context_custom::<WDebug, _>(r), + )?; + if p != 0 { + self.text.cursor.just(p, &self.text.rope); } - sym::GoTo::R(range) => range, - }; - let p = self.text.l_position(r.start).ok_or( - report!("provided range out of bound") - .context_custom::<WDebug, _>(r), - )?; - if p != 0 { - self.text.cursor.just(p, &self.text.rope); + self.text.scroll_to_cursor_centering(); } - self.text.scroll_to_cursor_centering(); + { + log::error!("alas! {e}"); } - { - log::error!("alas! {e}"); } } Some(Do::RenameSymbol(to)) => { @@ -2056,8 +2072,8 @@ pub fn handle2<'a>( text.right(); text.backspace() } - Character("d") if alt() && ctrl() => text.word_left(), - Character("a") if alt() && ctrl() => text.word_right(), + Character("a") if alt() && ctrl() => text.word_left(), + Character("d") if alt() && ctrl() => text.word_right(), Character("a") if alt() => text.left(), Character("w") if alt() => text.up(), Character("s") if alt() => text.down(), diff --git a/src/main.rs b/src/main.rs index 45bbdd1..3eae1e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![feature( + field_projections, trim_prefix_suffix, const_closures, yeet_expr, diff --git a/src/menu/generic.rs b/src/menu/generic.rs index 90daa33..ac815c2 100644 --- a/src/menu/generic.rs +++ b/src/menu/generic.rs @@ -56,7 +56,7 @@ pub trait MenuData: Sized { type Element<'a>: Key<'a>; type E = !; - fn complete_or_accept<'a>(_x: Self::Element<'a>) -> CorA { + fn complete_or_accept<'a>(_x: &Self::Element<'a>) -> CorA { CorA::Accept } fn map<'a>( @@ -80,6 +80,10 @@ pub fn render( style: Style { fg: BG, secondary_color: BG, bg: BG, flags: 0 }, letter: None, }); + let bmks = text.bookmarks .iter().filter_map(|x| { + text.try_char_to_line(x.position).ok() + }).collect::<Vec<_>>(); + let z = match &ed.state { State::Selection => Some( text.cursor @@ -90,13 +94,14 @@ pub fn render( ), _ => None, }; + text.line_numbers( (c, r - 1), [67, 76, 87], BG, (cells, (c, r)), (1, 0), - |mut f, y| { + |_text, mut f, y| { if let State::GoToL(menu) = &ed.state && let Some(Ok((p, r))) = menu.sel() && Some(p) == ed.origin.as_deref() @@ -114,6 +119,10 @@ pub fn render( f.style.fg = col!("#FFCC66"); } } + if bmks.contains(&y) { + f.style.bg = col!("#4060aa"); + f.style.fg = col!("#a7bcd6"); + } f }, ); @@ -11,28 +11,44 @@ use lsp_types::*; use crate::FG; use crate::menu::generic::{GenericMenu, MenuData}; use crate::menu::{Key, charc}; -use crate::text::{col, color_, set_a}; +use crate::text::{Bookmarks, col, color_, set_a}; pub enum Symb {} impl MenuData for Symb { - type Data = (SymbolsList, Vec<SymbolInformation>, SymbolsType); + type Data = + (SymbolsList, Vec<SymbolInformation>, Bookmarks, SymbolsType); type Element<'a> = UsedSI<'a>; fn gn( - (r, tree, _): &Self::Data, + (r, tree, bmks, _): &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(x.iter().flat_map(|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.into(); + Iter3::B( + gen { + for bmk in &**bmks { + yield UsedSI { + name: &bmk.text, + kind: SymbolKind::BOOKMARK, + tags: None, + at: GoTo::P(None, 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.into(); + } + }, + )), + ), SymbolsList::Workspace(WorkspaceSymbolResponse::Flat(x)) => Iter3::C(chain(tree, x.iter()).map(UsedSI::from)), _ => unreachable!("please no"), @@ -57,6 +73,7 @@ pub type Symbols = GenericMenu<Symb>; pub enum GoTo<'a> { Loc(&'a Location), R(Range), + P(Option<&'a Url>, usize), } #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct UsedSI<'a> { @@ -126,7 +143,7 @@ impl Default for SymbolsList { } impl GenericMenu<Symb> { - pub fn new(tree: &[PathBuf]) -> Self { + pub fn new(tree: &[PathBuf], bmk: Bookmarks) -> Self { let tree = tree .iter() .map(|x| SymbolInformation { @@ -145,7 +162,7 @@ impl GenericMenu<Symb> { tags: None, }) .collect(); - Self { data: (default(), tree, default()), ..default() } + Self { data: (default(), tree, bmk, default()), ..default() } } } @@ -169,7 +186,7 @@ fn r<'a>( let ds: Style = 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); 70] = { + const MAP: [([u8; 3], [u8; 3], &str); 85] = { car::map!( amap::amap! { const { SymbolKind::FILE.0 as usize } => ("#9a9b9a", " "), @@ -193,6 +210,7 @@ fn r<'a>( const { SymbolKind::MACRO.0 as usize } => ("#f28f74", "! "), const { SymbolKind::PROC_MACRO.0 as usize } => ("#f28f74", "r!"), + const { SymbolKind::BOOKMARK.0 as usize } => ("#73D0FF", " "), _ => ("#9a9b9a", " ") }, |(x, y)| (set_a(color_(x), 0.5), color_(x), y) @@ -213,8 +231,9 @@ fn r<'a>( - (charc(&x.name) as i32 + qualifier.clone().count() as i32) - 3; let loc = match x.at { - GoTo::Loc(x) => Some(x.uri.to_file_path().unwrap()), - GoTo::R(_) => None, + GoTo::Loc(Location { uri, .. }) | GoTo::P(Some(uri), _) => + Some(uri.to_file_path().unwrap()), + _ => None, }; let locs = if sty == SymbolsType::Workspace { loc.as_ref() diff --git a/src/text.rs b/src/text.rs index 8b892df..2e1de2b 100644 --- a/src/text.rs +++ b/src/text.rs @@ -33,10 +33,11 @@ use semantic_tokens::TokenD; pub mod hist; pub mod theme_treesitter; use hist::Changes; +mod bookmark; +pub use bookmark::*; use crate::sni::{Snippet, StopP}; use crate::text::hist::Action; - pub const fn color_(x: &str) -> [u8; 3] { let Some(x): Option<[u8; 7]> = x.as_bytes().try_into().ok() else { panic!() @@ -107,8 +108,11 @@ pub struct TextArea { pub tabstops: Option<Snippet>, pub inlays: BTreeSet<Inlay>, pub tokens: Vec<TokenD>, + #[serde(default)] + pub bookmarks: Bookmarks, pub changes: Changes, } + #[derive(Serialize, Deserialize)] pub struct CellBuffer { pub c: usize, @@ -395,6 +399,7 @@ impl TextArea { }; self.tabstops.as_mut().map(|x| x.manipulate(manip)); self.cursor.manipulate(manip); + self.bookmarks.manipulate(manip); for pos in self .inlays .range(Marking::idx(r.end as _)..) @@ -428,6 +433,7 @@ impl TextArea { }; 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 _)..) @@ -1040,7 +1046,7 @@ impl TextArea { bg: [u8; 3], (into, (w, _)): (&mut [Cell], (usize, usize)), (ox, oy): (usize, usize), - mut m: impl FnMut(Cell, usize) -> Cell, + mut m: impl FnMut(&Self, Cell, usize) -> Cell, ) { for y in 0..r { if (self.vo + y) < self.l() { @@ -1050,6 +1056,7 @@ impl TextArea { .zip(into[(y + oy) * w..].iter_mut().skip(ox)) .for_each(|(a, b)| { *b = m( + self, Cell { style: Style::new(color, bg), letter: Some(a), diff --git a/src/text/bookmark.rs b/src/text/bookmark.rs new file mode 100644 index 0000000..ebda796 --- /dev/null +++ b/src/text/bookmark.rs @@ -0,0 +1,33 @@ +use std::ops::{Deref, DerefMut}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, Default, Debug)] +pub struct Bookmark { + pub position: usize, + pub text: String, +} + +#[derive(Clone, Serialize, Deserialize, Default, Debug)] +pub struct Bookmarks(Vec<Bookmark>); + +impl DerefMut for Bookmarks { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deref for Bookmarks { + type Target = Vec<Bookmark>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Bookmarks { + pub fn manipulate(&mut self, mut f: impl FnMut(usize) -> usize) { + for lem in &mut self.0 { + lem.position = f(lem.position); + } + } +} |