Skip to content

Commit

Permalink
Merge pull request #774 from pulsar-edit/filtering-api
Browse files Browse the repository at this point in the history
  • Loading branch information
mauricioszabo authored Dec 13, 2023
2 parents 3d4440d + f52fa4e commit f3b9bd8
Show file tree
Hide file tree
Showing 16 changed files with 596 additions and 5,070 deletions.
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

0 comments on commit f3b9bd8

Please sign in to comment.