diff --git a/packages/fuzzy-finder/README.md b/packages/fuzzy-finder/README.md index b7cc3a9fe..a1ed6f24f 100644 --- a/packages/fuzzy-finder/README.md +++ b/packages/fuzzy-finder/README.md @@ -12,6 +12,7 @@ When opening a file, you can control the behavior. * shift-enter defaults to switching to another pane if the file is already open there * cmd-k right *(macOS)* or ctrl-k right *(Linux/Windows)* (or any other directional arrow) will open the highlighted file in a new pane on the side indicated by the arrow * Adding `:` to the end of your search will go directly to the line number you specify, or the last line if the number is larger + * Beginning your search with `!` will search for VCS ignored files (only if using ripgrep indexing) Turning on the "Search All Panes" setting reverses the behavior of enter and shift-enter so enter opens the file in any pane and shift-enter creates a new tab in the current pane. diff --git a/packages/fuzzy-finder/lib/fuzzy-finder-view.js b/packages/fuzzy-finder/lib/fuzzy-finder-view.js index dab6afb72..01ec60775 100644 --- a/packages/fuzzy-finder/lib/fuzzy-finder-view.js +++ b/packages/fuzzy-finder/lib/fuzzy-finder-view.js @@ -12,7 +12,9 @@ const MAX_RESULTS = 10 module.exports = class FuzzyFinderView { constructor () { this.previousQueryWasLineJump = false + this.previousQueryOverrodeIgnore = false this.items = [] + this.ignoredItems = [] this.filterFn = this.filterFn.bind(this) this.selectListView = new SelectListView({ @@ -25,6 +27,11 @@ module.exports = class FuzzyFinderView { if (colon !== -1) { query = query.slice(0, colon) } + + if (query.indexOf('!') === 0 ) { + query = query.slice(1) + } + // Normalize to backslashes on Windows if (process.platform === 'win32') { query = query.replace(/\//g, '\\') @@ -45,7 +52,11 @@ module.exports = class FuzzyFinderView { this.iconDisposables = null } const isLineJump = this.isQueryALineJump() + const overridesIgnore = this.queryOverridesIgnore() + + // if last query was not line jump and this one is, clear the list of items if (isLineJump) { + this.previousQueryWasLineJump = true const query = this.selectListView.getQuery() let emptyMessage = null @@ -65,8 +76,24 @@ module.exports = class FuzzyFinderView { emptyMessage: emptyMessage, errorMessage: errorMessage }) - } else if (this.previousQueryWasLineJump) { + + // if last query did not override ignored paths and this one does, + // set the filter items to the ignored paths + } else if (!this.previousQueryOverrodeIgnore && overridesIgnore) { + this.previousQueryOverrodeIgnore = true + this.selectListView.update({ + items: this.ignoredItems, + emptyMessage: this.getEmptyMessage(), + errorMessage: null + }) + + // if last query was line jump and this one is not, + // OR if last query overrode ignored paths and this one doesn't + // reset the filter items to the regular items + } else if ((this.previousQueryWasLineJump && !isLineJump) || + (this.previousQueryOverrodeIgnore && !overridesIgnore)) { this.previousQueryWasLineJump = false + this.previousQueryOverrodeIgnore = false this.selectListView.update({ items: this.items, emptyMessage: this.getEmptyMessage(), @@ -124,10 +151,13 @@ module.exports = class FuzzyFinderView { } }) - if (!this.nativeFuzzy) { - this.nativeFuzzy = atom.ui.fuzzyMatcher.setCandidates( + if (!this.nativeFuzzyTracked) { + this.nativeFuzzyTracked = atom.ui.fuzzyMatcher.setCandidates( this.items.map(el => el.label) ); + this.nativeFuzzyIgnored = atom.ui.fuzzyMatcher.setCandidates( + this.ignoredItems.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. @@ -268,6 +298,10 @@ module.exports = class FuzzyFinderView { ) } + queryOverridesIgnore () { + return this.selectListView.getQuery().indexOf('!') === 0 + } + getCaretPosition () { const query = this.selectListView.getQuery() const firstColon = query.indexOf(':') @@ -287,12 +321,18 @@ module.exports = class FuzzyFinderView { return position } - setItems (items) { + setItems (items, ignoredItems = []) { this.items = items + this.ignoredItems = ignoredItems + atom.ui.fuzzyMatcher.setCandidates( - this.nativeFuzzy, + this.nativeFuzzyTracked, this.items.map(item => item.label) ); + atom.ui.fuzzyMatcher.setCandidates( + this.nativeFuzzyIgnored, + this.ignoredItems.map(item => item.label) + ); if (this.isQueryALineJump()) { this.selectListView.update({ @@ -303,7 +343,9 @@ module.exports = class FuzzyFinderView { }) } else { this.selectListView.update({ - items: this.items, + items: this.queryOverridesIgnore() + ? this.ignoredItems + : this.items, infoMessage: null, loadingMessage: null, loadingBadge: null @@ -337,7 +379,15 @@ module.exports = class FuzzyFinderView { filterFn(items, query) { if (!query) return items - return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS, algorithm: 'command-t'}) + + if (this.queryOverridesIgnore()) { + return this.nativeFuzzyIgnored + .match(query, {maxResults: MAX_RESULTS, algorithm: 'command-t'}) + .map(({id}) => this.ignoredItems[id]) + } + + return this.nativeFuzzyTracked + .match(query, {maxResults: MAX_RESULTS, algorithm: 'command-t'}) .map(({id}) => this.items[id]) } } diff --git a/packages/fuzzy-finder/spec/fuzzy-finder-spec.js b/packages/fuzzy-finder/spec/fuzzy-finder-spec.js index acbdb2c41..b5a0c20a3 100644 --- a/packages/fuzzy-finder/spec/fuzzy-finder-spec.js +++ b/packages/fuzzy-finder/spec/fuzzy-finder-spec.js @@ -1644,7 +1644,7 @@ describe('FuzzyFinder', () => { }) if (useRipGrep) { - it('does excludes paths that are git ignored', async () => { + it('excludes paths that are git ignored', async () => { fs.writeFileSync(path.join(projectPath, 'dir', 'a.txt'), 'something') await projectView.toggle() @@ -1693,6 +1693,81 @@ describe('FuzzyFinder', () => { a.textContent.includes('HEAD'))).not.toBeDefined() }) }) + + describe('when the query starts with an exclamation point', () => { + beforeEach(() => { + const ignoreFile = path.join(projectPath, '.gitignore') + fs.writeFileSync(ignoreFile, "ignored.txt\nanother.txt") + + fs.writeFileSync( + path.join(projectPath, 'ignored.txt'), + 'this text is not important' + ) + fs.writeFileSync( + path.join(projectPath, 'another.txt'), + 'this text is not important' + ) + }) + + it('excludes paths that are tracked when indexIgnoredPaths is true', async () => { + atom.config.set('fuzzy-finder.indexIgnoredPaths', true) + projectView.selectListView.refs.queryEditor.insertText('!') + + await projectView.toggle() + await waitForPathsToDisplay(projectView) + + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('a.txt')) + ).not.toBeDefined() + }) + + it('includes paths that are git ignored when indexIgnoredPaths is true', async () => { + atom.config.set('fuzzy-finder.indexIgnoredPaths', true) + projectView.selectListView.refs.queryEditor.insertText('!') + + await projectView.toggle() + await waitForPathsToDisplay(projectView) + + expect(projectView.queryOverridesIgnore()).toBe(true) + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('ignored.txt')) + ).toBeDefined() + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('another.txt')) + ).toBeDefined() + }) + + it('matches paths that are git ignored when indexIgnoredPaths is true', async () => { + atom.config.set('fuzzy-finder.indexIgnoredPaths', true) + projectView.selectListView.refs.queryEditor.insertText('!anoth') + + await projectView.toggle() + await waitForPathsToDisplay(projectView) + + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('ignored.txt')) + ).not.toBeDefined() + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('another.txt')) + ).toBeDefined() + }) + + it('excludes paths that are git ignored when indexIgnoredPaths is false', async () => { + atom.config.set('fuzzy-finder.indexIgnoredPaths', false) + projectView.selectListView.refs.queryEditor.insertText('!ig') + + await projectView.toggle() + await waitForReCrawlerToFinish(projectView) + + expect(projectView.queryOverridesIgnore()).toBe(true) + expect(projectView.element.querySelectorAll('li').length).toBe(0) + }) + }) }) describe('when core.excludeVcsIgnoredPaths is set to false', () => { @@ -1715,6 +1790,29 @@ describe('FuzzyFinder', () => { expect(Array.from(projectView.element.querySelectorAll('li')).find(a => a.textContent.includes('ignored.txt'))).toBeDefined() }) }) + + describe('when the query starts with an exclamation point', () => { + beforeEach(() => { + const ignoreFile = path.join(projectPath, '.gitignore') + fs.writeFileSync(ignoreFile, 'ignored.txt') + + const ignoredFile = path.join(projectPath, 'ignored.txt') + fs.writeFileSync(ignoredFile, 'ignored text') + }) + + it('excludes paths that are git ignored when indexIgnoredPaths is true', async () => { + atom.config.set('fuzzy-finder.indexIgnoredPaths', false) + projectView.selectListView.refs.queryEditor.insertText('!ig') + + await projectView.toggle() + await waitForReCrawlerToFinish(projectView) + + expect( + Array.from(projectView.element.querySelectorAll('li')) + .find(a => a.textContent.includes('ignored.txt')) + ).not.toBeDefined() + }) + }) }) })