Repository: mikaelmello/inquire (Rust)

Issue:
with_starting_cursor does not apply
**Describe the bug**
When using the method with_starting_cursor on a Select prompt, it doesn't start with the cursor on the custom index but goes on the default one (the first).

**To Reproduce**
Steps to reproduce the behavior:
1. modify the example select.rs to add with_starting_cursor like this:
```rust
use inquire::Select;

fn main() {
    let options = vec![
        "Banana",
        "Apple",
        "Strawberry",
        "Grapes",
        "Lemon",
        "Tangerine",
        "Watermelon",
        "Orange",
        "Pear",
        "Avocado",
        "Pineapple",
    ];

    let ans = Select::new("What's your favorite fruit?", options)
        .with_starting_cursor(2)
        .prompt();

    match ans {
        Ok(choice) => println!("{choice}! That's mine too!"),
        Err(_) => println!("There was an error, please try again"),
    }
}

```
2. execute
```
cargo run --example select
```
3. See that the cursor is on "Banana", instead of "Strawberry".

**Expected behavior**
With index set on 2 in the example, the cursor should start on "Strawberry".


**Additional context**
on main branch last commit as of now 1c7ece7364978817e8069250316437ab3b12484d



Discussion:
The `DEFAULT_RESET_CURSOR` was set by default to `true` to prevent any "behaviour" and when the function run_scorer is called it verifies the reset_cursor flag and that's why it puts it on default 0 every time.
```
fn run_scorer(&mut self) {
        let mut options = self.score_options();
        options.sort_unstable_by_key(|(_idx, score)| Reverse(*score));
        self.scored_options = options.into_iter().map(|(idx, _)| idx).collect();
        if self.config.reset_cursor {
            let _ = self.update_cursor_position(0);
        } else if self.scored_options.len() <= self.cursor_index {
            let _ = self.update_cursor_position(self.scored_options.len().saturating_sub(1));
        }
    }
```
    
Thanks for the report! I'm pushing a fix and also adding a test so this regression doesn't happen again



Patch:
diff --git a/inquire/src/prompts/multiselect/mod.rs b/inquire/src/prompts/multiselect/mod.rs
index 48a9b57e..0c97c25e 100644
--- a/inquire/src/prompts/multiselect/mod.rs
+++ b/inquire/src/prompts/multiselect/mod.rs
@@ -302,6 +302,9 @@ where
     }
 
     /// Sets the starting cursor index.
+    ///
+    /// This index might be overriden if the `reset_cursor` option is set to true (default)
+    /// and starting_filter_input is set to something other than None.
     pub fn with_starting_cursor(mut self, starting_cursor: usize) -> Self {
         self.starting_cursor = starting_cursor;
         self
@@ -313,9 +316,10 @@ where
         self
     }
 
-    /// Sets the reset_cursor behaviour.
-    /// Will reset cursor to first option on filter input change.
-    /// Defaults to true.
+    /// Sets the reset_cursor behaviour. Defaults to true.
+    ///
+    /// When there's an input change that results in a different list of options being displayed,
+    /// whether by filtering or re-ordering, the cursor will be reset to highlight the first option.
     pub fn with_reset_cursor(mut self, reset_cursor: bool) -> Self {
         self.reset_cursor = reset_cursor;
         self
diff --git a/inquire/src/prompts/multiselect/prompt.rs b/inquire/src/prompts/multiselect/prompt.rs
index 22ed075d..02dd4c5d 100644
--- a/inquire/src/prompts/multiselect/prompt.rs
+++ b/inquire/src/prompts/multiselect/prompt.rs
@@ -199,7 +199,14 @@ where
         let mut options = self.score_options();
         options.sort_unstable_by_key(|(_idx, score)| Reverse(*score));
 
-        self.scored_options = options.into_iter().map(|(idx, _)| idx).collect();
+        let new_scored_options = options.iter().map(|(idx, _)| *idx).collect::<Vec<usize>>();
+
+        if self.scored_options == new_scored_options {
+            return;
+        }
+
+        self.scored_options = new_scored_options;
+
         if self.config.reset_cursor {
             let _ = self.update_cursor_position(0);
         } else if self.scored_options.len() <= self.cursor_index {
diff --git a/inquire/src/prompts/select/mod.rs b/inquire/src/prompts/select/mod.rs
index 9bfff8a8..9f3b9912 100644
--- a/inquire/src/prompts/select/mod.rs
+++ b/inquire/src/prompts/select/mod.rs
@@ -244,6 +244,9 @@ where
     }
 
     /// Sets the starting cursor index.
+    ///
+    /// This index might be overriden if the `reset_cursor` option is set to true (default)
+    /// and starting_filter_input is set to something other than None.
     pub fn with_starting_cursor(mut self, starting_cursor: usize) -> Self {
         self.starting_cursor = starting_cursor;
         self
@@ -255,9 +258,10 @@ where
         self
     }
 
-    /// Sets the reset_cursor behaviour.
-    /// Will reset cursor to first option on filter input change.
-    /// Defaults to true.
+    /// Sets the reset_cursor behaviour. Defaults to true.
+    ///
+    /// When there's an input change that results in a different list of options being displayed,
+    /// whether by filtering or re-ordering, the cursor will be reset to highlight the first option.
     pub fn with_reset_cursor(mut self, reset_cursor: bool) -> Self {
         self.reset_cursor = reset_cursor;
         self
diff --git a/inquire/src/prompts/select/prompt.rs b/inquire/src/prompts/select/prompt.rs
index 16a07b9b..d79314c8 100644
--- a/inquire/src/prompts/select/prompt.rs
+++ b/inquire/src/prompts/select/prompt.rs
@@ -138,7 +138,14 @@ where
         let mut options = self.score_options();
         options.sort_unstable_by_key(|(_idx, score)| Reverse(*score));
 
-        self.scored_options = options.into_iter().map(|(idx, _)| idx).collect();
+        let new_scored_options = options.iter().map(|(idx, _)| *idx).collect::<Vec<usize>>();
+
+        if self.scored_options == new_scored_options {
+            return;
+        }
+
+        self.scored_options = new_scored_options;
+
         if self.config.reset_cursor {
             let _ = self.update_cursor_position(0);
         } else if self.scored_options.len() <= self.cursor_index {


Test patch:
diff --git a/inquire/src/prompts/multiselect/test.rs b/inquire/src/prompts/multiselect/test.rs
index a4b4aa62..2419f82f 100644
--- a/inquire/src/prompts/multiselect/test.rs
+++ b/inquire/src/prompts/multiselect/test.rs
@@ -129,3 +129,68 @@ fn list_option_indexes_are_relative_to_input_vec() {
 
     assert_eq!(vec![ListOption::new(1, 2), ListOption::new(2, 3)], ans);
 }
+
+#[test]
+// Anti-regression test: https://github.com/mikaelmello/inquire/issues/195
+fn starting_cursor_is_respected() {
+    let read: Vec<KeyEvent> = [KeyCode::Char(' '), KeyCode::Enter]
+        .iter()
+        .map(|c| KeyEvent::from(*c))
+        .collect();
+
+    let mut read = read.iter();
+
+    let options = vec![1, 2, 3];
+
+    let mut write: Vec<u8> = Vec::new();
+    let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
+    let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
+
+    let ans = MultiSelect::new("Question", options)
+        .with_starting_cursor(2)
+        .prompt_with_backend(&mut backend)
+        .unwrap();
+
+    assert_eq!(vec![ListOption::new(2, 3)], ans);
+}
+
+#[test]
+fn naive_assert_fuzzy_match_as_default_scorer() {
+    let read: Vec<KeyEvent> = [
+        KeyCode::Char('w'),
+        KeyCode::Char('r'),
+        KeyCode::Char('r'),
+        KeyCode::Char('y'),
+        KeyCode::Char(' '),
+        KeyCode::Enter,
+    ]
+    .iter()
+    .map(|c| KeyEvent::from(*c))
+    .collect();
+
+    let mut read = read.iter();
+
+    let options = vec![
+        "Banana",
+        "Apple",
+        "Strawberry",
+        "Grapes",
+        "Lemon",
+        "Tangerine",
+        "Watermelon",
+        "Orange",
+        "Pear",
+        "Avocado",
+        "Pineapple",
+    ];
+
+    let mut write: Vec<u8> = Vec::new();
+    let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
+    let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
+
+    let ans = MultiSelect::new("Question", options)
+        .prompt_with_backend(&mut backend)
+        .unwrap();
+
+    assert_eq!(vec![ListOption::new(2, "Strawberry")], ans);
+}
diff --git a/inquire/src/prompts/select/test.rs b/inquire/src/prompts/select/test.rs
index da0da91b..ff9f2d51 100644
--- a/inquire/src/prompts/select/test.rs
+++ b/inquire/src/prompts/select/test.rs
@@ -93,3 +93,67 @@ fn down_arrow_on_empty_list_does_not_panic() {
 
     assert_eq!(ListOption::new(0, 1), ans);
 }
+
+#[test]
+// Anti-regression test: https://github.com/mikaelmello/inquire/issues/195
+fn starting_cursor_is_respected() {
+    let read: Vec<KeyEvent> = [KeyCode::Enter]
+        .iter()
+        .map(|c| KeyEvent::from(*c))
+        .collect();
+
+    let mut read = read.iter();
+
+    let options = vec![1, 2, 3];
+
+    let mut write: Vec<u8> = Vec::new();
+    let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
+    let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
+
+    let ans = Select::new("Question", options)
+        .with_starting_cursor(2)
+        .prompt_with_backend(&mut backend)
+        .unwrap();
+
+    assert_eq!(ListOption::new(2, 3), ans);
+}
+
+#[test]
+fn naive_assert_fuzzy_match_as_default_scorer() {
+    let read: Vec<KeyEvent> = [
+        KeyCode::Char('w'),
+        KeyCode::Char('r'),
+        KeyCode::Char('r'),
+        KeyCode::Char('y'),
+        KeyCode::Enter,
+    ]
+    .iter()
+    .map(|c| KeyEvent::from(*c))
+    .collect();
+
+    let mut read = read.iter();
+
+    let options = vec![
+        "Banana",
+        "Apple",
+        "Strawberry",
+        "Grapes",
+        "Lemon",
+        "Tangerine",
+        "Watermelon",
+        "Orange",
+        "Pear",
+        "Avocado",
+        "Pineapple",
+    ];
+
+    let mut write: Vec<u8> = Vec::new();
+    let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
+    let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
+
+    let ans = Select::new("Question", options)
+        .prompt_with_backend(&mut backend)
+        .unwrap();
+
+    assert_eq!(ListOption::new(2, "Strawberry"), ans);
+}