Update marked text helpers to use more distinctive characters for markers

This commit is contained in:
Max Brunsfeld 2022-08-03 14:20:05 -07:00
parent 9c3b287a61
commit eabd9c02e5
9 changed files with 540 additions and 609 deletions

View file

@ -8050,33 +8050,33 @@ mod tests {
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {" cx.set_state(indoc! {"
const a: |A = ( const a: ˇA = (
(| (ˇ
[const_function}(|), «const_functionˇ»(ˇ),
so{m]et[h}ing_|else,| so«»et«»ing_ˇelse,ˇ
)| )ˇ
|);| ˇ);ˇ
"}); "});
cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx)); cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
const a: A = ( const a: A = (
| ˇ
( (
| ˇ
const_function(), const_function(),
| ˇ
| ˇ
something_else, something_else,
| ˇ
| ˇ
| ˇ
| ˇ
) )
| ˇ
); );
| ˇ
| ˇ
"}); "});
} }
#[gpui::test] #[gpui::test]
@ -8115,25 +8115,25 @@ mod tests {
}); });
}); });
cx.set_state(indoc! {" cx.set_state(indoc! {"
|ab|c ˇabˇc
|🏀|🏀|efg ˇ🏀ˇ🏀ˇefg
d| dˇ
"}); "});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
|ab |c ˇab ˇc
|🏀 |🏀 |efg ˇ🏀 ˇ🏀 ˇefg
d | d ˇ
"}); "});
cx.set_state(indoc! {" cx.set_state(indoc! {"
a a
[🏀}🏀[🏀}🏀[🏀} «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
"}); "});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
a a
[🏀}🏀[🏀}🏀[🏀} «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
"}); "});
} }
@ -8154,26 +8154,26 @@ mod tests {
// a soft tab. cursors that are to the left of the suggested indent // a soft tab. cursors that are to the left of the suggested indent
// auto-indent their line. // auto-indent their line.
cx.set_state(indoc! {" cx.set_state(indoc! {"
| ˇ
const a: B = ( const a: B = (
c( c(
d( d(
| ˇ
) )
| ˇ
| ) ˇ )
); );
"}); "});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
| ˇ
const a: B = ( const a: B = (
c( c(
d( d(
| ˇ
) )
| ˇ
|) ˇ)
); );
"}); "});
@ -8181,16 +8181,16 @@ mod tests {
cx.set_state(indoc! {" cx.set_state(indoc! {"
const a: B = ( const a: B = (
c( c(
| | ˇ ˇ
| ) ˇ )
); );
"}); "});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
const a: B = ( const a: B = (
c( c(
| ˇ
|) ˇ)
); );
"}); "});
} }
@ -8200,58 +8200,68 @@ mod tests {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {" cx.set_state(indoc! {"
[one} [two} «oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
[one} [two} «oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
[one} [two} «oneˇ» «twoˇ»
three three
four"}); four
"});
// select across line ending // select across line ending
cx.set_state(indoc! {" cx.set_state(indoc! {"
one two one two
t[hree t«hree
} four"}); ˇ» four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
t[hree t«hree
} four"}); ˇ» four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
t[hree t«hree
} four"}); ˇ» four
"});
// Ensure that indenting/outdenting works when the cursor is at column 0. // Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {" cx.set_state(indoc! {"
one two one two
|three ˇthree
four"}); four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
|three ˇthree
four"}); four
"});
cx.set_state(indoc! {" cx.set_state(indoc! {"
one two one two
| three ˇ three
four"}); four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
|three ˇthree
four"}); four
"});
} }
#[gpui::test] #[gpui::test]
@ -8265,75 +8275,90 @@ mod tests {
// select two ranges on one line // select two ranges on one line
cx.set_state(indoc! {" cx.set_state(indoc! {"
[one} [two} «oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
\t[one} [two} \t«oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
\t\t[one} [two} \t\t«oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
\t[one} [two} \t«oneˇ» «twoˇ»
three three
four"}); four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
[one} [two} «oneˇ» «twoˇ»
three three
four"}); four
"});
// select across a line ending // select across a line ending
cx.set_state(indoc! {" cx.set_state(indoc! {"
one two one two
t[hree t«hree
}four"}); ˇ»four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
\tt[hree \tt«hree
}four"}); ˇ»four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx)); cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
\t\tt[hree \t\tt«hree
}four"}); ˇ»four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
\tt[hree \tt«hree
}four"}); ˇ»four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
t[hree t«hree
}four"}); ˇ»four
"});
// Ensure that indenting/outdenting works when the cursor is at column 0. // Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {" cx.set_state(indoc! {"
one two one two
|three ˇthree
four"}); four
cx.assert_editor_state(indoc! {" "});
one two
|three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
\t|three
four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
|three ˇthree
four"}); four
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
\tˇthree
four
"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
ˇthree
four
"});
} }
#[gpui::test] #[gpui::test]
@ -8412,10 +8437,10 @@ mod tests {
select_ranges( select_ranges(
&mut editor, &mut editor,
indoc! {" indoc! {"
[a] = 1 «» = 1
b = 2 b = 2
[const c:] usize = 3; «const c:ˇ» usize = 3;
"}, "},
cx, cx,
); );
@ -8424,10 +8449,10 @@ mod tests {
assert_text_with_selections( assert_text_with_selections(
&mut editor, &mut editor,
indoc! {" indoc! {"
[a] = 1 «» = 1
b = 2 b = 2
[const c:] usize = 3; «const c:ˇ» usize = 3;
"}, "},
cx, cx,
); );
@ -8435,10 +8460,10 @@ mod tests {
assert_text_with_selections( assert_text_with_selections(
&mut editor, &mut editor,
indoc! {" indoc! {"
[a] = 1 «» = 1
b = 2 b = 2
[const c:] usize = 3; «const c:ˇ» usize = 3;
"}, "},
cx, cx,
); );
@ -8450,43 +8475,48 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_backspace(cx: &mut gpui::TestAppContext) { async fn test_backspace(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
// Basic backspace // Basic backspace
cx.set_state(indoc! {" cx.set_state(indoc! {"
on|e two three onˇe two three
fou[r} five six fou«» five six
seven {eight nine seven «ˇeight nine
]ten"}); »ten
"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
o|e two three oˇe two three
fou| five six fouˇ five six
seven |ten"}); seven ˇten
"});
// Test backspace inside and around indents // Test backspace inside and around indents
cx.set_state(indoc! {" cx.set_state(indoc! {"
zero zero
|one ˇone
|two ˇtwo
| | | three ˇ ˇ ˇ three
| | four"}); ˇ ˇ four
"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
zero zero
|one ˇone
|two ˇtwo
| three| four"}); ˇ threeˇ four
"});
// Test backspace with line_mode set to true // Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true); cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {" cx.set_state(indoc! {"
The |quick |brown The ˇquick ˇbrown
fox jumps over fox jumps over
the lazy dog the lazy dog
|The qu[ick b}rown"}); ˇThe qu«ick »rown"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
|fox jumps over ˇfox jumps over
the lazy dog|"}); the lazy dogˇ"});
} }
#[gpui::test] #[gpui::test]
@ -8494,25 +8524,27 @@ mod tests {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {" cx.set_state(indoc! {"
on|e two three onˇe two three
fou[r} five six fou«» five six
seven {eight nine seven «ˇeight nine
]ten"}); »ten
"});
cx.update_editor(|e, cx| e.delete(&Delete, cx)); cx.update_editor(|e, cx| e.delete(&Delete, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
on| two three onˇ two three
fou| five six fouˇ five six
seven |ten"}); seven ˇten
"});
// Test backspace with line_mode set to true // Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true); cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {" cx.set_state(indoc! {"
The |quick |brown The ˇquick ˇbrown
fox {jum]ps over fox «ˇjum»ps over
the lazy dog the lazy dog
|The qu[ick b}rown"}); ˇThe qu«ick »rown"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state("|the lazy dog|"); cx.assert_editor_state("ˇthe lazy dogˇ");
} }
#[gpui::test] #[gpui::test]
@ -8824,19 +8856,19 @@ mod tests {
async fn test_clipboard(cx: &mut gpui::TestAppContext) { async fn test_clipboard(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
cx.set_state("[one✅ }two [three }four [five }six "); cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
cx.update_editor(|e, cx| e.cut(&Cut, cx)); cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state("|two |four |six "); cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
// Paste with three cursors. Each cursor pastes one slice of the clipboard text. // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
cx.set_state("two |four |six |"); cx.set_state("two ˇfour ˇsix ˇ");
cx.update_editor(|e, cx| e.paste(&Paste, cx)); cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state("two one✅ |four three |six five |"); cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
// Paste again but with only two cursors. Since the number of cursors doesn't // Paste again but with only two cursors. Since the number of cursors doesn't
// match the number of slices in the clipboard, the entire clipboard text // match the number of slices in the clipboard, the entire clipboard text
// is pasted at each cursor. // is pasted at each cursor.
cx.set_state("|two one✅ four three six five |"); cx.set_state("ˇtwo one✅ four three six five ˇ");
cx.update_editor(|e, cx| { cx.update_editor(|e, cx| {
e.handle_input("( ", cx); e.handle_input("( ", cx);
e.paste(&Paste, cx); e.paste(&Paste, cx);
@ -8845,37 +8877,37 @@ mod tests {
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
( one ( one
three three
five ) |two one four three six five ( one five ) ˇtwo one four three six five ( one
three three
five ) |"}); five ) ˇ"});
// Cut with three selections, one of which is full-line. // Cut with three selections, one of which is full-line.
cx.set_state(indoc! {" cx.set_state(indoc! {"
1[2}3 1«2ˇ»3
4|567 4ˇ567
[8}9"}); «8ˇ»9"});
cx.update_editor(|e, cx| e.cut(&Cut, cx)); cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
1|3 1ˇ3
|9"}); ˇ9"});
// Paste with three selections, noticing how the copied selection that was full-line // Paste with three selections, noticing how the copied selection that was full-line
// gets inserted before the second cursor. // gets inserted before the second cursor.
cx.set_state(indoc! {" cx.set_state(indoc! {"
1|3 1ˇ3
9| 9ˇ
[o}ne"}); «»ne"});
cx.update_editor(|e, cx| e.paste(&Paste, cx)); cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
12|3 12ˇ3
4567 4567
9| 9ˇ
8|ne"}); 8ˇne"});
// Copy with a single cursor only, which writes the whole line into the clipboard. // Copy with a single cursor only, which writes the whole line into the clipboard.
cx.set_state(indoc! {" cx.set_state(indoc! {"
The quick brown The quick brown
fox ju|mps over fox juˇmps over
the lazy dog"}); the lazy dog"});
cx.update_editor(|e, cx| e.copy(&Copy, cx)); cx.update_editor(|e, cx| e.copy(&Copy, cx));
cx.cx.assert_clipboard_content(Some("fox jumps over\n")); cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
@ -8883,17 +8915,17 @@ mod tests {
// Paste with three selections, noticing how the copied full-line selection is inserted // Paste with three selections, noticing how the copied full-line selection is inserted
// before the empty selections but replaces the selection that is non-empty. // before the empty selections but replaces the selection that is non-empty.
cx.set_state(indoc! {" cx.set_state(indoc! {"
T|he quick brown Tˇhe quick brown
[fo}x jumps over «foˇ»x jumps over
t|he lazy dog"}); tˇhe lazy dog"});
cx.update_editor(|e, cx| e.paste(&Paste, cx)); cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fox jumps over fox jumps over
T|he quick brown Tˇhe quick brown
fox jumps over fox jumps over
|x jumps over ˇx jumps over
fox jumps over fox jumps over
t|he lazy dog"}); tˇhe lazy dog"});
} }
#[gpui::test] #[gpui::test]
@ -8909,17 +8941,17 @@ mod tests {
cx.set_state(indoc! {" cx.set_state(indoc! {"
const a: B = ( const a: B = (
c(), c(),
[d( «d(
e, e,
f f
)} )ˇ»
); );
"}); "});
cx.update_editor(|e, cx| e.cut(&Cut, cx)); cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
const a: B = ( const a: B = (
c(), c(),
| ˇ
); );
"}); "});
@ -8931,13 +8963,13 @@ mod tests {
d( d(
e, e,
f f
)| )ˇ
); );
"}); "});
// Paste it at a line with a lower indent level. // Paste it at a line with a lower indent level.
cx.set_state(indoc! {" cx.set_state(indoc! {"
| ˇ
const a: B = ( const a: B = (
c(), c(),
); );
@ -8947,7 +8979,7 @@ mod tests {
d( d(
e, e,
f f
)| )ˇ
const a: B = ( const a: B = (
c(), c(),
); );
@ -8957,17 +8989,17 @@ mod tests {
cx.set_state(indoc! {" cx.set_state(indoc! {"
const a: B = ( const a: B = (
c(), c(),
[ d( « d(
e, e,
f f
) )
}); ˇ»);
"}); "});
cx.update_editor(|e, cx| e.cut(&Cut, cx)); cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
const a: B = ( const a: B = (
c(), c(),
|); ˇ);
"}); "});
// Paste it at the same position. // Paste it at the same position.
@ -8979,7 +9011,7 @@ mod tests {
e, e,
f f
) )
|); ˇ);
"}); "});
// Paste it at a line with a higher indent level. // Paste it at a line with a higher indent level.
@ -8988,7 +9020,7 @@ mod tests {
c(), c(),
d( d(
e, e,
f| fˇ
) )
); );
"}); "});
@ -9002,7 +9034,7 @@ mod tests {
e, e,
f f
) )
| ˇ
) )
); );
"}); "});
@ -10293,16 +10325,18 @@ mod tests {
.await; .await;
cx.set_state(indoc! {" cx.set_state(indoc! {"
one| oneˇ
two two
three"}); three
"});
cx.simulate_keystroke("."); cx.simulate_keystroke(".");
handle_completion_request( handle_completion_request(
&mut cx, &mut cx,
indoc! {" indoc! {"
one.|<> one.|<>
two two
three"}, three
"},
vec!["first_completion", "second_completion"], vec!["first_completion", "second_completion"],
) )
.await; .await;
@ -10315,9 +10349,10 @@ mod tests {
.unwrap() .unwrap()
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one.second_completion| one.second_completionˇ
two two
three"}); three
"});
handle_resolve_completion_request( handle_resolve_completion_request(
&mut cx, &mut cx,
@ -10325,23 +10360,26 @@ mod tests {
indoc! {" indoc! {"
one.second_completion one.second_completion
two two
three<>"}, three<>
"},
"\nadditional edit", "\nadditional edit",
)), )),
) )
.await; .await;
apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one.second_completion| one.second_completionˇ
two two
three three
additional edit"}); additional edit
"});
cx.set_state(indoc! {" cx.set_state(indoc! {"
one.second_completion one.second_completion
two| twoˇ
three| threeˇ
additional edit"}); additional edit
"});
cx.simulate_keystroke(" "); cx.simulate_keystroke(" ");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.is_none()));
cx.simulate_keystroke("s"); cx.simulate_keystroke("s");
@ -10349,16 +10387,18 @@ mod tests {
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one.second_completion one.second_completion
two s| two
three s| three
additional edit"}); additional edit
"});
handle_completion_request( handle_completion_request(
&mut cx, &mut cx,
indoc! {" indoc! {"
one.second_completion one.second_completion
two s two s
three <s|> three <s|>
additional edit"}, additional edit
"},
vec!["fourth_completion", "fifth_completion", "sixth_completion"], vec!["fourth_completion", "fifth_completion", "sixth_completion"],
) )
.await; .await;
@ -10373,7 +10413,8 @@ mod tests {
one.second_completion one.second_completion
two si two si
three <si|> three <si|>
additional edit"}, additional edit
"},
vec!["fourth_completion", "fifth_completion", "sixth_completion"], vec!["fourth_completion", "fifth_completion", "sixth_completion"],
) )
.await; .await;
@ -10387,9 +10428,10 @@ mod tests {
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one.second_completion one.second_completion
two sixth_completion| two sixth_completionˇ
three sixth_completion| three sixth_completionˇ
additional edit"}); additional edit
"});
handle_resolve_completion_request(&mut cx, None).await; handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
@ -10399,13 +10441,13 @@ mod tests {
settings.show_completions_on_input = false; settings.show_completions_on_input = false;
}) })
}); });
cx.set_state("editor|"); cx.set_state("editorˇ");
cx.simulate_keystroke("."); cx.simulate_keystroke(".");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.is_none()));
cx.simulate_keystroke("c"); cx.simulate_keystroke("c");
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.simulate_keystroke("o"); cx.simulate_keystroke("o");
cx.assert_editor_state("editor.clo|"); cx.assert_editor_state("editor.cloˇ");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.is_none()));
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
editor.show_completions(&ShowCompletions, cx); editor.show_completions(&ShowCompletions, cx);
@ -10418,7 +10460,7 @@ mod tests {
.confirm_completion(&ConfirmCompletion::default(), cx) .confirm_completion(&ConfirmCompletion::default(), cx)
.unwrap() .unwrap()
}); });
cx.assert_editor_state("editor.close|"); cx.assert_editor_state("editor.closeˇ");
handle_resolve_completion_request(&mut cx, None).await; handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();

View file

@ -32,13 +32,10 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use indoc::indoc;
use language::{BracketPair, Language, LanguageConfig};
use crate::test::EditorLspTestContext;
use super::*; use super::*;
use crate::test::EditorLspTestContext;
use indoc::indoc;
use language::{BracketPair, Language, LanguageConfig};
#[gpui::test] #[gpui::test]
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) { async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
@ -76,67 +73,61 @@ mod tests {
.await; .await;
// positioning cursor inside bracket highlights both // positioning cursor inside bracket highlights both
cx.set_state_by( cx.set_state(indoc! {r#"
vec!['|'.into()], pub fn test("Test ˇargument") {
indoc! {r#" another_test(1, 2, 3);
pub fn test("Test |argument") { }
another_test(1, 2, 3); "#});
}"#},
);
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#" cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test[(]"Test argument"[)] { pub fn test«(»"Test argument"«)» {
another_test(1, 2, 3); another_test(1, 2, 3);
}"#}); }
"#});
cx.set_state_by( cx.set_state(indoc! {r#"
vec!['|'.into()], pub fn test("Test argument") {
indoc! {r#" another_test(1, ˇ2, 3);
pub fn test("Test argument") { }
another_test(1, |2, 3); "#});
}"#},
);
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#" cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") { pub fn test("Test argument") {
another_test[(]1, 2, 3[)]; another_test«(»1, 2, 3«)»;
}"#}); }
"#});
cx.set_state_by( cx.set_state(indoc! {r#"
vec!['|'.into()], pub fn test("Test argument") {
indoc! {r#" anotherˇ_test(1, 2, 3);
pub fn test("Test argument") { }
another|_test(1, 2, 3); "#});
}"#},
);
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#" cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") [{] pub fn test("Test argument") «{»
another_test(1, 2, 3); another_test(1, 2, 3);
[}]"#}); «}»
"#});
// positioning outside of brackets removes highlight // positioning outside of brackets removes highlight
cx.set_state_by( cx.set_state(indoc! {r#"
vec!['|'.into()], pub fˇn test("Test argument") {
indoc! {r#" another_test(1, 2, 3);
pub f|n test("Test argument") { }
another_test(1, 2, 3); "#});
}"#},
);
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#" cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") { pub fn test("Test argument") {
another_test(1, 2, 3); another_test(1, 2, 3);
}"#}); }
"#});
// non empty selection dismisses highlight // non empty selection dismisses highlight
// positioning outside of brackets removes highlight cx.set_state(indoc! {r#"
cx.set_state_by( pub fn test("Te«st argˇ»ument") {
vec![('<', '>').into()], another_test(1, 2, 3);
indoc! {r#" }
pub fn test("Te<st arg>ument") { "#});
another_test(1, 2, 3);
}"#},
);
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#" cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") { pub fn test("Test argument") {
another_test(1, 2, 3); another_test(1, 2, 3);
}"#}); }
"#});
} }
} }

View file

@ -439,11 +439,11 @@ mod tests {
// Basic hover delays and then pops without moving the mouse // Basic hover delays and then pops without moving the mouse
cx.set_state(indoc! {" cx.set_state(indoc! {"
fn |test() fn ˇtest() { println!(); }
println!();"}); "});
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn test() fn test() { printˇln!(); }
print|ln!();"}); "});
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
hover_at( hover_at(
@ -458,16 +458,16 @@ mod tests {
// After delay, hover should be visible. // After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {" let symbol_range = cx.lsp_range(indoc! {"
fn test() fn test() { «println!»(); }
[println!]();"}); "});
let mut requests = let mut requests =
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover { Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent { contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown, kind: lsp::MarkupKind::Markdown,
value: indoc! {" value: indoc! {"
# Some basic docs # Some basic docs
Some test documentation"} Some test documentation"}
.to_string(), .to_string(),
}), }),
range: Some(symbol_range), range: Some(symbol_range),
@ -496,8 +496,8 @@ mod tests {
// Mouse moved with no hover response dismisses // Mouse moved with no hover response dismisses
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn te|st() fn teˇst() { println!(); }
println!();"}); "});
let mut request = cx let mut request = cx
.lsp .lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) }); .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
@ -531,12 +531,12 @@ mod tests {
// Hover with keyboard has no delay // Hover with keyboard has no delay
cx.set_state(indoc! {" cx.set_state(indoc! {"
f|n test() fˇn test() { println!(); }
println!();"}); "});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
let symbol_range = cx.lsp_range(indoc! {" let symbol_range = cx.lsp_range(indoc! {"
[fn] test() «fn» test() { println!(); }
println!();"}); "});
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover { Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent { contents: lsp::HoverContents::Markup(lsp::MarkupContent {
@ -584,13 +584,13 @@ mod tests {
// Hover with just diagnostic, pops DiagnosticPopover immediately and then // Hover with just diagnostic, pops DiagnosticPopover immediately and then
// info popover once request completes // info popover once request completes
cx.set_state(indoc! {" cx.set_state(indoc! {"
fn te|st() fn teˇst() { println!(); }
println!();"}); "});
// Send diagnostic to client // Send diagnostic to client
let range = cx.text_anchor_range(indoc! {" let range = cx.text_anchor_range(indoc! {"
fn [test]() fn «test»() { println!(); }
println!();"}); "});
cx.update_buffer(|buffer, cx| { cx.update_buffer(|buffer, cx| {
let snapshot = buffer.text_snapshot(); let snapshot = buffer.text_snapshot();
let set = DiagnosticSet::from_sorted_entries( let set = DiagnosticSet::from_sorted_entries(
@ -616,15 +616,15 @@ mod tests {
// Info Popover shows after request responded to // Info Popover shows after request responded to
let range = cx.lsp_range(indoc! {" let range = cx.lsp_range(indoc! {"
fn [test]() fn «test»() { println!(); }
println!();"}); "});
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover { Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent { contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown, kind: lsp::MarkupKind::Markdown,
value: indoc! {" value: indoc! {"
# Some other basic docs # Some other basic docs
Some other test documentation"} Some other test documentation"}
.to_string(), .to_string(),
}), }),
range: Some(range), range: Some(range),

View file

@ -405,20 +405,20 @@ mod tests {
cx.set_state(indoc! {" cx.set_state(indoc! {"
struct A; struct A;
let v|ariable = A; let vˇariable = A;
"}); "});
// Basic hold cmd+shift, expect highlight in region if response contains type definition // Basic hold cmd+shift, expect highlight in region if response contains type definition
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
struct A; struct A;
let v|ariable = A; let vˇariable = A;
"}); "});
let symbol_range = cx.lsp_range(indoc! {" let symbol_range = cx.lsp_range(indoc! {"
struct A; struct A;
let [variable] = A; let «variable» = A;
"}); "});
let target_range = cx.lsp_range(indoc! {" let target_range = cx.lsp_range(indoc! {"
struct [A]; struct «A»;
let variable = A; let variable = A;
"}); "});
@ -450,7 +450,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
struct A; struct A;
let [variable] = A; let «variable» = A;
"}); "});
// Unpress shift causes highlight to go away (normal goto-definition is not valid here) // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
@ -473,10 +473,10 @@ mod tests {
// Cmd+shift click without existing definition requests and jumps // Cmd+shift click without existing definition requests and jumps
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
struct A; struct A;
let v|ariable = A; let vˇariable = A;
"}); "});
let target_range = cx.lsp_range(indoc! {" let target_range = cx.lsp_range(indoc! {"
struct [A]; struct «A»;
let variable = A; let variable = A;
"}); "});
@ -503,7 +503,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
struct [A}; struct «»;
let variable = A; let variable = A;
"}); "});
} }
@ -520,34 +520,22 @@ mod tests {
.await; .await;
cx.set_state(indoc! {" cx.set_state(indoc! {"
fn |test() fn ˇtest() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Basic hold cmd, expect highlight in region if response contains definition // Basic hold cmd, expect highlight in region if response contains definition
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn test() fn test() { do_wˇork(); }
do_w|ork(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let symbol_range = cx.lsp_range(indoc! {" let symbol_range = cx.lsp_range(indoc! {"
fn test() fn test() { «do_work»(); }
[do_work](); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let target_range = cx.lsp_range(indoc! {" let target_range = cx.lsp_range(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn «do_work»() { test(); }
fn [do_work]()
test();
"}); "});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move { let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@ -575,11 +563,8 @@ mod tests {
requests.next().await; requests.next().await;
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { «do_work»(); }
[do_work](); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Unpress cmd causes highlight to go away // Unpress cmd causes highlight to go away
@ -593,13 +578,11 @@ mod tests {
cx, cx,
); );
}); });
// Assert no link highlights // Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Response without source range still highlights word // Response without source range still highlights word
@ -630,20 +613,14 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { «do_work»(); }
[do_work](); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Moving mouse to location with no response dismisses highlight // Moving mouse to location with no response dismisses highlight
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
f|n test() fˇn test() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let mut requests = cx let mut requests = cx
.lsp .lsp
@ -667,20 +644,14 @@ mod tests {
// Assert no link highlights // Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Move mouse without cmd and then pressing cmd triggers highlight // Move mouse without cmd and then pressing cmd triggers highlight
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { teˇst(); }
fn do_work()
te|st();
"}); "});
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
update_go_to_definition_link( update_go_to_definition_link(
@ -697,26 +668,17 @@ mod tests {
// Assert no link highlights // Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let symbol_range = cx.lsp_range(indoc! {" let symbol_range = cx.lsp_range(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { «test»(); }
fn do_work()
[test]();
"}); "});
let target_range = cx.lsp_range(indoc! {" let target_range = cx.lsp_range(indoc! {"
fn [test]() fn «test»() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move { let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@ -743,20 +705,14 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { «test»(); }
fn do_work()
[test]();
"}); "});
// Moving within symbol range doesn't re-request // Moving within symbol range doesn't re-request
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { tesˇt(); }
fn do_work()
tes|t();
"}); "});
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
update_go_to_definition_link( update_go_to_definition_link(
@ -771,11 +727,8 @@ mod tests {
}); });
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { «test»(); }
fn do_work()
[test]();
"}); "});
// Cmd click with existing definition doesn't re-request and dismisses highlight // Cmd click with existing definition doesn't re-request and dismisses highlight
@ -790,35 +743,24 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn [test}() fn «testˇ»() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Assert no link highlights after jump // Assert no link highlights after jump
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {" cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
// Cmd click without existing definition requests and jumps // Cmd click without existing definition requests and jumps
let hover_point = cx.display_point(indoc! {" let hover_point = cx.display_point(indoc! {"
fn test() fn test() { do_wˇork(); }
do_w|ork(); fn do_work() { test(); }
fn do_work()
test();
"}); "});
let target_range = cx.lsp_range(indoc! {" let target_range = cx.lsp_range(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn «do_work»() { test(); }
fn [do_work]()
test();
"}); "});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move { let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@ -836,13 +778,9 @@ mod tests {
}); });
requests.next().await; requests.next().await;
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn test() fn test() { do_work(); }
do_work(); fn «do_workˇ»() { test(); }
fn [do_work}()
test();
"}); "});
} }
} }

View file

@ -67,11 +67,9 @@ pub fn deploy_context_menu(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use indoc::indoc;
use crate::test::EditorLspTestContext;
use super::*; use super::*;
use crate::test::EditorLspTestContext;
use indoc::indoc;
#[gpui::test] #[gpui::test]
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
@ -85,11 +83,15 @@ mod tests {
.await; .await;
cx.set_state(indoc! {" cx.set_state(indoc! {"
fn te|st() fn teˇst() {
do_work();"}); do_work();
}
"});
let point = cx.display_point(indoc! {" let point = cx.display_point(indoc! {"
fn test() fn test() {
do_w|ork();"}); do_wˇork();
}
"});
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
deploy_context_menu( deploy_context_menu(
editor, editor,
@ -102,8 +104,10 @@ mod tests {
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn test() fn test() {
do_w|ork();"}); do_wˇork();
}
"});
cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible())); cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible()));
} }
} }

View file

@ -8,7 +8,6 @@ use anyhow::Result;
use futures::{Future, StreamExt}; use futures::{Future, StreamExt};
use indoc::indoc; use indoc::indoc;
use collections::BTreeMap;
use gpui::{ use gpui::{
json, keymap::Keystroke, AppContext, ModelContext, ModelHandle, ViewContext, ViewHandle, json, keymap::Keystroke, AppContext, ModelContext, ModelHandle, ViewContext, ViewHandle,
}; };
@ -20,7 +19,7 @@ use project::Project;
use settings::Settings; use settings::Settings;
use util::{ use util::{
assert_set_eq, set_eq, assert_set_eq, set_eq,
test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError, TextRangeMarker}, test::{generate_marked_text, marked_text, parse_marked_text},
}; };
use workspace::{pane, AppState, Workspace, WorkspaceHandle}; use workspace::{pane, AppState, Workspace, WorkspaceHandle};
@ -65,7 +64,7 @@ pub fn marked_display_snapshot(
} }
pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) { pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
let (umarked_text, text_ranges) = marked_text_ranges(marked_text); let (umarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
assert_eq!(editor.text(cx), umarked_text); assert_eq!(editor.text(cx), umarked_text);
editor.change_selections(None, cx, |s| s.select_ranges(text_ranges)); editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
} }
@ -75,8 +74,7 @@ pub fn assert_text_with_selections(
marked_text: &str, marked_text: &str,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text); let (unmarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
assert_eq!(editor.text(cx), unmarked_text); assert_eq!(editor.text(cx), unmarked_text);
assert_eq!(editor.selections.ranges(cx), text_ranges); assert_eq!(editor.selections.ranges(cx), text_ranges);
} }
@ -190,94 +188,49 @@ impl<'a> EditorTestContext<'a> {
} }
} }
pub fn display_point(&mut self, cursor_location: &str) -> DisplayPoint { pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
let (_, locations) = marked_text(cursor_location); let (unmarked_text, ranges) = parse_marked_text(marked_text, false).unwrap();
assert_eq!(self.buffer_text(), unmarked_text);
ranges
}
pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
let ranges = self.ranges(marked_text);
let snapshot = self let snapshot = self
.editor .editor
.update(self.cx, |editor, cx| editor.snapshot(cx)); .update(self.cx, |editor, cx| editor.snapshot(cx));
locations[0].to_display_point(&snapshot.display_snapshot) ranges[0].start.to_display_point(&snapshot)
} }
// Returns anchors for the current buffer using `[`..`]` // Returns anchors for the current buffer using `«` and `»`
pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> { pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
let range_marker: TextRangeMarker = ('[', ']').into(); let ranges = self.ranges(marked_text);
let (unmarked_text, mut ranges) =
marked_text_ranges_by(&marked_text, vec![range_marker.clone()]);
assert_eq!(self.buffer_text(), unmarked_text);
let offset_range = ranges.remove(&range_marker).unwrap()[0].clone();
let snapshot = self.buffer_snapshot(); let snapshot = self.buffer_snapshot();
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
snapshot.anchor_before(offset_range.start)..snapshot.anchor_after(offset_range.end)
} }
// Sets the editor state via a marked string. pub fn set_state(&mut self, marked_text: &str) {
// `|` characters represent empty selections let (unmarked_text, selection_ranges) = parse_marked_text(marked_text, true).unwrap();
// `[` to `}` represents a non empty selection with the head at `}`
// `{` to `]` represents a non empty selection with the head at `{`
pub fn set_state(&mut self, text: &str) {
self.set_state_by(
vec![
'|'.into(),
('[', '}').into(),
TextRangeMarker::ReverseRange('{', ']'),
],
text,
);
}
pub fn set_state_by(&mut self, range_markers: Vec<TextRangeMarker>, text: &str) {
self.editor.update(self.cx, |editor, cx| { self.editor.update(self.cx, |editor, cx| {
let (unmarked_text, selection_ranges) = marked_text_ranges_by(&text, range_markers);
editor.set_text(unmarked_text, cx); editor.set_text(unmarked_text, cx);
let selection_ranges: Vec<Range<usize>> = selection_ranges
.values()
.into_iter()
.flatten()
.cloned()
.collect();
editor.change_selections(Some(Autoscroll::Fit), cx, |s| { editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.select_ranges(selection_ranges) s.select_ranges(selection_ranges)
}) })
}) })
} }
// Asserts the editor state via a marked string. pub fn assert_editor_state(&mut self, marked_text: &str) {
// `|` characters represent empty selections let (unmarked_text, expected_selections) = parse_marked_text(marked_text, true).unwrap();
// `[` to `}` represents a non empty selection with the head at `}`
// `{` to `]` represents a non empty selection with the head at `{`
pub fn assert_editor_state(&mut self, text: &str) {
let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
&text,
vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
);
let buffer_text = self.buffer_text(); let buffer_text = self.buffer_text();
assert_eq!( assert_eq!(
buffer_text, unmarked_text, buffer_text, unmarked_text,
"Unmarked text doesn't match buffer text" "Unmarked text doesn't match buffer text"
); );
self.assert_selections(expected_selections, marked_text.to_string())
let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
let expected_reverse_selections = selection_ranges
.remove(&('{', ']').into())
.unwrap_or_default();
let expected_forward_selections = selection_ranges
.remove(&('[', '}').into())
.unwrap_or_default();
self.assert_selections(
expected_empty_selections,
expected_reverse_selections,
expected_forward_selections,
Some(text.to_string()),
)
} }
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) { pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]); let expected_ranges = self.ranges(marked_text);
assert_eq!(unmarked, self.buffer_text());
let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| { let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);
editor editor
@ -289,176 +242,58 @@ impl<'a> EditorTestContext<'a> {
.map(|range| range.to_offset(&snapshot.buffer_snapshot)) .map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect() .collect()
}); });
assert_set_eq!(actual_ranges, expected_ranges);
assert_set_eq!(asserted_ranges, actual_ranges);
} }
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) { pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]); let expected_ranges = self.ranges(marked_text);
assert_eq!(unmarked, self.buffer_text());
let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
let actual_ranges: Vec<Range<usize>> = snapshot let actual_ranges: Vec<Range<usize>> = snapshot
.display_snapshot
.highlight_ranges::<Tag>() .highlight_ranges::<Tag>()
.map(|ranges| ranges.as_ref().clone().1) .map(|ranges| ranges.as_ref().clone().1)
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot)) .map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect(); .collect();
assert_set_eq!(actual_ranges, expected_ranges);
assert_set_eq!(asserted_ranges, actual_ranges);
} }
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) { pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
let mut empty_selections = Vec::new(); let expected_selections = expected_selections
let mut reverse_selections = Vec::new(); .into_iter()
let mut forward_selections = Vec::new(); .map(|s| s.range())
.collect::<Vec<_>>();
for selection in expected_selections { let expected_marked_text =
let range = selection.range(); generate_marked_text(&self.buffer_text(), &expected_selections, true);
if selection.is_empty() { self.assert_selections(expected_selections, expected_marked_text)
empty_selections.push(range);
} else if selection.reversed {
reverse_selections.push(range);
} else {
forward_selections.push(range)
}
}
self.assert_selections(
empty_selections,
reverse_selections,
forward_selections,
None,
)
} }
fn assert_selections( fn assert_selections(
&mut self, &mut self,
expected_empty_selections: Vec<Range<usize>>, expected_selections: Vec<Range<usize>>,
expected_reverse_selections: Vec<Range<usize>>, expected_marked_text: String,
expected_forward_selections: Vec<Range<usize>>,
asserted_text: Option<String>,
) { ) {
let (empty_selections, reverse_selections, forward_selections) = let actual_selections = self
self.editor.read_with(self.cx, |editor, cx| { .editor
let mut empty_selections = Vec::new(); .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
let mut reverse_selections = Vec::new(); .into_iter()
let mut forward_selections = Vec::new(); .map(|s| s.range())
.collect::<Vec<_>>();
for selection in editor.selections.all::<usize>(cx) { let actual_marked_text =
let range = selection.range(); generate_marked_text(&self.buffer_text(), &actual_selections, true);
if selection.is_empty() { if expected_selections != actual_selections {
empty_selections.push(range); panic!(
} else if selection.reversed { indoc! {"
reverse_selections.push(range); Editor has unexpected selections.
} else { Expected selections:
forward_selections.push(range) {}
} Actual selections:
} {}",
},
(empty_selections, reverse_selections, forward_selections) expected_marked_text, actual_marked_text,
}); );
let asserted_selections = asserted_text.unwrap_or_else(|| {
self.insert_markers(
&expected_empty_selections,
&expected_reverse_selections,
&expected_forward_selections,
)
});
let actual_selections =
self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
let unmarked_text = self.buffer_text();
let all_eq: Result<(), SetEqError<String>> =
set_eq!(expected_empty_selections, empty_selections)
.map_err(|err| {
err.map(|missing| {
let mut error_text = unmarked_text.clone();
error_text.insert(missing.start, '|');
error_text
})
})
.and_then(|_| {
set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
err.map(|missing| {
let mut error_text = unmarked_text.clone();
error_text.insert(missing.start, '{');
error_text.insert(missing.end, ']');
error_text
})
})
})
.and_then(|_| {
set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
err.map(|missing| {
let mut error_text = unmarked_text.clone();
error_text.insert(missing.start, '[');
error_text.insert(missing.end, '}');
error_text
})
})
});
match all_eq {
Err(SetEqError::LeftMissing(location_text)) => {
panic!(
indoc! {"
Editor has extra selection
Extra Selection Location:
{}
Asserted selections:
{}
Actual selections:
{}"},
location_text, asserted_selections, actual_selections,
);
}
Err(SetEqError::RightMissing(location_text)) => {
panic!(
indoc! {"
Editor is missing empty selection
Missing Selection Location:
{}
Asserted selections:
{}
Actual selections:
{}"},
location_text, asserted_selections, actual_selections,
);
}
_ => {}
} }
} }
fn insert_markers(
&mut self,
empty_selections: &Vec<Range<usize>>,
reverse_selections: &Vec<Range<usize>>,
forward_selections: &Vec<Range<usize>>,
) -> String {
let mut editor_text_with_selections = self.buffer_text();
let mut selection_marks = BTreeMap::new();
for range in empty_selections {
selection_marks.insert(&range.start, '|');
}
for range in reverse_selections {
selection_marks.insert(&range.start, '{');
selection_marks.insert(&range.end, ']');
}
for range in forward_selections {
selection_marks.insert(&range.start, '[');
selection_marks.insert(&range.end, '}');
}
for (offset, mark) in selection_marks.into_iter().rev() {
editor_text_with_selections.insert(*offset, mark);
}
editor_text_with_selections
}
} }
impl<'a> Deref for EditorTestContext<'a> { impl<'a> Deref for EditorTestContext<'a> {
@ -575,10 +410,8 @@ impl<'a> EditorLspTestContext<'a> {
// Constructs lsp range using a marked string with '[', ']' range delimiters // Constructs lsp range using a marked string with '[', ']' range delimiters
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range { pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]); let ranges = self.ranges(marked_text);
assert_eq!(unmarked, self.buffer_text()); self.to_lsp_range(ranges[0].clone())
let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone();
self.to_lsp_range(offset_range)
} }
pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range { pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {

View file

@ -15,6 +15,9 @@ futures = "0.3"
log = { version = "0.4.16", features = ["kv_unstable_serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde"] }
rand = { version = "0.8", optional = true } rand = { version = "0.8", optional = true }
tempdir = { version = "0.3.7", optional = true } tempdir = { version = "0.3.7", optional = true }
serde_json = { version = "1.0", features = [ serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
"preserve_order",
], optional = true } [dev-dependencies]
rand = { version = "0.8" }
tempdir = { version = "0.3.7" }
serde_json = { version = "1.0", features = ["preserve_order"] }

View file

@ -1,4 +1,4 @@
#[cfg(feature = "test-support")] #[cfg(any(test, feature = "test-support"))]
pub mod test; pub mod test;
use futures::Future; use futures::Future;

View file

@ -1,4 +1,5 @@
use std::{collections::HashMap, ops::Range}; use anyhow::{anyhow, Result};
use std::{cmp::Ordering, collections::HashMap, ops::Range};
pub fn marked_text_by( pub fn marked_text_by(
marked_text: &str, marked_text: &str,
@ -125,3 +126,122 @@ pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>)
combined_ranges.sort_by_key(|range| range.start); combined_ranges.sort_by_key(|range| range.start);
(unmarked, combined_ranges) (unmarked, combined_ranges)
} }
///
pub fn parse_marked_text(
input_text: &str,
indicate_cursors: bool,
) -> Result<(String, Vec<Range<usize>>)> {
let mut output_text = String::with_capacity(input_text.len());
let mut ranges = Vec::new();
let mut prev_input_ix = 0;
let mut current_range_start = None;
let mut current_range_cursor = None;
for (input_ix, marker) in input_text.match_indices(&['«', '»', 'ˇ']) {
output_text.push_str(&input_text[prev_input_ix..input_ix]);
let output_len = output_text.len();
let len = marker.len();
prev_input_ix = input_ix + len;
match marker {
"ˇ" => {
if current_range_start.is_some() {
if current_range_cursor.is_some() {
Err(anyhow!("duplicate point marker 'ˇ' at index {input_ix}"))?;
} else {
current_range_cursor = Some(output_len);
}
} else {
ranges.push(output_len..output_len);
}
}
"«" => {
if current_range_start.is_some() {
Err(anyhow!(
"unexpected range start marker '«' at index {input_ix}"
))?;
}
current_range_start = Some(output_len);
}
"»" => {
let current_range_start = current_range_start.take().ok_or_else(|| {
anyhow!("unexpected range end marker '»' at index {input_ix}")
})?;
let mut reversed = false;
if let Some(current_range_cursor) = current_range_cursor.take() {
if current_range_cursor == current_range_start {
reversed = true;
} else if current_range_cursor != output_len {
Err(anyhow!("unexpected 'ˇ' marker in the middle of a range"))?;
}
} else if indicate_cursors {
Err(anyhow!("missing 'ˇ' marker to indicate range direction"))?;
}
ranges.push(if reversed {
output_len..current_range_start
} else {
current_range_start..output_len
});
}
_ => unreachable!(),
}
}
output_text.push_str(&input_text[prev_input_ix..]);
Ok((output_text, ranges))
}
pub fn generate_marked_text(
output_text: &str,
ranges: &[Range<usize>],
indicate_cursors: bool,
) -> String {
let mut marked_text = output_text.to_string();
for range in ranges.iter().rev() {
if indicate_cursors {
match range.start.cmp(&range.end) {
Ordering::Less => {
marked_text.insert_str(range.end, "ˇ»");
marked_text.insert_str(range.start, "«");
}
Ordering::Equal => {
marked_text.insert_str(range.start, "ˇ");
}
Ordering::Greater => {
marked_text.insert_str(range.start, "»");
marked_text.insert_str(range.end, "«ˇ");
}
}
} else {
marked_text.insert_str(range.end, "»");
marked_text.insert_str(range.start, "«");
}
}
marked_text
}
#[cfg(test)]
mod tests {
use super::{generate_marked_text, parse_marked_text};
#[test]
fn test_marked_text() {
let (text, ranges) =
parse_marked_text("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true).unwrap();
assert_eq!(text, "one two three four five six");
assert_eq!(ranges.len(), 4);
assert_eq!(ranges[0], 7..4);
assert_eq!(ranges[1], 8..13);
assert_eq!(ranges[2], 18..14);
assert_eq!(ranges[3], 23..23);
assert_eq!(
generate_marked_text(&text, &ranges, true),
"one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six"
);
}
}