on master help sign in
Determine whether to use a margin of 0 or 1 when uncommenting (#476)
112ae5cOmnikar
helix-core/src/comment.rs+57−32
@@ -1,17 +1,27 @@
1use crate::{
1use crate::{
2 find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
2 find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
3};
3};
4−use core::ops::Range;
5use std::borrow::Cow;
4use std::borrow::Cow;
6
5
6+/// Given text, a comment token, and a set of line indices, returns the following:
7+/// - Whether the given lines should be considered commented
8+/// - If any of the lines are uncommented, all lines are considered as such.
9+/// - The lines to change for toggling comments
10+/// - This is all provided lines excluding blanks lines.
11+/// - The column of the comment tokens
12+/// - Column of existing tokens, if the lines are commented; column to place tokens at otherwise.
13+/// - The margin to the right of the comment tokens
14+/// - Defaults to `1`. If any existing comment token is not followed by a space, changes to `0`.
7fn find_line_comment(
15fn find_line_comment(
8 token: &str,
16 token: &str,
9 text: RopeSlice,
17 text: RopeSlice,
10− lines: Range<usize>,
18+ lines: impl IntoIterator<Item = usize>,
11−) -> (bool, Vec<usize>, usize) {
19+) -> (bool, Vec<usize>, usize, usize) {
12 let mut commented = true;
20 let mut commented = true;
13− let mut skipped = Vec::new();
21+ let mut to_change = Vec::new();
14 let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
22 let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
23+ let mut margin = 1;
24+ let token_len = token.chars().count();
15 for line in lines {
25 for line in lines {
16 let line_slice = text.line(line);
26 let line_slice = text.line(line);
17 if let Some(pos) = find_first_non_whitespace_char(line_slice) {
27 if let Some(pos) = find_first_non_whitespace_char(line_slice) {
@@ -29,47 +39,53 @@ fn find_line_comment(
29 // considered uncommented.
39 // considered uncommented.
30 commented = false;
40 commented = false;
31 }
41 }
32− } else {
42+
33− // blank line
43+ // determine margin of 0 or 1 for uncommenting; if any comment token is not followed by a space,
44+ // a margin of 0 is used for all lines.
45+ if matches!(line_slice.get_char(pos + token_len), Some(c) if c != ' ') {
46+ margin = 0;
47+ }
48+
49+ // blank lines don't get pushed.
34− skipped.push(line);
50+ to_change.push(line);
35 }
51 }
36 }
52 }
37− (commented, skipped, min)
53+ (commented, to_change, min, margin)
38}
54}
39
55
40#[must_use]
56#[must_use]
41pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
57pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
42 let text = doc.slice(..);
58 let text = doc.slice(..);
43− let mut changes: Vec<Change> = Vec::new();
44
59
45 let token = token.unwrap_or("//");
60 let token = token.unwrap_or("//");
46 let comment = Tendril::from(format!("{} ", token));
61 let comment = Tendril::from(format!("{} ", token));
47
62
63+ let mut lines: Vec<usize> = Vec::new();
64+
65+ let mut min_next_line = 0;
48 for selection in selection {
66 for selection in selection {
49− let start = text.char_to_line(selection.from());
67+ let start = text.char_to_line(selection.from()).max(min_next_line);
50− let end = text.char_to_line(selection.to());
68+ let end = text.char_to_line(selection.to()) + 1;
51− let lines = start..end + 1;
69+ lines.extend(start..end);
52− let (commented, skipped, min) = find_line_comment(&token, text, lines.clone());
70+ min_next_line = end + 1;
71+ }
53
72
54− changes.reserve((end - start).saturating_sub(skipped.len()));
73+ let (commented, to_change, min, margin) = find_line_comment(&token, text, lines);
55
74
56− for line in lines {
75+ let mut changes: Vec<Change> = Vec::with_capacity(to_change.len());
57− if skipped.contains(&line) {
58− continue;
59− }
60
76
77+ for line in to_change {
61− let pos = text.line_to_char(line) + min;
78+ let pos = text.line_to_char(line) + min;
62
79
63− if !commented {
80+ if !commented {
64− // comment line
81+ // comment line
65− changes.push((pos, pos, Some(comment.clone())))
82+ changes.push((pos, pos, Some(comment.clone())));
66− } else {
83+ } else {
67− // uncomment line
84+ // uncomment line
68− let margin = 1; // TODO: margin is hardcoded 1 but could easily be 0
69− changes.push((pos, pos + token.len() + margin, None))
85+ changes.push((pos, pos + token.len() + margin, None));
70− }
71 }
86 }
72 }
87 }
88+
73 Transaction::change(doc, changes.into_iter())
89 Transaction::change(doc, changes.into_iter())
74}
90}
75
91
@@ -91,23 +107,32 @@ mod test {
91 let text = state.doc.slice(..);
107 let text = state.doc.slice(..);
92
108
93 let res = find_line_comment("//", text, 0..3);
109 let res = find_line_comment("//", text, 0..3);
94− // (commented = true, skipped = [line 1], min = col 2)
110+ // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 1)
95− assert_eq!(res, (false, vec![1], 2));
111+ assert_eq!(res, (false, vec![0, 2], 2, 1));
96
112
97 // comment
113 // comment
98 let transaction = toggle_line_comments(&state.doc, &state.selection, None);
114 let transaction = toggle_line_comments(&state.doc, &state.selection, None);
99 transaction.apply(&mut state.doc);
115 transaction.apply(&mut state.doc);
100− state.selection = state.selection.clone().map(transaction.changes());
116+ state.selection = state.selection.map(transaction.changes());
101
117
102 assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
118 assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
103
119
104 // uncomment
120 // uncomment
105 let transaction = toggle_line_comments(&state.doc, &state.selection, None);
121 let transaction = toggle_line_comments(&state.doc, &state.selection, None);
106 transaction.apply(&mut state.doc);
122 transaction.apply(&mut state.doc);
107− state.selection = state.selection.clone().map(transaction.changes());
123+ state.selection = state.selection.map(transaction.changes());
124+ assert_eq!(state.doc, " 1\n\n 2\n 3");
125+
126+ // 0 margin comments
127+ state.doc = Rope::from(" //1\n\n //2\n //3");
128+ // reset the selection.
129+ state.selection = Selection::single(0, state.doc.len_chars() - 1);
130+
131+ let transaction = toggle_line_comments(&state.doc, &state.selection, None);
132+ transaction.apply(&mut state.doc);
133+ state.selection = state.selection.map(transaction.changes());
108 assert_eq!(state.doc, " 1\n\n 2\n 3");
134 assert_eq!(state.doc, " 1\n\n 2\n 3");
109
135
110− // TODO: account for no margin after comment
111 // TODO: account for uncommenting with uneven comment indentation
136 // TODO: account for uncommenting with uneven comment indentation
112 }
137 }
113}
138}