A simple CPU rendered GUI IDE experience.
rudimentary multi cursor
bendn 3 weeks ago
parent b78e20d · commit d55e867
-rw-r--r--Cargo.toml2
-rw-r--r--src/bar.rs16
-rw-r--r--src/com.rs4
-rw-r--r--src/edi.rs428
-rw-r--r--src/edi/st.rs50
-rw-r--r--src/lsp.rs13
-rw-r--r--src/main.rs13
-rw-r--r--src/meta.rs7
-rw-r--r--src/rnd.rs27
-rw-r--r--src/text.rs671
-rw-r--r--src/text/cursor.rs499
11 files changed, 1146 insertions, 584 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 36548aa..5bfcaf2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,7 +37,7 @@ lsp-server = { git = "https://git.bendn.org/rust-analyzer" }
rust-analyzer = { git = "https://git.bendn.org/rust-analyzer" }
serde_json = "1.0.145"
-serde = "1.0.228"
+serde = { version = "1.0.228", features = ["unstable"] }
serde_derive = "1.0.228"
log = "0.4.28"
crossbeam = { version = "0.8.4", features = ["nightly", "crossbeam-channel"] }
diff --git a/src/bar.rs b/src/bar.rs
index 9d50e3c..6cc9371 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -7,7 +7,7 @@ use lsp_types::WorkDoneProgress;
use crate::lsp::{Client, Rq};
use crate::rnd::simplify_path;
use crate::sym::Symbols;
-use crate::text::TextArea;
+use crate::text::{RopeExt, TextArea};
#[derive(Default, Debug)]
pub struct Bar {
pub last_action: String,
@@ -130,9 +130,17 @@ impl Bar {
}
});
}
- State::Selection(x) => {
- let [(x1, y1), (x2, y2)] = t.position(x.clone());
- format!("selection from ({x1}, {y1}) to ({x2}, {y2})")
+ // State::Selection => {
+ // let [(x1, y1), (x2, y2)] = t.position(x.clone()).unwrap();
+ // format!("selection from ({x1}, {y1}) to ({x2}, {y2})")
+ // .chars()
+ // .rev()
+ // .zip(row.iter_mut().rev())
+ // .for_each(|(x, y)| y.letter = Some(x));
+ // }
+ State::Selection => {
+ // let [(x1, y1), (x2, y2)] = t.position(x.clone()).unwrap();
+ format!("selection mode")
.chars()
.rev()
.zip(row.iter_mut().rev())
diff --git a/src/com.rs b/src/com.rs
index 9640d91..5c3dfb0 100644
--- a/src/com.rs
+++ b/src/com.rs
@@ -229,8 +229,8 @@ fn r(
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::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", "& "),
diff --git a/src/edi.rs b/src/edi.rs
index dd1e064..c8dc77d 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -32,8 +32,12 @@ use crate::hov::{self, Hovr};
use crate::lsp::{
self, Anonymize, Client, Map_, PathURI, RedrawAfter, RequestError, Rq,
};
+use crate::meta::META;
use crate::sym::SymbolsType;
-use crate::text::{self, CoerceOption, Mapping, SortTedits, TextArea};
+use crate::text::cursor::{Ronge, ceach};
+use crate::text::{
+ self, CoerceOption, Mapping, RopeExt, SortTedits, TextArea,
+};
use crate::{
BoolRequest, CDo, ClickHistory, CompletionAction, CompletionState,
Hist, act, alt, ctrl, filter, hash, shift, sig, sym, trm,
@@ -186,7 +190,7 @@ impl Editor {
std::env::args().nth(1).map(|x| {
me.text.insert(&std::fs::read_to_string(x).unwrap()).unwrap();
- me.text.cursor = 0;
+ me.text.cursor = default();
});
me.workspace = o
.as_ref()
@@ -341,8 +345,8 @@ impl Editor {
eprintln!("unhappy fmt")
}
}
- self.text.cursor =
- self.text.cursor.min(self.text.rope.len_chars());
+ // self.text.cursor =
+ // self.text.cursor.min(self.text.rope.len_chars());
change!(self);
self.hist.push(&self.text);
l.notify::<lsp_notification!("textDocument/didSave")>(
@@ -504,17 +508,18 @@ impl Editor {
) {
match self.state.consume(Action::C(cursor_position)).unwrap() {
Some(Do::ExtendSelectionToMouse) => {
- *self.state.sel() = self.text.extend_selection_to(
- self.text.mapped_index_at(cursor_position),
- self.state.sel().clone(),
- );
+ let p = self.text.mapped_index_at(cursor_position);
+ self.text
+ .cursor
+ .first_mut()
+ .extend_selection_to(p, &self.text.rope);
w.request_redraw();
}
Some(Do::StartSelection) => {
let x = self.text.mapped_index_at(cursor_position);
- self.hist.last.cursor = x;
- self.text.cursor = x;
- *self.state.sel() = x..x;
+ self.hist.last.cursor.first_mut().position = x;
+ self.text.cursor.first_mut().position = x;
+ self.text.cursor.first_mut().sel = Some((x..x).into());
}
Some(Do::Hover)
if let Some(hover) =
@@ -731,49 +736,63 @@ impl Editor {
.unwrap();
match self.state.consume(Action::M(bt)).unwrap() {
Some(Do::MoveCursor) => {
- text.cursor = text.mapped_index_at(cursor_position);
+ text.cursor.just(
+ text.mapped_index_at(cursor_position),
+ &text.rope,
+ );
if let Some((lsp, path)) = lsp!(self + p) {
self.requests.sig_help.request(lsp.runtime.spawn(
- w.redraw_after(
- lsp.request_sig_help(path, text.cursor()),
- ),
+ w.redraw_after(lsp.request_sig_help(
+ path,
+ text.primary_cursor(),
+ )),
));
self.requests.document_highlights.request(
- lsp.runtime.spawn(w.redraw_after(
- lsp.document_highlights(
- path,
- text.to_l_position(text.cursor).unwrap(),
+ lsp.runtime.spawn(
+ w.redraw_after(
+ lsp.document_highlights(
+ path,
+ text.to_l_position(
+ text.cursor.first().position,
+ )
+ .unwrap(),
+ ),
),
- )),
+ ),
);
}
- self.hist.last.cursor = text.cursor;
- self.chist.push(text.cursor());
- text.setc();
+ self.hist.last.cursor = text.cursor.clone();
+ self.chist.push(text.primary_cursor());
+ text.cursor.first().setc(&text.rope);
}
Some(Do::NavForward) => {
self.chist.forth().map(|x| {
- text.cursor = text.rope.line_to_char(x.1) + x.0;
+ text.cursor.just(
+ text.rope.line_to_char(x.1) + x.0,
+ &text.rope,
+ );
text.scroll_to_cursor();
});
}
Some(Do::NavBack) => {
self.chist.back().map(|x| {
- text.cursor = text.rope.line_to_char(x.1) + x.0;
+ text.cursor.just(
+ text.rope.line_to_char(x.1) + x.0,
+ &text.rope,
+ );
text.scroll_to_cursor();
});
}
Some(Do::ExtendSelectionToMouse) => {
- *self.state.sel() = text.extend_selection_to(
- text.mapped_index_at(cursor_position),
- self.state.sel().clone(),
- );
+ let p = text.mapped_index_at(cursor_position);
+ text.cursor.first_mut().extend_selection_to(p, &text.rope);
}
Some(Do::StartSelection) => {
- let x = text.mapped_index_at(cursor_position);
- self.hist.last.cursor = x;
- *self.state.sel() =
- text.extend_selection_to(x, text.cursor..text.cursor);
+ let p = text.mapped_index_at(cursor_position);
+ self.hist.last.cursor.just(p, &text.rope);
+ let x = *text.cursor.first();
+ text.cursor.first_mut().sel = Some((x..x).into());
+ text.cursor.first_mut().extend_selection_to(p, &text.rope);
}
Some(Do::GoToDefinition) => {
if let Some(LocationLink {
@@ -788,11 +807,22 @@ impl Editor {
)
.unwrap();
- self.text.cursor =
- self.text.l_position(target_range.start).unwrap();
+ self.text.cursor.just(
+ self.text.l_position(target_range.start).unwrap(),
+ &self.text.rope,
+ );
self.text.scroll_to_cursor();
}
}
+ Some(Do::InsertCursorAtMouse) => {
+ text.cursor.add(
+ text.mapped_index_at(cursor_position),
+ &text.rope,
+ );
+ self.hist.last.cursor = text.cursor.clone();
+ self.chist.push(text.primary_cursor());
+ text.cursor.first().setc(&text.rope);
+ }
None => {}
_ => unreachable!(),
}
@@ -834,16 +864,25 @@ impl Editor {
_ => {}
}
match o {
- Some(Do::MaybeRemoveSigHelp) => {
+ Some(Do::Escape) => {
take(&mut self.requests.complete);
take(&mut self.requests.sig_help);
+ self.text.cursor.alone();
}
- Some(Do::Comment(x)) => {
- if x == (0..0) {
- self.text.comment(self.text.cursor..self.text.cursor);
- } else {
- self.text.comment(x);
- }
+ Some(Do::Comment) => {
+ ceach!(self.text.cursor, |cursor| {
+ Some(
+ if let Some(x) = cursor.sel
+ && matches!(self.state, State::Selection)
+ {
+ self.text.comment(x.into());
+ } else {
+ self.text
+ .comment(cursor.position..cursor.position);
+ },
+ )
+ });
+ self.text.cursor.clear_selections();
change!(self);
}
Some(Do::SpawnTerminal) => {
@@ -957,7 +996,7 @@ impl Editor {
let p = self.text
.l_position(x.location.range.start).ok_or(anyhow::anyhow!("rah"))?;
if p != 0 {
- self.text.cursor = p;
+ self.text.cursor.just(p, &self.text.rope);
}
self.text.scroll_to_cursor_centering();
} {
@@ -975,7 +1014,10 @@ impl Editor {
position: self
.text
.to_l_position(
- self.text.cursor,
+ self.text
+ .cursor
+ .first()
+ .position,
)
.unwrap(),
},
@@ -1025,7 +1067,7 @@ impl Editor {
range: self
.text
.to_l_range(
- self.text.cursor..self.text.cursor,
+ self.text.cursor.first().position..self.text.cursor.first().position,
)
.unwrap(),
context: CodeActionContext {
@@ -1068,7 +1110,7 @@ impl Editor {
let Some(act) = c.right() else { break 'out };
let act = act.clone();
self.state = State::Default;
- self.hist.last.cursor = self.text.cursor;
+ self.hist.last.cursor = self.text.cursor.clone();
self.hist.test_push(&self.text);
let act = lsp
.runtime
@@ -1107,8 +1149,10 @@ impl Editor {
Some(
Do::Reinsert
| Do::GoToDefinition
+ | Do::MoveCursor
| Do::NavBack
- | Do::NavForward,
+ | Do::NavForward
+ | Do::InsertCursorAtMouse,
) => panic!(),
Some(Do::Save) => match &self.origin {
Some(_) => {
@@ -1124,8 +1168,9 @@ impl Editor {
self.save();
}
Some(Do::Edit) => {
+ self.text.cursor.clear_selections();
self.hist.test_push(&self.text);
- let cb4 = self.text.cursor;
+ let cb4 = self.text.cursor.first();
if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) =
event.logical_key
&& let CompletionState::Complete(..) =
@@ -1140,27 +1185,36 @@ impl Editor {
}
self.text.scroll_to_cursor();
inlay!(self);
- if cb4 != self.text.cursor
+ if cb4 != self.text.cursor.first()
&& let CompletionState::Complete(Rq {
result: Some(c),
..
}) = &self.requests.complete
- && ((self.text.cursor < c.start)
- || (!super::is_word(self.text.at_())
- && (self.text.at_() != '.'
- || self.text.at_() != ':')))
+ && let at =
+ self.text.cursor.first().at_(&self.text.rope)
+ && ((self.text.cursor.first() < c.start)
+ || (!super::is_word(at)
+ && (at != '.' || at != ':')))
{
self.requests.complete = CompletionState::None;
}
if self.requests.sig_help.running()
- && cb4 != self.text.cursor
+ && cb4 != self.text.cursor.first()
&& let Some((lsp, path)) = lsp!(self + p)
{
- self.requests.sig_help.request(lsp.runtime.spawn(
- window.redraw_after(
- lsp.request_sig_help(path, self.text.cursor()),
+ self.requests.sig_help.request(
+ lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.request_sig_help(
+ path,
+ self.text
+ .cursor
+ .first()
+ .cursor(&self.text.rope),
+ ),
+ ),
),
- ));
+ );
}
if self.hist.record(&self.text)
&& let Some((lsp, path)) = lsp!(self + p)
@@ -1179,12 +1233,17 @@ impl Editor {
&& x.contains(&y.to_string()) =>
{
self.requests.sig_help.request(
- lsp.runtime.spawn(window.redraw_after(
- lsp.request_sig_help(
- o,
- self.text.cursor(),
+ lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.request_sig_help(
+ o,
+ self.text
+ .cursor
+ .first()
+ .cursor(&self.text.rope),
+ ),
),
- )),
+ ),
);
}
_ => {}
@@ -1198,13 +1257,20 @@ impl Editor {
.unwrap()
{
Some(CDo::Request(ctx)) => {
- let h = DropH::new(lsp.runtime.spawn(
- window.redraw_after(lsp.request_complete(
- o,
- self.text.cursor(),
- ctx,
- )),
- ));
+ let h = DropH::new(
+ lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.request_complete(
+ o,
+ self.text
+ .cursor
+ .first()
+ .cursor(&self.text.rope),
+ ctx,
+ ),
+ ),
+ ),
+ );
let CompletionState::Complete(Rq {
request: x,
result: c,
@@ -1217,7 +1283,7 @@ impl Editor {
c.as_ref()
.map(|x| x.start)
.or(x.as_ref().map(|x| x.1))
- .unwrap_or(self.text.cursor),
+ .unwrap_or(*self.text.cursor.first()),
));
}
Some(CDo::SelectNext) => {
@@ -1265,7 +1331,10 @@ impl Editor {
_ => {
let (s, _) =
self.text.apply(&ed).unwrap();
- self.text.cursor =
+ self.text
+ .cursor
+ .first_mut()
+ .position =
s + ed.new_text.chars().count();
}
}
@@ -1283,12 +1352,17 @@ impl Editor {
change!(self);
}
self.requests.sig_help = Rq::new(
- lsp.runtime.spawn(window.redraw_after(
- lsp.request_sig_help(
- o,
- self.text.cursor(),
+ lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.request_sig_help(
+ o,
+ self.text
+ .cursor
+ .first()
+ .cursor(&self.text.rope),
+ ),
),
- )),
+ ),
);
}
None => return,
@@ -1309,54 +1383,152 @@ impl Editor {
}
Some(Do::Quit) => return ControlFlow::Break(()),
Some(Do::SetCursor(x)) => {
- self.text.cursor = x;
- self.text.setc();
+ self.text.cursor.each(|c| {
+ let Some(r) = c.sel else { return };
+ match x {
+ LR::Left => c.position = r.start,
+ LR::Right => c.position = r.end,
+ }
+ });
+ self.text.cursor.clear_selections();
}
Some(Do::StartSelection) => {
let Key::Named(y) = event.logical_key else { panic!() };
- *self.state.sel() = self.text.extend_selection(
- y,
- self.text.cursor..self.text.cursor,
- );
+ // let mut z = vec![];
+ self.text.cursor.each(|x| {
+ x.sel = Some(Ronge::from(**x..**x));
+ x.extend_selection(
+ y,
+ // **x..**x,
+ &self.text.rope,
+ &mut self.text.vo,
+ self.text.r,
+ );
+ });
+ // *self.state.sel() = z;
}
Some(Do::UpdateSelection) => {
let Key::Named(y) = event.logical_key else { panic!() };
- *self.state.sel() = self
- .text
- .extend_selection(y, self.state.sel().clone());
+ self.text.cursor.each(|x| {
+ x.extend_selection(
+ y,
+ // dbg!(r.clone()),
+ &self.text.rope,
+ &mut self.text.vo,
+ self.text.r,
+ );
+ });
+
self.text.scroll_to_cursor();
inlay!(self);
}
- Some(Do::Insert(x, c)) => {
+ Some(Do::Insert(c)) => {
+ // self.text.cursor.inner.clear();
self.hist.push_if_changed(&self.text);
- _ = self.text.remove(x.clone());
- self.text.cursor = x.start;
- self.text.setc();
- self.text.insert(&c);
+ ceach!(self.text.cursor, |cursor| {
+ let Some(r) = cursor.sel else { return };
+ _ = self.text.remove(r.into());
+ // self.text.cursor.first().setc(&self.text.rope);
+ });
+ self.text.insert(&c).unwrap();
+ self.text.cursor.clear_selections();
self.hist.push_if_changed(&self.text);
change!(self);
}
- Some(Do::Delete(x)) => {
+ Some(Do::Delete) => {
self.hist.push_if_changed(&self.text);
- self.text.cursor = x.start;
- _ = self.text.remove(x);
+ 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(&self.text);
change!(self);
}
- Some(Do::Copy(x)) => {
- clipp::copy(self.text.rope.slice(x).to_string());
+
+ Some(Do::Copy) => {
+ self.hist.push_if_changed(&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());
+ }
+ });
+ unsafe {
+ META.splits.push(clip.len());
+ META.hash = hash(&clip)
+ };
+ clipp::copy(clip);
+ self.text.cursor.clear_selections();
+ self.hist.push_if_changed(&self.text);
+ change!(self);
}
- Some(Do::Cut(x)) => {
+ Some(Do::Cut) => {
self.hist.push_if_changed(&self.text);
- clipp::copy(self.text.rope.slice(x.clone()).to_string());
- self.text.rope.remove(x.clone());
- self.text.cursor = x.start;
+ 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());
+ }
+ });
+ unsafe {
+ META.splits.push(clip.len());
+ META.hash = hash(&clip)
+ };
+ clipp::copy(clip);
+ ceach!(self.text.cursor, |x| {
+ if let Some(x) = x.sel {
+ self.text.remove(x.into()).unwrap();
+ }
+ });
+ self.text.cursor.clear_selections();
self.hist.push_if_changed(&self.text);
change!(self);
}
Some(Do::Paste) => {
self.hist.push_if_changed(&self.text);
- self.text.insert(&clipp::paste());
+ let r = clipp::paste();
+ if unsafe { META.hash == hash(&r) } {
+ 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 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).unwrap();
+ eprintln!("hrmst");
+ }
+ } else {
+ self.text.insert(&clipp::paste()).unwrap();
+ }
self.hist.push_if_changed(&self.text);
change!(self);
}
@@ -1377,11 +1549,13 @@ impl Editor {
s.clone()
.find_iter(&self.text.rope.to_string())
.enumerate()
- .find(|(_, x)| x.start() > self.text.cursor)
+ .find(|(_, x)| x.start() > *self.text.cursor.first())
.map(|(x, m)| {
self.state = State::Search(s, x, n);
- self.text.cursor =
- self.text.rope.byte_to_char(m.end());
+ self.text.cursor.just(
+ self.text.rope.byte_to_char(m.end()),
+ &self.text.rope,
+ );
self.text.scroll_to_cursor_centering();
inlay!(self);
})
@@ -1393,7 +1567,10 @@ impl Editor {
let (re, index, _) = self.state.search();
let s = self.text.rope.to_string();
let m = re.find_iter(&s).nth(*index).unwrap();
- self.text.cursor = self.text.rope.byte_to_char(m.end());
+ self.text.cursor.just(
+ self.text.rope.byte_to_char(m.end()),
+ &self.text.rope,
+ );
self.text.scroll_to_cursor_centering();
inlay!(self);
}
@@ -1405,13 +1582,31 @@ impl Editor {
)
.unwrap(),
);
- self.text.cursor =
- self.text.cursor.min(self.text.rope.len_chars());
+
+ 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(&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);
+ }
None => {}
}
ControlFlow::Continue(())
@@ -1513,8 +1708,13 @@ impl Editor {
)
.unwrap(),
);
- self.text.cursor =
- self.text.cursor.min(self.text.rope.len_chars());
+
+ 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);
@@ -1532,7 +1732,7 @@ impl Editor {
let new = std::fs::read_to_string(&x)?;
take(&mut self.text);
self.text.insert(&new)?;
- self.text.cursor = 0;
+ 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;
@@ -1594,11 +1794,11 @@ pub fn handle2<'a>(
Named(Backspace) if ctrl() => text.backspace_word(),
Named(Backspace) => text.backspace(),
Named(Home) if ctrl() => {
- text.cursor = 0;
+ text.cursor.just(0, &text.rope);
text.vo = 0;
}
Named(End) if ctrl() => {
- text.cursor = text.rope.len_chars();
+ text.cursor.just(text.rope.len_chars(), &text.rope);
text.vo = text.l().saturating_sub(text.r);
}
Named(Home) => text.home(),
@@ -1628,10 +1828,6 @@ pub fn handle2<'a>(
}
impl State {
- fn sel(&mut self) -> &mut std::ops::Range<usize> {
- let State::Selection(x) = self else { panic!() };
- x
- }
fn search(&mut self) -> (&mut Regex, &mut usize, &mut usize) {
let State::Search(x, y, z) = self else { panic!() };
(x, y, z)
diff --git a/src/edi/st.rs b/src/edi/st.rs
index 8eac701..adf0486 100644
--- a/src/edi/st.rs
+++ b/src/edi/st.rs
@@ -11,7 +11,7 @@ use crate::lsp::{AQErr, RequestError, Rq, RqS};
use crate::sym::{Symbols, SymbolsType};
use crate::text::TextArea;
use crate::{
- BoolRequest, CLICKING, InputRequest, act, ctrl, handle, shift,
+ BoolRequest, CLICKING, InputRequest, act, alt, ctrl, handle, shift,
};
impl Default for State {
@@ -19,6 +19,16 @@ impl Default for State {
Self::Default
}
}
+#[derive(Debug, Copy, Clone)]
+pub enum Direction {
+ Above,
+ Below,
+}
+#[derive(Debug, Copy, Clone)]
+pub enum LR {
+ Left,
+ Right,
+}
rust_fsm::state_machine! {
#[derive(Debug)]
pub(crate) State => #[derive(Debug)] pub(crate) Action => #[derive(Debug)] pub(crate) Do
@@ -37,17 +47,21 @@ Default => {
K(Key::Character(x) if x == "." && ctrl()) => _ [CodeAction],
K(Key::Character(x) if x == "0" && ctrl()) => _ [MatchingBrace],
K(Key::Character(x) if x == "`" && ctrl()) => _ [SpawnTerminal],
- K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment(Range<usize> => 0..0)],
+ K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment],
K(Key::Named(F1)) => Procure((default(), InputRequest::RenameSymbol)),
- K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
- M(MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
+ K(Key::Named(k @ (ArrowUp | ArrowDown)) if alt()) => _ [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()) => _ [InsertCursorAtMouse],
M(MouseButton::Left if ctrl()) => _ [GoToDefinition],
M(MouseButton::Left) => _ [MoveCursor],
M(MouseButton::Back) => _ [NavBack],
M(MouseButton::Forward) => _ [NavForward],
- C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection(0..0) [StartSelection],
+ C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection [StartSelection],
Changed => RequestBoolean(BoolRequest => BoolRequest::ReloadFile),
- K(Key::Named(Escape)) => _ [MaybeRemoveSigHelp],
+ K(Key::Named(Escape)) => _ [Escape],
C(_) => _ [Hover],
K(_) => _ [Edit],
M(_) => _,
@@ -79,22 +93,22 @@ CodeAction(RqS<act::CodeActions, lsp_request!("textDocument/codeAction")> => rq)
M(_) => _,
K(_) => _,
},
-Selection(x if shift()) => {
- K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End)) => Selection(x) [UpdateSelection],
- M(MouseButton => MouseButton::Left) => Selection(x) [ExtendSelectionToMouse],
+Selection => {
+ K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection [UpdateSelection],
+ M(MouseButton::Left if shift()) => Selection [ExtendSelectionToMouse],
}, // note: it does in fact fall through. this syntax is not an arm, merely shorthand.
-Selection(x) => {
+Selection => {
C(_ if unsafe { CLICKING }) => _ [ExtendSelectionToMouse],
- C(_) => Selection(x),
+ C(_) => _,
M(MouseButton => MouseButton::Left) => Default [MoveCursor],
- K(Key::Named(Backspace)) => Default [Delete(Range<usize> => x)],
- K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)],
- K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy(Range<usize> => x)],
- K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment(Range<usize> => x)],
+ K(Key::Named(Backspace)) => Default [Delete],
+ K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut],
+ K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy],
+ K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment],
- K(Key::Character(y) if !ctrl()) => Default [Insert((Range<usize>, SmolStr) => (x, y))],
- K(Key::Named(ArrowLeft)) => Default [SetCursor(usize => x.start)],
- K(Key::Named(ArrowRight)) => Default [SetCursor(usize => x.end)],
+ K(Key::Character(y) if !ctrl()) => Default [Insert(SmolStr => y)],
+ K(Key::Named(ArrowLeft)) => Default [SetCursor(LR => LR::Left)],
+ K(Key::Named(ArrowRight)) => Default [SetCursor(LR::Right)],
K(_) => Default [Edit],
},
Save => {
diff --git a/src/lsp.rs b/src/lsp.rs
index 5f0218b..0ebfeb3 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -10,7 +10,8 @@ use std::sync::atomic::Ordering::Relaxed;
use std::task::Poll;
use std::thread::spawn;
use std::time::Instant;
-
+use tokio::task;
+use crate::text::{SortTedits, TextArea, cursor::ceach};
use Default::default;
use anyhow::bail;
use crossbeam::channel::{
@@ -488,13 +489,13 @@ impl Client {
self.request::<lsp_request!("experimental/matchingBrace")>(
&MatchingBraceParams {
text_document: f.tid(),
- positions: vec![t.to_l_position(t.cursor).unwrap()],
+ positions: vec![t.to_l_position(*t.cursor.first()).unwrap()],
},
)
.unwrap()
.0,
) {
- t.cursor = t.l_position(x).unwrap();
+ t.cursor.first_mut().position = t.l_position(x).unwrap();
}
}
pub fn inlay(
@@ -603,13 +604,14 @@ impl Client {
}
pub fn enter<'a>(&self, f: &Path, t: &'a mut TextArea) {
+ ceach!(t.cursor, |c| {
let r = self
.runtime
.block_on(
self.request::<lsp_request!("experimental/onEnter")>(
&TextDocumentPositionParams {
text_document: f.tid(),
- position: t.to_l_position(t.cursor).unwrap(),
+ position: t.to_l_position(*c).unwrap(),
},
)
.unwrap()
@@ -625,6 +627,7 @@ impl Client {
}
}
}
+ });
}
}
pub fn run(
@@ -1114,9 +1117,7 @@ impl<T, U, F: FnMut(T) -> U, Fu: Future<Output = T>> Map_<T, U, F> for Fu {
Map(self, f)
}
}
-use tokio::task;
-use crate::text::{CoerceOption, SortTedits, TextArea};
#[derive(Debug)]
pub enum OnceOff<T> {
Waiting(task::JoinHandle<Result<T, oneshot::error::RecvError>>),
diff --git a/src/main.rs b/src/main.rs
index f4c2c65..e4087ee 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,7 @@
#![feature(tuple_trait, unboxed_closures, fn_traits)]
#![feature(
+ type_alias_impl_trait,
+ decl_macro,
duration_millis_float,
anonymous_lifetime_in_impl_trait,
try_blocks_heterogeneous,
@@ -36,6 +38,7 @@
#![allow(incomplete_features, irrefutable_let_patterns, static_mut_refs)]
mod act;
mod edi;
+mod meta;
// mod new;
mod rnd;
mod sym;
@@ -167,8 +170,8 @@ impl Hist {
))
.unwrap(),
data: [
- (self.last.cursor, self.last.column, self.last.vo),
- (x.cursor, x.column, x.vo),
+ (self.last.cursor.clone(), self.last.vo),
+ (x.cursor.clone(), x.vo),
],
});
println!("push {}", self.history.last().unwrap());
@@ -603,13 +606,17 @@ impl Default for CompletionState {
fn filter(text: &TextArea) -> String {
if text
.cursor
+ .first()
.checked_sub(1)
.is_none_or(|x| matches!(text.rope.get_char(x), Some('.' | ':')))
{
"".to_string()
} else {
text.rope
- .slice(text.word_left_p()..text.cursor)
+ .slice(
+ text.cursor.first().word_left_p(&text.rope)
+ ..*text.cursor.first(),
+ )
.chars()
.collect::<String>()
}
diff --git a/src/meta.rs b/src/meta.rs
new file mode 100644
index 0000000..62a8cbd
--- /dev/null
+++ b/src/meta.rs
@@ -0,0 +1,7 @@
+#[derive(Clone, Default)]
+pub struct Meta {
+ pub hash: u64,
+ pub splits: Vec<usize>,
+ pub count: usize,
+}
+pub static mut META: Meta = Meta { count: 0, splits: vec![], hash: 4 };
diff --git a/src/rnd.rs b/src/rnd.rs
index 1717021..e091de5 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -22,7 +22,7 @@ use winit::window::Window;
use crate::edi::st::State;
use crate::edi::{Editor, lsp_m};
use crate::lsp::Rq;
-use crate::text::{CoerceOption, col};
+use crate::text::{CoerceOption, RopeExt, col};
use crate::{
BG, BORDER, CompletionAction, CompletionState, FG, FONT, com, filter,
lsp, sig,
@@ -45,7 +45,7 @@ pub fn render(
mut i: Image<&mut [u8], 3>,
) {
let text = &mut ed.text;
- let (cx, cy) = text.cursor_visual();
+ let (cx, cy) = text.primary_cursor_visual();
let met = super::FONT.metrics(&[]);
let fac = ppem / met.units_per_em as f32;
window.set_ime_cursor_area(
@@ -75,7 +75,7 @@ pub fn render(
letter: None,
});
let x = match &ed.state {
- State::Selection(x) => Some(x.clone()),
+ State::Selection => Some(text.cursor.iter().filter_map(|x| x.sel).map(std::ops::Range::from).collect()),
_ => None,
};
text.line_numbers(
@@ -505,7 +505,7 @@ pub fn render(
State::CodeAction(Rq { result: Some(x), .. }) => 'out: {
let m = x.maxc();
let c = x.write(m);
- let (_x, _y) = text.cursor_visual();
+ let (_x, _y) = (cx, cy);
let _x = _x + text.line_number_offset() + 1;
let Some(_y) = _y.checked_sub(text.vo) else {
println!("rah");
@@ -590,7 +590,7 @@ pub fn render(
{
let (sig, p) = sig::active(x);
let c = sig::sig((sig, p), 40);
- let (_x, _y) = text.cursor_visual();
+ let (_x, _y) = (cx, cy);
let _x = _x + text.line_number_offset() + 1;
let Some(_y) = _y.checked_sub(text.vo) else { break 'out };
let Ok((is_above, left, top, w, mut h)) = place_around(
@@ -688,7 +688,7 @@ pub fn render(
}
} else if let Some(c) = com {
let ppem = 20.0;
- let (_x, _y) = text.cursor_visual();
+ let (_x, _y) = text.primary_cursor_visual();
let _x = _x + text.line_number_offset() + 1;
let _y = _y.wrapping_sub(text.vo);
let Ok((_, left, top, w, h)) = place_around(
@@ -735,7 +735,14 @@ pub fn render(
);
}
};
- let (x, y) = text.cursor_visual();
+ if matches!(ed.state, State::Default | State::Selection) {
+
+ }
+ text.cursor.each_ref(|c| {
+ let(x,y)=text.visual_xy(*c).unwrap();
+ draw_at(x, y, &cursor);
+ });
+ // let (x, y) = text.cursor_visual();
let image = Image::<_, 4>::build(2, (fh).ceil() as u32)
.fill([82, 82, 82, 255]);
for stop in
@@ -746,9 +753,7 @@ pub fn render(
};
draw_at(x, y, &image);
}
- if matches!(ed.state, State::Default | State::Selection(_)) {
- draw_at(x, y, &cursor);
- }
+
window.pre_present_notify();
let buffer = surface.buffer_mut().unwrap();
let x = unsafe {
@@ -768,7 +773,7 @@ pub fn simplify_path(x: &str) -> String {
static DEP: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\.cargo\/git\/checkouts\/(?<name>[^/]+)\-[a-f0-9]+\/[a-f0-9]+").unwrap());
static DEP2: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\.cargo\/registry\/src/index.crates.io-[0-9a-f]+/(?<name>[^/]+)\-(?<version>[0-9]+\.[0-9]+\.[0-9]+)").unwrap());
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(env!("HOME"), " ");
[
(&*RUST_SRC, " "),
(&*DEP, " /$name"),
diff --git a/src/text.rs b/src/text.rs
index 76fd7f5..55cf331 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -24,6 +24,8 @@ use ropey::{Rope, RopeSlice};
use serde::{Deserialize, Serialize};
use tree_house::Language;
use winit::keyboard::{NamedKey, SmolStr};
+pub mod cursor;
+use cursor::*;
use crate::sni::{Snippet, StopP};
use crate::text::semantic::{MCOLORS, MODIFIED, MSTYLE};
@@ -217,7 +219,7 @@ pub struct Diff {
pub forth: Patches<u8>,
#[serde(deserialize_with = "from_t", serialize_with = "to_t")]
pub back: Patches<u8>,
- pub data: [(usize, usize, usize); 2],
+ pub data: [(Cursors, usize); 2],
}
impl Display for Diff {
@@ -239,9 +241,8 @@ impl Diff {
.unwrap()
.0,
);
- let (cu, co, vo) = self.data[redo as usize];
+ let (cu, vo) = self.data[redo as usize].clone();
t.cursor = cu;
- t.column = co;
t.scroll_to_cursor_centering();
// t.vo = vo;
}
@@ -260,8 +261,8 @@ pub struct TextArea {
deserialize_with = "deserialize_from_string"
)]
pub rope: Rope,
- pub cursor: usize,
- pub column: usize,
+ pub cursor: Cursors,
+
/// ┌─────────────────┐
/// │#invisible text │
/// │╶╶╶view offset╶╶╶│
@@ -334,7 +335,7 @@ impl Debug for TextArea {
f.debug_struct("TextArea")
.field("rope", &self.rope)
.field("cursor", &self.cursor)
- .field("column", &self.column)
+ // .field("column", &self.column)
.field("vo", &self.vo)
.field("r", &self.r)
.field("c", &self.c)
@@ -342,6 +343,94 @@ impl Debug for TextArea {
}
}
+impl Deref for TextArea {
+ type Target = Rope;
+
+ fn deref(&self) -> &Self::Target {
+ &self.rope
+ }
+}
+
+pub trait RopeExt {
+ fn position(&self, r: Range<usize>) -> Option<[(usize, usize); 2]>;
+ /// number of lines
+ fn l(&self) -> usize;
+
+ // input: char, output: utf8
+ fn x_bytes(&self, c: usize) -> Option<usize>;
+ fn y(&self, c: usize) -> Option<usize>;
+ fn x(&self, c: usize) -> Option<usize>;
+ fn xy(&self, c: usize) -> Option<(usize, usize)>;
+
+ fn beginning_of_line(&self, c: usize) -> Option<usize>;
+
+ fn indentation_of(&self, n: usize) -> usize;
+
+ /// or eof
+ fn eol(&self, li: usize) -> usize;
+}
+impl RopeExt for Rope {
+ fn position(
+ &self,
+ Range { start, end }: Range<usize>,
+ ) -> Option<[(usize, usize); 2]> {
+ let y1 = self.try_char_to_line(start).ok()?;
+ let y2 = self.try_char_to_line(end).ok()?;
+ let x1 = start - self.try_line_to_char(y1).ok()?;
+ let x2 = end - self.try_line_to_char(y2).ok()?;
+ [(x1, y1), (x2, y2)].into()
+ }
+ /// number of lines
+ fn l(&self) -> usize {
+ self.len_lines()
+ }
+
+ // input: char, output: utf8
+ fn x_bytes(&self, c: usize) -> Option<usize> {
+ let y = self.try_char_to_line(c).ok()?;
+ let x = self
+ .try_char_to_byte(c)
+ .ok()?
+ .checked_sub(self.try_line_to_byte(y).ok()?)?;
+ Some(x)
+ }
+ fn y(&self, c: usize) -> Option<usize> {
+ self.try_char_to_line(c).ok()
+ }
+
+ fn xy(&self, c: usize) -> Option<(usize, usize)> {
+ let y = self.try_char_to_line(c).ok()?;
+ let x = c.checked_sub(self.try_line_to_char(y).ok()?)?;
+ Some((x, y))
+ }
+
+ fn x(&self, c: usize) -> Option<usize> {
+ self.xy(c).map(|x| x.0)
+ }
+ fn beginning_of_line(&self, c: usize) -> Option<usize> {
+ self.y(c).and_then(|x| self.try_line_to_char(x).ok())
+ }
+ #[implicit_fn]
+ fn indentation_of(&self, n: usize) -> usize {
+ let Some(l) = self.get_line(n) else { return 0 };
+ l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count()
+ }
+
+ /// or eof
+ #[lower::apply(saturating)]
+ fn eol(&self, li: usize) -> usize {
+ self.try_line_to_char(li)
+ .map(|l| {
+ l + self
+ .get_line(li)
+ .map(|x| x.len_chars() - 1)
+ .unwrap_or_default()
+ })
+ .unwrap_or(usize::MAX)
+ .min(self.len_chars())
+ }
+}
+
impl TextArea {
#[implicit_fn::implicit_fn]
pub fn set_inlay(&mut self, inlay: &[InlayHint]) {
@@ -377,19 +466,12 @@ impl TextArea {
});
self.decorations = decorations;
}
- pub fn position(
- &self,
- Range { start, end }: Range<usize>,
- ) -> [(usize, usize); 2] {
- let y1 = self.rope.char_to_line(start);
- let y2 = self.rope.char_to_line(end);
- let x1 = start - self.rope.line_to_char(y1);
- let x2 = end - self.rope.line_to_char(y2);
- [(x1, y1), (x2, y2)]
- }
- pub fn visual_position(&self, r: Range<usize>) -> [(usize, usize); 2] {
- self.position(r).map(|x| self.map_to_visual(x))
+ pub fn visual_position(
+ &self,
+ r: Range<usize>,
+ ) -> Option<[(usize, usize); 2]> {
+ self.position(r).map(|x| x.map(|x| self.map_to_visual(x)))
}
pub fn visual_xy(&self, x: usize) -> Option<(usize, usize)> {
@@ -405,11 +487,6 @@ impl TextArea {
)
}
- /// number of lines
- pub fn l(&self) -> usize {
- self.rope.len_lines()
- }
-
pub fn source_map(
&'_ self,
l: usize,
@@ -453,22 +530,6 @@ impl TextArea {
Some(self.source_map(li)?.count())
}
- /// or eof
- #[lower::apply(saturating)]
- pub fn eol(&self, li: usize) -> usize {
- self.rope
- .try_line_to_char(li)
- .map(|l| {
- l + self
- .rope
- .get_line(li)
- .map(|x| x.len_chars() - 1)
- .unwrap_or_default()
- })
- .unwrap_or(usize::MAX)
- .min(self.rope.len_chars())
- }
-
#[implicit_fn::implicit_fn]
pub fn raw_index_at(&self, (x, y): (usize, usize)) -> Option<usize> {
let x = x.checked_sub(self.line_number_offset() + 1)? + self.ho;
@@ -491,7 +552,7 @@ impl TextArea {
pub fn mapped_index_at(&'_ self, (x, y): (usize, usize)) -> usize {
match self.visual_index_at((x, y)) {
Some(Mapping::Char(_, _, index)) => index,
- Some(Mapping::Fake(_, real, ..)) => real,
+ Some(Mapping::Fake(_, _, real, ..)) => real,
None => self.eol(self.vo + y),
}
}
@@ -508,7 +569,7 @@ impl TextArea {
}
};
self.tabstops.as_mut().map(|x| x.manipulate(manip));
- self.cursor = manip(self.cursor);
+ self.cursor.manipulate(manip);
Ok(())
}
@@ -522,16 +583,17 @@ impl TextArea {
if x < c { x } else { x + with.chars().count() }
};
self.tabstops.as_mut().map(|x| x.manipulate(manip));
- self.cursor = manip(self.cursor);
+ self.cursor.manipulate(manip);
Ok(())
}
pub fn insert(&mut self, c: &str) -> Result<(), ropey::Error> {
- let oc = self.cursor;
- self.insert_at(self.cursor, c)?;
- assert_eq!(self.cursor, oc + c.chars().count());
- self.cursor = oc + c.chars().count();
- self.setc();
+ // let oc = self.cursor;
+ ceach!(self.cursor, |cursor| {
+ self.insert_at(cursor.position, c).unwrap();
+ // assert_eq!(*cursor, oc + c.chars().count());
+ // self.cursor = oc + c.chars().count();
+ });
self.set_ho();
Ok(())
}
@@ -546,7 +608,8 @@ impl TextArea {
pub fn apply_adjusting(&mut self, x: &TextEdit) -> Result<(), ()> {
let (b, e) = self.apply(&x)?;
- if e < self.cursor {
+
+ if e < self.cursor.first().position {
if !self.visible(e) {
// line added behind, not visible
self.vo +=
@@ -584,25 +647,28 @@ impl TextArea {
crate::sni::Snippet::parse(&x.new_text, begin)
.ok_or(anyhow!("failed to parse snippet"))?;
self.insert_at(begin, &tex)?;
- self.cursor = match sni.next() {
+ self.cursor.one(match sni.next() {
Some(x) => {
self.tabstops = Some(sni);
- x.r().end
+ Cursor::new(x.r().end, &self.rope)
}
None => {
self.tabstops = None;
- sni.last
- .map(|x| x.r().end)
- .unwrap_or_else(|| begin + x.new_text.chars().count())
+ Cursor::new(
+ sni.last.map(|x| x.r().end).unwrap_or_else(|| {
+ begin + x.new_text.chars().count()
+ }),
+ &self.rope,
+ )
}
- };
+ });
Ok(())
}
- pub fn cursor(&self) -> (usize, usize) {
- self.xy(self.cursor).unwrap()
+ pub fn primary_cursor(&self) -> (usize, usize) {
+ self.cursor.first().cursor(&self.rope)
}
- pub fn cursor_visual(&self) -> (usize, usize) {
- let (x, y) = self.cursor();
+ pub fn primary_cursor_visual(&self) -> (usize, usize) {
+ let (x, y) = self.primary_cursor();
let mut z = self.reverse_source_map(y).unwrap();
(z.nth(x).unwrap_or(x), y)
}
@@ -613,213 +679,92 @@ impl TextArea {
pub fn visible(&self, x: usize) -> bool {
(self.vo..self.vo + self.r).contains(&self.rope.char_to_line(x))
}
- pub fn x(&self, c: usize) -> usize {
- self.xy(c).unwrap().0
- }
- pub fn beginning_of_line(&self, c: usize) -> Option<usize> {
- self.rope.try_line_to_char(self.y(c)).ok()
- }
-
- // input: char, output: utf8
- pub fn x_bytes(&self, c: usize) -> Option<usize> {
- let y = self.rope.try_char_to_line(c).ok()?;
- let x = self
- .rope
- .try_char_to_byte(c)
- .ok()?
- .checked_sub(self.rope.try_line_to_byte(y).ok()?)?;
- Some(x)
- }
- pub fn y(&self, c: usize) -> usize {
- self.rope.char_to_line(c)
- }
-
- pub fn xy(&self, c: usize) -> Option<(usize, usize)> {
- let y = self.rope.try_char_to_line(c).ok()?;
- let x = c.checked_sub(self.rope.try_line_to_char(y).ok()?)?;
- Some((x, y))
- }
-
- fn cl(&self) -> RopeSlice<'_> {
- self.rope.line(self.rope.char_to_line(self.cursor))
- }
-
- pub fn setc(&mut self) {
- self.column =
- self.cursor - self.beginning_of_line(self.cursor).unwrap();
- }
pub fn page_down(&mut self) {
- self.cursor = self.rope.line_to_char(min(
- self.rope.char_to_line(self.cursor) + self.r,
- self.l(),
- ));
+ let l = self.l();
+ self.cursor.each(|x| {
+ x.position = self.rope.line_to_char(min(
+ self.rope.char_to_line(**x) + self.r,
+ l,
+ ));
+ });
+ // for c in &mut self.cursor {
+ // *c = self.rope.line_to_char(min(
+ // self.rope.char_to_line(*c) + self.r,
+ // self.l(),
+ // ));
+ // }
self.scroll_to_cursor();
}
#[lower::apply(saturating)]
pub fn page_up(&mut self) {
- self.cursor = self
- .rope
- .line_to_char(self.rope.char_to_line(self.cursor) - self.r);
+ self.cursor.each(|x| {
+ x.position = self
+ .rope
+ .line_to_char(self.rope.char_to_line(**x) - self.r);
+ });
+ // self.cursor = self
+ // .rope
+ // .line_to_char(self.rope.char_to_line(self.cursor) - self.r);
self.scroll_to_cursor();
}
#[lower::apply(saturating)]
pub fn left(&mut self) {
- self.cursor -= 1;
- self.setc();
- self.set_ho();
- }
- #[implicit_fn]
- fn indentation_of(&self, n: usize) -> usize {
- let Some(l) = self.rope.get_line(n) else { return 0 };
- l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count()
+ self.cursor.left(&self.rope);
+ // self.cursor -= 1;
+ // self.setc();
+ // self.set_ho();
}
- #[implicit_fn]
- fn indentation(&self) -> usize {
- self.indentation_of(self.cursor().1)
- }
+ // #[implicit_fn]
+ // fn indentation(&self) -> usize {
+ // self.indentation_of(self.cursor().1)
+ // }
#[implicit_fn]
pub fn home(&mut self) {
- let l = self.rope.char_to_line(self.cursor);
- let beg = self.rope.line_to_char(l);
- let i = self.cursor - beg;
- let whitespaces = self.indentation();
- if self.rope.line(l).chars().all(_.is_whitespace()) {
- self.cursor = beg;
- self.column = 0;
- } else if i == whitespaces {
- self.cursor = beg;
- self.column = 0;
- } else {
- self.cursor = whitespaces + beg;
- self.column = whitespaces;
- }
- self.set_ho();
+ self.cursor.home(&self.rope);
}
pub fn end(&mut self) {
- let i = self.rope.char_to_line(self.cursor);
- let beg = self.rope.line_to_char(i);
-
- self.cursor = beg + self.cl().len_chars()
- - self.rope.get_line(i + 1).map(|_| 1).unwrap_or(0);
- self.setc();
- self.set_ho();
+ self.cursor.end(&self.rope);
}
pub fn set_ho(&mut self) {
- let x = self.cursor_visual().0;
- if x < self.ho + 4 {
- self.ho = x.saturating_sub(4);
- } else if x + 4 > (self.ho + self.c) {
- self.ho = (x.saturating_sub(self.c)) + 4;
- }
+ // let x = self.cursor_visual().0;
+ // if x < self.ho + 4 {
+ // self.ho = x.saturating_sub(4);
+ // } else if x + 4 > (self.ho + self.c) {
+ // self.ho = (x.saturating_sub(self.c)) + 4;
+ // }
}
#[lower::apply(saturating)]
pub fn right(&mut self) {
- self.cursor += 1;
- self.cursor = self.cursor.min(self.rope.len_chars());
- self.setc();
- self.set_ho();
+ self.cursor.right(&self.rope);
}
- pub fn at_(&self) -> char {
- self.rope.get_char(self.cursor - 1).unwrap_or('\n')
- }
- /// ??
- pub fn at_plus_one(&self) -> char {
- self.rope.get_char(self.cursor).unwrap_or('\n')
- }
#[implicit_fn]
pub fn word_right(&mut self) {
- self.cursor += self
- .rope
- .slice(self.cursor..)
- .chars()
- .take_while(_.is_whitespace())
- .count();
-
- self.cursor += if is_word(self.at_plus_one()).not()
- && !self.at_plus_one().is_whitespace()
- && !is_word(self.rope.char(self.cursor + 1))
- {
- self.rope
- .slice(self.cursor..)
- .chars()
- .take_while(|&x| {
- is_word(x).not() && x.is_whitespace().not()
- })
- .count()
- } else {
- self.right();
- self.rope
- .slice(self.cursor..)
- .chars()
- .take_while(|&x| is_word(x))
- .count()
- };
- self.setc();
- self.set_ho();
+ self.cursor.word_right(&self.rope);
}
// from μ
pub fn word_left(&mut self) {
- self.cursor = self.word_left_p();
- self.setc();
- self.set_ho();
- }
- #[lower::apply(saturating)]
- pub fn word_left_p(&self) -> usize {
- let mut c = self.cursor - 1;
- if self.x(self.cursor) == 0 {
- return c;
- }
- macro_rules! at {
- () => {
- self.rope.get_char(c).unwrap_or('\n')
- };
- }
- while at!().is_whitespace() {
- if self.x(c) == 0 {
- return c;
- }
- c -= 1
- }
- if is_word(at!()).not()
- && !at!().is_whitespace()
- && !is_word(self.rope.char(c - 1))
- {
- while is_word(at!()).not() && at!().is_whitespace().not() {
- if self.x(c) == 0 {
- return c;
- }
- c -= 1;
- }
- c += 1;
- } else {
- c -= 1;
- while is_word(at!()) {
- if self.x(c) == 0 {
- return c;
- }
- c -= 1;
- }
- c += 1;
- }
- c
+ self.cursor.word_left(&self.rope);
}
pub fn tab(&mut self) {
match &mut self.tabstops {
None => self.insert(" ").unwrap(),
Some(x) => match x.next() {
Some(x) => {
- self.cursor = x.r().end;
+ self.cursor.one(Cursor::new(x.r().end, &self.rope));
}
None => {
- self.cursor = x.last.clone().unwrap().r().end;
+ self.cursor.one(Cursor::new(
+ x.last.clone().unwrap().r().end,
+ &self.rope,
+ ));
self.tabstops = None;
}
},
@@ -827,89 +772,64 @@ impl TextArea {
}
pub fn enter(&mut self) {
use run::Run;
- let n = self.indentation();
- self.insert("\n");
- (|| self.insert(" ")).run(n);
- self.set_ho();
+
+ // let oc = self.cursor;
+ ceach!(self.cursor, |cursor| {
+ let n = cursor.indentation(&self.rope);
+ self.insert_at(cursor.position, "\n").unwrap();
+ // assert_eq!(*cursor, oc + c.chars().count());
+ // self.cursor = oc + c.chars().count();
+
+ // cursor.set_ho();
+ (|| self.insert(" ")).run(n);
+ });
}
pub fn down(&mut self) {
- let l = self.rope.try_char_to_line(self.cursor).unwrap_or(0);
-
- // next line size
- let Some(s) = self.rope.get_line(l + 1) else {
- return;
- };
- if s.len_chars() == 0 {
- return self.cursor += 1;
- }
- // position of start of next line
- let b = self.rope.line_to_char(l.wrapping_add(1));
- self.cursor = b + if s.len_chars() > self.column {
- // if next line is long enough to position the cursor at column, do so
- self.column
- } else {
- // otherwise, put it at the end of the next line, as it is too short.
- s.len_chars()
- - self
- .rope
- .get_line(l.wrapping_add(2))
- .map(|_| 1)
- .unwrap_or(0)
- };
- if self.rope.char_to_line(self.cursor)
- >= (self.vo + self.r).saturating_sub(5)
- {
- self.vo += 1;
- // self.vo = self.vo.min(self.l() - self.r);
- }
- self.set_ho();
+ self.cursor.down(&self.rope, &mut self.vo, self.r);
}
pub fn up(&mut self) {
- let l = self.rope.try_char_to_line(self.cursor).unwrap_or(0);
- let Some(s) = self.rope.get_line(l.wrapping_sub(1)) else {
- return;
- };
- let b = self.rope.line_to_char(l - 1);
- self.cursor = b + if s.len_chars() > self.column {
- self.column
- } else {
- s.len_chars() - 1
- };
- if self.rope.char_to_line(self.cursor).saturating_sub(4) < self.vo
- {
- self.vo = self.vo.saturating_sub(1);
- }
- self.set_ho();
+ self.cursor.up(&self.rope, &mut self.vo);
}
pub fn backspace_word(&mut self) {
- let c = self.word_left_p();
- _ = self.remove(c..self.cursor);
- self.cursor = c;
- self.setc();
- self.set_ho();
+ ceach!(self.cursor, |cursor| {
+ let c = cursor.word_left_p(&self.rope);
+ _ = self.remove(c..*cursor);
+ // FIXME maybe?
+ // cursor.position = c;
+
+ // cursor.setc(&self.rope);
+ // cursor.set_ho();
+ });
+ self.cursor.each(|cursor| {
+ cursor.setc(&self.rope);
+ cursor.set_ho();
+ });
}
#[lower::apply(saturating)]
pub fn backspace(&mut self) {
if let Some(tabstops) = &mut self.tabstops
&& let Some((_, StopP::Range(find))) =
tabstops.stops.get_mut(tabstops.index - 1)
- && find.end == self.cursor
+ && find.end == self.cursor.first().position
{
- self.cursor = find.start;
+ self.cursor.one(Cursor::new(find.start, &self.rope));
let f = find.clone();
*find = find.end..find.end;
_ = self.remove(f);
} else {
- _ = self.remove(self.cursor - 1..self.cursor);
+ ceach!(self.cursor, |cursor| {
+ _ = self.remove((*cursor) - 1..*cursor);
+ // FIXME: maybe?
+ });
self.set_ho();
}
}
#[lower::apply(saturating)]
pub fn scroll_to_cursor(&mut self) {
- let (_, y) = self.cursor();
+ let (_, y) = self.primary_cursor();
if !(self.vo..self.vo + self.r).contains(&y) {
if self.vo > y {
@@ -925,7 +845,7 @@ impl TextArea {
#[lower::apply(saturating)]
pub fn scroll_to_cursor_centering(&mut self) {
- let (_, y) = self.cursor();
+ let (_, y) = self.primary_cursor();
if !(self.vo..self.vo + self.r).contains(&y) {
self.vo = y - (self.r / 2);
@@ -992,9 +912,10 @@ impl TextArea {
(c, _r): (usize, usize),
cell: &'c mut [Cell],
range: Range<usize>,
- ) -> &'c mut [Cell] {
- let [(x1, y1), (x2, y2)] = self.position(range);
- &mut cell[y1 * c + x1..y2 * c + x2]
+ ) -> Option<&'c mut [Cell]> {
+ self.position(range).map(|[(x1, y1), (x2, y2)]| {
+ &mut cell[y1 * c + x1..y2 * c + x2]
+ })
}
pub fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)> {
@@ -1012,7 +933,7 @@ impl TextArea {
}
pub fn to_l_position(&self, l: usize) -> Option<lsp_types::Position> {
Some(Position {
- line: self.y(l) as _,
+ line: self.y(l)? as _,
character: self.x_bytes(l)? as _,
})
}
@@ -1031,7 +952,7 @@ impl TextArea {
&self,
(into, into_s): (&mut [Cell], (usize, usize)),
(ox, oy): (usize, usize),
- selection: Option<Range<usize>>,
+ selection: Option<Vec<Range<usize>>>,
apply: impl FnOnce((usize, usize), &Self, Output),
path: Option<&Path>,
tokens: Option<(&[SemanticToken], &SemanticTokensLegend)>,
@@ -1081,14 +1002,16 @@ impl TextArea {
}
}
}
- cells
- .get_range(
- (self.ho, self.y(self.cursor)),
- (self.ho + c, self.y(self.cursor)),
- )
- .for_each(|x| {
- x.style.bg = const { color(b"#1a1f29") };
- });
+ self.cursor.each_ref(|c| {
+ cells
+ .get_range(
+ (self.ho, self.y(*c).unwrap()),
+ (self.ho + *c, self.y(*c).unwrap()),
+ )
+ .for_each(|x| {
+ x.style.bg = const { color(b"#1a1f29") };
+ });
+ });
// let tokens = None::<(
// arc_swap::Guard<Arc<Box<[SemanticToken]>>>,
@@ -1223,37 +1146,39 @@ impl TextArea {
for (_, tabstop) in
tabstops.stops.iter().skip(tabstops.index - 1)
{
- let [a, b] = self.visual_position(tabstop.r());
+ let [a, b] = self.visual_position(tabstop.r()).unwrap();
for char in cells.get_range(a, b) {
char.style.bg = [55, 86, 81];
}
}
}
selection.map(|x| {
- let [a, b] = self.position(x);
- let a = self.map_to_visual(a);
- let b = self.map_to_visual(b);
- cells
- .get_range_enumerated(a, b)
- .filter(|(c, (x, y))| {
- c.letter.is_some()
- || *x == 0
- || (self
- .rope
- .get_line(*y)
- .map(_.len_chars())
- .unwrap_or_default()
- .saturating_sub(1)
- == *x)
- })
- .for_each(|(x, _)| {
- if x.letter == Some(' ') {
- x.letter = Some('·'); // tabs? what are those
- x.style.fg = [0x4e, 0x62, 0x79];
- }
- x.style.bg = [0x27, 0x43, 0x64];
- // 0x23, 0x34, 0x4B
- })
+ for x in x {
+ let [a, b] = self.position(x).unwrap();
+ let a = self.map_to_visual(a);
+ let b = self.map_to_visual(b);
+ cells
+ .get_range_enumerated(a, b)
+ .filter(|(c, (x, y))| {
+ c.letter.is_some()
+ || *x == 0
+ || (self
+ .rope
+ .get_line(*y)
+ .map(_.len_chars())
+ .unwrap_or_default()
+ .saturating_sub(1)
+ == *x)
+ })
+ .for_each(|(x, _)| {
+ if x.letter == Some(' ') {
+ x.letter = Some('·'); // tabs? what are those
+ x.style.fg = [0x4e, 0x62, 0x79];
+ }
+ x.style.bg = [0x27, 0x43, 0x64];
+ // 0x23, 0x34, 0x4B
+ })
+ }
});
// for (y, inlay) in inlay
@@ -1298,7 +1223,7 @@ impl TextArea {
) {
for y in 0..r {
if (self.vo + y) < self.l() {
- (self.vo + y)
+ (self.vo + y + 1)
.to_string()
.chars()
.zip(into[(y + oy) * w..].iter_mut().skip(ox))
@@ -1312,106 +1237,6 @@ impl TextArea {
}
}
- pub fn extend_selection(
- &mut self,
- key: NamedKey,
- r: std::ops::Range<usize>,
- ) -> std::ops::Range<usize> {
- macro_rules! left {
- () => {
- if self.cursor != 0 && self.cursor >= r.start {
- // left to right going left (shrink right end)
- r.start..self.cursor
- } else {
- // right to left going left (extend left end)
- self.cursor..r.end
- }
- };
- }
- macro_rules! right {
- () => {
- if self.cursor == self.rope.len_chars() {
- r
- } else if self.cursor > r.end {
- // left to right (extend right end)
- r.start..self.cursor
- } else {
- // right to left (shrink left end)
- self.cursor..r.end
- }
- };
- }
- match key {
- NamedKey::Home => {
- self.home();
- left!()
- }
- NamedKey::End => {
- self.end();
- right!()
- }
- NamedKey::ArrowLeft if super::ctrl() => {
- self.word_left();
- left!()
- }
- NamedKey::ArrowRight if super::ctrl() => {
- self.word_right();
- right!()
- }
- NamedKey::ArrowLeft => {
- self.left();
- left!()
- }
- NamedKey::ArrowRight => {
- self.right();
- right!()
- }
- NamedKey::ArrowUp => {
- self.up();
- left!()
- }
- NamedKey::ArrowDown => {
- self.down();
- right!()
- }
- _ => unreachable!(),
- }
- }
-
- pub fn extend_selection_to(
- &mut self,
- to: usize,
- r: std::ops::Range<usize>,
- ) -> std::ops::Range<usize> {
- if [r.start, r.end].contains(&to) {
- return r;
- }
- let r = if self.cursor == r.start {
- if to < r.start {
- to..r.end
- } else if to > r.end {
- r.end..to
- } else {
- to..r.end
- }
- } else if self.cursor == r.end {
- if to > r.end {
- r.start..to
- } else if to < r.start {
- to..r.start
- } else {
- r.start..to
- }
- } else {
- panic!()
- };
- assert!(r.start < r.end);
- dbg!(to, &r);
-
- self.cursor = to;
- self.setc();
- r
- }
pub fn comment(&mut self, selection: std::ops::Range<usize>) {
let a = self.rope.char_to_line(selection.start);
let b = self.rope.char_to_line(selection.end);
@@ -1935,7 +1760,7 @@ fn apply() {
new_text: "let x = var_name;\n ".to_owned(),
})
.unwrap();
- assert_eq!(t.cursor, 8);
+ assert_eq!(t.cursor.first().position, 8);
}
#[test]
fn apply2() {
diff --git a/src/text/cursor.rs b/src/text/cursor.rs
new file mode 100644
index 0000000..8b555ff
--- /dev/null
+++ b/src/text/cursor.rs
@@ -0,0 +1,499 @@
+use std::ops::{Deref, Not, Range, RangeBounds};
+
+use implicit_fn::implicit_fn;
+use itertools::Itertools;
+use serde_derive::{Deserialize, Serialize};
+
+use crate::is_word;
+use crate::text::RopeExt;
+/// a..=b
+#[derive(Default, Clone, Serialize, Deserialize, Debug, Copy)]
+pub struct Ronge {
+ pub start: usize,
+ pub end: usize,
+}
+impl RangeBounds<usize> for Ronge {
+ fn start_bound(&self) -> std::ops::Bound<&usize> {
+ std::ops::Bound::Included(&self.start)
+ }
+
+ fn end_bound(&self) -> std::ops::Bound<&usize> {
+ std::ops::Bound::Excluded(&self.end)
+ }
+}
+
+impl From<Ronge> for Range<usize> {
+ fn from(Ronge { start, end }: Ronge) -> Self {
+ Range { start, end }
+ }
+}
+impl From<Range<usize>> for Ronge {
+ fn from(std::ops::Range { start, end }: Range<usize>) -> Self {
+ Ronge { start, end }
+ }
+}
+#[derive(Default, Clone, Serialize, Deserialize, Debug, Copy)]
+pub struct Cursor {
+ pub position: usize,
+ pub column: usize,
+ pub sel: Option<Ronge>,
+}
+impl PartialOrd<Cursor> for Cursor {
+ fn partial_cmp(&self, other: &Cursor) -> Option<std::cmp::Ordering> {
+ self.position.partial_cmp(&other.position)
+ }
+}
+impl Ord for Cursor {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.position.cmp(&other.position)
+ }
+}
+impl PartialOrd<usize> for Cursor {
+ fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> {
+ (**self).partial_cmp(other)
+ }
+}
+impl Eq for Cursor {}
+impl PartialEq<Cursor> for Cursor {
+ fn eq(&self, other: &Cursor) -> bool {
+ self.position == other.position
+ }
+}
+impl PartialEq<usize> for Cursor {
+ fn eq(&self, &other: &usize) -> bool {
+ **self == other
+ }
+}
+impl Deref for Cursor {
+ type Target = usize;
+
+ fn deref(&self) -> &Self::Target {
+ &self.position
+ }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
+pub struct Cursors {
+ inner: Vec<Cursor>,
+}
+use Default::default;
+use ropey::{Rope, RopeSlice};
+use winit::keyboard::NamedKey;
+
+use crate::ctrl;
+impl Default for Cursors {
+ fn default() -> Self {
+ Self { inner: vec![default()] }
+ }
+}
+
+pub fn caster<T, U>(x: impl FnMut(T) -> U) -> impl FnMut(T) -> U {
+ x
+}
+pub macro ceach($cursor: expr, $f:expr) {
+ (0..$cursor.inner.len()).for_each(|i| {
+ let c = *$cursor.inner.get(i).expect("aw dangit");
+ caster::<Cursor, _>($f)(c);
+ });
+ $cursor.coalesce();
+}
+// macro_rules! ceach_mut {
+// ($cursor: expr, |cursor| $f:block) => {{
+// let n = $cursor.inner.len();
+// {
+// (0..n).map(|i: usize| {
+// let cursor: &mut Cursor = &mut $cursor.inner[i];
+// unhygienic2::unhygienic! { $f }
+// })
+// }
+// }};
+// }
+// pub(crate) use ceach_mut;
+// use unhygienic2::unhygienic;
+impl Cursor {
+ pub fn new(c: usize, r: &Rope) -> Self {
+ Self { column: r.x(c).unwrap(), position: c, sel: None }
+ }
+ fn cl(self, r: &Rope) -> RopeSlice<'_> {
+ r.line(r.char_to_line(*self))
+ }
+ pub fn setc(&mut self, r: &Rope) {
+ self.column =
+ self.position - r.beginning_of_line(self.position).unwrap();
+ }
+ pub fn set_ho(&mut self) {}
+ pub fn cursor(self, r: &Rope) -> (usize, usize) {
+ r.xy(*self).unwrap()
+ }
+ pub fn indentation(self, r: &Rope) -> usize {
+ r.indentation_of(self.cursor(r).1)
+ }
+
+ #[implicit_fn]
+ pub fn home(&mut self, r: &Rope) {
+ let l = r.char_to_line(**self);
+ let beg = r.line_to_char(l);
+ let i = **self - beg;
+ let whitespaces = self.indentation(r);
+ if r.line(l).chars().all(_.is_whitespace()) {
+ self.position = beg;
+ self.column = 0;
+ } else if i == whitespaces {
+ self.position = beg;
+ self.column = 0;
+ } else {
+ self.position = whitespaces + beg;
+ self.column = whitespaces;
+ }
+ self.set_ho();
+ }
+ pub fn end(&mut self, r: &Rope) {
+ let i = r.char_to_line(**self);
+ let beg = r.line_to_char(i);
+
+ self.position = beg + self.cl(r).len_chars()
+ - r.get_line(i + 1).map(|_| 1).unwrap_or(0);
+ self.setc(r);
+ self.set_ho();
+ }
+ pub fn at_(self, r: &Rope) -> char {
+ r.get_char(*self - 1).unwrap_or('\n')
+ }
+ /// ??
+ pub fn at_plus_one(self, r: &Rope) -> char {
+ r.get_char(*self).unwrap_or('\n')
+ }
+
+ #[implicit_fn]
+ pub fn word_right(&mut self, r: &Rope) {
+ self.position += r
+ .slice(**self..)
+ .chars()
+ .take_while(_.is_whitespace())
+ .count();
+
+ self.position += if is_word(self.at_plus_one(r)).not()
+ && !self.at_plus_one(r).is_whitespace()
+ && !is_word(r.char(**self + 1))
+ {
+ r.slice(**self..)
+ .chars()
+ .take_while(|&x| {
+ is_word(x).not() && x.is_whitespace().not()
+ })
+ .count()
+ } else {
+ self.right(r);
+ r.slice(**self..).chars().take_while(|&x| is_word(x)).count()
+ };
+ self.setc(r);
+ self.set_ho();
+ }
+ // from μ
+ pub fn word_left(&mut self, r: &Rope) {
+ self.position = self.word_left_p(r);
+ self.setc(r);
+ self.set_ho();
+ }
+ #[lower::apply(saturating)]
+ pub fn word_left_p(self, r: &Rope) -> usize {
+ let mut c = *self - 1;
+ if r.x(*self).unwrap() == 0 {
+ return c;
+ }
+ macro_rules! at {
+ () => {
+ r.get_char(c).unwrap_or('\n')
+ };
+ }
+ while at!().is_whitespace() {
+ if r.x(c).unwrap() == 0 {
+ return c;
+ }
+ c -= 1
+ }
+ if is_word(at!()).not()
+ && !at!().is_whitespace()
+ && !is_word(r.char(c - 1))
+ {
+ while is_word(at!()).not() && at!().is_whitespace().not() {
+ if r.x(c).unwrap() == 0 {
+ return c;
+ }
+ c -= 1;
+ }
+ c += 1;
+ } else {
+ c -= 1;
+ while is_word(at!()) {
+ if r.x(c).unwrap() == 0 {
+ return c;
+ }
+ c -= 1;
+ }
+ c += 1;
+ }
+ c
+ }
+
+ fn right(&mut self, r: &Rope) {
+ self.position += 1;
+ self.position = (**self).min(r.len_chars());
+ self.setc(r);
+ self.set_ho();
+ }
+
+ fn left(&mut self, r: &Rope) {
+ self.position -= 1;
+ self.setc(r);
+ }
+
+ pub fn down(&mut self, r: &Rope, vo: &mut usize, r_: usize) {
+ let l = r.try_char_to_line(**self).unwrap_or(0);
+
+ // next line size
+ let Some(s) = r.get_line(l + 1) else {
+ return;
+ };
+ if s.len_chars() == 0 {
+ return self.position += 1;
+ }
+ // position of start of next line
+ let b = r.line_to_char(l.wrapping_add(1));
+ self.position = b + if s.len_chars() > self.column {
+ // if next line is long enough to position the cursor at column, do so
+ self.column
+ } else {
+ // otherwise, put it at the end of the next line, as it is too short.
+ s.len_chars()
+ - r.get_line(l.wrapping_add(2)).map(|_| 1).unwrap_or(0)
+ };
+ if r.char_to_line(**self) >= (*vo + r_).saturating_sub(5) {
+ *vo += 1;
+ // self.vo = self.vo.min(self.l() - self.r);
+ }
+ self.set_ho();
+ }
+
+ pub fn up(&mut self, r: &Rope, vo: &mut usize) {
+ let l = r.try_char_to_line(**self).unwrap_or(0);
+ let Some(s) = r.get_line(l.wrapping_sub(1)) else {
+ return;
+ };
+ let b = r.line_to_char(l - 1);
+ self.position = b + if s.len_chars() > self.column {
+ self.column
+ } else {
+ s.len_chars() - 1
+ };
+ if r.char_to_line(**self).saturating_sub(4) < *vo {
+ *vo = vo.saturating_sub(1);
+ }
+ self.set_ho();
+ }
+ pub fn extend_selection(
+ &mut self,
+ key: NamedKey,
+ rope: &Rope,
+ vo: &mut usize,
+ r_: usize,
+ ) {
+ let Some(r) = self.sel else { unreachable!() };
+ macro_rules! left {
+ () => {
+ if **self != 0 && **self >= r.start {
+ // left to right going left (shrink right end)
+ r.start..**self
+ } else {
+ // right to left going left (extend left end)
+ **self..r.end
+ }
+ };
+ }
+ macro_rules! right {
+ () => {
+ if **self == rope.len_chars() {
+ r.into()
+ } else if **self > r.end {
+ // left to right (extend right end)
+ r.start..**self
+ } else {
+ // right to left (shrink left end)
+ **self..r.end
+ }
+ };
+ }
+ let v = match key {
+ NamedKey::Home => {
+ let pself = *self;
+ self.home(rope);
+ if pself > *self { left!() } else { right!() }
+ }
+ NamedKey::End => {
+ self.end(rope);
+ right!()
+ }
+ NamedKey::ArrowLeft if ctrl() => {
+ self.word_left(rope);
+ left!()
+ }
+ NamedKey::ArrowRight if ctrl() => {
+ self.word_right(rope);
+ right!()
+ }
+ NamedKey::ArrowLeft => {
+ self.left(rope);
+ left!()
+ }
+ NamedKey::ArrowRight => {
+ self.right(rope);
+ right!()
+ }
+ NamedKey::ArrowUp => {
+ self.up(rope, vo);
+ left!()
+ }
+ NamedKey::ArrowDown => {
+ self.down(rope, vo, r_);
+ right!()
+ }
+ _ => unreachable!(),
+ };
+ self.sel = Some(v.into());
+ }
+ #[track_caller]
+ pub fn extend_selection_to(&mut self, to: usize, rope: &Rope) {
+ let Some(r) = self.sel else { unreachable!() };
+ if [r.start, r.end].contains(&to) {
+ return;
+ }
+ let r = if **self == r.start {
+ if to < r.start {
+ to..r.end
+ } else if to > r.end {
+ r.end..to
+ } else {
+ to..r.end
+ }
+ } else if **self == r.end {
+ if to > r.end {
+ r.start..to
+ } else if to < r.start {
+ to..r.start
+ } else {
+ r.start..to
+ }
+ } else {
+ panic!()
+ };
+ assert!(r.start < r.end);
+ // dbg!(to, &r);
+ self.position = to;
+ self.setc(rope);
+ self.sel = Some(r.into());
+ }
+}
+impl Cursors {
+ pub fn clear_selections(&mut self) {
+ self.each(|x| x.sel = None);
+ }
+ pub fn iter(
+ &self,
+ ) -> impl Iterator<Item = Cursor> + ExactSizeIterator {
+ self.inner.iter().copied()
+ }
+ pub fn alone(&mut self) {
+ self.inner.truncate(1);
+ }
+ pub fn add(&mut self, c: usize, rope: &Rope) {
+ self.inner.push(Cursor::new(c, rope));
+ }
+ pub fn max(&self) -> Cursor {
+ *self.inner.iter().max().unwrap()
+ }
+ pub fn min(&self) -> Cursor {
+ *self.inner.iter().min().unwrap()
+ }
+ pub fn first(&self) -> Cursor {
+ self.inner[0]
+ }
+ pub fn coalesce(&mut self) {
+ self.inner = self.inner.iter().copied().dedup().collect();
+ }
+ pub fn first_mut(&mut self) -> &mut Cursor {
+ &mut self.inner[0]
+ }
+ pub fn just(&mut self, c: usize, rope: &Rope) {
+ self.one(Cursor::new(c, rope));
+ }
+ pub fn one(&mut self, c: Cursor) {
+ self.inner = vec![c];
+ }
+ pub fn each(&mut self, f: impl FnMut(&mut Cursor)) {
+ self.inner.iter_mut().rev().for_each(f);
+ }
+ pub fn each_ref(&self, f: impl FnMut(Cursor)) {
+ self.inner.iter().copied().rev().for_each(f);
+ }
+ pub fn manipulate(&mut self, mut f: impl FnMut(usize) -> usize) {
+ self.each(|lem| {
+ lem.position = f(lem.position);
+ if let Some(sel) = &mut lem.sel {
+ sel.start = f(sel.start);
+ sel.end = f(sel.end);
+ }
+ });
+ }
+ pub fn left(&mut self, r: &Rope) {
+ self.each(|cursor| cursor.left(r));
+ self.coalesce();
+ }
+ pub fn right(&mut self, r: &Rope) {
+ self.each(|cursor| cursor.right(r));
+ self.coalesce();
+ }
+
+ pub fn home(&mut self, r: &Rope) {
+ self.each(|c| c.home(r));
+ self.coalesce();
+ }
+ pub fn end(&mut self, r: &Rope) {
+ self.each(|c| c.end(r));
+ self.coalesce();
+ }
+ pub fn word_right(&mut self, r: &Rope) {
+ self.each(|c| c.word_right(r));
+ self.coalesce();
+ }
+ pub fn word_left(&mut self, r: &Rope) {
+ self.each(|c| c.word_left(r));
+ self.coalesce();
+ }
+ pub fn set_ho(&mut self) {
+ // let x = self.cursor_visual().0;
+ // if x < self.ho + 4 {
+ // self.ho = x.saturating_sub(4);
+ // } else if x + 4 > (self.ho + self.c) {
+ // self.ho = (x.saturating_sub(self.c)) + 4;
+ // }
+ }
+
+ pub fn down(&mut self, rope: &Rope, vo: &mut usize, r: usize) {
+ self.each(|x| x.down(rope, vo, r));
+ self.coalesce();
+ }
+ pub fn up(&mut self, rope: &Rope, vo: &mut usize) {
+ self.each(|x| x.up(rope, vo));
+ self.coalesce();
+ }
+ // pub fn extend_selection(
+ // &mut self,
+ // key: NamedKey,
+ // r: Vec<std::ops::Range<usize>>,
+ // rope: &Rope,
+ // vo: &mut usize,
+ // r_: usize,
+ // ) -> Vec<std::ops::Range<usize>> {
+ // panic!();
+ // }
+}