A simple CPU rendered GUI IDE experience.
rudimentary multi cursor
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/bar.rs | 16 | ||||
| -rw-r--r-- | src/com.rs | 4 | ||||
| -rw-r--r-- | src/edi.rs | 428 | ||||
| -rw-r--r-- | src/edi/st.rs | 50 | ||||
| -rw-r--r-- | src/lsp.rs | 13 | ||||
| -rw-r--r-- | src/main.rs | 13 | ||||
| -rw-r--r-- | src/meta.rs | 7 | ||||
| -rw-r--r-- | src/rnd.rs | 27 | ||||
| -rw-r--r-- | src/text.rs | 671 | ||||
| -rw-r--r-- | src/text/cursor.rs | 499 |
11 files changed, 1146 insertions, 584 deletions
@@ -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"] } @@ -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()) @@ -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", "& "), @@ -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 => { @@ -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 }; @@ -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!(); + // } +} |