vim: Make vaf include const for arrow functions in JS/TS/TSX (#45327)
Closes #24264 Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
@@ -205,6 +205,49 @@ impl EditorLspTestContext {
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
||||
"#})),
|
||||
text_objects: Some(Cow::from(indoc! {r#"
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
; Arrow function in variable declaration - capture the full declaration
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
]) @function.around
|
||||
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function)))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function)))
|
||||
]) @function.around
|
||||
|
||||
; Catch-all for arrow functions in other contexts (callbacks, etc.)
|
||||
((arrow_function) @function.around (#not-has-parent? @function.around variable_declarator))
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
@@ -276,6 +319,49 @@ impl EditorLspTestContext {
|
||||
(jsx_opening_element) @start
|
||||
(jsx_closing_element)? @end) @indent
|
||||
"#})),
|
||||
text_objects: Some(Cow::from(indoc! {r#"
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
; Arrow function in variable declaration - capture the full declaration
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
]) @function.around
|
||||
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function)))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function)))
|
||||
]) @function.around
|
||||
|
||||
; Catch-all for arrow functions in other contexts (callbacks, etc.)
|
||||
((arrow_function) @function.around (#not-has-parent? @function.around variable_declarator))
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
@@ -1141,6 +1141,104 @@ fn test_text_objects(cx: &mut App) {
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_text_objects_with_has_parent_predicate(cx: &mut App) {
|
||||
use std::borrow::Cow;
|
||||
|
||||
// Create a language with a custom text_objects query that uses #has-parent?
|
||||
// This query only matches closure_expression when it's inside a call_expression
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
text_objects: Some(Cow::from(indoc! {r#"
|
||||
; Only match closures that are arguments to function calls
|
||||
(closure_expression) @function.around
|
||||
(#has-parent? @function.around arguments)
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
let (text, ranges) = marked_text_ranges(
|
||||
indoc! {r#"
|
||||
fn main() {
|
||||
let standalone = |x| x + 1;
|
||||
let result = foo(|y| y * ˇ2);
|
||||
}"#
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx));
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let matches = snapshot
|
||||
.text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
|
||||
.map(|(range, text_object)| (&text[range], text_object))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Should only match the closure inside foo(), not the standalone closure
|
||||
assert_eq!(matches, &[("|y| y * 2", TextObject::AroundFunction),]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_text_objects_with_not_has_parent_predicate(cx: &mut App) {
|
||||
use std::borrow::Cow;
|
||||
|
||||
// Create a language with a custom text_objects query that uses #not-has-parent?
|
||||
// This query only matches closure_expression when it's NOT inside a call_expression
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
text_objects: Some(Cow::from(indoc! {r#"
|
||||
; Only match closures that are NOT arguments to function calls
|
||||
(closure_expression) @function.around
|
||||
(#not-has-parent? @function.around arguments)
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
let (text, ranges) = marked_text_ranges(
|
||||
indoc! {r#"
|
||||
fn main() {
|
||||
let standalone = |x| x +ˇ 1;
|
||||
let result = foo(|y| y * 2);
|
||||
}"#
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx));
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let matches = snapshot
|
||||
.text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
|
||||
.map(|(range, text_object)| (&text[range], text_object))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Should only match the standalone closure, not the one inside foo()
|
||||
assert_eq!(matches, &[("|x| x + 1", TextObject::AroundFunction),]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges(cx: &mut App) {
|
||||
#[track_caller]
|
||||
|
||||
@@ -19,7 +19,10 @@ use std::{
|
||||
use streaming_iterator::StreamingIterator;
|
||||
use sum_tree::{Bias, Dimensions, SeekTarget, SumTree};
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
|
||||
use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
|
||||
use tree_sitter::{
|
||||
Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatch, QueryMatches,
|
||||
QueryPredicateArg, Tree,
|
||||
};
|
||||
|
||||
pub const MAX_BYTES_TO_QUERY: usize = 16 * 1024;
|
||||
|
||||
@@ -82,6 +85,7 @@ struct SyntaxMapMatchesLayer<'a> {
|
||||
next_captures: Vec<QueryCapture<'a>>,
|
||||
has_next: bool,
|
||||
matches: QueryMatches<'a, 'a, TextProvider<'a>, &'a [u8]>,
|
||||
query: &'a Query,
|
||||
grammar_index: usize,
|
||||
_query_cursor: QueryCursorHandle,
|
||||
}
|
||||
@@ -1163,6 +1167,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||
depth: layer.depth,
|
||||
grammar_index,
|
||||
matches,
|
||||
query,
|
||||
next_pattern_index: 0,
|
||||
next_captures: Vec::new(),
|
||||
has_next: false,
|
||||
@@ -1260,13 +1265,20 @@ impl SyntaxMapCapturesLayer<'_> {
|
||||
|
||||
impl SyntaxMapMatchesLayer<'_> {
|
||||
fn advance(&mut self) {
|
||||
if let Some(mat) = self.matches.next() {
|
||||
self.next_captures.clear();
|
||||
self.next_captures.extend_from_slice(mat.captures);
|
||||
self.next_pattern_index = mat.pattern_index;
|
||||
self.has_next = true;
|
||||
} else {
|
||||
self.has_next = false;
|
||||
loop {
|
||||
if let Some(mat) = self.matches.next() {
|
||||
if !satisfies_custom_predicates(self.query, mat) {
|
||||
continue;
|
||||
}
|
||||
self.next_captures.clear();
|
||||
self.next_captures.extend_from_slice(mat.captures);
|
||||
self.next_pattern_index = mat.pattern_index;
|
||||
self.has_next = true;
|
||||
return;
|
||||
} else {
|
||||
self.has_next = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,6 +1307,39 @@ impl<'a> Iterator for SyntaxMapCaptures<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn satisfies_custom_predicates(query: &Query, mat: &QueryMatch) -> bool {
|
||||
for predicate in query.general_predicates(mat.pattern_index) {
|
||||
let satisfied = match predicate.operator.as_ref() {
|
||||
"has-parent?" => has_parent(&predicate.args, mat),
|
||||
"not-has-parent?" => !has_parent(&predicate.args, mat),
|
||||
_ => true,
|
||||
};
|
||||
if !satisfied {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn has_parent(args: &[QueryPredicateArg], mat: &QueryMatch) -> bool {
|
||||
let (
|
||||
Some(QueryPredicateArg::Capture(capture_ix)),
|
||||
Some(QueryPredicateArg::String(parent_kind)),
|
||||
) = (args.first(), args.get(1))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(capture) = mat.captures.iter().find(|c| c.index == *capture_ix) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
capture
|
||||
.node
|
||||
.parent()
|
||||
.is_some_and(|p| p.kind() == parent_kind.as_ref())
|
||||
}
|
||||
|
||||
fn join_ranges(
|
||||
a: impl Iterator<Item = Range<usize>>,
|
||||
b: impl Iterator<Item = Range<usize>>,
|
||||
|
||||
@@ -18,13 +18,47 @@
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
((arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
|
||||
(arrow_function) @function.around
|
||||
; Arrow function in variable declaration - capture the full declaration
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
]) @function.around
|
||||
|
||||
; Arrow function in variable declaration (captures body for expression-bodied arrows)
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
]) @function.around
|
||||
|
||||
; Catch-all for arrow functions in other contexts (callbacks, etc.)
|
||||
((arrow_function
|
||||
body: (_) @function.inside) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
|
||||
(generator_function
|
||||
body: (_
|
||||
|
||||
@@ -18,13 +18,47 @@
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
((arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
|
||||
(arrow_function) @function.around
|
||||
; Arrow function in variable declaration - capture the full declaration
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
]) @function.around
|
||||
|
||||
; Arrow function in variable declaration (expression body fallback)
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
]) @function.around
|
||||
|
||||
; Catch-all for arrow functions in other contexts (callbacks, etc.)
|
||||
((arrow_function
|
||||
body: (_) @function.inside) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
(function_signature) @function.around
|
||||
|
||||
(generator_function
|
||||
|
||||
@@ -18,13 +18,48 @@
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
((arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
|
||||
(arrow_function) @function.around
|
||||
; Arrow function in variable declaration - capture the full declaration
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}"))))
|
||||
]) @function.around
|
||||
|
||||
; Arrow function in variable declaration - capture body as @function.inside
|
||||
; (for statement blocks, the more specific pattern above captures just the contents)
|
||||
([
|
||||
(lexical_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
(variable_declaration
|
||||
(variable_declarator
|
||||
value: (arrow_function
|
||||
body: (_) @function.inside)))
|
||||
]) @function.around
|
||||
|
||||
; Catch-all for arrow functions in other contexts (callbacks, etc.)
|
||||
((arrow_function
|
||||
body: (_) @function.inside) @function.around
|
||||
(#not-has-parent? @function.around variable_declarator))
|
||||
(function_signature) @function.around
|
||||
|
||||
(generator_function
|
||||
|
||||
@@ -3407,4 +3407,390 @@ mod test {
|
||||
.assert_eq(" ˇf = (x: unknown) => {");
|
||||
cx.shared_clipboard().await.assert_eq("const ");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_arrow_function_text_object(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new_typescript(cx).await;
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const foo = () => {
|
||||
return ˇ1;
|
||||
};
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const foo = () => {
|
||||
return 1;
|
||||
};ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
arr.map(() => {
|
||||
return ˇ1;
|
||||
});
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
arr.map(«() => {
|
||||
return 1;
|
||||
}ˇ»);
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const foo = () => {
|
||||
return ˇ1;
|
||||
};
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v i f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
const foo = () => {
|
||||
«return 1;ˇ»
|
||||
};
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
(() => {
|
||||
console.log(ˇ1);
|
||||
})();
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
(«() => {
|
||||
console.log(1);
|
||||
}ˇ»)();
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const foo = () => {
|
||||
return ˇ1;
|
||||
};
|
||||
export { foo };
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const foo = () => {
|
||||
return 1;
|
||||
};ˇ»
|
||||
export { foo };
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
let bar = () => {
|
||||
return ˇ2;
|
||||
};
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«let bar = () => {
|
||||
return 2;
|
||||
};ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
var baz = () => {
|
||||
return ˇ3;
|
||||
};
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«var baz = () => {
|
||||
return 3;
|
||||
};ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const add = (a, b) => a + ˇb;
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const add = (a, b) => a + b;ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const add = ˇ(a, b) => a + b;
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const add = (a, b) => a + b;ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const add = (a, b) => a + bˇ;
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const add = (a, b) => a + b;ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const add = (a, b) =ˇ> a + b;
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
«const add = (a, b) => a + b;ˇ»
|
||||
"},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_arrow_function_in_jsx(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new_tsx(cx).await;
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => {
|
||||
alert("Hello world!");
|
||||
console.log(ˇ"clicked");
|
||||
}}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => {
|
||||
alert("Hello world!");
|
||||
console.log("clicked");
|
||||
}ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => console.log("clickˇed")}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={ˇ() => console.log("clicked")}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => console.log("clicked"ˇ)}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() =ˇ> console.log("clicked")}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => {
|
||||
console.log("cliˇcked");
|
||||
}}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => {
|
||||
console.log("clicked");
|
||||
}ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
|
||||
cx.set_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => fˇoo()}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a f");
|
||||
cx.assert_state(
|
||||
indoc! {r#"
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={«() => foo()ˇ»}>Hello world!</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
"#},
|
||||
Mode::VisualLine,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,12 +522,16 @@ impl Vim {
|
||||
selection.start = original_point.to_display_point(map)
|
||||
}
|
||||
} else {
|
||||
selection.end = movement::saturating_right(
|
||||
map,
|
||||
original_point.to_display_point(map),
|
||||
);
|
||||
if original_point.column > 0 {
|
||||
selection.reversed = true
|
||||
let original_display_point =
|
||||
original_point.to_display_point(map);
|
||||
if selection.end <= original_display_point {
|
||||
selection.end = movement::saturating_right(
|
||||
map,
|
||||
original_display_point,
|
||||
);
|
||||
if original_point.column > 0 {
|
||||
selection.reversed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user