mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +03:00
Implement toggle-comments
This commit is contained in:
parent
2e2bce7322
commit
f0db748ba1
@ -564,6 +564,10 @@ impl Buffer {
|
||||
self.content().line_len(row)
|
||||
}
|
||||
|
||||
pub fn is_line_blank(&self, row: u32) -> bool {
|
||||
self.content().is_line_blank(row)
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> Point {
|
||||
self.visible_text.max_point()
|
||||
}
|
||||
@ -1557,6 +1561,10 @@ impl Snapshot {
|
||||
self.content().line_len(row)
|
||||
}
|
||||
|
||||
pub fn is_line_blank(&self, row: u32) -> bool {
|
||||
self.content().is_line_blank(row)
|
||||
}
|
||||
|
||||
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
||||
self.content().indent_column_for_line(row)
|
||||
}
|
||||
@ -1574,8 +1582,7 @@ impl Snapshot {
|
||||
}
|
||||
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
self.visible_text.chunks_in_range(range)
|
||||
self.content().text_for_range(range)
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range<T>(&self, range: Range<T>) -> TextSummary
|
||||
@ -1725,6 +1732,11 @@ impl<'a> Content<'a> {
|
||||
(row_end_offset - row_start_offset) as u32
|
||||
}
|
||||
|
||||
fn is_line_blank(&self, row: u32) -> bool {
|
||||
self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
|
||||
.all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
|
||||
}
|
||||
|
||||
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
||||
let mut result = 0;
|
||||
for c in self.chars_at(Point::new(row, 0)) {
|
||||
|
@ -83,6 +83,7 @@ action!(SelectLine);
|
||||
action!(SplitSelectionIntoLines);
|
||||
action!(AddSelectionAbove);
|
||||
action!(AddSelectionBelow);
|
||||
action!(ToggleComments);
|
||||
action!(SelectLargerSyntaxNode);
|
||||
action!(SelectSmallerSyntaxNode);
|
||||
action!(MoveToEnclosingBracket);
|
||||
@ -184,6 +185,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("Editor")),
|
||||
Binding::new("cmd-alt-down", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-/", ToggleComments, Some("Editor")),
|
||||
Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
|
||||
@ -244,6 +246,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::split_selection_into_lines);
|
||||
cx.add_action(Editor::add_selection_above);
|
||||
cx.add_action(Editor::add_selection_below);
|
||||
cx.add_action(Editor::toggle_comments);
|
||||
cx.add_action(Editor::select_larger_syntax_node);
|
||||
cx.add_action(Editor::select_smaller_syntax_node);
|
||||
cx.add_action(Editor::move_to_enclosing_bracket);
|
||||
@ -2127,6 +2130,96 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext<Self>) {
|
||||
// Get the line comment prefix. Split its trailing whitespace into a separate string,
|
||||
// as that portion won't be used for detecting if a line is a comment.
|
||||
let full_comment_prefix =
|
||||
if let Some(prefix) = self.language(cx).and_then(|l| l.line_comment_prefix()) {
|
||||
prefix.to_string()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let comment_prefix = full_comment_prefix.trim_end_matches(' ');
|
||||
let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
|
||||
|
||||
self.start_transaction(cx);
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
let mut all_selection_lines_are_comments = true;
|
||||
let mut edit_ranges = Vec::new();
|
||||
let mut last_toggled_row = None;
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
for selection in &mut selections {
|
||||
edit_ranges.clear();
|
||||
|
||||
let end_row =
|
||||
if selection.end.row > selection.start.row && selection.end.column == 0 {
|
||||
selection.end.row
|
||||
} else {
|
||||
selection.end.row + 1
|
||||
};
|
||||
|
||||
for row in selection.start.row..end_row {
|
||||
// If multiple selections contain a given row, avoid processing that
|
||||
// row more than once.
|
||||
if last_toggled_row == Some(row) {
|
||||
continue;
|
||||
} else {
|
||||
last_toggled_row = Some(row);
|
||||
}
|
||||
|
||||
if buffer.is_line_blank(row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = Point::new(row, buffer.indent_column_for_line(row));
|
||||
let mut line_bytes = buffer.bytes_at(start);
|
||||
|
||||
// If this line currently begins with the line comment prefix, then record
|
||||
// the range containing the prefix.
|
||||
if all_selection_lines_are_comments
|
||||
&& line_bytes
|
||||
.by_ref()
|
||||
.take(comment_prefix.len())
|
||||
.eq(comment_prefix.bytes())
|
||||
{
|
||||
// Include any whitespace that matches the comment prefix.
|
||||
let matching_whitespace_len = line_bytes
|
||||
.zip(comment_prefix_whitespace.bytes())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count() as u32;
|
||||
let end = Point::new(
|
||||
row,
|
||||
start.column + comment_prefix.len() as u32 + matching_whitespace_len,
|
||||
);
|
||||
edit_ranges.push(start..end);
|
||||
}
|
||||
// If this line does not begin with the line comment prefix, then record
|
||||
// the position where the prefix should be inserted.
|
||||
else {
|
||||
all_selection_lines_are_comments = false;
|
||||
edit_ranges.push(start..start);
|
||||
}
|
||||
}
|
||||
|
||||
if !edit_ranges.is_empty() {
|
||||
if all_selection_lines_are_comments {
|
||||
buffer.edit(edit_ranges.iter().cloned(), "", cx);
|
||||
} else {
|
||||
let min_column = edit_ranges.iter().map(|r| r.start.column).min().unwrap();
|
||||
let edit_ranges = edit_ranges.iter().map(|range| {
|
||||
let position = Point::new(range.start.row, min_column);
|
||||
position..position
|
||||
});
|
||||
buffer.edit(edit_ranges, &full_comment_prefix, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.update_selections(self.selections::<usize>(cx).collect(), true, cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
|
||||
pub fn select_larger_syntax_node(
|
||||
&mut self,
|
||||
_: &SelectLargerSyntaxNode,
|
||||
@ -4890,6 +4983,91 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_comment(mut cx: gpui::TestAppContext) {
|
||||
let settings = cx.read(EditorSettings::test);
|
||||
let language = Some(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comment: Some("// ".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
tree_sitter_rust::language(),
|
||||
)));
|
||||
|
||||
let text = "
|
||||
fn a() {
|
||||
//b();
|
||||
// c();
|
||||
// d();
|
||||
}
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
|
||||
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
|
||||
|
||||
view.update(&mut cx, |editor, cx| {
|
||||
// If multiple selections intersect a line, the line is only
|
||||
// toggled once.
|
||||
editor
|
||||
.select_display_ranges(
|
||||
&[
|
||||
DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
|
||||
DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
|
||||
],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
editor.toggle_comments(&ToggleComments, cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"
|
||||
fn a() {
|
||||
b();
|
||||
c();
|
||||
d();
|
||||
}
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
|
||||
// The comment prefix is inserted at the same column for every line
|
||||
// in a selection.
|
||||
editor
|
||||
.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)], cx)
|
||||
.unwrap();
|
||||
editor.toggle_comments(&ToggleComments, cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"
|
||||
fn a() {
|
||||
// b();
|
||||
// c();
|
||||
// d();
|
||||
}
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
|
||||
// If a selection ends at the beginning of a line, that line is not toggled.
|
||||
editor
|
||||
.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)], cx)
|
||||
.unwrap();
|
||||
editor.toggle_comments(&ToggleComments, cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"
|
||||
fn a() {
|
||||
// b();
|
||||
c();
|
||||
// d();
|
||||
}
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) {
|
||||
let settings = cx.read(EditorSettings::test);
|
||||
|
@ -14,6 +14,7 @@ pub struct LanguageConfig {
|
||||
pub name: String,
|
||||
pub path_suffixes: Vec<String>,
|
||||
pub brackets: Vec<BracketPair>,
|
||||
pub line_comment: Option<String>,
|
||||
pub language_server: Option<LanguageServerConfig>,
|
||||
}
|
||||
|
||||
@ -115,6 +116,10 @@ impl Language {
|
||||
self.config.name.as_str()
|
||||
}
|
||||
|
||||
pub fn line_comment_prefix(&self) -> Option<&str> {
|
||||
self.config.line_comment.as_deref()
|
||||
}
|
||||
|
||||
pub fn start_server(
|
||||
&self,
|
||||
root_path: &Path,
|
||||
|
@ -1654,11 +1654,6 @@ impl Snapshot {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_line_blank(&self, row: u32) -> bool {
|
||||
self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
|
||||
.all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
|
||||
}
|
||||
|
||||
pub fn chunks<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
|
@ -1,5 +1,6 @@
|
||||
name = "Rust"
|
||||
path_suffixes = ["rs"]
|
||||
line_comment = "// "
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
|
Loading…
Reference in New Issue
Block a user