Detailed changes
@@ -63,8 +63,8 @@
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-k ctrl-q": "editor::Rewrap",
"ctrl-k q": "editor::Rewrap",
- "ctrl-backspace": "editor::DeleteToPreviousWordStart",
- "ctrl-delete": "editor::DeleteToNextWordEnd",
+ "ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"cut": "editor::Cut",
"shift-delete": "editor::Cut",
"ctrl-x": "editor::Cut",
@@ -70,9 +70,9 @@
"cmd-k q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
- "alt-backspace": "editor::DeleteToPreviousWordStart",
- "ctrl-w": "editor::DeleteToPreviousWordStart",
- "alt-delete": "editor::DeleteToNextWordEnd",
+ "alt-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "ctrl-w": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "alt-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"cmd-x": "editor::Cut",
"cmd-c": "editor::Copy",
"cmd-v": "editor::Paste",
@@ -66,8 +66,8 @@
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-k ctrl-q": "editor::Rewrap",
"ctrl-k q": "editor::Rewrap",
- "ctrl-backspace": "editor::DeleteToPreviousWordStart",
- "ctrl-delete": "editor::DeleteToNextWordEnd",
+ "ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"cut": "editor::Cut",
"shift-delete": "editor::Cut",
"ctrl-x": "editor::Cut",
@@ -42,7 +42,7 @@
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
- "alt-d": "editor::DeleteToNextWordEnd", // kill-word
+ "alt-d": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }], // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
@@ -50,8 +50,8 @@
"ctrl-k ctrl-u": "editor::ConvertToUpperCase",
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
- "ctrl-backspace": "editor::DeleteToPreviousWordStart",
- "ctrl-delete": "editor::DeleteToNextWordEnd",
+ "ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"alt-right": "editor::MoveToNextSubwordEnd",
"alt-left": "editor::MoveToPreviousSubwordStart",
"alt-shift-right": "editor::SelectToNextSubwordEnd",
@@ -42,7 +42,7 @@
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
- "alt-d": "editor::DeleteToNextWordEnd", // kill-word
+ "alt-d": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }], // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
@@ -52,8 +52,8 @@
"cmd-k cmd-l": "editor::ConvertToLowerCase",
"cmd-shift-j": "editor::JoinLines",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
- "ctrl-backspace": "editor::DeleteToPreviousWordStart",
- "ctrl-delete": "editor::DeleteToNextWordEnd",
+ "ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"ctrl-right": "editor::MoveToNextSubwordEnd",
"ctrl-left": "editor::MoveToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
@@ -21,10 +21,10 @@
{
"context": "Editor",
"bindings": {
- "alt-backspace": "editor::DeleteToPreviousWordStart",
- "alt-shift-backspace": "editor::DeleteToNextWordEnd",
- "alt-delete": "editor::DeleteToNextWordEnd",
- "alt-shift-delete": "editor::DeleteToNextWordEnd",
+ "alt-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
+ "alt-shift-backspace": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
+ "alt-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
+ "alt-shift-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
"ctrl-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-delete": "editor::DeleteToNextSubwordEnd",
"alt-left": ["editor::MoveToPreviousWordStart", { "stop_at_soft_wraps": true }],
@@ -337,7 +337,7 @@
"ctrl-x ctrl-z": "editor::Cancel",
"ctrl-x ctrl-e": "vim::LineDown",
"ctrl-x ctrl-y": "vim::LineUp",
- "ctrl-w": "editor::DeleteToPreviousWordStart",
+ "ctrl-w": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
@@ -228,21 +228,29 @@ pub struct ShowCompletions {
pub struct HandleInput(pub String);
/// Deletes from the cursor to the end of the next word.
+/// Stops before the end of the next word, if whitespace sequences of length >= 2 are encountered.
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
#[action(namespace = editor)]
#[serde(deny_unknown_fields)]
pub struct DeleteToNextWordEnd {
#[serde(default)]
pub ignore_newlines: bool,
+ // Whether to stop before the end of the next word, if language-defined bracket is encountered.
+ #[serde(default)]
+ pub ignore_brackets: bool,
}
/// Deletes from the cursor to the start of the previous word.
+/// Stops before the start of the previous word, if whitespace sequences of length >= 2 are encountered.
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
#[action(namespace = editor)]
#[serde(deny_unknown_fields)]
pub struct DeleteToPreviousWordStart {
#[serde(default)]
pub ignore_newlines: bool,
+ // Whether to stop before the start of the previous word, if language-defined bracket is encountered.
+ #[serde(default)]
+ pub ignore_brackets: bool,
}
/// Folds all code blocks at the specified indentation level.
@@ -13153,11 +13153,17 @@ impl Editor {
this.change_selections(Default::default(), window, cx, |s| {
s.move_with(|map, selection| {
if selection.is_empty() {
- let cursor = if action.ignore_newlines {
+ let mut cursor = if action.ignore_newlines {
movement::previous_word_start(map, selection.head())
} else {
movement::previous_word_start_or_newline(map, selection.head())
};
+ cursor = movement::adjust_greedy_deletion(
+ map,
+ selection.head(),
+ cursor,
+ action.ignore_brackets,
+ );
selection.set_head(cursor, SelectionGoal::None);
}
});
@@ -13178,7 +13184,9 @@ impl Editor {
this.change_selections(Default::default(), window, cx, |s| {
s.move_with(|map, selection| {
if selection.is_empty() {
- let cursor = movement::previous_subword_start(map, selection.head());
+ let mut cursor = movement::previous_subword_start(map, selection.head());
+ cursor =
+ movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
selection.set_head(cursor, SelectionGoal::None);
}
});
@@ -13254,11 +13262,17 @@ impl Editor {
this.change_selections(Default::default(), window, cx, |s| {
s.move_with(|map, selection| {
if selection.is_empty() {
- let cursor = if action.ignore_newlines {
+ let mut cursor = if action.ignore_newlines {
movement::next_word_end(map, selection.head())
} else {
movement::next_word_end_or_newline(map, selection.head())
};
+ cursor = movement::adjust_greedy_deletion(
+ map,
+ selection.head(),
+ cursor,
+ action.ignore_brackets,
+ );
selection.set_head(cursor, SelectionGoal::None);
}
});
@@ -13278,7 +13292,9 @@ impl Editor {
this.change_selections(Default::default(), window, cx, |s| {
s.move_with(|map, selection| {
if selection.is_empty() {
- let cursor = movement::next_subword_end(map, selection.head());
+ let mut cursor = movement::next_subword_end(map, selection.head());
+ cursor =
+ movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
selection.set_head(cursor, SelectionGoal::None);
}
});
@@ -2476,51 +2476,379 @@ async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
}
#[gpui::test]
-fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
+async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let editor = cx.add_window(|window, cx| {
- let buffer = MultiBuffer::build_simple("one two three four", cx);
- build_editor(buffer, window, cx)
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // For an empty selection, the preceding word fragment is deleted.
+ // For non-empty selections, only selected characters are deleted.
+ cx.set_state("onˇe two t«hreˇ»e four");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: false,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
});
+ cx.assert_editor_state("ˇe two tˇe four");
- _ = editor.update(cx, |editor, window, cx| {
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
- s.select_display_ranges([
- // an empty selection - the preceding word fragment is deleted
- DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
- // characters selected - they are deleted
- DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
- ])
- });
+ cx.set_state("e tˇwo te «fˇ»our");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: false,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("e tˇ te ˇour");
+}
+
+#[gpui::test]
+async fn test_delete_whitespaces(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.set_state("here is some text ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
editor.delete_to_previous_word_start(
&DeleteToPreviousWordStart {
ignore_newlines: false,
+ ignore_brackets: true,
},
window,
cx,
);
- assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
});
+ // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
+ cx.assert_editor_state("here is some textˇwith a space");
- _ = editor.update(cx, |editor, window, cx| {
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
- s.select_display_ranges([
- // an empty selection - the following word fragment is deleted
- DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
- // characters selected - they are deleted
- DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
- ])
- });
+ cx.set_state("here is some text ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: false,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("here is some textˇwith a space");
+
+ cx.set_state("here is some textˇ with a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: false,
+ ignore_brackets: true,
+ },
+ window,
+ cx,
+ );
+ });
+ // Same happens in the other direction.
+ cx.assert_editor_state("here is some textˇwith a space");
+
+ cx.set_state("here is some textˇ with a space");
+ cx.update_editor(|editor, window, cx| {
editor.delete_to_next_word_end(
&DeleteToNextWordEnd {
ignore_newlines: false,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("here is some textˇwith a space");
+
+ cx.set_state("here is some textˇ with a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("here is some textˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("here is some ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Single whitespaces are removed with the word behind them.
+ cx.assert_editor_state("here is ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("here ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇwith a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Same happens in the other direction.
+ cx.assert_editor_state("ˇ a space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇ space");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇ");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇ");
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state("ˇ");
+}
+
+#[gpui::test]
+async fn test_delete_to_bracket(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let language = Arc::new(
+ Language::new(
+ LanguageConfig {
+ brackets: BracketPairConfig {
+ pairs: vec![
+ BracketPair {
+ start: "\"".to_string(),
+ end: "\"".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "(".to_string(),
+ end: ")".to_string(),
+ close: true,
+ surround: true,
+ newline: true,
+ },
+ ],
+ ..BracketPairConfig::default()
+ },
+ ..LanguageConfig::default()
+ },
+ Some(tree_sitter_rust::LANGUAGE.into()),
+ )
+ .with_brackets_query(
+ r#"
+ ("(" @open ")" @close)
+ ("\"" @open "\"" @close)
+ "#,
+ )
+ .unwrap(),
+ );
+
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+ cx.set_state(r#"macro!("// ˇCOMMENT");"#);
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Deletion stops before brackets if asked to not ignore them.
+ cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Deletion has to remove a single bracket and then stop again.
+ cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
},
window,
cx,
);
- assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
});
+ cx.assert_editor_state(r#"ˇCOMMENT");"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state(r#"ˇCOMMENT");"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Brackets on the right are not paired anymore, hence deletion does not stop at them
+ cx.assert_editor_state(r#"ˇ");"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state(r#"ˇ"#);
+
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_next_word_end(
+ &DeleteToNextWordEnd {
+ ignore_newlines: true,
+ ignore_brackets: false,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state(r#"ˇ"#);
+
+ cx.set_state(r#"macro!("// ˇCOMMENT");"#);
+ cx.update_editor(|editor, window, cx| {
+ editor.delete_to_previous_word_start(
+ &DeleteToPreviousWordStart {
+ ignore_newlines: true,
+ ignore_brackets: true,
+ },
+ window,
+ cx,
+ );
+ });
+ cx.assert_editor_state(r#"macroˇCOMMENT");"#);
}
#[gpui::test]
@@ -2533,9 +2861,11 @@ fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
});
let del_to_prev_word_start = DeleteToPreviousWordStart {
ignore_newlines: false,
+ ignore_brackets: false,
};
let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
ignore_newlines: true,
+ ignore_brackets: false,
};
_ = editor.update(cx, |editor, window, cx| {
@@ -2569,9 +2899,11 @@ fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
});
let del_to_next_word_end = DeleteToNextWordEnd {
ignore_newlines: false,
+ ignore_brackets: false,
};
let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
ignore_newlines: true,
+ ignore_brackets: false,
};
_ = editor.update(cx, |editor, window, cx| {
@@ -2600,6 +2932,8 @@ fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
+ assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
+ editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
});
}
@@ -289,12 +289,114 @@ pub fn previous_word_start_or_newline(map: &DisplaySnapshot, point: DisplayPoint
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
- (classifier.kind(left) != classifier.kind(right) && !right.is_whitespace())
+ (classifier.kind(left) != classifier.kind(right) && !classifier.is_whitespace(right))
|| left == '\n'
|| right == '\n'
})
}
+/// Text movements are too greedy, making deletions too greedy too.
+/// Makes deletions more ergonomic by potentially reducing the deletion range based on its text contents:
+/// * whitespace sequences with length >= 2 stop the deletion after removal (despite movement jumping over the word behind the whitespaces)
+/// * brackets stop the deletion after removal (despite movement currently not accounting for these and jumping over)
+pub fn adjust_greedy_deletion(
+ map: &DisplaySnapshot,
+ delete_from: DisplayPoint,
+ delete_until: DisplayPoint,
+ ignore_brackets: bool,
+) -> DisplayPoint {
+ if delete_from == delete_until {
+ return delete_until;
+ }
+ let is_backward = delete_from > delete_until;
+ let delete_range = if is_backward {
+ map.display_point_to_point(delete_until, Bias::Left)
+ .to_offset(&map.buffer_snapshot)
+ ..map
+ .display_point_to_point(delete_from, Bias::Right)
+ .to_offset(&map.buffer_snapshot)
+ } else {
+ map.display_point_to_point(delete_from, Bias::Left)
+ .to_offset(&map.buffer_snapshot)
+ ..map
+ .display_point_to_point(delete_until, Bias::Right)
+ .to_offset(&map.buffer_snapshot)
+ };
+
+ let trimmed_delete_range = if ignore_brackets {
+ delete_range
+ } else {
+ let brackets_in_delete_range = map
+ .buffer_snapshot
+ .bracket_ranges(delete_range.clone())
+ .into_iter()
+ .flatten()
+ .flat_map(|(left_bracket, right_bracket)| {
+ [
+ left_bracket.start,
+ left_bracket.end,
+ right_bracket.start,
+ right_bracket.end,
+ ]
+ })
+ .filter(|&bracket| delete_range.start < bracket && bracket < delete_range.end);
+ let closest_bracket = if is_backward {
+ brackets_in_delete_range.max()
+ } else {
+ brackets_in_delete_range.min()
+ };
+
+ if is_backward {
+ closest_bracket.unwrap_or(delete_range.start)..delete_range.end
+ } else {
+ delete_range.start..closest_bracket.unwrap_or(delete_range.end)
+ }
+ };
+
+ let mut whitespace_sequences = Vec::new();
+ let mut current_offset = trimmed_delete_range.start;
+ let mut whitespace_sequence_length = 0;
+ let mut whitespace_sequence_start = 0;
+ for ch in map
+ .buffer_snapshot
+ .text_for_range(trimmed_delete_range.clone())
+ .flat_map(str::chars)
+ {
+ if ch.is_whitespace() {
+ if whitespace_sequence_length == 0 {
+ whitespace_sequence_start = current_offset;
+ }
+ whitespace_sequence_length += 1;
+ } else {
+ if whitespace_sequence_length >= 2 {
+ whitespace_sequences.push((whitespace_sequence_start, current_offset));
+ }
+ whitespace_sequence_start = 0;
+ whitespace_sequence_length = 0;
+ }
+ current_offset += ch.len_utf8();
+ }
+ if whitespace_sequence_length >= 2 {
+ whitespace_sequences.push((whitespace_sequence_start, current_offset));
+ }
+
+ let closest_whitespace_end = if is_backward {
+ whitespace_sequences.last().map(|&(start, _)| start)
+ } else {
+ whitespace_sequences.first().map(|&(_, end)| end)
+ };
+
+ closest_whitespace_end
+ .unwrap_or_else(|| {
+ if is_backward {
+ trimmed_delete_range.start
+ } else {
+ trimmed_delete_range.end
+ }
+ })
+ .to_display_point(map)
+}
+
/// Returns a position of the previous subword boundary, where a subword is defined as a run of
/// word characters of the same "subkind" - where subcharacter kinds are '_' character,
/// lowerspace characters and uppercase characters.
@@ -1250,6 +1250,7 @@ struct InjectionPatternConfig {
combined: bool,
}
+#[derive(Debug)]
struct BracketsConfig {
query: Query,
open_capture_ix: u32,