aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/commands.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r--helix-term/src/commands.rs136
1 files changed, 122 insertions, 14 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index fdad31a8..bd0a60b7 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -12,7 +12,7 @@ pub use typed::*;
use helix_core::{
char_idx_at_visual_offset, comment,
doc_formatter::TextFormat,
- encoding, find_first_non_whitespace_char, find_workspace, graphemes,
+ encoding, find_workspace, graphemes,
history::UndoKind,
increment, indent,
indent::IndentStyle,
@@ -23,7 +23,7 @@ use helix_core::{
regex::{self, Regex},
search::{self, CharMatcher},
selection, shellwords, surround,
- syntax::LanguageServerFeature,
+ syntax::{BlockCommentToken, LanguageServerFeature},
text_annotations::TextAnnotations,
textobject,
tree_sitter::Node,
@@ -415,6 +415,8 @@ impl MappableCommand {
completion, "Invoke completion popup",
hover, "Show docs for item under cursor",
toggle_comments, "Comment/uncomment selections",
+ toggle_line_comments, "Line comment/uncomment selections",
+ toggle_block_comments, "Block comment/uncomment selections",
rotate_selections_forward, "Rotate selections forward",
rotate_selections_backward, "Rotate selections backward",
rotate_selection_contents_forward, "Rotate selection contents forward",
@@ -822,7 +824,7 @@ fn kill_to_line_start(cx: &mut Context) {
let head = if anchor == first_char && line != 0 {
// select until previous line
line_end_char_index(&text, line - 1)
- } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
+ } else if let Some(pos) = text.line(line).first_non_whitespace_char() {
if first_char + pos < anchor {
// select until first non-blank in line if cursor is after it
first_char + pos
@@ -884,7 +886,7 @@ fn goto_first_nonwhitespace_impl(view: &mut View, doc: &mut Document, movement:
let selection = doc.selection(view.id).clone().transform(|range| {
let line = range.cursor_line(text);
- if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
+ if let Some(pos) = text.line(line).first_non_whitespace_char() {
let pos = pos + text.line_to_char(line);
range.put_cursor(text, pos, movement == Movement::Extend)
} else {
@@ -3087,11 +3089,11 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
} else {
// move cursor to the fallback position
let pos = match cursor_fallback {
- IndentFallbackPos::LineStart => {
- find_first_non_whitespace_char(text.line(cursor_line))
- .map(|ws_offset| ws_offset + cursor_line_start)
- .unwrap_or(cursor_line_start)
- }
+ IndentFallbackPos::LineStart => text
+ .line(cursor_line)
+ .first_non_whitespace_char()
+ .map(|ws_offset| ws_offset + cursor_line_start)
+ .unwrap_or(cursor_line_start),
IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line),
};
@@ -4462,18 +4464,124 @@ pub fn completion(cx: &mut Context) {
}
// comments
-fn toggle_comments(cx: &mut Context) {
+type CommentTransactionFn = fn(
+ line_token: Option<&str>,
+ block_tokens: Option<&[BlockCommentToken]>,
+ doc: &Rope,
+ selection: &Selection,
+) -> Transaction;
+
+fn toggle_comments_impl(cx: &mut Context, comment_transaction: CommentTransactionFn) {
let (view, doc) = current!(cx.editor);
- let token = doc
+ let line_token: Option<&str> = doc
+ .language_config()
+ .and_then(|lc| lc.comment_tokens.as_ref())
+ .and_then(|tc| tc.first())
+ .map(|tc| tc.as_str());
+ let block_tokens: Option<&[BlockCommentToken]> = doc
.language_config()
- .and_then(|lc| lc.comment_token.as_ref())
- .map(|tc| tc.as_ref());
- let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id), token);
+ .and_then(|lc| lc.block_comment_tokens.as_ref())
+ .map(|tc| &tc[..]);
+
+ let transaction =
+ comment_transaction(line_token, block_tokens, doc.text(), doc.selection(view.id));
doc.apply(&transaction, view.id);
exit_select_mode(cx);
}
+/// commenting behavior:
+/// 1. only line comment tokens -> line comment
+/// 2. each line block commented -> uncomment all lines
+/// 3. whole selection block commented -> uncomment selection
+/// 4. all lines not commented and block tokens -> comment uncommented lines
+/// 5. no comment tokens and not block commented -> line comment
+fn toggle_comments(cx: &mut Context) {
+ toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
+ let text = doc.slice(..);
+
+ // only have line comment tokens
+ if line_token.is_some() && block_tokens.is_none() {
+ return comment::toggle_line_comments(doc, selection, line_token);
+ }
+
+ let split_lines = comment::split_lines_of_selection(text, selection);
+
+ let default_block_tokens = &[BlockCommentToken::default()];
+ let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
+
+ let (line_commented, line_comment_changes) =
+ comment::find_block_comments(block_comment_tokens, text, &split_lines);
+
+ // block commented by line would also be block commented so check this first
+ if line_commented {
+ return comment::create_block_comment_transaction(
+ doc,
+ &split_lines,
+ line_commented,
+ line_comment_changes,
+ )
+ .0;
+ }
+
+ let (block_commented, comment_changes) =
+ comment::find_block_comments(block_comment_tokens, text, selection);
+
+ // check if selection has block comments
+ if block_commented {
+ return comment::create_block_comment_transaction(
+ doc,
+ selection,
+ block_commented,
+ comment_changes,
+ )
+ .0;
+ }
+
+ // not commented and only have block comment tokens
+ if line_token.is_none() && block_tokens.is_some() {
+ return comment::create_block_comment_transaction(
+ doc,
+ &split_lines,
+ line_commented,
+ line_comment_changes,
+ )
+ .0;
+ }
+
+ // not block commented at all and don't have any tokens
+ comment::toggle_line_comments(doc, selection, line_token)
+ })
+}
+
+fn toggle_line_comments(cx: &mut Context) {
+ toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
+ if line_token.is_none() && block_tokens.is_some() {
+ let default_block_tokens = &[BlockCommentToken::default()];
+ let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
+ comment::toggle_block_comments(
+ doc,
+ &comment::split_lines_of_selection(doc.slice(..), selection),
+ block_comment_tokens,
+ )
+ } else {
+ comment::toggle_line_comments(doc, selection, line_token)
+ }
+ });
+}
+
+fn toggle_block_comments(cx: &mut Context) {
+ toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
+ if line_token.is_some() && block_tokens.is_none() {
+ comment::toggle_line_comments(doc, selection, line_token)
+ } else {
+ let default_block_tokens = &[BlockCommentToken::default()];
+ let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
+ comment::toggle_block_comments(doc, selection, block_comment_tokens)
+ }
+ });
+}
+
fn rotate_selections(cx: &mut Context, direction: Direction) {
let count = cx.count();
let (view, doc) = current!(cx.editor);