aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/comment.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core/src/comment.rs')
-rw-r--r--helix-core/src/comment.rs105
1 files changed, 105 insertions, 0 deletions
diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs
new file mode 100644
index 00000000..87becfa3
--- /dev/null
+++ b/helix-core/src/comment.rs
@@ -0,0 +1,105 @@
+use crate::{find_first_non_whitespace_char2, Change, RopeSlice, State, Transaction};
+use core::ops::Range;
+use std::borrow::Cow;
+
+fn find_line_comment(
+ token: &str,
+ text: RopeSlice,
+ lines: Range<usize>,
+) -> (bool, Vec<usize>, usize) {
+ // selection ranges map char_to_line from() .. to()
+ let mut commented = true;
+ let mut skipped = Vec::new();
+ let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
+ for line in lines {
+ let line_slice = text.line(line);
+ if let Some(pos) = find_first_non_whitespace_char2(line_slice) {
+ let len = line_slice.len_chars();
+
+ if pos < min {
+ min = pos;
+ }
+
+ // line can be shorter than pos + token len
+ let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len)));
+
+ if fragment != token {
+ // as soon as one of the non-blank lines doesn't have a comment, the whole block is
+ // considered uncommented.
+ commented = false;
+ }
+ } else {
+ // blank line
+ skipped.push(line);
+ }
+ }
+ (commented, skipped, min)
+}
+
+fn toggle_line_comments(state: &State) -> Transaction {
+ let text = state.doc.slice(..);
+ let mut changes: Vec<Change> = Vec::new();
+
+ let token = "//";
+
+ for selection in state.selection.ranges() {
+ let start = text.char_to_line(selection.from());
+ let end = text.char_to_line(selection.to());
+ let lines = start..end + 1;
+ let (commented, skipped, min) = find_line_comment(token, text, lines.clone());
+
+ changes.reserve(end - start - skipped.len());
+
+ for line in lines {
+ if skipped.contains(&line) {
+ continue;
+ }
+
+ let pos = text.line_to_char(line) + min;
+
+ if !commented {
+ // comment line
+ changes.push((pos, pos, Some(format!("{} ", token).into())))
+ } else {
+ // uncomment line
+ let margin = 1; // TODO: margin is hardcoded 1 but could easily be 0
+ changes.push((pos, pos + token.len() + margin, None))
+ }
+ }
+ }
+ Transaction::change(state, changes.into_iter())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_find_line_comment() {
+ use crate::{Rope, Selection};
+
+ // four lines, two space indented, except for line 1 which is blank.
+ let doc = Rope::from(" 1\n\n 2\n 3");
+
+ let mut state = State::new(doc);
+ // select whole document
+ state.selection = Selection::single(0, state.doc.len_chars() - 1);
+
+ let text = state.doc.slice(..);
+
+ let res = find_line_comment("//", text, 0..3);
+ // (commented = true, skipped = [line 1], min = col 2)
+ assert_eq!(res, (false, vec![1], 2));
+
+ // comment
+ let transaction = toggle_line_comments(&state);
+ transaction.apply(&mut state);
+
+ assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
+
+ // uncomment
+ let transaction = toggle_line_comments(&state);
+ transaction.apply(&mut state);
+ assert_eq!(state.doc, " 1\n\n 2\n 3");
+ }
+}