rschmitt / heatseeker

A high-performance Selecta clone, written in Rust.
MIT License
214 stars 10 forks source link

Keep the output ordered as it was in the input. #13

Closed SirVer closed 8 years ago

SirVer commented 9 years ago

I use heatseeker to search through my shell history to find previously used commands lines.

MSG=$(history 1 | sort -n -r | sed -e 's/\s*[0-9]*\s*\(.*\)/\1/' | ~/bin/heatseeker -F)

The clue here is that when I startup heatseeker, the last used command is at the top and the oldest is at the bottom. If I now start typing, I get the results sorted by score - which is not what I want. I want to keep them chronologically, so that I can find the most recently used commands at the top.

Essentially, I want a 'filter only, do not sort' mode.

rschmitt commented 9 years ago

Your use case (Ctrl-R replacement for the shell) is a really interesting one that I've considered before. Can you paste any bash script fragments or zle widgets that you're using to implement this feature?

There may be a clever trick to support this use case. With a stable sort, it would be sufficient for the scoring algorithm to only return -1 (filtered out) or 1 (query matches). If I prototype this, can you build heatseeker yourself, or do you need a binary? Which OS?

SirVer commented 9 years ago

Here is the zsh magic I came up with. Rather simple: history 1 shows the full history, I reverse sort so that the latest entry is at the top, then I remove the numbers and feed it to heatseeker.

fuzzy_history() {
   MSG=$(history 1 | sort -n -r | sed -e 's/\s*[0-9]*\s*\(.*\)/\1/' | ~/bin/heatseeker -F)
   BUFFER="$MSG"
   echo "-> $BUFFER"
   zle .accept-line
}
zle -N fuzzy_history

bindkey -M vicmd "/" fuzzy_history

I think your stable sort trick will work nicely. I can build heatseeker and will gladly test your implementation :).

rschmitt commented 9 years ago

Ok, I'll take a look at it this weekend.

SirVer commented 9 years ago

Did you make any progress - very keen on having that feature.

rschmitt commented 9 years ago

Well, I noticed this comment in the code:

        if other.score == self.score {
            // We fall back to an array index comparison in order to guarantee a stable sort;
            // otherwise the matches may be displayed in a nondeterministic order.
            Some(self.idx.cmp(&other.idx))
        } else {
            other.score.partial_cmp(&self.score)
        }

Thanks, past self! Now all we need to do is this (and delete the tests):

diff --git src/matching.rs src/matching.rs
index 9f89dba..08e8e5c 100644
--- src/matching.rs
+++ src/matching.rs
@@ -114,10 +114,7 @@ fn score(choice: &str, query: &str) -> f64 {

     match compute_match_length(choice, query) {
         None => return 0.0,
-        Some(match_length) => {
-            let score = query.len() as f64 / match_length as f64;
-            return score as f64 / choice.len() as f64;
-        }
+        Some(match_length) => return 1.0,
     }
 }

Build d10c3377e06066de229d8d5ad293a53920fe2b39 from source and let me know what you think.

SirVer commented 9 years ago

https://github.com/rschmitt/heatseeker/commit/d10c3377e06066de229d8d5ad293a53920fe2b39 works really nicely for that use case. I have been using it many times today and it feels much better for history browsing. Thanks for looking into that.