mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
editor: Add action to toggle block comments (#48752)
Closes #4751 ## Testing - Manually tested by comparing the behaviors with vscode. - Those requirements are added to unit tests. Release Notes: - Added action to toggle block comments --------- Co-authored-by: ozacod <ozacod@users.noreply.github.com>
This commit is contained in:
parent
64c69cae9f
commit
525f10a133
22 changed files with 770 additions and 3 deletions
|
|
@ -529,6 +529,8 @@
|
|||
"ctrl-k ctrl-b": "editor::BlameHover",
|
||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-k ctrl-c": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-k ctrl-/": "editor::ToggleBlockComments",
|
||||
"shift-alt-a": "editor::ToggleBlockComments",
|
||||
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"f2": "editor::Rename",
|
||||
|
|
|
|||
|
|
@ -579,6 +579,8 @@
|
|||
"cmd-k cmd-i": "editor::Hover",
|
||||
"cmd-k cmd-b": "editor::BlameHover",
|
||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"cmd-k cmd-/": "editor::ToggleBlockComments",
|
||||
"shift-alt-a": "editor::ToggleBlockComments",
|
||||
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"f2": "editor::Rename",
|
||||
|
|
|
|||
|
|
@ -530,6 +530,8 @@
|
|||
"ctrl-k ctrl-f": "editor::FormatSelections",
|
||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-k ctrl-c": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-k ctrl-/": "editor::ToggleBlockComments",
|
||||
"shift-alt-a": "editor::ToggleBlockComments",
|
||||
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
|
||||
"f2": "editor::Rename",
|
||||
|
|
|
|||
|
|
@ -263,6 +263,7 @@
|
|||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPreviousHunk",
|
||||
"g c": "vim::PushToggleComments",
|
||||
"g b": "vim::PushToggleBlockComments",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -319,6 +320,7 @@
|
|||
"a": ["vim::PushObject", { "around": true }],
|
||||
"g shift-r": ["vim::Paste", { "preserve_clipboard": true }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g b": "vim::ToggleBlockComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"g w": "vim::Rewrap",
|
||||
"g ?": "vim::ConvertToRot13",
|
||||
|
|
@ -791,6 +793,12 @@
|
|||
"c": "vim::CurrentLine",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gb",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gR",
|
||||
"bindings": {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,12 @@ pub struct ToggleComments {
|
|||
pub ignore_indent: bool,
|
||||
}
|
||||
|
||||
/// Toggles block comment markers for the selected text.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ToggleBlockComments;
|
||||
|
||||
/// Moves the cursor up by a specified number of lines.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ mod code_completion_tests;
|
|||
#[cfg(test)]
|
||||
mod edit_prediction_tests;
|
||||
#[cfg(test)]
|
||||
mod editor_block_comment_tests;
|
||||
#[cfg(test)]
|
||||
mod editor_tests;
|
||||
mod signature_help;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
|
@ -16462,6 +16464,197 @@ impl Editor {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn toggle_block_comments(
|
||||
&mut self,
|
||||
_: &ToggleBlockComments,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, _window, cx| {
|
||||
let mut selections = this
|
||||
.selections
|
||||
.all::<MultiBufferPoint>(&this.display_snapshot(cx));
|
||||
let mut edits = Vec::new();
|
||||
let snapshot = this.buffer.read(cx).read(cx);
|
||||
let empty_str: Arc<str> = Arc::default();
|
||||
let mut markers_inserted = Vec::new();
|
||||
|
||||
for selection in &mut selections {
|
||||
let start_point = selection.start;
|
||||
let end_point = selection.end;
|
||||
|
||||
let Some(language) =
|
||||
snapshot.language_scope_at(Point::new(start_point.row, start_point.column))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(BlockCommentConfig {
|
||||
start: comment_start,
|
||||
end: comment_end,
|
||||
..
|
||||
}) = language.block_comment()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let prefix_needle = comment_start.trim_end().as_bytes();
|
||||
let suffix_needle = comment_end.trim_start().as_bytes();
|
||||
|
||||
// Collect full lines spanning the selection as the search region
|
||||
let region_start = Point::new(start_point.row, 0);
|
||||
let region_end = Point::new(
|
||||
end_point.row,
|
||||
snapshot.line_len(MultiBufferRow(end_point.row)),
|
||||
);
|
||||
let region_bytes: Vec<u8> = snapshot
|
||||
.bytes_in_range(region_start..region_end)
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let region_start_offset = snapshot.point_to_offset(region_start);
|
||||
let start_byte = snapshot.point_to_offset(start_point) - region_start_offset;
|
||||
let end_byte = snapshot.point_to_offset(end_point) - region_start_offset;
|
||||
|
||||
let mut is_commented = false;
|
||||
let mut prefix_range = start_point..start_point;
|
||||
let mut suffix_range = end_point..end_point;
|
||||
|
||||
// Find rightmost /* at or before the selection end
|
||||
if let Some(prefix_pos) = region_bytes[..end_byte.min(region_bytes.len())]
|
||||
.windows(prefix_needle.len())
|
||||
.rposition(|w| w == prefix_needle)
|
||||
{
|
||||
let after_prefix = prefix_pos + prefix_needle.len();
|
||||
|
||||
// Find the first */ after that /*
|
||||
if let Some(suffix_pos) = region_bytes[after_prefix..]
|
||||
.windows(suffix_needle.len())
|
||||
.position(|w| w == suffix_needle)
|
||||
.map(|p| p + after_prefix)
|
||||
{
|
||||
let suffix_end = suffix_pos + suffix_needle.len();
|
||||
|
||||
// Case 1: /* ... */ surrounds the selection
|
||||
let markers_surround = prefix_pos <= start_byte
|
||||
&& suffix_end >= end_byte
|
||||
&& start_byte < suffix_end;
|
||||
|
||||
// Case 2: selection contains /* ... */ (only whitespace padding)
|
||||
let selection_contains = start_byte <= prefix_pos
|
||||
&& suffix_end <= end_byte
|
||||
&& region_bytes[start_byte..prefix_pos]
|
||||
.iter()
|
||||
.all(|&b| b.is_ascii_whitespace())
|
||||
&& region_bytes[suffix_end..end_byte]
|
||||
.iter()
|
||||
.all(|&b| b.is_ascii_whitespace());
|
||||
|
||||
if markers_surround || selection_contains {
|
||||
is_commented = true;
|
||||
let prefix_pt =
|
||||
snapshot.offset_to_point(region_start_offset + prefix_pos);
|
||||
let suffix_pt =
|
||||
snapshot.offset_to_point(region_start_offset + suffix_pos);
|
||||
prefix_range = prefix_pt
|
||||
..Point::new(
|
||||
prefix_pt.row,
|
||||
prefix_pt.column + prefix_needle.len() as u32,
|
||||
);
|
||||
suffix_range = suffix_pt
|
||||
..Point::new(
|
||||
suffix_pt.row,
|
||||
suffix_pt.column + suffix_needle.len() as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_commented {
|
||||
// Also remove the space after /* and before */
|
||||
if snapshot
|
||||
.bytes_in_range(prefix_range.end..snapshot.max_point())
|
||||
.flatten()
|
||||
.next()
|
||||
== Some(&b' ')
|
||||
{
|
||||
prefix_range.end.column += 1;
|
||||
}
|
||||
if suffix_range.start.column > 0 {
|
||||
let before =
|
||||
Point::new(suffix_range.start.row, suffix_range.start.column - 1);
|
||||
if snapshot
|
||||
.bytes_in_range(before..suffix_range.start)
|
||||
.flatten()
|
||||
.next()
|
||||
== Some(&b' ')
|
||||
{
|
||||
suffix_range.start.column -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
edits.push((prefix_range, empty_str.clone()));
|
||||
edits.push((suffix_range, empty_str.clone()));
|
||||
} else {
|
||||
let prefix: Arc<str> = if comment_start.ends_with(' ') {
|
||||
comment_start.clone()
|
||||
} else {
|
||||
format!("{} ", comment_start).into()
|
||||
};
|
||||
let suffix: Arc<str> = if comment_end.starts_with(' ') {
|
||||
comment_end.clone()
|
||||
} else {
|
||||
format!(" {}", comment_end).into()
|
||||
};
|
||||
|
||||
edits.push((start_point..start_point, prefix.clone()));
|
||||
edits.push((end_point..end_point, suffix.clone()));
|
||||
markers_inserted.push((
|
||||
selection.id,
|
||||
prefix.len(),
|
||||
suffix.len(),
|
||||
selection.is_empty(),
|
||||
end_point.row,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
});
|
||||
|
||||
let mut selections = this
|
||||
.selections
|
||||
.all::<MultiBufferPoint>(&this.display_snapshot(cx));
|
||||
for selection in &mut selections {
|
||||
if let Some((_, prefix_len, suffix_len, was_empty, suffix_row)) = markers_inserted
|
||||
.iter()
|
||||
.find(|(id, _, _, _, _)| *id == selection.id)
|
||||
{
|
||||
if *was_empty {
|
||||
selection.start.column = selection
|
||||
.start
|
||||
.column
|
||||
.saturating_sub((*prefix_len + *suffix_len) as u32);
|
||||
} else {
|
||||
selection.start.column =
|
||||
selection.start.column.saturating_sub(*prefix_len as u32);
|
||||
if selection.end.row == *suffix_row {
|
||||
selection.end.column += *suffix_len as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.change_selections(Default::default(), _window, cx, |s| s.select(selections));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_comments(
|
||||
&mut self,
|
||||
action: &ToggleComments,
|
||||
|
|
|
|||
293
crates/editor/src/editor_block_comment_tests.rs
Normal file
293
crates/editor/src/editor_block_comment_tests.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
use crate::ToggleBlockComments;
|
||||
use crate::editor_tests::init_test;
|
||||
use crate::test::editor_test_context::EditorTestContext;
|
||||
use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
use language::{BlockCommentConfig, Language, LanguageConfig};
|
||||
use std::sync::Arc;
|
||||
|
||||
async fn setup_rust_context(cx: &mut TestAppContext) -> EditorTestContext {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let rust_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
block_comment: Some(BlockCommentConfig {
|
||||
start: "/* ".into(),
|
||||
prefix: "".into(),
|
||||
end: " */".into(),
|
||||
tab_size: 0,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
|
||||
cx.language_registry().add(rust_language.clone());
|
||||
cx.update_buffer(|buffer, cx| {
|
||||
buffer.set_language(Some(rust_language), cx);
|
||||
});
|
||||
|
||||
cx
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «1ˇ» + 2;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «/* 1 */ˇ» + 2;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «1ˇ» + 2;
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_with_selection(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn main() {
|
||||
«let x = 1 + 2;ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
«/* let x = 1 + 2; */ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
«let x = 1 + 2;ˇ»
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_multiline(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
«fn main() {
|
||||
let x = 1;
|
||||
}ˇ»
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«/* fn main() {
|
||||
let x = 1;
|
||||
} */ˇ»
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«fn main() {
|
||||
let x = 1;
|
||||
}ˇ»
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_cursor_inside(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn main() {
|
||||
let x = /* 1ˇ */ + 2;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = 1ˇ + 2;
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_multiple_cursors(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «1ˇ» + 2;
|
||||
let y = «3ˇ» + 4;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «/* 1 */ˇ» + 2;
|
||||
let y = «/* 3 */ˇ» + 4;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = «1ˇ» + 2;
|
||||
let y = «3ˇ» + 4;
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_selection_ending_on_empty_line(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
«fn main() {
|
||||
ˇ»
|
||||
let x = 1;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«/* fn main() {
|
||||
*/ˇ»
|
||||
let x = 1;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«fn main() {
|
||||
ˇ»
|
||||
let x = 1;
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_empty_selection_roundtrip(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn main() {
|
||||
let x = ˇ1 + 2;
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn main() {
|
||||
let x = ˇ1 + 2;
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
// Multi-byte Unicode characters (√ is 3 bytes in UTF-8) must not cause
|
||||
// incorrect offset arithmetic or panics.
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_unicode_before_selection(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state("let √ = «42ˇ»;");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state("let √ = «/* 42 */ˇ»;");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state("let √ = «42ˇ»;");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_unicode_in_selection(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state("«√√√ˇ»");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state("«/* √√√ */ˇ»");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state("«√√√ˇ»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments_cursor_inside_unicode_comment(cx: &mut TestAppContext) {
|
||||
let mut cx = setup_rust_context(cx).await;
|
||||
|
||||
cx.set_state("/* √√√ˇ */");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_block_comments(&ToggleBlockComments, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state("√√√ˇ");
|
||||
}
|
||||
|
|
@ -397,6 +397,7 @@ impl EditorElement {
|
|||
editor.find_previous_match(action, window, cx).log_err();
|
||||
});
|
||||
register_action(editor, window, Editor::toggle_comments);
|
||||
register_action(editor, window, Editor::toggle_block_comments);
|
||||
register_action(editor, window, Editor::select_larger_syntax_node);
|
||||
register_action(editor, window, Editor::select_smaller_syntax_node);
|
||||
register_action(editor, window, Editor::select_next_syntax_node);
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ brackets = [
|
|||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
block_comment = { start = "/*", prefix = "", end = "*/", tab_size = 1 }
|
||||
documentation_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
|
||||
|
|
|
|||
|
|
@ -18,4 +18,5 @@ brackets = [
|
|||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
block_comment = { start = "/*", prefix = "", end = "*/", tab_size = 1 }
|
||||
documentation_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ brackets = [
|
|||
tab_size = 4
|
||||
hard_tabs = true
|
||||
debuggers = ["Delve"]
|
||||
block_comment = { start = "/*", prefix = "", end = "*/", tab_size = 1 }
|
||||
documentation_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ linked_edit_characters = ["."]
|
|||
|
||||
[overrides.element]
|
||||
line_comments = { remove = true }
|
||||
block_comment = { start = "{/* ", prefix = "", end = "*/}", tab_size = 0 }
|
||||
block_comment = { start = "{/* ", prefix = "", end = "*/}", tab_size = 1 }
|
||||
opt_into_language_servers = ["emmet-language-server"]
|
||||
|
||||
[overrides.string]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ name = "JSONC"
|
|||
grammar = "jsonc"
|
||||
path_suffixes = ["jsonc", "bun.lock", "devcontainer.json", "pyrightconfig.json", "tsconfig.json", "luaurc", "swcrc", "babelrc", "eslintrc", "stylelintrc", "jshintrc"]
|
||||
line_comments = ["// "]
|
||||
block_comment = { start = "/*", prefix = "", end = "*/", tab_size = 1 }
|
||||
autoclose_before = ",]}"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, surround = true, newline = true },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ grammar = "markdown"
|
|||
path_suffixes = ["md", "mdx", "mdwn", "mdc", "markdown", "MD"]
|
||||
modeline_aliases = ["md"]
|
||||
completion_query_characters = ["-"]
|
||||
block_comment = { start = "<!--", prefix = "", end = "-->", tab_size = 0 }
|
||||
block_comment = { start = "<!--", prefix = "", end = "-->", tab_size = 1 }
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ path_suffixes = ["py", "pyi", "mpy"]
|
|||
first_line_pattern = '^#!.*((\bpython[0-9.]*\b)|(\buv run\b))'
|
||||
modeline_aliases = ["py"]
|
||||
line_comments = ["# "]
|
||||
block_comment = { start = "\"\"\"", end = "\"\"\"", prefix = "", tab_size = 1 }
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "f\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ brackets = [
|
|||
]
|
||||
collapsed_placeholder = " /* ... */ "
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
block_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
|
||||
documentation_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ linked_edit_characters = ["."]
|
|||
|
||||
[overrides.element]
|
||||
line_comments = { remove = true }
|
||||
block_comment = { start = "{/*", prefix = "", end = "*/}", tab_size = 0 }
|
||||
block_comment = { start = "{/*", prefix = "", end = "*/}", tab_size = 1 }
|
||||
opt_into_language_servers = ["emmet-language-server"]
|
||||
|
||||
[overrides.string]
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ actions!(
|
|||
ConvertToRot47,
|
||||
/// Toggles comments for selected lines.
|
||||
ToggleComments,
|
||||
/// Toggles block comments for selected lines.
|
||||
ToggleBlockComments,
|
||||
/// Shows the current location in the file.
|
||||
ShowLocation,
|
||||
/// Undoes the last change.
|
||||
|
|
@ -125,6 +127,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
Vim::action(editor, cx, Vim::yank_line);
|
||||
Vim::action(editor, cx, Vim::yank_to_end_of_line);
|
||||
Vim::action(editor, cx, Vim::toggle_comments);
|
||||
Vim::action(editor, cx, Vim::toggle_block_comments);
|
||||
Vim::action(editor, cx, Vim::paste);
|
||||
Vim::action(editor, cx, Vim::show_location);
|
||||
|
||||
|
|
@ -463,6 +466,9 @@ impl Vim {
|
|||
Some(Operator::ToggleComments) => {
|
||||
self.toggle_comments_motion(motion, times, forced_motion, window, cx)
|
||||
}
|
||||
Some(Operator::ToggleBlockComments) => {
|
||||
self.toggle_block_comments_motion(motion, times, forced_motion, window, cx)
|
||||
}
|
||||
Some(Operator::ReplaceWithRegister) => {
|
||||
self.replace_with_register_motion(motion, times, forced_motion, window, cx)
|
||||
}
|
||||
|
|
@ -533,6 +539,9 @@ impl Vim {
|
|||
Some(Operator::ToggleComments) => {
|
||||
self.toggle_comments_object(object, around, times, window, cx)
|
||||
}
|
||||
Some(Operator::ToggleBlockComments) => {
|
||||
self.toggle_block_comments_object(object, around, times, window, cx)
|
||||
}
|
||||
Some(Operator::ReplaceWithRegister) => {
|
||||
self.replace_with_register_object(object, around, window, cx)
|
||||
}
|
||||
|
|
@ -985,6 +994,38 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
fn toggle_block_comments(
|
||||
&mut self,
|
||||
_: &ToggleBlockComments,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.record_current_action(cx);
|
||||
self.store_visual_marks(window, cx);
|
||||
let is_visual_line = self.mode == Mode::VisualLine;
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
if is_visual_line {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let start_row = selection.start.to_point(map).row;
|
||||
let end_row = selection.end.to_point(map).row;
|
||||
let end_col = map.buffer_snapshot().line_len(MultiBufferRow(end_row));
|
||||
selection.start = Point::new(start_row, 0).to_display_point(map);
|
||||
selection.end = Point::new(end_row, end_col).to_display_point(map);
|
||||
});
|
||||
});
|
||||
}
|
||||
editor.toggle_block_comments(&Default::default(), window, cx);
|
||||
vim.restore_selection_cursors(editor, window, cx, original_positions);
|
||||
});
|
||||
});
|
||||
if self.mode.is_visual() {
|
||||
self.switch_mode(Mode::Normal, true, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normal_replace(
|
||||
&mut self,
|
||||
text: Arc<str>,
|
||||
|
|
|
|||
|
|
@ -71,4 +71,75 @@ impl Vim {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_block_comments_motion(
|
||||
&mut self,
|
||||
motion: Motion,
|
||||
times: Option<usize>,
|
||||
forced_motion: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window, cx);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
||||
selection_starts.insert(selection.id, anchor);
|
||||
motion.expand_selection(
|
||||
map,
|
||||
selection,
|
||||
times,
|
||||
&text_layout_details,
|
||||
forced_motion,
|
||||
);
|
||||
});
|
||||
});
|
||||
editor.toggle_block_comments(&Default::default(), window, cx);
|
||||
editor.set_clip_at_line_ends(true, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let anchor = selection_starts.remove(&selection.id).unwrap();
|
||||
selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_block_comments_object(
|
||||
&mut self,
|
||||
object: Object,
|
||||
around: bool,
|
||||
times: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut original_positions: HashMap<_, _> = Default::default();
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
||||
original_positions.insert(selection.id, anchor);
|
||||
object.expand_selection(map, selection, around, times);
|
||||
});
|
||||
});
|
||||
editor.toggle_block_comments(&Default::default(), window, cx);
|
||||
editor.set_clip_at_line_ends(true, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let anchor = original_positions.remove(&selection.id).unwrap();
|
||||
selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ pub enum Operator {
|
|||
RecordRegister,
|
||||
ReplayRegister,
|
||||
ToggleComments,
|
||||
ToggleBlockComments,
|
||||
ReplaceWithRegister,
|
||||
Exchange,
|
||||
HelixMatch,
|
||||
|
|
@ -1078,6 +1079,7 @@ impl Operator {
|
|||
Operator::RecordRegister => "q",
|
||||
Operator::ReplayRegister => "@",
|
||||
Operator::ToggleComments => "gc",
|
||||
Operator::ToggleBlockComments => "gb",
|
||||
Operator::HelixMatch => "helix_m",
|
||||
Operator::HelixNext { .. } => "helix_next",
|
||||
Operator::HelixPrevious { .. } => "helix_previous",
|
||||
|
|
@ -1157,6 +1159,7 @@ impl Operator {
|
|||
| Operator::ChangeSurrounds { target: None, .. }
|
||||
| Operator::OppositeCase
|
||||
| Operator::ToggleComments
|
||||
| Operator::ToggleBlockComments
|
||||
| Operator::HelixMatch
|
||||
| Operator::HelixNext { .. }
|
||||
| Operator::HelixPrevious { .. } => false,
|
||||
|
|
@ -1180,6 +1183,7 @@ impl Operator {
|
|||
| Operator::Rot13
|
||||
| Operator::Rot47
|
||||
| Operator::ToggleComments
|
||||
| Operator::ToggleBlockComments
|
||||
| Operator::ReplaceWithRegister
|
||||
| Operator::Rewrap
|
||||
| Operator::ShellCommand
|
||||
|
|
|
|||
|
|
@ -1694,6 +1694,134 @@ async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_toggle_block_comments(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
let language = std::sync::Arc::new(language::Language::new(
|
||||
language::LanguageConfig {
|
||||
block_comment: Some(language::BlockCommentConfig {
|
||||
start: "/* ".into(),
|
||||
prefix: "".into(),
|
||||
end: " */".into(),
|
||||
tab_size: 1,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
Some(language::tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
// works in normal mode with current-line shorthand
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("g b c");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
/* ˇone */
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// toggle off with cursor inside the comment
|
||||
cx.simulate_keystrokes("g b c");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// works in visual line mode (wraps full lines)
|
||||
cx.simulate_keystrokes("shift-v j g b");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
/* ˇone
|
||||
two */
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// works in visual mode and restores the cursor to the selection start
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
«oneˇ»
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
cx.simulate_keystrokes("g b");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
/* ˇone */
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// works with multiple visual selections and restores each cursor
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
«oneˇ» «twoˇ»
|
||||
three
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
cx.simulate_keystrokes("g b");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
/* ˇone */ /* ˇtwo */
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// works with count
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("g b 2 j");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
/* ˇone
|
||||
two
|
||||
three */
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// works with motion object
|
||||
cx.simulate_keystrokes("shift-g");
|
||||
cx.simulate_keystrokes("g b g g");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
one
|
||||
two
|
||||
three
|
||||
ˇ"},
|
||||
Mode::Normal,
|
||||
);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
|
||||
|
|
|
|||
|
|
@ -247,6 +247,8 @@ actions!(
|
|||
PushReplaceWithRegister,
|
||||
/// Toggles comments.
|
||||
PushToggleComments,
|
||||
/// Toggles block comments.
|
||||
PushToggleBlockComments,
|
||||
/// Selects (count) next menu item
|
||||
MenuSelectNext,
|
||||
/// Selects (count) previous menu item
|
||||
|
|
@ -899,6 +901,14 @@ impl Vim {
|
|||
vim.push_operator(Operator::ToggleComments, window, cx)
|
||||
});
|
||||
|
||||
Vim::action(
|
||||
editor,
|
||||
cx,
|
||||
|vim, _: &PushToggleBlockComments, window, cx| {
|
||||
vim.push_operator(Operator::ToggleBlockComments, window, cx)
|
||||
},
|
||||
);
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &ClearOperators, window, cx| {
|
||||
vim.clear_operator(window, cx)
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue