A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/commands.rs | 26 | ||||
| -rw-r--r-- | src/edi.rs | 112 | ||||
| -rw-r--r-- | src/git.rs | 2 | ||||
| -rw-r--r-- | src/gotolist.rs | 92 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/rnd.rs | 11 | ||||
| -rw-r--r-- | src/sym.rs | 71 | ||||
| -rw-r--r-- | src/text/bookmark.rs | 1 |
9 files changed, 196 insertions, 123 deletions
@@ -70,6 +70,8 @@ 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 = "0.12.1" +ttools = "0.1.0" +bind = { version = "0.1.0", path = "../bind" } [profile.dev.package] rust-analyzer.opt-level = 3 fimg.opt-level = 3 diff --git a/src/commands.rs b/src/commands.rs index a0b28f8..d32f3c9 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,12 +13,12 @@ use rust_analyzer::lsp::ext::*; use crate::FG; use crate::edi::{Editor, lsp_m}; +use crate::gotolist::{At, GoToList}; use crate::lsp::{PathURI, Rq}; use crate::menu::charc; use crate::menu::generic::{CorA, GenericMenu, MenuData}; -use crate::text::{ - Bookmark, RopeExt, SortTedits, TextArea, col, color_, -}; +use crate::sym::GoTo; +use crate::text::{Bookmark, RopeExt, SortTedits, TextArea, col, color_}; macro_rules! repl { ($x:ty, $($with:tt)+) => { @@ -117,6 +117,8 @@ commands!( | DefineBookmark(String): "bookmark", /// Remove bookmark | RemoveBookmark: "remove-bookmark", + /// Global bookmark menu + | Bookmarks: "goto-bookmarks", ); pub enum Cmds {} @@ -240,6 +242,24 @@ impl Editor { .rev() .for_each(|x| drop(self.text.bookmarks.remove(x))); } + Cmd::Bookmarks => { + let src = self + .files + .iter() + .chain(self.origin.as_ref().zip(Some(&*self))) + .flat_map(|(f, x)| { + x.text.bookmarks.iter().zip(repeat(f)) + }) + .map(|(b, path)| GoTo { + path: path.clone().into(), + at: At::P(b.position), + }) + .collect::<Vec<_>>(); + self.state = crate::edi::st::State::GoToL(GoToList { + data: (src, Some(crate::gotolist::O::Bmk)), + ..default() + }); + } z if z.needs_lsp() => return self.handle_lsp_command(z, w), x => unimplemented!("{x:?}"), } @@ -8,6 +8,7 @@ use std::sync::Arc; use std::time::SystemTime; use Default::default; +use bind::Bind; use implicit_fn::implicit_fn; use lsp_server::{Connection, Request as LRq, ResponseError}; use lsp_types::request::*; @@ -34,6 +35,7 @@ use crate::bar::Bar; use crate::commands::Cmds; use crate::complete::Complete; use crate::error::WDebug; +use crate::gotolist::{At, GoTo}; use crate::hov::{self, Hovr}; use crate::lsp::{ self, Anonymize, Client, Map_, PathURI, RequestError, Rq, tdpp, @@ -244,13 +246,13 @@ macro_rules! change { } pub(crate) use change; -fn rooter(x: &Path) -> Option<PathBuf> { - for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) { - if f.file_name() == "Cargo.toml" { +fn rooter(x: &Path, search: &str) -> Option<PathBuf> { + for f in std::fs::read_dir(&x).ok()?.filter_map(Result::ok) { + if f.file_name() == search { return Some(f.path().with_file_name("").to_path_buf()); } } - x.parent().and_then(rooter) + x.parent().and_then(rooter.rbind(search)) } impl Editor { @@ -269,7 +271,7 @@ impl Editor { me.workspace = o .as_ref() .and_then(|x| x.parent()) - .and_then(|x| rooter(&x)) + .and_then(|x| rooter(&x, "Cargo.toml")) .and_then(|x| x.canonicalize().ok()); let mut loaded_state = false; if let Some(ws) = me.workspace.as_deref() @@ -466,7 +468,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, p.data.3), + data: (r, p.data.1, p.data.2, p.data.3, p.data.4), selection: 0, vo: 0, ..p @@ -513,36 +515,25 @@ impl Editor { z.data.0 = match x { GotoDefinitionResponse::Scalar( location, - ) => vec![( - location - .uri - .to_file_path() - .ok()?, - location.range, + ) => vec![GoTo::from( + location, )], GotoDefinitionResponse::Array( locations, ) => locations .into_iter() - .filter_map(|x| try { - ( - x.uri - .to_file_path() - .ok()?, - x.range, - ) - }) + .map(GoTo::from) .collect(), GotoDefinitionResponse::Link( location_links, ) => location_links .into_iter() - .filter_map(|x| try { - ( - x.target_uri - .to_file_path() - .ok()?, - x.target_range, + .map(|LocationLink {target_uri, target_range, .. }| { + GoTo::from( + Location { + uri: target_uri, + range: target_range, + } ) }) .collect(), @@ -1018,7 +1009,7 @@ impl Editor { } } Some(Do::Symbols) => - if let Some(lsp) = lsp!(self) { + if let Some((lsp, o)) = lsp!(self + p) { let mut q = Rq::new( lsp.runtime.spawn( lsp.workspace_symbols("".into()) @@ -1033,6 +1024,7 @@ impl Editor { q.result = Some(Symbols::new( self.tree.as_deref().unwrap(), self.text.bookmarks.clone(), + o.into(), )); self.state = State::Symbols(q); }, @@ -1131,15 +1123,16 @@ impl Editor { unreachable!() }; x.next(); - if let Some(Ok(x)) = x.sel() { + if let Some(Ok(x)) = x.sel() + && Some(&*x.at.path) == self.origin.as_deref() + { match x.at { - sym::GoTo::R(x) => { + sym::GoTo { path: _, at: At::R(x) } => { let x = self.text.l_range(x).unwrap(); self.text.vo = self.text.char_to_line(x.start); } - sym::GoTo::P(None, x) => + sym::GoTo { path: _, at: At::P(x) } => self.text.vo = self.text.char_to_line(x), - _ => {} } } } @@ -1150,15 +1143,16 @@ impl Editor { unreachable!() }; x.back(); - if let Some(Ok(x)) = x.sel() { - match x.at { - sym::GoTo::R(x) => { + if let Some(Ok(x)) = x.sel() + && Some(&*x.at.path) == self.origin.as_deref() + { + match x.at.at { + At::R(x) => { let x = self.text.l_range(x).unwrap(); self.text.vo = self.text.char_to_line(x.start); } - sym::GoTo::P(None, x) => + At::P(x) => self.text.vo = self.text.char_to_line(x), - _ => {} } } } @@ -1166,44 +1160,34 @@ impl Editor { { 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()?; + match x.at.at { + At::R(r) => { + let f = x.at.path.canonicalize()?; self.state = State::Default; self.requests.complete = CompletionState::None; if Some(&f) != self.origin.as_ref() { self.open(&f, window.clone())?; } - x.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(); } - sym::GoTo::P(_u, x) => { + At::P(x) => { self.text .cursor .just(x, &self.text.rope); self.text.scroll_to_cursor_centering(); break 'out; } - 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(); } { log::error!("alas! {e}"); @@ -1820,8 +1804,8 @@ impl Editor { } } Some(Do::GTLSelect(x)) => { - if let Some(Ok((p, r))) = x.sel() - && Some(p) == self.origin.as_deref() + if let Some(Ok(GoTo { path: p, at: At::R(r) })) = x.sel() + && Some(&*p) == self.origin.as_deref() { let x = self.text.l_range(r).unwrap(); self.text.vo = self.text.char_to_line(x.start); @@ -1832,8 +1816,8 @@ impl Editor { let State::GoToL(x) = &mut self.state else { unreachable!() }; - if let Some(Ok((p, r))) = x.sel() - && Some(p) == self.origin.as_deref() + if let Some(Ok(GoTo { path: p, at: At::R(r) })) = x.sel() + && Some(&*p) == self.origin.as_deref() { // let x = self.text.l_range(r).unwrap(); self.text.scroll_to_ln_centering(r.start.line as _); @@ -5,7 +5,7 @@ use imara_diff::InternedInput; use ropey::Rope; pub fn load(p: &Path, ws: &Path) -> Result<Vec<u8>, git2::Error> { - let r = Repository::open(ws).unwrap(); + let r = Repository::open(ws)?; let o = r.head()?.peel_to_commit()?.tree()?.get_path(p)?.to_object(&r)?; let blob = o diff --git a/src/gotolist.rs b/src/gotolist.rs index 5a38fb9..8461021 100644 --- a/src/gotolist.rs +++ b/src/gotolist.rs @@ -1,9 +1,10 @@ -use std::path::{Path, PathBuf}; +use std::borrow::Cow; +use std::path::Path; use dsb::Cell; use dsb::cell::Style; -use lsp_types::Range; use lsp_types::request::GotoImplementation; +use lsp_types::{Location, Range}; use crate::FG; use crate::lsp::RqS; @@ -15,23 +16,25 @@ pub enum GTL {} #[derive(Debug)] pub enum O { Impl(RqS<(), GotoImplementation>), + Bmk, } -impl<'a> Key<'a> for (&'a Path, Range) { +impl<'a> Key<'a> for GoTo<'a> { fn key(&self) -> impl Into<std::borrow::Cow<'a, str>> { - self.0.display().to_string() + self.path.display().to_string() + // self.display().to_string() } } impl MenuData for GTL { - type Data = (Vec<(PathBuf, Range)>, Option<O>); + type Data = (Vec<GoTo<'static>>, Option<O>); - type Element<'a> = (&'a Path, Range); + type Element<'a> = GoTo<'a>; type E = !; fn gn<'a>( x: &'a Self::Data, ) -> impl Iterator<Item = Self::Element<'a>> { - x.0.iter().map(|(x, y)| (&**x, *y)) + x.0.iter().map(GoTo::asref) } fn r( @@ -53,20 +56,25 @@ impl MenuData for GTL { into.iter_mut() // .skip(1) .zip( - elem.0 + elem.path .strip_prefix(workspace) - .unwrap_or(&elem.0) + .unwrap_or(&elem.path) .display() .to_string() .chars() .chain( - format!( - " {}:{} - {}:{}", - elem.1.start.line, - elem.1.start.character, - elem.1.end.line, - elem.1.end.character - ) + match elem.at { + At::R(r) => format!( + " {}:{} - {}:{}", + r.start.line, + r.start.character, + r.end.line, + r.end.character + ), + At::P(at) => { + format!(" {at}") + } + } .chars(), ), ) @@ -74,3 +82,55 @@ impl MenuData for GTL { to.extend(into); } } + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct GoTo<'a> { + pub path: Cow<'a, Path>, + pub at: At, +} + +impl From<Location> for GoTo<'static> { + fn from(Location { uri, range }: Location) -> Self { + Self { + path: Cow::Owned(uri.to_file_path().unwrap()), + at: At::R(range), + } + } +} +impl From<&Location> for GoTo<'static> { + fn from(Location { uri, range }: &Location) -> Self { + Self { + path: Cow::Owned(uri.to_file_path().unwrap()), + at: At::R(*range), + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum At { + R(Range), + P(usize), +} + +impl From<usize> for At { + fn from(v: usize) -> Self { + Self::P(v) + } +} + +impl From<Range> for At { + fn from(v: Range) -> Self { + Self::R(v) + } +} +impl GoTo<'static> { + fn asref<'n>(&'n self) -> GoTo<'n> { + GoTo { + at: self.at, + path: match &self.path { + Cow::Borrowed(x) => Cow::Borrowed(x), + Cow::Owned(x) => Cow::Borrowed(&**x), + }, + } + } +} diff --git a/src/main.rs b/src/main.rs index 3eae1e0..6c92fe4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,7 +141,7 @@ pub(crate) fn entry(event_loop: EventLoop) { }; assert_eq!(unsafe { atexit(cleanup) }, 0); unsafe { signal(libc::SIGINT, sigint as *const () as usize) }; - let ed = unsafe { __ED.assume_init_mut() }; + let ed: &'static mut Editor = unsafe { __ED.assume_init_mut() }; let ppem = 20.0; let ls = 20.0; // let ed = Box::leak(Box::new(ed)); @@ -18,8 +18,9 @@ use winit::window::{ImeRequestData, Window}; use crate::edi::st::State; use crate::edi::{Editor, lsp_m}; +use crate::gotolist::{At, GoTo}; use crate::lsp::Rq; -use crate::sym::{GoTo, UsedSI}; +use crate::sym::{UsedSI}; use crate::text::{CoerceOption, RopeExt, col, color_}; use crate::{ BG, BORDER, CompletionAction, CompletionState, FG, FONT, complete, @@ -103,16 +104,16 @@ pub fn render( (1, 0), |_text, mut f, y| { if let State::GoToL(menu) = &ed.state - && let Some(Ok((p, r))) = menu.sel() - && Some(p) == ed.origin.as_deref() - { + && let Some(Ok( GoTo{ at: At::R(r),path})) = menu.sel() + && Some(&*path) == ed.origin.as_deref() + { 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::R(x), .. })) = + && let Some(Ok(UsedSI { at: GoTo{ at: At::R(x),..}, .. })) = menu.sel() { if (x.start.line..=x.end.line).contains(&(y as _)) { @@ -9,18 +9,25 @@ use itern::Iter3; use lsp_types::*; use crate::FG; +use crate::gotolist::At; +pub use crate::gotolist::GoTo; use crate::menu::generic::{GenericMenu, MenuData}; use crate::menu::{Key, charc}; use crate::text::{Bookmarks, col, color_, set_a}; pub enum Symb {} impl MenuData for Symb { - type Data = - (SymbolsList, Vec<SymbolInformation>, Bookmarks, SymbolsType); + type Data = ( + SymbolsList, + Vec<SymbolInformation>, + Bookmarks, + SymbolsType, + PathBuf, // origin + ); type Element<'a> = UsedSI<'a>; fn gn( - (r, tree, bmks, _): &Self::Data, + (r, tree, bmks, _, origin): &Self::Data, ) -> impl Iterator<Item = Self::Element<'_>> { match r { SymbolsList::Document(DocumentSymbolResponse::Flat(x)) => @@ -33,7 +40,10 @@ impl MenuData for Symb { name: &bmk.text, kind: SymbolKind::BOOKMARK, tags: None, - at: GoTo::P(None, bmk.position), + at: GoTo { + path: origin.into(), + at: At::P(bmk.position), + }, right: None, } } @@ -44,7 +54,7 @@ impl MenuData for Symb { q.push_back(x); while let Some(x) = q.pop_front() { q.extend(x.children.iter().flatten()); - yield x.into(); + yield (x, origin).into(); } }, )), @@ -56,7 +66,7 @@ impl MenuData for Symb { } fn r( - &(.., sty): &Self::Data, + &(.., sty, _): &Self::Data, x: Self::Element<'_>, workspace: &Path, c: usize, @@ -69,13 +79,7 @@ impl MenuData for Symb { } pub type Symbols = GenericMenu<Symb>; -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum GoTo<'a> { - Loc(&'a Location), - R(Range), - P(Option<&'a Url>, usize), -} -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone)] pub struct UsedSI<'a> { pub name: &'a str, pub kind: SymbolKind, @@ -98,28 +102,31 @@ impl<'a> From<&'a SymbolInformation> for UsedSI<'a> { name: &name, kind: *kind, tags: tags.as_deref(), - at: GoTo::Loc(location), + at: GoTo::from(location), right: container_name.as_deref(), } } } -impl<'a> From<&'a DocumentSymbol> for UsedSI<'a> { +impl<'a> From<(&'a DocumentSymbol, &'a PathBuf)> for UsedSI<'a> { fn from( - DocumentSymbol { - name, - detail, - kind, - tags, - range, - selection_range: _, - .. - }: &'a DocumentSymbol, + ( + DocumentSymbol { + name, + detail, + kind, + tags, + range, + selection_range: _, + .. + }, + path, + ): (&'a DocumentSymbol, &'a PathBuf), ) -> Self { UsedSI { name: &name, kind: *kind, tags: tags.as_deref(), - at: GoTo::R(*range), + at: GoTo { path: path.into(), at: At::R(*range) }, right: detail.as_deref(), } } @@ -143,7 +150,7 @@ impl Default for SymbolsList { } impl GenericMenu<Symb> { - pub fn new(tree: &[PathBuf], bmk: Bookmarks) -> Self { + pub fn new(tree: &[PathBuf], bmk: Bookmarks, orig: PathBuf) -> Self { let tree = tree .iter() .map(|x| SymbolInformation { @@ -162,7 +169,7 @@ impl GenericMenu<Symb> { tags: None, }) .collect(); - Self { data: (default(), tree, bmk, default()), ..default() } + Self { data: (default(), tree, bmk, default(), orig), ..default() } } } @@ -230,14 +237,12 @@ fn r<'a>( let left = i.len() as i32 - (charc(&x.name) as i32 + qualifier.clone().count() as i32) - 3; - let loc = match x.at { - GoTo::Loc(Location { uri, .. }) | GoTo::P(Some(uri), _) => - Some(uri.to_file_path().unwrap()), - _ => None, - }; + let loc = x.at.path; let locs = if sty == SymbolsType::Workspace { loc.as_ref() - .and_then(|x| x.strip_prefix(workspace).unwrap_or(&x).to_str()) + .strip_prefix(workspace) + .unwrap_or(&*loc) + .to_str() .unwrap_or("") } else { "" diff --git a/src/text/bookmark.rs b/src/text/bookmark.rs index ebda796..4080d21 100644 --- a/src/text/bookmark.rs +++ b/src/text/bookmark.rs @@ -30,4 +30,5 @@ impl Bookmarks { lem.position = f(lem.position); } } + // pub fn to_gtl_d(&self) -> Vec<(PathBuf, Range)> {} } |