Detailed changes
@@ -8050,33 +8050,33 @@ mod tests {
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
- const a: |A = (
- (|
- [const_function}(|),
- so{m]et[h}ing_|else,|
- )|
- |);|
- "});
+ const a: ˇA = (
+ (ˇ
+ «const_functionˇ»(ˇ),
+ so«mˇ»et«hˇ»ing_ˇelse,ˇ
+ )ˇ
+ ˇ);ˇ
+ "});
cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
cx.assert_editor_state(indoc! {"
const a: A = (
- |
+ ˇ
(
- |
+ ˇ
const_function(),
- |
- |
+ ˇ
+ ˇ
something_else,
- |
- |
- |
- |
+ ˇ
+ ˇ
+ ˇ
+ ˇ
)
- |
+ ˇ
);
- |
- |
- "});
+ ˇ
+ ˇ
+ "});
}
#[gpui::test]
@@ -8115,25 +8115,25 @@ mod tests {
});
});
cx.set_state(indoc! {"
- |ab|c
- |🏀|🏀|efg
- d|
+ ˇabˇc
+ ˇ🏀ˇ🏀ˇefg
+ dˇ
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
- |ab |c
- |🏀 |🏀 |efg
- d |
+ ˇab ˇc
+ ˇ🏀 ˇ🏀 ˇefg
+ d ˇ
"});
cx.set_state(indoc! {"
a
- [🏀}🏀[🏀}🏀[🏀}
+ «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
a
- [🏀}🏀[🏀}🏀[🏀}
+ «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
"});
}
@@ -8154,26 +8154,26 @@ mod tests {
// a soft tab. cursors that are to the left of the suggested indent
// auto-indent their line.
cx.set_state(indoc! {"
- |
+ ˇ
const a: B = (
c(
d(
- |
+ ˇ
)
- |
- | )
+ ˇ
+ ˇ )
);
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
- |
+ ˇ
const a: B = (
c(
d(
- |
+ ˇ
)
- |
- |)
+ ˇ
+ ˇ)
);
"});
@@ -8181,16 +8181,16 @@ mod tests {
cx.set_state(indoc! {"
const a: B = (
c(
- | |
- | )
+ ˇ ˇ
+ ˇ )
);
"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
const a: B = (
c(
- |
- |)
+ ˇ
+ ˇ)
);
"});
}
@@ -8200,58 +8200,68 @@ mod tests {
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
- [one} [two}
+ «oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
- [one} [two}
+ «oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
- [one} [two}
+ «oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
// select across line ending
cx.set_state(indoc! {"
one two
- t[hree
- } four"});
+ t«hree
+ ˇ» four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
- t[hree
- } four"});
+ t«hree
+ ˇ» four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- t[hree
- } four"});
+ t«hree
+ ˇ» four
+ "});
// Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
cx.set_state(indoc! {"
one two
- | three
- four"});
+ ˇ three
+ four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
}
#[gpui::test]
@@ -8265,75 +8275,90 @@ mod tests {
// select two ranges on one line
cx.set_state(indoc! {"
- [one} [two}
+ «oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
- \t[one} [two}
+ \t«oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
- \t\t[one} [two}
+ \t\t«oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
- \t[one} [two}
+ \t«oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
- [one} [two}
+ «oneˇ» «twoˇ»
three
- four"});
+ four
+ "});
// select across a line ending
cx.set_state(indoc! {"
one two
- t[hree
- }four"});
+ t«hree
+ ˇ»four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
- \tt[hree
- }four"});
+ \tt«hree
+ ˇ»four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
- \t\tt[hree
- }four"});
+ \t\tt«hree
+ ˇ»four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- \tt[hree
- }four"});
+ \tt«hree
+ ˇ»four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- t[hree
- }four"});
+ t«hree
+ ˇ»four
+ "});
// Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
+ cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
- \t|three
- four"});
+ \tˇthree
+ four
+ "});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
- |three
- four"});
+ ˇthree
+ four
+ "});
}
#[gpui::test]
@@ -8412,10 +8437,10 @@ mod tests {
select_ranges(
&mut editor,
indoc! {"
- [a] = 1
+ «aˇ» = 1
b = 2
- [const c:] usize = 3;
+ «const c:ˇ» usize = 3;
"},
cx,
);
@@ -8424,10 +8449,10 @@ mod tests {
assert_text_with_selections(
&mut editor,
indoc! {"
- [a] = 1
+ «aˇ» = 1
b = 2
- [const c:] usize = 3;
+ «const c:ˇ» usize = 3;
"},
cx,
);
@@ -8435,10 +8460,10 @@ mod tests {
assert_text_with_selections(
&mut editor,
indoc! {"
- [a] = 1
+ «aˇ» = 1
b = 2
- [const c:] usize = 3;
+ «const c:ˇ» usize = 3;
"},
cx,
);
@@ -8450,43 +8475,48 @@ mod tests {
#[gpui::test]
async fn test_backspace(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
+
// Basic backspace
cx.set_state(indoc! {"
- on|e two three
- fou[r} five six
- seven {eight nine
- ]ten"});
+ onˇe two three
+ fou«rˇ» five six
+ seven «ˇeight nine
+ »ten
+ "});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
- o|e two three
- fou| five six
- seven |ten"});
+ oˇe two three
+ fouˇ five six
+ seven ˇten
+ "});
// Test backspace inside and around indents
cx.set_state(indoc! {"
zero
- |one
- |two
- | | | three
- | | four"});
+ ˇone
+ ˇtwo
+ ˇ ˇ ˇ three
+ ˇ ˇ four
+ "});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
zero
- |one
- |two
- | three| four"});
+ ˇone
+ ˇtwo
+ ˇ threeˇ four
+ "});
// Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {"
- The |quick |brown
+ The ˇquick ˇbrown
fox jumps over
the lazy dog
- |The qu[ick b}rown"});
+ ˇThe qu«ick bˇ»rown"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
- |fox jumps over
- the lazy dog|"});
+ ˇfox jumps over
+ the lazy dogˇ"});
}
#[gpui::test]
@@ -8494,25 +8524,27 @@ mod tests {
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
- on|e two three
- fou[r} five six
- seven {eight nine
- ]ten"});
+ onˇe two three
+ fou«rˇ» five six
+ seven «ˇeight nine
+ »ten
+ "});
cx.update_editor(|e, cx| e.delete(&Delete, cx));
cx.assert_editor_state(indoc! {"
- on| two three
- fou| five six
- seven |ten"});
+ onˇ two three
+ fouˇ five six
+ seven ˇten
+ "});
// Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {"
- The |quick |brown
- fox {jum]ps over
+ The ˇquick ˇbrown
+ fox «ˇjum»ps over
the lazy dog
- |The qu[ick b}rown"});
+ ˇThe qu«ick bˇ»rown"});
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]
@@ -8824,19 +8856,19 @@ mod tests {
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
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.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.
- cx.set_state("two |four |six |");
+ cx.set_state("two ˇfour ˇsix ˇ");
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
// match the number of slices in the clipboard, the entire clipboard text
// 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| {
e.handle_input("( ", cx);
e.paste(&Paste, cx);
@@ -8845,37 +8877,37 @@ mod tests {
cx.assert_editor_state(indoc! {"
( one✅
three
- five ) |two one✅ four three six five ( one✅
+ five ) ˇtwo one✅ four three six five ( one✅
three
- five ) |"});
+ five ) ˇ"});
// Cut with three selections, one of which is full-line.
cx.set_state(indoc! {"
- 1[2}3
- 4|567
- [8}9"});
+ 1«2ˇ»3
+ 4ˇ567
+ «8ˇ»9"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
- 1|3
- |9"});
+ 1ˇ3
+ ˇ9"});
// Paste with three selections, noticing how the copied selection that was full-line
// gets inserted before the second cursor.
cx.set_state(indoc! {"
- 1|3
- 9|
- [o}ne"});
+ 1ˇ3
+ 9ˇ
+ «oˇ»ne"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
- 12|3
+ 12ˇ3
4567
- 9|
- 8|ne"});
+ 9ˇ
+ 8ˇne"});
// Copy with a single cursor only, which writes the whole line into the clipboard.
cx.set_state(indoc! {"
The quick brown
- fox ju|mps over
+ fox juˇmps over
the lazy dog"});
cx.update_editor(|e, cx| e.copy(&Copy, cx));
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
// before the empty selections but replaces the selection that is non-empty.
cx.set_state(indoc! {"
- T|he quick brown
- [fo}x jumps over
- t|he lazy dog"});
+ Tˇhe quick brown
+ «foˇ»x jumps over
+ tˇhe lazy dog"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
fox jumps over
- T|he quick brown
+ Tˇhe quick brown
fox jumps over
- |x jumps over
+ ˇx jumps over
fox jumps over
- t|he lazy dog"});
+ tˇhe lazy dog"});
}
#[gpui::test]
@@ -8909,17 +8941,17 @@ mod tests {
cx.set_state(indoc! {"
const a: B = (
c(),
- [d(
+ «d(
e,
f
- )}
+ )ˇ»
);
"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
const a: B = (
c(),
- |
+ ˇ
);
"});
@@ -8931,13 +8963,13 @@ mod tests {
d(
e,
f
- )|
+ )ˇ
);
"});
// Paste it at a line with a lower indent level.
cx.set_state(indoc! {"
- |
+ ˇ
const a: B = (
c(),
);
@@ -8947,7 +8979,7 @@ mod tests {
d(
e,
f
- )|
+ )ˇ
const a: B = (
c(),
);
@@ -8957,17 +8989,17 @@ mod tests {
cx.set_state(indoc! {"
const a: B = (
c(),
- [ d(
+ « d(
e,
f
)
- });
+ ˇ»);
"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
const a: B = (
c(),
- |);
+ ˇ);
"});
// Paste it at the same position.
@@ -8979,7 +9011,7 @@ mod tests {
e,
f
)
- |);
+ ˇ);
"});
// Paste it at a line with a higher indent level.
@@ -8988,7 +9020,7 @@ mod tests {
c(),
d(
e,
- f|
+ fˇ
)
);
"});
@@ -9002,7 +9034,7 @@ mod tests {
e,
f
)
- |
+ ˇ
)
);
"});
@@ -10293,16 +10325,18 @@ mod tests {
.await;
cx.set_state(indoc! {"
- one|
+ oneˇ
two
- three"});
+ three
+ "});
cx.simulate_keystroke(".");
handle_completion_request(
&mut cx,
indoc! {"
one.|<>
two
- three"},
+ three
+ "},
vec!["first_completion", "second_completion"],
)
.await;
@@ -10315,9 +10349,10 @@ mod tests {
.unwrap()
});
cx.assert_editor_state(indoc! {"
- one.second_completion|
+ one.second_completionˇ
two
- three"});
+ three
+ "});
handle_resolve_completion_request(
&mut cx,
@@ -10325,23 +10360,26 @@ mod tests {
indoc! {"
one.second_completion
two
- three<>"},
+ three<>
+ "},
"\nadditional edit",
)),
)
.await;
apply_additional_edits.await.unwrap();
cx.assert_editor_state(indoc! {"
- one.second_completion|
+ one.second_completionˇ
two
three
- additional edit"});
+ additional edit
+ "});
cx.set_state(indoc! {"
one.second_completion
- two|
- three|
- additional edit"});
+ twoˇ
+ threeˇ
+ additional edit
+ "});
cx.simulate_keystroke(" ");
assert!(cx.editor(|e, _| e.context_menu.is_none()));
cx.simulate_keystroke("s");
@@ -10349,16 +10387,18 @@ mod tests {
cx.assert_editor_state(indoc! {"
one.second_completion
- two s|
- three s|
- additional edit"});
+ two sˇ
+ three sˇ
+ additional edit
+ "});
handle_completion_request(
&mut cx,
indoc! {"
one.second_completion
two s
three <s|>
- additional edit"},
+ additional edit
+ "},
vec!["fourth_completion", "fifth_completion", "sixth_completion"],
)
.await;
@@ -10373,7 +10413,8 @@ mod tests {
one.second_completion
two si
three <si|>
- additional edit"},
+ additional edit
+ "},
vec!["fourth_completion", "fifth_completion", "sixth_completion"],
)
.await;
@@ -10387,9 +10428,10 @@ mod tests {
});
cx.assert_editor_state(indoc! {"
one.second_completion
- two sixth_completion|
- three sixth_completion|
- additional edit"});
+ two sixth_completionˇ
+ three sixth_completionˇ
+ additional edit
+ "});
handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap();
@@ -10399,13 +10441,13 @@ mod tests {
settings.show_completions_on_input = false;
})
});
- cx.set_state("editor|");
+ cx.set_state("editorˇ");
cx.simulate_keystroke(".");
assert!(cx.editor(|e, _| e.context_menu.is_none()));
cx.simulate_keystroke("c");
cx.simulate_keystroke("l");
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()));
cx.update_editor(|editor, cx| {
editor.show_completions(&ShowCompletions, cx);
@@ -10418,7 +10460,7 @@ mod tests {
.confirm_completion(&ConfirmCompletion::default(), cx)
.unwrap()
});
- cx.assert_editor_state("editor.close|");
+ cx.assert_editor_state("editor.closeˇ");
handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap();
@@ -32,14 +32,11 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
#[cfg(test)]
mod tests {
+ use super::*;
+ use crate::test::EditorLspTestContext;
use indoc::indoc;
-
use language::{BracketPair, Language, LanguageConfig};
- use crate::test::EditorLspTestContext;
-
- use super::*;
-
#[gpui::test]
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new(
@@ -76,67 +73,61 @@ mod tests {
.await;
// positioning cursor inside bracket highlights both
- cx.set_state_by(
- vec!['|'.into()],
- indoc! {r#"
- pub fn test("Test |argument") {
- another_test(1, 2, 3);
- }"#},
- );
+ cx.set_state(indoc! {r#"
+ pub fn test("Test ˇargument") {
+ another_test(1, 2, 3);
+ }
+ "#});
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
- pub fn test[(]"Test argument"[)] {
- another_test(1, 2, 3);
- }"#});
+ pub fn test«(»"Test argument"«)» {
+ another_test(1, 2, 3);
+ }
+ "#});
- cx.set_state_by(
- vec!['|'.into()],
- indoc! {r#"
- pub fn test("Test argument") {
- another_test(1, |2, 3);
- }"#},
- );
+ cx.set_state(indoc! {r#"
+ pub fn test("Test argument") {
+ another_test(1, ˇ2, 3);
+ }
+ "#});
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") {
- another_test[(]1, 2, 3[)];
- }"#});
+ another_test«(»1, 2, 3«)»;
+ }
+ "#});
- cx.set_state_by(
- vec!['|'.into()],
- indoc! {r#"
- pub fn test("Test argument") {
- another|_test(1, 2, 3);
- }"#},
- );
+ cx.set_state(indoc! {r#"
+ pub fn test("Test argument") {
+ anotherˇ_test(1, 2, 3);
+ }
+ "#});
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
- pub fn test("Test argument") [{]
+ pub fn test("Test argument") «{»
another_test(1, 2, 3);
- [}]"#});
+ «}»
+ "#});
// positioning outside of brackets removes highlight
- cx.set_state_by(
- vec!['|'.into()],
- indoc! {r#"
- pub f|n test("Test argument") {
- another_test(1, 2, 3);
- }"#},
- );
+ cx.set_state(indoc! {r#"
+ pub fˇn test("Test argument") {
+ another_test(1, 2, 3);
+ }
+ "#});
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") {
another_test(1, 2, 3);
- }"#});
+ }
+ "#});
// non empty selection dismisses highlight
- // positioning outside of brackets removes highlight
- cx.set_state_by(
- vec![('<', '>').into()],
- indoc! {r#"
- pub fn test("Te<st arg>ument") {
- another_test(1, 2, 3);
- }"#},
- );
+ cx.set_state(indoc! {r#"
+ pub fn test("Te«st argˇ»ument") {
+ another_test(1, 2, 3);
+ }
+ "#});
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
pub fn test("Test argument") {
another_test(1, 2, 3);
- }"#});
+ }
+ "#});
}
}
@@ -439,11 +439,11 @@ mod tests {
// Basic hover delays and then pops without moving the mouse
cx.set_state(indoc! {"
- fn |test()
- println!();"});
+ fn ˇtest() { println!(); }
+ "});
let hover_point = cx.display_point(indoc! {"
- fn test()
- print|ln!();"});
+ fn test() { printˇln!(); }
+ "});
cx.update_editor(|editor, cx| {
hover_at(
@@ -458,16 +458,16 @@ mod tests {
// After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {"
- fn test()
- [println!]();"});
+ fn test() { «println!»(); }
+ "});
let mut requests =
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
- # Some basic docs
- Some test documentation"}
+ # Some basic docs
+ Some test documentation"}
.to_string(),
}),
range: Some(symbol_range),
@@ -496,8 +496,8 @@ mod tests {
// Mouse moved with no hover response dismisses
let hover_point = cx.display_point(indoc! {"
- fn te|st()
- println!();"});
+ fn teˇst() { println!(); }
+ "});
let mut request = cx
.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
@@ -531,12 +531,12 @@ mod tests {
// Hover with keyboard has no delay
cx.set_state(indoc! {"
- f|n test()
- println!();"});
+ fˇn test() { println!(); }
+ "});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
let symbol_range = cx.lsp_range(indoc! {"
- [fn] test()
- println!();"});
+ «fn» test() { println!(); }
+ "});
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
@@ -584,13 +584,13 @@ mod tests {
// Hover with just diagnostic, pops DiagnosticPopover immediately and then
// info popover once request completes
cx.set_state(indoc! {"
- fn te|st()
- println!();"});
+ fn teˇst() { println!(); }
+ "});
// Send diagnostic to client
let range = cx.text_anchor_range(indoc! {"
- fn [test]()
- println!();"});
+ fn «test»() { println!(); }
+ "});
cx.update_buffer(|buffer, cx| {
let snapshot = buffer.text_snapshot();
let set = DiagnosticSet::from_sorted_entries(
@@ -616,15 +616,15 @@ mod tests {
// Info Popover shows after request responded to
let range = cx.lsp_range(indoc! {"
- fn [test]()
- println!();"});
+ fn «test»() { println!(); }
+ "});
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
- # Some other basic docs
- Some other test documentation"}
+ # Some other basic docs
+ Some other test documentation"}
.to_string(),
}),
range: Some(range),
@@ -405,20 +405,20 @@ mod tests {
cx.set_state(indoc! {"
struct A;
- let v|ariable = A;
+ let vˇariable = A;
"});
// Basic hold cmd+shift, expect highlight in region if response contains type definition
let hover_point = cx.display_point(indoc! {"
struct A;
- let v|ariable = A;
+ let vˇariable = A;
"});
let symbol_range = cx.lsp_range(indoc! {"
struct A;
- let [variable] = A;
+ let «variable» = A;
"});
let target_range = cx.lsp_range(indoc! {"
- struct [A];
+ struct «A»;
let variable = A;
"});
@@ -450,7 +450,7 @@ mod tests {
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
struct A;
- let [variable] = A;
+ let «variable» = A;
"});
// 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
let hover_point = cx.display_point(indoc! {"
struct A;
- let v|ariable = A;
+ let vˇariable = A;
"});
let target_range = cx.lsp_range(indoc! {"
- struct [A];
+ struct «A»;
let variable = A;
"});
@@ -503,7 +503,7 @@ mod tests {
cx.foreground().run_until_parked();
cx.assert_editor_state(indoc! {"
- struct [A};
+ struct «Aˇ»;
let variable = A;
"});
}
@@ -520,34 +520,22 @@ mod tests {
.await;
cx.set_state(indoc! {"
- fn |test()
- do_work();
-
- fn do_work()
- test();
+ fn ˇtest() { do_work(); }
+ fn do_work() { test(); }
"});
// Basic hold cmd, expect highlight in region if response contains definition
let hover_point = cx.display_point(indoc! {"
- fn test()
- do_w|ork();
-
- fn do_work()
- test();
+ fn test() { do_wˇork(); }
+ fn do_work() { test(); }
"});
let symbol_range = cx.lsp_range(indoc! {"
- fn test()
- [do_work]();
-
- fn do_work()
- test();
+ fn test() { «do_work»(); }
+ fn do_work() { test(); }
"});
let target_range = cx.lsp_range(indoc! {"
- fn test()
- do_work();
-
- fn [do_work]()
- test();
+ fn test() { do_work(); }
+ fn «do_work»() { test(); }
"});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@@ -575,11 +563,8 @@ mod tests {
requests.next().await;
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- [do_work]();
-
- fn do_work()
- test();
+ fn test() { «do_work»(); }
+ fn do_work() { test(); }
"});
// Unpress cmd causes highlight to go away
@@ -593,13 +578,11 @@ mod tests {
cx,
);
});
+
// Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- test();
+ fn test() { do_work(); }
+ fn do_work() { test(); }
"});
// Response without source range still highlights word
@@ -630,20 +613,14 @@ mod tests {
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- [do_work]();
-
- fn do_work()
- test();
+ fn test() { «do_work»(); }
+ fn do_work() { test(); }
"});
// Moving mouse to location with no response dismisses highlight
let hover_point = cx.display_point(indoc! {"
- f|n test()
- do_work();
-
- fn do_work()
- test();
+ fˇn test() { do_work(); }
+ fn do_work() { test(); }
"});
let mut requests = cx
.lsp
@@ -667,20 +644,14 @@ mod tests {
// Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- test();
+ fn test() { do_work(); }
+ fn do_work() { test(); }
"});
// Move mouse without cmd and then pressing cmd triggers highlight
let hover_point = cx.display_point(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- te|st();
+ fn test() { do_work(); }
+ fn do_work() { teˇst(); }
"});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
@@ -697,26 +668,17 @@ mod tests {
// Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- test();
+ fn test() { do_work(); }
+ fn do_work() { test(); }
"});
let symbol_range = cx.lsp_range(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- [test]();
+ fn test() { do_work(); }
+ fn do_work() { «test»(); }
"});
let target_range = cx.lsp_range(indoc! {"
- fn [test]()
- do_work();
-
- fn do_work()
- test();
+ fn «test»() { do_work(); }
+ fn do_work() { test(); }
"});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@@ -743,20 +705,14 @@ mod tests {
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- [test]();
+ fn test() { do_work(); }
+ fn do_work() { «test»(); }
"});
// Moving within symbol range doesn't re-request
let hover_point = cx.display_point(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- tes|t();
+ fn test() { do_work(); }
+ fn do_work() { tesˇt(); }
"});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
@@ -771,11 +727,8 @@ mod tests {
});
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- [test]();
+ fn test() { do_work(); }
+ fn do_work() { «test»(); }
"});
// Cmd click with existing definition doesn't re-request and dismisses highlight
@@ -790,35 +743,24 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
});
cx.assert_editor_state(indoc! {"
- fn [test}()
- do_work();
-
- fn do_work()
- test();
+ fn «testˇ»() { do_work(); }
+ fn do_work() { test(); }
"});
+
// Assert no link highlights after jump
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
- fn test()
- do_work();
-
- fn do_work()
- test();
+ fn test() { do_work(); }
+ fn do_work() { test(); }
"});
// Cmd click without existing definition requests and jumps
let hover_point = cx.display_point(indoc! {"
- fn test()
- do_w|ork();
-
- fn do_work()
- test();
+ fn test() { do_wˇork(); }
+ fn do_work() { test(); }
"});
let target_range = cx.lsp_range(indoc! {"
- fn test()
- do_work();
-
- fn [do_work]()
- test();
+ fn test() { do_work(); }
+ fn «do_work»() { test(); }
"});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
@@ -836,13 +778,9 @@ mod tests {
});
requests.next().await;
cx.foreground().run_until_parked();
-
cx.assert_editor_state(indoc! {"
- fn test()
- do_work();
-
- fn [do_work}()
- test();
+ fn test() { do_work(); }
+ fn «do_workˇ»() { test(); }
"});
}
}
@@ -67,11 +67,9 @@ pub fn deploy_context_menu(
#[cfg(test)]
mod tests {
- use indoc::indoc;
-
- use crate::test::EditorLspTestContext;
-
use super::*;
+ use crate::test::EditorLspTestContext;
+ use indoc::indoc;
#[gpui::test]
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
@@ -85,11 +83,15 @@ mod tests {
.await;
cx.set_state(indoc! {"
- fn te|st()
- do_work();"});
+ fn teˇst() {
+ do_work();
+ }
+ "});
let point = cx.display_point(indoc! {"
- fn test()
- do_w|ork();"});
+ fn test() {
+ do_wˇork();
+ }
+ "});
cx.update_editor(|editor, cx| {
deploy_context_menu(
editor,
@@ -102,8 +104,10 @@ mod tests {
});
cx.assert_editor_state(indoc! {"
- fn test()
- do_w|ork();"});
+ fn test() {
+ do_wˇork();
+ }
+ "});
cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible()));
}
}
@@ -8,7 +8,6 @@ use anyhow::Result;
use futures::{Future, StreamExt};
use indoc::indoc;
-use collections::BTreeMap;
use gpui::{
json, keymap::Keystroke, AppContext, ModelContext, ModelHandle, ViewContext, ViewHandle,
};
@@ -20,7 +19,7 @@ use project::Project;
use settings::Settings;
use util::{
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};
@@ -65,7 +64,7 @@ pub fn marked_display_snapshot(
}
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);
editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
}
@@ -75,8 +74,7 @@ pub fn assert_text_with_selections(
marked_text: &str,
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.selections.ranges(cx), text_ranges);
}
@@ -190,94 +188,49 @@ impl<'a> EditorTestContext<'a> {
}
}
- pub fn display_point(&mut self, cursor_location: &str) -> DisplayPoint {
- let (_, locations) = marked_text(cursor_location);
+ pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
+ 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
.editor
.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> {
- let range_marker: TextRangeMarker = ('[', ']').into();
- 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 ranges = self.ranges(marked_text);
let snapshot = self.buffer_snapshot();
-
- snapshot.anchor_before(offset_range.start)..snapshot.anchor_after(offset_range.end)
- }
-
- // Sets the editor state via a marked string.
- // `|` characters represent empty selections
- // `[` 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,
- );
+ snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
}
- pub fn set_state_by(&mut self, range_markers: Vec<TextRangeMarker>, text: &str) {
+ pub fn set_state(&mut self, marked_text: &str) {
+ let (unmarked_text, selection_ranges) = parse_marked_text(marked_text, true).unwrap();
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);
-
- let selection_ranges: Vec<Range<usize>> = selection_ranges
- .values()
- .into_iter()
- .flatten()
- .cloned()
- .collect();
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.select_ranges(selection_ranges)
})
})
}
- // Asserts the editor state via a marked string.
- // `|` characters represent empty selections
- // `[` 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()],
- );
+ pub fn assert_editor_state(&mut self, marked_text: &str) {
+ let (unmarked_text, expected_selections) = parse_marked_text(marked_text, true).unwrap();
let buffer_text = self.buffer_text();
assert_eq!(
buffer_text, unmarked_text,
"Unmarked text doesn't match buffer text"
);
-
- 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()),
- )
+ self.assert_selections(expected_selections, marked_text.to_string())
}
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()]);
- assert_eq!(unmarked, self.buffer_text());
-
- let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
+ let expected_ranges = self.ranges(marked_text);
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
editor
@@ -289,175 +242,57 @@ impl<'a> EditorTestContext<'a> {
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect()
});
-
- assert_set_eq!(asserted_ranges, actual_ranges);
+ assert_set_eq!(actual_ranges, expected_ranges);
}
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()]);
- assert_eq!(unmarked, self.buffer_text());
-
- let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
+ let expected_ranges = self.ranges(marked_text);
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
let actual_ranges: Vec<Range<usize>> = snapshot
- .display_snapshot
.highlight_ranges::<Tag>()
.map(|ranges| ranges.as_ref().clone().1)
.unwrap_or_default()
.into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect();
-
- assert_set_eq!(asserted_ranges, actual_ranges);
+ assert_set_eq!(actual_ranges, expected_ranges);
}
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
- let mut empty_selections = Vec::new();
- let mut reverse_selections = Vec::new();
- let mut forward_selections = Vec::new();
-
- for selection in expected_selections {
- let range = selection.range();
- if selection.is_empty() {
- 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,
- )
+ let expected_selections = expected_selections
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>();
+ let expected_marked_text =
+ generate_marked_text(&self.buffer_text(), &expected_selections, true);
+ self.assert_selections(expected_selections, expected_marked_text)
}
fn assert_selections(
&mut self,
- expected_empty_selections: Vec<Range<usize>>,
- expected_reverse_selections: Vec<Range<usize>>,
- expected_forward_selections: Vec<Range<usize>>,
- asserted_text: Option<String>,
+ expected_selections: Vec<Range<usize>>,
+ expected_marked_text: String,
) {
- let (empty_selections, reverse_selections, forward_selections) =
- self.editor.read_with(self.cx, |editor, cx| {
- let mut empty_selections = Vec::new();
- let mut reverse_selections = Vec::new();
- let mut forward_selections = Vec::new();
-
- for selection in editor.selections.all::<usize>(cx) {
- let range = selection.range();
- if selection.is_empty() {
- empty_selections.push(range);
- } else if selection.reversed {
- reverse_selections.push(range);
- } else {
- forward_selections.push(range)
- }
- }
-
- (empty_selections, reverse_selections, forward_selections)
- });
-
- 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);
+ let actual_selections = self
+ .editor
+ .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>();
+ let actual_marked_text =
+ generate_marked_text(&self.buffer_text(), &actual_selections, true);
+ if expected_selections != actual_selections {
+ panic!(
+ indoc! {"
+ Editor has unexpected selections.
+ Expected selections:
+ {}
+ Actual selections:
+ {}",
+ },
+ expected_marked_text, actual_marked_text,
+ );
}
-
- editor_text_with_selections
}
}
@@ -575,10 +410,8 @@ impl<'a> EditorLspTestContext<'a> {
// Constructs lsp range using a marked string with '[', ']' range delimiters
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
- let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
- assert_eq!(unmarked, self.buffer_text());
- let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone();
- self.to_lsp_range(offset_range)
+ let ranges = self.ranges(marked_text);
+ self.to_lsp_range(ranges[0].clone())
}
pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
@@ -15,6 +15,9 @@ futures = "0.3"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
rand = { version = "0.8", optional = true }
tempdir = { version = "0.3.7", optional = true }
-serde_json = { version = "1.0", features = [
- "preserve_order",
-], optional = true }
+serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
+
+[dev-dependencies]
+rand = { version = "0.8" }
+tempdir = { version = "0.3.7" }
+serde_json = { version = "1.0", features = ["preserve_order"] }
@@ -1,4 +1,4 @@
-#[cfg(feature = "test-support")]
+#[cfg(any(test, feature = "test-support"))]
pub mod test;
use futures::Future;
@@ -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(
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);
(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"
+ );
+ }
+}