diff --git a/lib/default-file-icons.coffee b/lib/default-file-icons.coffee new file mode 100644 index 00000000..541b701c --- /dev/null +++ b/lib/default-file-icons.coffee @@ -0,0 +1,23 @@ +fs = require 'fs-plus' +path = require 'path' + +class DefaultFileIcons + iconClassForPath: (filePath) -> + extension = path.extname(filePath) + + if fs.isSymbolicLinkSync(filePath) + 'icon-file-symlink-file' + else if fs.isReadmePath(filePath) + 'icon-book' + else if fs.isCompressedExtension(extension) + 'icon-file-zip' + else if fs.isImageExtension(extension) + 'icon-file-media' + else if fs.isPdfExtension(extension) + 'icon-file-pdf' + else if fs.isBinaryExtension(extension) + 'icon-file-binary' + else + 'icon-file-text' + +module.exports = DefaultFileIcons diff --git a/lib/default-file-icons.js b/lib/default-file-icons.js deleted file mode 100644 index 96c7a8d3..00000000 --- a/lib/default-file-icons.js +++ /dev/null @@ -1,26 +0,0 @@ -const fs = require('fs-plus') -const path = require('path') - -class DefaultFileIcons { - iconClassForPath (filePath) { - const extension = path.extname(filePath) - - if (fs.isSymbolicLinkSync(filePath)) { - return 'icon-file-symlink-file' - } else if (fs.isReadmePath(filePath)) { - return 'icon-book' - } else if (fs.isCompressedExtension(extension)) { - return 'icon-file-zip' - } else if (fs.isImageExtension(extension)) { - return 'icon-file-media' - } else if (fs.isPdfExtension(extension)) { - return 'icon-file-pdf' - } else if (fs.isBinaryExtension(extension)) { - return 'icon-file-binary' - } else { - return 'icon-file-text' - } - } -} - -module.exports = new DefaultFileIcons() diff --git a/lib/file-icons.coffee b/lib/file-icons.coffee new file mode 100644 index 00000000..fdc809db --- /dev/null +++ b/lib/file-icons.coffee @@ -0,0 +1,15 @@ +DefaultFileIcons = require './default-file-icons' + +class FileIcons + constructor: -> + @service = new DefaultFileIcons + + getService: -> + @service + + resetService: -> + @service = new DefaultFileIcons + + setService: (@service) -> + +module.exports = new FileIcons diff --git a/lib/find.coffee b/lib/find.coffee index 89103e4d..6ba473f3 100644 --- a/lib/find.coffee +++ b/lib/find.coffee @@ -4,7 +4,7 @@ SelectNext = require './select-next' {History, HistoryCycler} = require './history' FindOptions = require './find-options' BufferSearch = require './buffer-search' -getIconServices = require './get-icon-services' +FileIcons = require './file-icons' FindView = require './find-view' ProjectFindView = require './project-find-view' ResultsModel = require './project/results-model' @@ -117,15 +117,10 @@ module.exports = 'find-and-replace:select-skip': (event) -> selectNextObjectForEditorElement(this).skipCurrentSelection() - consumeElementIcons: (service) -> - getIconServices().setElementIcons service - new Disposable => - getIconServices().resetElementIcons() - consumeFileIcons: (service) -> - getIconServices().setFileIcons service + FileIcons.setService service new Disposable -> - getIconServices().resetFileIcons() + FileIcons.resetService() toggleAutocompletions: (value) -> if not @findView? diff --git a/lib/get-icon-services.js b/lib/get-icon-services.js deleted file mode 100644 index 233d3094..00000000 --- a/lib/get-icon-services.js +++ /dev/null @@ -1,65 +0,0 @@ -const DefaultFileIcons = require('./default-file-icons') -const {Emitter, CompositeDisposable} = require('atom') - -let iconServices -module.exports = function () { - if (!iconServices) iconServices = new IconServices() - return iconServices -} - -class IconServices { - constructor () { - this.emitter = new Emitter() - this.elementIcons = null - this.elementIconDisposables = new CompositeDisposable() - this.fileIcons = DefaultFileIcons - } - - onDidChange (callback) { - return this.emitter.on('did-change', callback) - } - - resetElementIcons () { - this.setElementIcons(null) - } - - resetFileIcons () { - this.setFileIcons(DefaultFileIcons) - } - - setElementIcons (service) { - if (service !== this.elementIcons) { - if (this.elementIconDisposables != null) { - this.elementIconDisposables.dispose() - } - if (service) { this.elementIconDisposables = new CompositeDisposable() } - this.elementIcons = service - return this.emitter.emit('did-change') - } - } - - setFileIcons (service) { - if (service !== this.fileIcons) { - this.fileIcons = service - return this.emitter.emit('did-change') - } - } - - getIconClasses (view) { - let iconClass = '' - if (this.elementIcons) { - if (!view.iconDisposable) { - view.iconDisposable = new CompositeDisposable() - } - process.nextTick(() => { - view.iconDisposable.add(this.elementIcons(view.refs.icon, view.filePath)) - }) - } else { - iconClass = this.fileIcons.iconClassForPath(view.filePath, 'find-and-replace') || '' - if (Array.isArray(iconClass)) { - iconClass = iconClass.join(' ') - } - } - return iconClass - } -} diff --git a/lib/project/result-view.js b/lib/project/result-view.js index f6b79e41..57e099b2 100644 --- a/lib/project/result-view.js +++ b/lib/project/result-view.js @@ -1,4 +1,4 @@ -const getIconServices = require('../get-icon-services'); +const FileIcons = require('../file-icons'); const MatchView = require('./match-view'); const path = require('path'); const etch = require('etch'); @@ -32,14 +32,6 @@ class ResultView { etch.initialize(this); } - destroy() { - if (this.iconDisposable) { - this.iconDisposable.dispose() - this.iconDisposable = null - } - return etch.destroy(this) - } - update({item, top, bottom} = {}) { const { filePath, matches, isSelected, selectedMatchIndex, isExpanded, regex, @@ -84,6 +76,10 @@ class ResultView { } render() { + let iconClass = FileIcons.getService().iconClassForPath(this.filePath, "find-and-replace"); + if (!iconClass) iconClass = []; + if (!Array.isArray(iconClass)) iconClass = iconClass.toString().split(/\s+/g); + let relativePath = this.filePath; if (atom.project) { let rootPath; @@ -97,9 +93,7 @@ class ResultView { !this.isExpanded || this.selectedMatchIndex === -1 ); - - const iconClass = getIconServices().getIconClasses(this) - + return ( $.li( { @@ -116,8 +110,7 @@ class ResultView { $.div({ref: 'pathDetails', className: 'path-details list-item'}, $.span({className: 'disclosure-arrow'}), $.span({ - ref: 'icon', - className: iconClass + ' icon', + className: iconClass.join(' ') + ' icon', dataset: {name: path.basename(this.filePath)} }), $.span({className: 'path-name bright'}, diff --git a/package.json b/package.json index fe3f07ab..ecb74b85 100644 --- a/package.json +++ b/package.json @@ -50,11 +50,6 @@ "versions": { "1.0.0": "consumeAutocompleteWatchEditor" } - }, - "file-icons.element-icons": { - "versions": { - "1.0.0": "consumeElementIcons" - } } }, "providedServices": { diff --git a/spec/results-view-spec.js b/spec/results-view-spec.js index 97255755..c0a1ad26 100644 --- a/spec/results-view-spec.js +++ b/spec/results-view-spec.js @@ -6,9 +6,7 @@ const temp = require('temp'); const fs = require('fs'); const etch = require('etch'); const ResultsPaneView = require('../lib/project/results-pane'); -const getIconServices = require('../lib/get-icon-services'); -const DefaultFileIcons = require('../lib/default-file-icons'); -const {Disposable} = require('atom') +const FileIcons = require('../lib/file-icons'); const {beforeEach, it, fit, ffit, fffit} = require('./async-spec-helpers') global.beforeEach(function() { @@ -670,115 +668,41 @@ describe('ResultsView', () => { }) }); - describe('icon services', () => { - describe('atom.file-icons', () => { - it('has a default handler', () => { - expect(getIconServices().fileIcons).toBe(DefaultFileIcons) - }) - - it('displays icons for common filetypes', () => { - expect(DefaultFileIcons.iconClassForPath('README.md')).toBe('icon-book') - expect(DefaultFileIcons.iconClassForPath('zip.zip')).toBe('icon-file-zip') - expect(DefaultFileIcons.iconClassForPath('a.gif')).toBe('icon-file-media') - expect(DefaultFileIcons.iconClassForPath('a.pdf')).toBe('icon-file-pdf') - expect(DefaultFileIcons.iconClassForPath('an.exe')).toBe('icon-file-binary') - expect(DefaultFileIcons.iconClassForPath('jg.js')).toBe('icon-file-text') - }) - - it('allows a service provider to change the handler', async () => { - const provider = { - iconClassForPath(path, context) { - expect(context).toBe('find-and-replace') - return (path.endsWith('one-long-line.coffee')) - ? 'first-icon-class second-icon-class' - : ['third-icon-class', 'fourth-icon-class'] + describe("icon-service lifecycle", () => { + it('renders file icon classes based on the provided file-icons service', async () => { + const fileIconsDisposable = atom.packages.serviceHub.provide('atom.file-icons', '1.0.0', { + iconClassForPath(path, context) { + expect(context).toBe("find-and-replace"); + if (path.endsWith('one-long-line.coffee')) { + return "first-icon-class second-icon-class"; + } else { + return ['third-icon-class', 'fourth-icon-class']; } } - const disposable = atom.packages.serviceHub.provide('atom.file-icons', '1.0.0', provider); - expect(getIconServices().fileIcons).toBe(provider) - - projectFindView.findEditor.setText('i'); - atom.commands.dispatch(projectFindView.element, 'core:confirm'); - await searchPromise; - - resultsView = getResultsView(); - let fileIconClasses = Array.from(resultsView.element.querySelectorAll('.path-details .icon')).map(el => el.className); - expect(fileIconClasses).toContain('first-icon-class second-icon-class icon'); - expect(fileIconClasses).toContain('third-icon-class fourth-icon-class icon'); - expect(fileIconClasses).not.toContain('icon-file-text icon'); - - disposable.dispose(); - projectFindView.findEditor.setText('e'); - atom.commands.dispatch(projectFindView.element, 'core:confirm'); - - await searchPromise; - resultsView = getResultsView(); - fileIconClasses = Array.from(resultsView.element.querySelectorAll('.path-details .icon')).map(el => el.className); - expect(fileIconClasses).not.toContain('first-icon-class second-icon-class icon'); - expect(fileIconClasses).not.toContain('third-icon-class fourth-icon-class icon'); - expect(fileIconClasses).toContain('icon-file-text icon'); - }) - }) - - describe('file-icons.element-icons', () => { - beforeEach(() => jasmine.useRealClock()) - - it('has no default handler', () => { - expect(getIconServices().elementIcons).toBe(null) - }) - - it('uses the element-icon service if available', () => { - const iconSelector = '.path-details .icon:not([data-name="fake-file-path"])' - const provider = (element, path) => { - expect(element).toBeInstanceOf(HTMLElement) - expect(typeof path === "string").toBe(true) - expect(path.length).toBeGreaterThan(0) - const classes = path.endsWith('one-long-line.coffee') - ? ['foo', 'bar'] - : ['baz', 'qlux'] - element.classList.add(...classes) - return new Disposable(() => { - element.classList.remove(...classes) - }) - } - let disposable - - waitsForPromise(() => { - disposable = atom.packages.serviceHub.provide('file-icons.element-icons', '1.0.0', provider) - expect(getIconServices().elementIcons).toBe(provider) - projectFindView.findEditor.setText('i'); - atom.commands.dispatch(projectFindView.element, 'core:confirm'); - return searchPromise - }) - - waitsForPromise(() => delayFor(35)) - - runs(() => { - resultsView = getResultsView() - const iconElements = resultsView.element.querySelectorAll(iconSelector) - expect(iconElements[0].className.trim()).toBe('icon foo bar') - expect(iconElements[1].className.trim()).toBe('icon baz qlux') - expect(resultsView.element.querySelector('.icon-file-text')).toBe(null) + }); - disposable.dispose() - projectFindView.findEditor.setText('e') - atom.commands.dispatch(projectFindView.element, 'core:confirm') - }) + projectFindView.findEditor.setText('i'); + atom.commands.dispatch(projectFindView.element, 'core:confirm'); + await searchPromise; - waitsForPromise(() => searchPromise) + resultsView = getResultsView(); + let fileIconClasses = Array.from(resultsView.element.querySelectorAll('.path-details .icon')).map(el => el.className); + expect(fileIconClasses).toContain('first-icon-class second-icon-class icon'); + expect(fileIconClasses).toContain('third-icon-class fourth-icon-class icon'); + expect(fileIconClasses).not.toContain('icon-file-text icon'); - waitsForPromise(() => delayFor(35)) + fileIconsDisposable.dispose(); + projectFindView.findEditor.setText('e'); + atom.commands.dispatch(projectFindView.element, 'core:confirm'); - runs(() => { - resultsView = getResultsView() - const iconElements = resultsView.element.querySelectorAll(iconSelector) - expect(iconElements[0].className.trim()).toBe('icon-file-text icon') - expect(iconElements[1].className.trim()).toBe('icon-file-text icon') - expect(resultsView.element.querySelector('.foo, .bar, .baz, .qlux')).toBe(null) - }) - }) + await searchPromise; + resultsView = getResultsView(); + fileIconClasses = Array.from(resultsView.element.querySelectorAll('.path-details .icon')).map(el => el.className); + expect(fileIconClasses).not.toContain('first-icon-class second-icon-class icon'); + expect(fileIconClasses).not.toContain('third-icon-class fourth-icon-class icon'); + expect(fileIconClasses).toContain('icon-file-text icon'); }) - }) + }); describe('updating the search while viewing results', () => { it('resets the results message', async () => { @@ -996,9 +920,3 @@ function buildMouseEvent(type, properties) { function clickOn(element) { element.dispatchEvent(buildMouseEvent('mousedown', { detail: 1 })); } - -function delayFor(ms) { - return new Promise(done => { - setTimeout(() => done(), ms) - }) -}