This repository was archived by the owner on Dec 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 128
Use fuzzy-native as an alternate scoring system
#374
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f526f7f
Rename the useAlternateScoring config option
rafeca e96fb8a
Execute fuzzy finder tests in different scoring systems
rafeca 180d317
Add fuzzy-native as a scoringSystem
rafeca b0b0bcc
Update appveyor.yml to use a newer image
rafeca ca2e1f1
Fix lint errors
rafeca 8a56b85
Use a fork of fuzzy-native with electron-v2 support
rafeca 0db223e
Use path.join() instead of hardcoding path separators
rafeca 8aec72c
Start using @atom/fuzzy-native fork package
rafeca File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| image: Visual Studio 2015 | ||
|
|
||
| version: "{build}" | ||
|
|
||
| platform: x64 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,20 +2,34 @@ const {Point, CompositeDisposable} = require('atom') | |
| const fs = require('fs-plus') | ||
| const fuzzaldrin = require('fuzzaldrin') | ||
| const fuzzaldrinPlus = require('fuzzaldrin-plus') | ||
| const NativeFuzzy = require('@atom/fuzzy-native') | ||
|
|
||
| const path = require('path') | ||
| const SelectListView = require('atom-select-list') | ||
|
|
||
| const {repositoryForPath} = require('./helpers') | ||
| const getIconServices = require('./get-icon-services') | ||
|
|
||
| module.exports = | ||
| class FuzzyFinderView { | ||
| /** | ||
| * These scoring systems should be synchronized with the enum values | ||
| * on the scoringSystem config option defined in package.json | ||
| **/ | ||
| const SCORING_SYSTEMS = { | ||
| STANDARD: 'standard', | ||
| ALTERNATE: 'alternate', | ||
| FAST: 'fast' | ||
| } | ||
|
|
||
| const MAX_RESULTS = 10 | ||
|
|
||
| module.exports = class FuzzyFinderView { | ||
| constructor () { | ||
| this.previousQueryWasLineJump = false | ||
| this.items = [] | ||
|
|
||
| this.selectListView = new SelectListView({ | ||
| items: this.items, | ||
| maxResults: 10, | ||
| maxResults: MAX_RESULTS, | ||
| emptyMessage: this.getEmptyMessage(), | ||
| filterKeyForItem: (item) => item.label, | ||
| filterQuery: (query) => { | ||
|
|
@@ -74,9 +88,26 @@ class FuzzyFinderView { | |
| }, | ||
| elementForItem: ({filePath, label, ownerGitHubUsername}) => { | ||
| const filterQuery = this.selectListView.getFilterQuery() | ||
| const matches = this.useAlternateScoring | ||
| ? fuzzaldrinPlus.match(label, filterQuery) | ||
| : fuzzaldrin.match(label, filterQuery) | ||
|
|
||
| let matches | ||
|
|
||
| switch (this.scoringSystem) { | ||
| case SCORING_SYSTEMS.STANDARD: | ||
| matches = fuzzaldrin.match(label, filterQuery) | ||
| break | ||
| case SCORING_SYSTEMS.FAST: | ||
| this.nativeFuzzyForResults.setCandidates([label]) | ||
| const items = this.nativeFuzzyForResults.match( | ||
| filterQuery, | ||
| {maxResults: 1, recordMatchIndexes: true} | ||
| ) | ||
| matches = items.length ? items[0].matchIndexes : [] | ||
| break | ||
| default: | ||
| matches = fuzzaldrinPlus.match(label, filterQuery) | ||
| break | ||
| } | ||
|
|
||
| const repository = repositoryForPath(filePath) | ||
|
|
||
| return new FuzzyFinderItem({ | ||
|
|
@@ -118,16 +149,38 @@ class FuzzyFinderView { | |
|
|
||
| this.subscriptions = new CompositeDisposable() | ||
| this.subscriptions.add( | ||
| atom.config.observe('fuzzy-finder.useAlternateScoring', (newValue) => { | ||
| this.useAlternateScoring = newValue | ||
| if (this.useAlternateScoring) { | ||
| atom.config.observe('fuzzy-finder.scoringSystem', (newValue) => { | ||
| this.scoringSystem = newValue | ||
|
|
||
| if (this.scoringSystem === SCORING_SYSTEMS.STANDARD) { | ||
| this.selectListView.update({filter: null}) | ||
| } else if (this.scoringSystem === SCORING_SYSTEMS.FAST) { | ||
| if (!this.nativeFuzzy) { | ||
| this.nativeFuzzy = new NativeFuzzy.Matcher(this.items.map(el => el.label)) | ||
|
|
||
| // We need a separate instance of the fuzzy finder to calculate the | ||
| // matched paths only for the returned results. This speeds up considerably | ||
| // the filtering of items. | ||
| this.nativeFuzzyForResults = new NativeFuzzy.Matcher([]) | ||
| } | ||
|
|
||
| this.selectListView.update({ | ||
| filter: (items, query) => { | ||
| return query ? fuzzaldrinPlus.filter(items, query, {key: 'label'}) : items | ||
| if (!query) { | ||
| return items | ||
| } | ||
|
|
||
| return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS}).map( | ||
| result => this.convertPathToSelectViewObject(this.getAbsolutePath(result.value)) | ||
| ) | ||
| } | ||
| }) | ||
| } else { | ||
| this.selectListView.update({filter: null}) | ||
| this.selectListView.update({ | ||
| filter: (items, query) => { | ||
| return query ? fuzzaldrinPlus.filter(items, query, {key: 'label'}) : items | ||
| } | ||
| }) | ||
| } | ||
| }) | ||
| ) | ||
|
|
@@ -289,6 +342,12 @@ class FuzzyFinderView { | |
|
|
||
| setItems (items) { | ||
| this.items = items | ||
|
|
||
| if (this.scoringSystem === SCORING_SYSTEMS.FAST) { | ||
| // Beware: this operation is quite slow for large projects! | ||
| this.nativeFuzzy.setCandidates(this.items.map(item => item.label)) | ||
| } | ||
|
|
||
| if (this.isQueryALineJump()) { | ||
| return this.selectListView.update({items: [], loadingMessage: null, loadingBadge: null}) | ||
| } else { | ||
|
|
@@ -299,21 +358,49 @@ class FuzzyFinderView { | |
| projectRelativePathsForFilePaths (filePaths) { | ||
| // Don't regenerate project relative paths unless the file paths have changed | ||
| if (filePaths !== this.filePaths) { | ||
| const projectHasMultipleDirectories = atom.project.getDirectories().length > 1 | ||
| this.filePaths = filePaths | ||
| this.projectRelativePaths = this.filePaths.map((filePath) => { | ||
| const [rootPath, projectRelativePath] = atom.project.relativizePath(filePath) | ||
| const label = | ||
| rootPath && projectHasMultipleDirectories | ||
| ? path.join(path.basename(rootPath), projectRelativePath) | ||
| : projectRelativePath | ||
|
|
||
| return {uri: filePath, filePath, label} | ||
| }) | ||
| this.projectRelativePaths = this.filePaths.map( | ||
| (filePath) => this.convertPathToSelectViewObject(filePath) | ||
| ) | ||
| } | ||
|
|
||
| return this.projectRelativePaths | ||
| } | ||
|
|
||
| convertPathToSelectViewObject (filePath) { | ||
| const projectHasMultipleDirectories = atom.project.getDirectories().length > 1 | ||
|
|
||
| const [rootPath, projectRelativePath] = atom.project.relativizePath(filePath) | ||
| const label = | ||
| rootPath && projectHasMultipleDirectories | ||
| ? path.join(path.basename(rootPath), projectRelativePath) | ||
| : projectRelativePath | ||
|
|
||
| return {uri: filePath, filePath, label} | ||
| } | ||
|
|
||
| getAbsolutePath (relativePath) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is not great because it'll do a few fs accesses via This means that it'll do |
||
| const directories = atom.project.getDirectories() | ||
|
|
||
| if (directories.length === 1) { | ||
| return path.join(directories[0].getPath(), relativePath) | ||
| } | ||
|
|
||
| // Remove the first part of the relative path, since it contains the project | ||
| // directory name if there are many directories opened. | ||
| relativePath = path.join(...relativePath.split(path.sep).slice(1)) | ||
|
|
||
| for (const directory of directories) { | ||
| const absolutePath = path.join(directory.getPath(), relativePath) | ||
|
|
||
| if (fs.existsSync(absolutePath)) { | ||
| return absolutePath | ||
| } | ||
| } | ||
|
|
||
| // Best effort: just return the relative path. | ||
| return relativePath | ||
| } | ||
| } | ||
|
|
||
| function highlight (path, matches, offsetIndex) { | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, we're executing
native-fuzzyon a single thread, but we can easily change this in the future