on master help sign in
core: Implement comment toggling module.
commitd0791e0f981a64b0c382c022ff79b0376cc09962 authorBlaž Hrastnik <blaz@mxxn.io> dateThu 18 Feb 2021, 18:35 (6 years ago) parentc9dd1c9
2 files changed+106
helix-core/src/comment.rs+105 helix-core/src/lib.rs+1
helix-core/src/comment.rsnew file+105
@@ -0,0 +1,105 @@
1+use crate::{find_first_non_whitespace_char2, Change, RopeSlice, State, Transaction};
2+use core::ops::Range;
3+use std::borrow::Cow;
4+
5+fn find_line_comment(
6+ token: &str,
7+ text: RopeSlice,
8+ lines: Range<usize>,
9+) -> (bool, Vec<usize>, usize) {
10+ // selection ranges map char_to_line from() .. to()
11+ let mut commented = true;
12+ let mut skipped = Vec::new();
13+ let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
14+ for line in lines {
15+ let line_slice = text.line(line);
16+ if let Some(pos) = find_first_non_whitespace_char2(line_slice) {
17+ let len = line_slice.len_chars();
18+
19+ if pos < min {
20+ min = pos;
21+ }
22+
23+ // line can be shorter than pos + token len
24+ let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len)));
25+
26+ if fragment != token {
27+ // as soon as one of the non-blank lines doesn't have a comment, the whole block is
28+ // considered uncommented.
29+ commented = false;
30+ }
31+ } else {
32+ // blank line
33+ skipped.push(line);
34+ }
35+ }
36+ (commented, skipped, min)
37+}
38+
39+fn toggle_line_comments(state: &State) -> Transaction {
40+ let text = state.doc.slice(..);
41+ let mut changes: Vec<Change> = Vec::new();
42+
43+ let token = "//";
44+
45+ for selection in state.selection.ranges() {
46+ let start = text.char_to_line(selection.from());
47+ let end = text.char_to_line(selection.to());
48+ let lines = start..end + 1;
49+ let (commented, skipped, min) = find_line_comment(token, text, lines.clone());
50+
51+ changes.reserve(end - start - skipped.len());
52+
53+ for line in lines {
54+ if skipped.contains(&line) {
55+ continue;
56+ }
57+
58+ let pos = text.line_to_char(line) + min;
59+
60+ if !commented {
61+ // comment line
62+ changes.push((pos, pos, Some(format!("{} ", token).into())))
63+ } else {
64+ // uncomment line
65+ let margin = 1; // TODO: margin is hardcoded 1 but could easily be 0
66+ changes.push((pos, pos + token.len() + margin, None))
67+ }
68+ }
69+ }
70+ Transaction::change(state, changes.into_iter())
71+}
72+
73+#[cfg(test)]
74+mod test {
75+ use super::*;
76+
77+ #[test]
78+ fn test_find_line_comment() {
79+ use crate::{Rope, Selection};
80+
81+ // four lines, two space indented, except for line 1 which is blank.
82+ let doc = Rope::from(" 1\n\n 2\n 3");
83+
84+ let mut state = State::new(doc);
85+ // select whole document
86+ state.selection = Selection::single(0, state.doc.len_chars() - 1);
87+
88+ let text = state.doc.slice(..);
89+
90+ let res = find_line_comment("//", text, 0..3);
91+ // (commented = true, skipped = [line 1], min = col 2)
92+ assert_eq!(res, (false, vec![1], 2));
93+
94+ // comment
95+ let transaction = toggle_line_comments(&state);
96+ transaction.apply(&mut state);
97+
98+ assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
99+
100+ // uncomment
101+ let transaction = toggle_line_comments(&state);
102+ transaction.apply(&mut state);
103+ assert_eq!(state.doc, " 1\n\n 2\n 3");
104+ }
105+}
helix-core/src/lib.rs+1
@@ -1,4 +1,5 @@
11#![allow(unused)]
2+pub mod comment;
23mod diagnostic;
34pub mod graphemes;
45mod history;