on master help sign in
Indent draft, linewise paste
00e661fBlaž Hrastnik
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 @@
1#![allow(unused)]
1#![allow(unused)]
2pub mod graphemes;
2pub mod graphemes;
3mod history;
3mod history;
4+mod indent;
4pub mod macros;
5pub mod macros;
5mod position;
6mod position;
6pub mod register;
7pub mod register;
helix-core/src/selection.rs−1
@@ -110,7 +110,6 @@ impl Range {
110
110
111 #[inline]
111 #[inline]
112 pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
112 pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
113− // end inclusive
114 Cow::from(text.slice(self.from()..self.to() + 1))
113 Cow::from(text.slice(self.from()..self.to() + 1))
115 }
114 }
116}
115}
helix-core/src/state.rs+2−1
@@ -47,6 +47,7 @@ impl State {
47 #[must_use]
47 #[must_use]
48 pub fn new(doc: Rope) -> Self {
48 pub fn new(doc: Rope) -> Self {
49 let changes = ChangeSet::new(&doc);
49 let changes = ChangeSet::new(&doc);
50+ let old_state = Some((doc.clone(), Selection::single(0, 0)));
50
51
51 Self {
52 Self {
52 path: None,
53 path: None,
@@ -56,7 +57,7 @@ impl State {
56 restore_cursor: false,
57 restore_cursor: false,
57 syntax: None,
58 syntax: None,
58 changes,
59 changes,
59− old_state: None,
60+ old_state,
60 }
61 }
61 }
62 }
62
63
helix-core/src/syntax.rs+2−2
@@ -146,7 +146,7 @@ pub struct Syntax {
146
146
147 config: Arc<HighlightConfiguration>,
147 config: Arc<HighlightConfiguration>,
148
148
149− root_layer: LanguageLayer,
149+ pub(crate) root_layer: LanguageLayer,
150}
150}
151
151
152impl Syntax {
152impl Syntax {
@@ -309,7 +309,7 @@ pub struct LanguageLayer {
309 // mode
309 // mode
310 // grammar
310 // grammar
311 // depth
311 // depth
312− tree: Option<Tree>,
312+ pub(crate) tree: Option<Tree>,
313}
313}
314
314
315use crate::state::coords_at_pos;
315use 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) {
241 let text = view.state.doc();
241 let text = view.state.doc();
242 let line = text.char_to_line(pos.head);
242 let line = text.char_to_line(pos.head);
243 let start = text.line_to_char(line);
243 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);
245
245
246 // TODO: use a transaction
246 // TODO: use a transaction
247 view.state.selection = Selection::single(start, end);
247 view.state.selection = Selection::single(start, end);
@@ -249,7 +249,7 @@ pub fn select_line(view: &mut View, _count: usize) {
249
249
250pub fn delete_selection(view: &mut View, _count: usize) {
250pub fn delete_selection(view: &mut View, _count: usize) {
251 let transaction =
251 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));
253 transaction.apply(&mut view.state);
253 transaction.apply(&mut view.state);
254
254
255 append_changes_to_history(view);
255 append_changes_to_history(view);
@@ -267,6 +267,13 @@ pub fn collapse_selection(view: &mut View, _count: usize) {
267 .transform(|range| Range::new(range.head, range.head))
267 .transform(|range| Range::new(range.head, range.head))
268}
268}
269
269
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+
270fn enter_insert_mode(view: &mut View) {
277fn enter_insert_mode(view: &mut View) {
271 view.state.mode = Mode::Insert;
278 view.state.mode = Mode::Insert;
272
279
@@ -463,7 +470,7 @@ pub fn delete_char_forward(view: &mut View, count: usize) {
463pub fn undo(view: &mut View, _count: usize) {
470pub fn undo(view: &mut View, _count: usize) {
464 view.history.undo(&mut view.state);
471 view.history.undo(&mut view.state);
465
472
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?
467}
474}
468
475
469pub fn redo(view: &mut View, _count: usize) {
476pub fn redo(view: &mut View, _count: usize) {
@@ -481,11 +488,15 @@ pub fn yank(view: &mut View, _count: usize) {
481 .map(|cow| cow.into_owned())
488 .map(|cow| cow.into_owned())
482 .collect();
489 .collect();
483
490
491+ // TODO: allow specifying reg
492+ let reg = '"';
484− register::set('"', values);
493+ register::set(reg, values);
485}
494}
486
495
487pub fn paste(view: &mut View, _count: usize) {
496pub fn paste(view: &mut View, _count: usize) {
497+ // TODO: allow specifying reg
498+ let reg = '"';
488− if let Some(values) = register::get('"') {
499+ if let Some(values) = register::get(reg) {
489 let repeat = std::iter::repeat(
500 let repeat = std::iter::repeat(
490 values
501 values
491 .last()
502 .last()
@@ -493,13 +504,74 @@ pub fn paste(view: &mut View, _count: usize) {
493 .unwrap(),
504 .unwrap(),
494 );
505 );
495
506
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+
496 let mut values = values.into_iter().map(Tendril::from).chain(repeat);
523 let mut values = values.into_iter().map(Tendril::from).chain(repeat);
497
524
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();
498− let transaction = Transaction::change_by_selection(&view.state, |range| {
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| {
499− (range.head + 1, range.head + 1, Some(values.next().unwrap()))
535+ (range.head + 1, range.head + 1, Some(values.next().unwrap()))
500− });
536+ })
537+ };
501
538
502 transaction.apply(&mut view.state);
539 transaction.apply(&mut view.state);
503 append_changes_to_history(view);
540 append_changes_to_history(view);
504 }
541 }
505}
542}
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 {
117 };
117 };
118}
118}
119
119
120+macro_rules! alt {
121+ ($ch:expr) => {
122+ Key {
123+ code: KeyCode::Char($ch),
124+ modifiers: Modifiers::ALT,
125+ }
126+ };
127+}
128+
120pub fn default() -> Keymaps {
129pub fn default() -> Keymaps {
121 hashmap!(
130 hashmap!(
122 state::Mode::Normal =>
131 state::Mode::Normal =>
@@ -145,11 +154,15 @@ pub fn default() -> Keymaps {
145 vec![key!('c')] => commands::change_selection,
154 vec![key!('c')] => commands::change_selection,
146 vec![key!('s')] => commands::split_selection_on_newline,
155 vec![key!('s')] => commands::split_selection_on_newline,
147 vec![key!(';')] => commands::collapse_selection,
156 vec![key!(';')] => commands::collapse_selection,
157+ // TODO should be alt(;)
158+ vec![key!('%')] => commands::flip_selections,
148 vec![key!('x')] => commands::select_line,
159 vec![key!('x')] => commands::select_line,
149 vec![key!('u')] => commands::undo,
160 vec![key!('u')] => commands::undo,
150 vec![shift!('U')] => commands::redo,
161 vec![shift!('U')] => commands::redo,
151 vec![key!('y')] => commands::yank,
162 vec![key!('y')] => commands::yank,
152 vec![key!('p')] => commands::paste,
163 vec![key!('p')] => commands::paste,
164+ vec![key!('>')] => commands::indent,
165+ vec![key!('<')] => commands::unindent,
153 vec![Key {
166 vec![Key {
154 code: KeyCode::Esc,
167 code: KeyCode::Esc,
155 modifiers: Modifiers::NONE
168 modifiers: Modifiers::NONE