A simple CPU rendered GUI IDE experience.
-rw-r--r--src/commands.rs100
-rw-r--r--src/edi.rs94
-rw-r--r--src/main.rs1
-rw-r--r--src/menu/generic.rs2
-rw-r--r--src/rnd.rs11
-rw-r--r--src/sym.rs49
-rw-r--r--src/text.rs11
-rw-r--r--src/text/bookmark.rs33
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(())
}
}
diff --git a/src/edi.rs b/src/edi.rs
index b861cad..4c1150e 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -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>(
diff --git a/src/rnd.rs b/src/rnd.rs
index 94ec906..231eaf0 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -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
},
);
diff --git a/src/sym.rs b/src/sym.rs
index 1a0db44..8e2a420 100644
--- a/src/sym.rs
+++ b/src/sym.rs
@@ -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);
+ }
+ }
+}