Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving fuzzy-native to core #774

Merged
merged 23 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a31bdbb
Moving fuzzy-native to core
mauricioszabo Oct 17, 2023
90e792a
Using pulsar's fuzzyMatcher API in fuzzy-finder
mauricioszabo Oct 17, 2023
c094b26
Wiring up fuzzyMatcher UI API
mauricioszabo Oct 17, 2023
2f9939d
Moved fuzzy-finder matching from autocomplete-plus
mauricioszabo Oct 17, 2023
a9bd1c2
Fixed a bug with `.score` on fuzzyMatcher
mauricioszabo Oct 17, 2023
140ef15
Removed unused param
mauricioszabo Oct 17, 2023
ef0f36c
Moved fuzzy finding from command-palette
mauricioszabo Oct 17, 2023
14659d6
Removed deps from command-palette
mauricioszabo Oct 17, 2023
73480e1
Fixing matcher options
mauricioszabo Oct 17, 2023
3917ba4
Fixed command-palette highlighting
mauricioszabo Oct 17, 2023
5bc996a
Fixed some suggestion elements from Autocomplete
mauricioszabo Oct 17, 2023
de63718
Moving fuzzyMatcher on settings-view
mauricioszabo Oct 17, 2023
ed73630
Lockfile update
mauricioszabo Oct 17, 2023
a987468
Merge remote-tracking branch 'origin/ui-api' into filtering-api
mauricioszabo Nov 7, 2023
db1ec10
Merge remote-tracking branch 'origin/master' into filtering-api
mauricioszabo Nov 7, 2023
4927f0d
Merge remote-tracking branch 'origin/master' into filtering-api
mauricioszabo Nov 9, 2023
b499a21
Merge remote-tracking branch 'origin/master' into filtering-api
mauricioszabo Nov 13, 2023
8526a9d
Update src/ui.js
mauricioszabo Nov 16, 2023
86aba18
Added API documentation
mauricioszabo Nov 23, 2023
a4b48d8
Bumped fuzzyNative
mauricioszabo Nov 23, 2023
4264de0
Making fuzzy-finder use the right algorithm
mauricioszabo Nov 23, 2023
2bc8b12
Fixed some constructors
mauricioszabo Nov 23, 2023
f52fa4e
Another fuzzy-native bump
mauricioszabo Nov 24, 2023
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"@atom/source-map-support": "^0.3.4",
"@babel/core": "7.18.6",
"@pulsar-edit/fuzzy-native": "https://github.com/pulsar-edit/fuzzy-native.git#c6ddd2e0ace7b3cfe8082fcbe5985c49f76da5b8",
"about": "file:packages/about",
"archive-view": "file:packages/archive-view",
"async": "3.2.4",
Expand Down
6 changes: 1 addition & 5 deletions packages/autocomplete-plus/lib/autocomplete-manager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const {CompositeDisposable, Disposable, Point, Range} = require('atom')
const path = require('path')
const fuzzaldrin = require('fuzzaldrin')
const fuzzaldrinPlus = require('fuzzaldrin-plus')

const ProviderManager = require('./provider-manager')
const SuggestionList = require('./suggestion-list')
Expand Down Expand Up @@ -190,7 +188,6 @@ class AutocompleteManager {
this.subscriptions.add(atom.config.observe('autocomplete-plus.enableAutoActivation', (value) => { this.autoActivationEnabled = value }))
this.subscriptions.add(atom.config.observe('autocomplete-plus.enableAutoConfirmSingleSuggestion', (value) => { this.autoConfirmSingleSuggestionEnabled = value }))
this.subscriptions.add(atom.config.observe('autocomplete-plus.consumeSuffix', (value) => { this.consumeSuffix = value }))
this.subscriptions.add(atom.config.observe('autocomplete-plus.useAlternateScoring', (value) => { this.useAlternateScoring = value }))
this.subscriptions.add(atom.config.observe('autocomplete-plus.fileBlacklist', (value) => {
if (value) {
this.fileBlacklist = value.map((s) => { return s.trim() })
Expand Down Expand Up @@ -365,7 +362,6 @@ class AutocompleteManager {

filterSuggestions (suggestions, {prefix}) {
const results = []
const fuzzaldrinProvider = this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
for (let i = 0; i < suggestions.length; i++) {
// sortScore mostly preserves in the original sorting. The function is
// chosen such that suggestions with a very high match score can break out.
Expand All @@ -382,7 +378,7 @@ class AutocompleteManager {
results.push(suggestion)
} else {
const keepMatching = suggestion.ranges || suggestionPrefix[0].toLowerCase() === text[0].toLowerCase()
if (keepMatching && (score = fuzzaldrinProvider.score(text, suggestionPrefix)) > 0) {
if (keepMatching && (score = atom.ui.fuzzyMatcher.score(text, suggestionPrefix)) > 0) {
suggestion.score = score * suggestion.sortScore
results.push(suggestion)
}
Expand Down
35 changes: 6 additions & 29 deletions packages/autocomplete-plus/lib/suggestion-list-element.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const {CompositeDisposable} = require('atom')
const SnippetParser = require('./snippet-parser')
const {isString} = require('./type-helpers')
const fuzzaldrinPlus = require('fuzzaldrin-plus')

const createSuggestionFrag = () => {
const frag = document.createDocumentFragment()
Expand Down Expand Up @@ -81,9 +80,6 @@ module.exports = class SuggestionListElement {
this.subscriptions.add(atom.config.observe('autocomplete-plus.maxVisibleSuggestions', maxVisibleSuggestions => {
this.maxVisibleSuggestions = maxVisibleSuggestions
}))
this.subscriptions.add(atom.config.observe('autocomplete-plus.useAlternateScoring', useAlternateScoring => {
this.useAlternateScoring = useAlternateScoring
}))
this.subscriptions.add(atom.config.observe('autocomplete-plus.moveToCancel', moveToCancel => {
this.moveToCancel = moveToCancel
}))
Expand Down Expand Up @@ -540,12 +536,8 @@ module.exports = class SuggestionListElement {

if (!characterMatchIndices) {
characterMatchIndices = this.findCharacterMatchIndices(replacementText, replacementPrefix)
} else {
characterMatchIndices = characterMatchIndices.reduce((matches, index) => {
matches[index] = true
return matches
}, {})
}
characterMatchIndices = new Set(characterMatchIndices);

const appendNonMatchChars = (el, nonMatchChars) => {
if (nonMatchChars) {
Expand All @@ -568,7 +560,7 @@ module.exports = class SuggestionListElement {
workingEl = s
}

if (characterMatchIndices && characterMatchIndices[index]) {
if (characterMatchIndices && characterMatchIndices.has(index)) {
appendNonMatchChars(workingEl, nonMatchChars)
nonMatchChars = ''

Expand Down Expand Up @@ -664,26 +656,11 @@ module.exports = class SuggestionListElement {
//
// Returns an {Object}
findCharacterMatchIndices (text, replacementPrefix) {
if (!text || !text.length || !replacementPrefix || !replacementPrefix.length) { return }
if (!text?.length || !replacementPrefix?.length) { return }
const matches = {}
if (this.useAlternateScoring) {
const matchIndices = fuzzaldrinPlus.match(text, replacementPrefix)
for (const i of matchIndices) {
matches[i] = true
}
} else {
let wordIndex = 0
for (let i = 0; i < replacementPrefix.length; i++) {
const ch = replacementPrefix[i]
while (wordIndex < text.length && text[wordIndex].toLowerCase() !== ch.toLowerCase()) {
wordIndex += 1
}
if (wordIndex >= text.length) { break }
matches[wordIndex] = true
wordIndex += 1
}
}
return matches
return atom.ui.fuzzyMatcher.match(
text, replacementPrefix, {recordMatchIndexes: true}
)?.matchIndexes || [];
}

dispose () {
Expand Down
8 changes: 0 additions & 8 deletions packages/autocomplete-plus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
},
"dependencies": {
"atom-slick": "^2.0.0",
"fuzzaldrin": "^2.1.0",
"fuzzaldrin-plus": "^0.6.0",
"grim": "^2.0.1",
"minimatch": "^3.0.3",
"selector-kit": "^0.1.0",
Expand Down Expand Up @@ -198,12 +196,6 @@
"default": true,
"order": 18
},
"useAlternateScoring": {
"description": "Prefers runs of consecutive characters, acronyms and start of words. (Experimental)",
"type": "boolean",
"default": true,
"order": 19
},
"useLocalityBonus": {
"description": "Gives words near the cursor position a higher score than those far away",
"type": "boolean",
Expand Down
20 changes: 13 additions & 7 deletions packages/autocomplete-plus/spec/suggestion-list-element-spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-env jasmine */
/* eslint-disable no-template-curly-in-string */
const SuggestionListElement = require('../lib/suggestion-list-element')
const { conditionPromise } = require('./spec-helper')

const fragmentToHtml = fragment => {
const el = document.createElement('span')
Expand Down Expand Up @@ -63,11 +64,12 @@ describe('Suggestion List Element', () => {
})

describe('itemChanged', () => {
beforeEach(() => jasmine.attachToDOM(suggestionListElement.element))

it('updates the list item', async () => {
beforeEach(() => {
jasmine.useRealClock()
jasmine.attachToDOM(suggestionListElement.element)
})

it('updates the list item', async () => {
const suggestion = {text: 'foo'}
const newSuggestion = {text: 'foo', description: 'foobar', rightLabel: 'foo'}
suggestionListElement.model = {items: [newSuggestion]}
Expand All @@ -77,6 +79,9 @@ describe('Suggestion List Element', () => {

suggestionListElement.itemChanged({suggestion: newSuggestion, index: 0})

await conditionPromise(() =>
suggestionListElement.element.querySelector('.right-label').innerText
)
expect(suggestionListElement.element.querySelector('.right-label').innerText)
.toBe('foo')

Expand Down Expand Up @@ -190,14 +195,15 @@ describe('Suggestion List Element', () => {
let snippets = suggestionListElement.snippetParser.findSnippets(text)
text = suggestionListElement.removeSnippetsFromText(snippets, text)
let matches = suggestionListElement.findCharacterMatchIndices(text, replacementPrefix)
matches = new Set(matches)

for (var i = 0; i <= text.length; i++) {
if (truthyIndices.indexOf(i) !== -1) {
expect(matches[i]).toBeTruthy()
expect(matches.has(i)).toBeTruthy()
} else {
let m = matches
if (m) {
m = m[i]
m = m.has(i)
}
expect(m).toBeFalsy()
}
Expand All @@ -208,14 +214,14 @@ describe('Suggestion List Element', () => {
assertMatches('hello', '', [])
assertMatches('hello', 'h', [0])
assertMatches('hello', 'hl', [0, 2])
assertMatches('hello', 'hlo', [0, 2, 4])
assertMatches('hello', 'hlo', [0, 3, 4])
})

it('finds matches when snippets exist', () => {
assertMatches('${0:hello}', '', [])
assertMatches('${0:hello}', 'h', [0])
assertMatches('${0:hello}', 'hl', [0, 2])
assertMatches('${0:hello}', 'hlo', [0, 2, 4])
assertMatches('${0:hello}', 'hlo', [0, 3, 4])
assertMatches('${0:hello}world', '', [])
assertMatches('${0:hello}world', 'h', [0])
assertMatches('${0:hello}world', 'hw', [0, 5])
Expand Down
3 changes: 0 additions & 3 deletions packages/command-palette/lib/command-palette-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ class CommandPalettePackage {
this.commandPaletteView.show(true)
}
}))
this.disposables.add(atom.config.observe('command-palette.useAlternateScoring', (newValue) => {
this.commandPaletteView.update({useAlternateScoring: newValue})
}))
this.disposables.add(atom.config.observe('command-palette.preserveLastSearch', (newValue) => {
this.commandPaletteView.update({preserveLastSearch: newValue})
}))
Expand Down
24 changes: 7 additions & 17 deletions packages/command-palette/lib/command-palette-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import SelectListView from 'atom-select-list'
import {humanizeKeystroke} from 'underscore-plus'
import fuzzaldrin from 'fuzzaldrin'
import fuzzaldrinPlus from 'fuzzaldrin-plus'

export default class CommandPaletteView {
constructor (initiallyVisibleItemCount = 10) {
Expand Down Expand Up @@ -55,7 +53,7 @@ export default class CommandPaletteView {

if (Array.isArray(item.tags)) {
const matchingTags = item.tags
.map(t => [t, this.fuzz.score(t, query)])
.map(t => [t, atom.ui.fuzzyMatcher.score(t, query)])
.filter(([t, s]) => s > 0)
.sort((a, b) => a.s - b.s)
.map(([t, s]) => t)
Expand Down Expand Up @@ -132,21 +130,13 @@ export default class CommandPaletteView {
if (props.hasOwnProperty('preserveLastSearch')) {
this.preserveLastSearch = props.preserveLastSearch
}

if (props.hasOwnProperty('useAlternateScoring')) {
this.useAlternateScoring = props.useAlternateScoring
}
}

get fuzz () {
return this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
}

highlightMatchesInElement (text, query, el) {
const matches = this.fuzz.match(text, query)
const matches = atom.ui.fuzzyMatcher.match(text, query, {recordMatchIndexes: true})
let matchedChars = []
let lastIndex = 0
for (const matchIndex of matches) {
matches.matchIndexes.forEach(matchIndex => {
const unmatched = text.substring(lastIndex, matchIndex)
if (unmatched) {
if (matchedChars.length > 0) {
Expand All @@ -162,7 +152,7 @@ export default class CommandPaletteView {

matchedChars.push(text[matchIndex])
lastIndex = matchIndex + 1
}
})

if (matchedChars.length > 0) {
const matchSpan = document.createElement('span')
Expand All @@ -184,15 +174,15 @@ export default class CommandPaletteView {

const scoredItems = []
for (const item of items) {
let score = this.fuzz.score(item.displayName, query)
let score = atom.ui.fuzzyMatcher.score(item.displayName, query)
if (item.tags) {
score += item.tags.reduce(
(currentScore, tag) => currentScore + this.fuzz.score(tag, query),
(currentScore, tag) => currentScore + atom.ui.fuzzyMatcher.score(tag, query),
0
)
}
if (item.description) {
score += this.fuzz.score(item.description, query)
score += atom.ui.fuzzyMatcher.score(item.description, query)
}

if (score > 0) {
Expand Down
7 changes: 0 additions & 7 deletions packages/command-palette/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
"atomTestRunner": "atom-mocha-test-runner",
"dependencies": {
"atom-select-list": "^0.7.1",
"fuzzaldrin": "^2.1.0",
"fuzzaldrin-plus": "^0.6.0",
"underscore-plus": "^1.0.0"
},
"devDependencies": {
Expand All @@ -27,11 +25,6 @@
"sinon": "^3.2.1"
},
"configSchema": {
"useAlternateScoring": {
"type": "boolean",
"default": true,
"description": "Use an alternative scoring approach which prefers run of consecutive characters, acronyms and start of words."
},
"preserveLastSearch": {
"type": "boolean",
"default": false,
Expand Down
30 changes: 11 additions & 19 deletions packages/fuzzy-finder/lib/fuzzy-finder-view.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const {Point, CompositeDisposable} = require('atom')
const fs = require('fs')
const NativeFuzzy = require('@pulsar-edit/fuzzy-native')

const path = require('path')
const SelectListView = require('atom-select-list')
Expand Down Expand Up @@ -79,10 +78,12 @@ module.exports = class FuzzyFinderView {
elementForItem: ({filePath, label, ownerGitHubUsername}) => {
const filterQuery = this.selectListView.getFilterQuery()

this.nativeFuzzyForResults.setCandidates([0], [label])
atom.ui.fuzzyMatcher.setCandidates(
this.nativeFuzzyForResults, [label]
);
const items = this.nativeFuzzyForResults.match(
filterQuery,
{maxResults: 1, recordMatchIndexes: true}
{maxResults: 1, recordMatchIndexes: true, algorithm: 'command-t'}
)
const matches = items.length ? items[0].matchIndexes : []
const repository = repositoryForPath(filePath)
Expand Down Expand Up @@ -125,14 +126,13 @@ module.exports = class FuzzyFinderView {
})

if (!this.nativeFuzzy) {
this.nativeFuzzy = new NativeFuzzy.Matcher(
indexArray(this.items.length),
this.nativeFuzzy = atom.ui.fuzzyMatcher.setCandidates(
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.nativeFuzzyForResults = atom.ui.fuzzyMatcher.setCandidates([]);
}
this.selectListView.update({ filter: this.filterFn })
}
Expand Down Expand Up @@ -290,10 +290,10 @@ module.exports = class FuzzyFinderView {

setItems (items) {
this.items = items
this.nativeFuzzy.setCandidates(
indexArray(this.items.length),
atom.ui.fuzzyMatcher.setCandidates(
this.nativeFuzzy,
this.items.map(item => item.label)
)
);

if (this.isQueryALineJump()) {
this.selectListView.update({
Expand Down Expand Up @@ -338,7 +338,7 @@ module.exports = class FuzzyFinderView {

filterFn(items, query) {
if (!query) return items
return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS})
return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS, algorithm: 'command-t'})
.map(({id}) => this.items[id])
}
}
Expand Down Expand Up @@ -382,14 +382,6 @@ function highlight (path, matches, offsetIndex) {
return fragment
}

function indexArray (length) {
const array = []
for (let i = 0; i < length; i++) {
array[i] = i
}
return array
}

class FuzzyFinderItem {
constructor ({filePath, label, ownerGitHubUsername, filterQuery, matches, repository}) {
this.filePath = filePath
Expand Down
Loading
Loading