Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
image: Visual Studio 2015

version: "{build}"

platform: x64
Expand Down
129 changes: 108 additions & 21 deletions lib/fuzzy-finder-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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(
Copy link
Contributor Author

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-fuzzy on a single thread, but we can easily change this in the future

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
}
})
}
})
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 fs.existSync, but it should not be very expensive because it does it against the list of filtered items (which is maximum 10 items).

This means that it'll do 10 * number of opened projects file system accesses. Since I don't expect users having many projects opened in the same window, this should be fine

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) {
Expand Down
Loading