on master help sign in
Indent draft, linewise paste
commit00e661f600b7465750a9bcce8e89eabd5b17b4af authorBlaž Hrastnik <blaz@mxxn.io> dateFri 9 Oct 2020, 16:58 (6 years ago) parent4a64855
7 files changed+209−12
helix-core/src/indent.rs+111 helix-core/src/lib.rs+1 helix-core/src/selection.rs−1 helix-core/src/state.rs+2−1 helix-core/src/syntax.rs+2−2 helix-view/src/commands.rs+80−8 helix-view/src/keymap.rs+13
helix-core/src/indent.rsnew file+111
@@ -0,0 +1,111 @@
1+use crate::{
2+ syntax::Syntax,
3+ tree_sitter::{Node, Tree},
4+ Rope, RopeSlice, State,
5+};
6+
7+const TAB_WIDTH: usize = 4;
8+
9+fn indent_level_for_line(line: RopeSlice) -> usize {
10+ let mut len = 0;
11+ for ch in line.chars() {
12+ match ch {
13+ '\t' => len += TAB_WIDTH,
14+ ' ' => len += 1,
15+ _ => break,
16+ }
17+ }
18+
19+ len / TAB_WIDTH
20+}
21+
22+/// Find the highest syntax node at position.
23+/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
24+fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
25+ let tree = syntax.root_layer.tree.as_ref().unwrap();
26+
27+ let mut node = match tree.root_node().named_descendant_for_byte_range(pos, pos) {
28+ Some(node) => node,
29+ None => return None,
30+ };
31+
32+ while let Some(parent) = node.parent() {
33+ if parent.start_byte() == node.start_byte() {
34+ node = parent
35+ } else {
36+ break;
37+ }
38+ }
39+
40+ Some(node)
41+}
42+
43+fn walk(node: Option<Node>) -> usize {
44+ let node = match node {
45+ Some(node) => node,
46+ None => return 0,
47+ };
48+
49+ let parent = match node.parent() {
50+ Some(node) => node,
51+ None => return 0,
52+ };
53+
54+ let mut increment = 0;
55+
56+ let not_first_or_last_sibling = node.next_sibling().is_some() && node.prev_sibling().is_some();
57+ let is_scope = true;
58+
59+ if not_first_or_last_sibling && is_scope {
60+ increment += 1;
61+ }
62+
63+ walk(Some(parent)) + increment
64+}
65+
66+// for_line_at_col
67+fn suggested_indent_for_line(state: &State, line_num: usize) -> usize {
68+ let line = state.doc.line(line_num);
69+ let current = indent_level_for_line(line);
70+
71+ let mut byte_start = state.doc.line_to_byte(line_num);
72+
73+ // find first non-whitespace char
74+ for ch in line.chars() {
75+ // TODO: could use memchr with chunks?
76+ if ch != ' ' && ch != '\t' {
77+ break;
78+ }
79+ byte_start += 1;
80+ }
81+
82+ if let Some(syntax) = &state.syntax {
83+ let node = get_highest_syntax_node_at_bytepos(state.syntax.as_ref().unwrap(), byte_start);
84+
85+ // let indentation = walk()
86+ // special case for comments
87+
88+ // if preserve_leading_whitespace
89+
90+ unimplemented!()
91+ } else {
92+ // TODO: case for non-tree sitter grammars
93+ 0
94+ }
95+}
96+
97+#[cfg(test)]
98+mod test {
99+ use super::*;
100+
101+ #[test]
102+ fn indent_level() {
103+ let line = Rope::from(" fn new"); // 8 spaces
104+ assert_eq!(indent_level_for_line(line.slice(..)), 2);
105+ let line = Rope::from("\t\t\tfn new"); // 3 tabs
106+ assert_eq!(indent_level_for_line(line.slice(..)), 3);
107+ // mixed indentation
108+ let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
109+ assert_eq!(indent_level_for_line(line.slice(..)), 3);
110+ }
111+}
helix-core/src/lib.rs+1
@@ -1,6 +1,7 @@
11#![allow(unused)]
22pub mod graphemes;
33mod history;
4+mod indent;
45pub mod macros;
56mod position;
67pub mod register;
helix-core/src/selection.rs−1
@@ -110,7 +110,6 @@ impl Range {
110110
111111 #[inline]
112112 pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
113− // end inclusive
114113 Cow::from(text.slice(self.from()..self.to() + 1))
115114 }
116115}
helix-core/src/state.rs+2−1
@@ -47,6 +47,7 @@ impl State {
4747 #[must_use]
4848 pub fn new(doc: Rope) -> Self {
4949 let changes = ChangeSet::new(&doc);
50+ let old_state = Some((doc.clone(), Selection::single(0, 0)));
5051
5152 Self {
5253 path: None,
@@ -56,7 +57,7 @@ impl State {
5657 restore_cursor: false,
5758 syntax: None,
5859 changes,
59− old_state: None,
60+ old_state,
6061 }
6162 }
6263
helix-core/src/syntax.rs+2−2
@@ -146,7 +146,7 @@ pub struct Syntax {
146146
147147 config: Arc<HighlightConfiguration>,
148148
149− root_layer: LanguageLayer,
149+ pub(crate) root_layer: LanguageLayer,
150150}
151151
152152impl Syntax {
@@ -309,7 +309,7 @@ pub struct LanguageLayer {
309309 // mode
310310 // grammar
311311 // depth
312− tree: Option<Tree>,
312+ pub(crate) tree: Option<Tree>,
313313}
314314
315315use crate::state::coords_at_pos;
helix-view/src/commands.rs+80−8
@@ -241,7 +241,7 @@ pub fn select_line(view: &mut View, _count: usize) {
241241 let text = view.state.doc();
242242 let line = text.char_to_line(pos.head);
243243 let start = text.line_to_char(line);
244− let end = text.line_to_char(line + 1);
244+ let end = text.line_to_char(line + 1).saturating_sub(1);
245245
246246 // TODO: use a transaction
247247 view.state.selection = Selection::single(start, end);
@@ -249,7 +249,7 @@ pub fn select_line(view: &mut View, _count: usize) {
249249
250250pub fn delete_selection(view: &mut View, _count: usize) {
251251 let transaction =
252− Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None));
252+ Transaction::change_by_selection(&view.state, |range| (range.from(), range.to() + 1, None));
253253 transaction.apply(&mut view.state);
254254
255255 append_changes_to_history(view);
@@ -267,6 +267,13 @@ pub fn collapse_selection(view: &mut View, _count: usize) {
267267 .transform(|range| Range::new(range.head, range.head))
268268}
269269
270+pub fn flip_selections(view: &mut View, _count: usize) {
271+ view.state.selection = view
272+ .state
273+ .selection
274+ .transform(|range| Range::new(range.head, range.anchor))
275+}
276+
270277fn enter_insert_mode(view: &mut View) {
271278 view.state.mode = Mode::Insert;
272279
@@ -463,7 +470,7 @@ pub fn delete_char_forward(view: &mut View, count: usize) {
463470pub fn undo(view: &mut View, _count: usize) {
464471 view.history.undo(&mut view.state);
465472
466− // TODO: each command should simply return a Option<transaction>, then the higher level handles storing it?
473+ // TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
467474}
468475
469476pub fn redo(view: &mut View, _count: usize) {
@@ -481,11 +488,15 @@ pub fn yank(view: &mut View, _count: usize) {
481488 .map(|cow| cow.into_owned())
482489 .collect();
483490
484− register::set('"', values);
491+ // TODO: allow specifying reg
492+ let reg = '"';
493+ register::set(reg, values);
485494}
486495
487496pub fn paste(view: &mut View, _count: usize) {
488− if let Some(values) = register::get('"') {
497+ // TODO: allow specifying reg
498+ let reg = '"';
499+ if let Some(values) = register::get(reg) {
489500 let repeat = std::iter::repeat(
490501 values
491502 .last()
@@ -493,13 +504,74 @@ pub fn paste(view: &mut View, _count: usize) {
493504 .unwrap(),
494505 );
495506
507+ // TODO: if any of values ends \n it's linewise paste
508+ //
509+ // p => paste after
510+ // P => paste before
511+ // alt-p => paste every yanked selection after selected text
512+ // alt-P => paste every yanked selection before selected text
513+ // R => replace selected text with yanked text
514+ // alt-R => replace selected text with every yanked text
515+ //
516+ // append => insert at next line
517+ // insert => insert at start of line
518+ // replace => replace
519+ // default insert
520+
521+ let linewise = values.iter().any(|value| value.ends_with('\n'));
522+
496523 let mut values = values.into_iter().map(Tendril::from).chain(repeat);
497524
498− let transaction = Transaction::change_by_selection(&view.state, |range| {
499− (range.head + 1, range.head + 1, Some(values.next().unwrap()))
500− });
525+ let transaction = if linewise {
526+ // paste on the next line
527+ // TODO: can simply take a range + modifier and compute the right pos without ifs
528+ let text = view.state.doc();
529+ Transaction::change_by_selection(&view.state, |range| {
530+ let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
531+ (line_end, line_end, Some(values.next().unwrap()))
532+ })
533+ } else {
534+ Transaction::change_by_selection(&view.state, |range| {
535+ (range.head + 1, range.head + 1, Some(values.next().unwrap()))
536+ })
537+ };
501538
502539 transaction.apply(&mut view.state);
503540 append_changes_to_history(view);
504541 }
505542}
543+
544+const TAB_WIDTH: usize = 4;
545+
546+pub fn indent(view: &mut View, _count: usize) {
547+ let mut lines = Vec::new();
548+
549+ // Get all line numbers
550+ for range in view.state.selection.ranges() {
551+ let start = view.state.doc.char_to_line(range.from());
552+ let end = view.state.doc.char_to_line(range.to());
553+
554+ for line in start..=end {
555+ lines.push(line)
556+ }
557+ }
558+ lines.sort_unstable(); // sorting by usize so _unstable is preferred
559+ lines.dedup();
560+
561+ // Indent by one level
562+ let indent = Tendril::from(" ".repeat(TAB_WIDTH));
563+
564+ let transaction = Transaction::change(
565+ &view.state,
566+ lines.into_iter().map(|line| {
567+ let pos = view.state.doc.line_to_char(line);
568+ (pos, pos, Some(indent.clone()))
569+ }),
570+ );
571+ transaction.apply(&mut view.state);
572+ append_changes_to_history(view);
573+}
574+
575+pub fn unindent(view: &mut View, _count: usize) {
576+ unimplemented!()
577+}
helix-view/src/keymap.rs+13
@@ -117,6 +117,15 @@ macro_rules! ctrl {
117117 };
118118}
119119
120+macro_rules! alt {
121+ ($ch:expr) => {
122+ Key {
123+ code: KeyCode::Char($ch),
124+ modifiers: Modifiers::ALT,
125+ }
126+ };
127+}
128+
120129pub fn default() -> Keymaps {
121130 hashmap!(
122131 state::Mode::Normal =>
@@ -145,11 +154,15 @@ pub fn default() -> Keymaps {
145154 vec![key!('c')] => commands::change_selection,
146155 vec![key!('s')] => commands::split_selection_on_newline,
147156 vec![key!(';')] => commands::collapse_selection,
157+ // TODO should be alt(;)
158+ vec![key!('%')] => commands::flip_selections,
148159 vec![key!('x')] => commands::select_line,
149160 vec![key!('u')] => commands::undo,
150161 vec![shift!('U')] => commands::redo,
151162 vec![key!('y')] => commands::yank,
152163 vec![key!('p')] => commands::paste,
164+ vec![key!('>')] => commands::indent,
165+ vec![key!('<')] => commands::unindent,
153166 vec![Key {
154167 code: KeyCode::Esc,
155168 modifiers: Modifiers::NONE