Skip to content

Conversation

@sQVe
Copy link
Contributor

@sQVe sQVe commented Aug 2, 2025

Summary

Enhance the fuzzy file picker with sibling file prioritization that considers your current context. When editing a file, the picker now prioritizes related files (similar names, nearby directories) while maintaining performance through optimized scoring algorithms.

This helps with common workflows where you need to quickly navigate between related files (e.g., user.ts and user.test.ts, or files in the same feature directory) by making the most relevant results appear first.

Key Changes

  • Jaro-Winkler Similarity Scoring: Add string similarity algorithm to identify related filenames with configurable thresholds
  • Performance-Optimized Scoring: Gate expensive similarity calculations behind distance checks to maintain fast response times
  • Configurable Preference System: New scoring.same_dir_preference option (0.0-1.0) to control prioritization strength
  • Pre-computed Path Data: Cache frequently accessed path components to reduce redundant calculations
  • Comprehensive Documentation: Updated README and Vim help with usage examples and configuration guidance

Configuration

Users can now control file prioritization behavior:

require("fff").setup({
  scoring = {
    same_dir_preference = 0.7,  -- Range: 0.0 (no preference) to 1.0 (strong preference)
  },
})

Performance

  • Similarity calculations are only performed for files within MAX_DISTANCE_FOR_SIMILARITY_BONUS directory levels (default: 2)
  • Path components are pre-computed and cached in CurrentFileData struct
  • Jaro-Winkler calculations use 0.5 similarity threshold to balance relevance and performance

Test Plan

  • Verify configuration validation with invalid values (< 0.0 or > 1.0)
  • Test file prioritization with different same_dir_preference values
  • Confirm related files (similar names) appear higher in results
  • Validate performance with large codebases (no noticeable lag)
  • Check documentation examples work as described

sQVe added 5 commits August 2, 2025 22:46
Implement Jaro-Winkler string similarity algorithm for enhanced file
prioritization. Add optimized scoring functions that gate expensive
similarity calculations behind distance checks to maintain performance
while improving relevance of file suggestions.

- Add strsim dependency for Jaro-Winkler similarity calculations
- Implement optimized path distance and filename similarity functions
- Add CurrentFileData struct to cache frequently accessed path components
- Gate similarity bonus calculations behind MAX_DISTANCE_FOR_SIMILARITY_BONUS
Introduce same_dir_preference configuration option to control how strongly
the file picker prioritizes files near the current file. This allows users
to customize the balance between relevance and broader search results.

- Add config_utils.lua with preference validation and parameter mapping
- Integrate scoring configuration into main setup function
- Map user preference (0.0-1.0) to internal scoring parameters
- Add validation with helpful error messages for invalid ranges
Connect the new scoring system to the file picker interface and preview
functionality. Optimize scoring operations by pre-computing current file
data and passing it to the scoring context for better performance.

- Update file picker to use optimized scoring with CurrentFileData
- Integrate scoring parameters from configuration into picker context
- Add tracing for scoring performance monitoring
- Export new scoring functions from Rust library
Add comprehensive documentation for the new same_dir_preference
configuration option. Update both README and Vim help documentation
to explain how users can control file prioritization behavior.

- Document scoring.same_dir_preference configuration option
- Add usage examples and recommended values (0.0-1.0 range)
- Update Vim help documentation with new configuration
- Add performance monitoring traces for scoring operations
Resolve merge conflicts between feat/optimize-sibling-prioritization and main

- Merged documentation in doc/fff.nvim.txt with enhanced configuration structure
- Fixed UI buffer management conflicts in picker_ui.lua
- Integrated sibling file prioritization features with main branch updates
- Resolved Rust backend conflicts preserving enhanced scoring system
- Updated test file to use new API with sibling prioritization parameters
- Ensured all code compiles successfully with release build
@dmtrKovalenko
Copy link
Owner

wow this is huge and makes a lot of sense, I'll give a proper review for this feature tomorrow! Thanks for a huge addition

- Apply consistent Rust formatting with proper line breaks and indentation
- Organize imports following standard Rust conventions
- Fix comment spacing in Lua configuration files
- Improve code readability without changing functionality
sQVe

This comment was marked as duplicate.

Copy link
Contributor Author

@sQVe sQVe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋🏼

I think this project has great potential, and it feels very snappy 👏🏼. I'm currently using snacks.nvim with a custom multi picker, which provides basic proximity handling for the active buffer.

This is really essential when working within large monorepos, and projects that is very structure / name oriented - like TypeScript & Next.js etc.


Do note that I'm very novice when it comes to Rust, so be thorough and kind. I'm more than happy to make whatever changes you feel are necessary to get this in! ❤️

Comment on lines +250 to +254
let max_typos = if query.len() <= 4 {
0
} else {
(query.len() as u16 / 5).clamp(1, 3)
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the previous max_typos value to be too permissive, which was problematic when scoring against proximity. I primarily work within a large TypeScript monorepo, and when typing Page it didn't filter out any items.

This change should hopefully be a bit more adaptive. I wouldn't be surprised if we would need to tweak this though.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably make it configurable as well while allowing typos starting from the very start. I make typos even in 4 lines and I find it absolutely useful to have them

similarity_threshold: f64,
) -> i32 {
use std::path::Path;
use strsim::jaro_winkler;
Copy link
Contributor Author

@sQVe sQVe Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Jaro Winkler is a good choice since:

  1. Prefix bias: Gives higher scores to strings that share common prefixes, which aligns with how developers typically name related files (e.g., user.rs, user_test.rs, user_service.rs)
  2. Typo tolerance: Handles minor typos and variations gracefully, which is important for fuzzy file finding
  3. Performance: It's computationally efficient compared to algorithms like Levenshtein.
  4. Score range: Returns normalized scores between 0.0 and 1.0, making it easy to set meaningful thresholds

Comment on lines +37 to +40
directory_distance_penalty = -8, -- Balanced penalty for different directories
filename_similarity_bonus_max = math.floor(50 * preference), -- Moderate sibling bonus (35 with default 0.7)
filename_similarity_threshold = 0.5, -- Good relevance/performance balance
max_search_directory_levels = math.floor(1 + 3 * preference),
Copy link
Contributor Author

@sQVe sQVe Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got these defaults by forcing multiple AI models to reach a consensus on what they believed was the optimal values. I then manually tested and iterated multiple times, feeding the results to the models.

With that said, we should really see these values as an initial best guess. My gut feeling is that we will lower these values rather than raising them in the future.

Copy link
Owner

@dmtrKovalenko dmtrKovalenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not getting your review fast enough. I'll spend a few days working with this version to see how it feels and will get back to you. Thank you very much this is an amazing contribution!


let results = picker.fuzzy_search(&query, max_results, max_threads, current_file.as_ref());
let distance_penalty = distance_penalty.unwrap_or(-8);
let relation_bonus_max = relation_bonus_max.unwrap_or(35);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should give a default dardcoded bonusws or penalties

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this should be configurable instead, or solved in some other way?

@sQVe
Copy link
Contributor Author

sQVe commented Aug 5, 2025

@dmtrKovalenko No need to rush ❤️ I understand if you want to make sure this feels right and that you might want to get the basics right in the picker - before adding further tweaks.

I prefer having a picker that solves proximity, open buffers, and fuzzy finding - but that might not be the goal of this project.

@dmtrKovalenko
Copy link
Owner

It actually is I am really grateful for your input I just want to double check that it doesn't break the existing flows and maybe tweak/make configurabel the scoring impact. We are going to merge this PR anyway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants