From 2ff283a6b0b820d855b6a466bd61a06177832712 Mon Sep 17 00:00:00 2001 From: Daniel Hengeveld Date: Tue, 20 Oct 2015 16:01:35 +0200 Subject: [PATCH] Async buffer events --- spec/git-repository-async-spec.coffee | 57 +++++++++++++++++++------- src/git-repository-async.js | 58 ++++++++++++++++++++++++--- src/git-repository.coffee | 6 ++- 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/spec/git-repository-async-spec.coffee b/spec/git-repository-async-spec.coffee index 55d4087d8..3bab16bf7 100644 --- a/spec/git-repository-async-spec.coffee +++ b/spec/git-repository-async-spec.coffee @@ -332,7 +332,11 @@ describe "GitRepositoryAsync", -> expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy() expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy() - xdescribe "buffer events", -> + # This tests the async implementation's events directly, but ultimately I + # think we want users to just be able to subscribe to events on GitRepository + # and have them bubble up from async-land + + describe "buffer events", -> [editor] = [] beforeEach -> @@ -345,32 +349,57 @@ describe "GitRepositoryAsync", -> editor.insertNewline() statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus statusHandler + repo = atom.project.getRepositories()[0] + repo.async.onDidChangeStatus statusHandler editor.save() - expect(statusHandler.callCount).toBe 1 - expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} + waitsFor -> + statusHandler.callCount == 1 + runs -> + expect(statusHandler.callCount).toBe 1 + expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} it "emits a status-changed event when a buffer is reloaded", -> fs.writeFileSync(editor.getPath(), 'changed') statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus statusHandler + atom.project.getRepositories()[0].async.onDidChangeStatus statusHandler editor.getBuffer().reload() - expect(statusHandler.callCount).toBe 1 - expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} - editor.getBuffer().reload() - expect(statusHandler.callCount).toBe 1 + reloadHandler = jasmine.createSpy 'reloadHandler' + + waitsFor -> + statusHandler.callCount == 1 + runs -> + expect(statusHandler.callCount).toBe 1 + expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} + buffer = editor.getBuffer() + buffer.onDidReload(reloadHandler) + buffer.reload() + + waitsFor -> + reloadHandler.callCount == 1 + runs -> + expect(statusHandler.callCount).toBe 1 it "emits a status-changed event when a buffer's path changes", -> fs.writeFileSync(editor.getPath(), 'changed') statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus statusHandler + atom.project.getRepositories()[0].async.onDidChangeStatus statusHandler editor.getBuffer().emitter.emit 'did-change-path' - expect(statusHandler.callCount).toBe 1 - expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} - editor.getBuffer().emitter.emit 'did-change-path' - expect(statusHandler.callCount).toBe 1 + waitsFor -> + statusHandler.callCount == 1 + runs -> + expect(statusHandler.callCount).toBe 1 + expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256} + + pathHandler = jasmine.createSpy('pathHandler') + buffer = editor.getBuffer() + buffer.onDidChangePath pathHandler + buffer.emitter.emit 'did-change-path' + waitsFor -> + pathHandler.callCount == 1 + runs -> + expect(statusHandler.callCount).toBe 1 it "stops listening to the buffer when the repository is destroyed (regression)", -> atom.project.getRepositories()[0].destroy() diff --git a/src/git-repository-async.js b/src/git-repository-async.js index 34f91de15..cbc040bbc 100644 --- a/src/git-repository-async.js +++ b/src/git-repository-async.js @@ -13,18 +13,35 @@ const GitUtils = require('git-utils') const _ = require('underscore-plus') module.exports = class GitRepositoryAsync { - static open (path) { + static open (path, options = {}) { // QUESTION: Should this wrap Git.Repository and reject with a nicer message? - return new GitRepositoryAsync(path) + return new GitRepositoryAsync(path, options) } - constructor (path) { + constructor (path, options) { this.repo = null this.emitter = new Emitter() this.subscriptions = new CompositeDisposable() this.pathStatusCache = {} this._gitUtilsRepo = GitUtils.open(path) // TODO remove after porting ::relativize this.repoPromise = Git.Repository.open(path) + + var {project, refreshOnWindowFocus} = options + this.project = project + if (refreshOnWindowFocus === undefined) { + refreshOnWindowFocus = true + } + if (refreshOnWindowFocus) { + // TODO + } + + if (this.project) { + this.subscriptions.add(this.project.onDidAddBuffer((buffer) => { + this.subscribeToBuffer(buffer) + })) + + this.project.getBuffers().forEach((buffer) => { this.subscribeToBuffer(buffer) }) + } } destroy () { @@ -73,6 +90,7 @@ module.exports = class GitRepositoryAsync { // Returns a Promise that resolves to the status bit of a given path if it has // one, otherwise 'current'. getPathStatus (_path) { + console.log('getting path status for', _path) var relativePath = this._gitUtilsRepo.relativize(_path) return this.repoPromise.then((repo) => { return this._filterStatusesByPath(_path) @@ -80,6 +98,7 @@ module.exports = class GitRepositoryAsync { var cachedStatus = this.pathStatusCache[relativePath] || 0 var status = statuses[0] ? statuses[0].statusBit() : Git.Status.STATUS.CURRENT if (status !== cachedStatus) { + console.log('async emitting', {path: _path, pathStatus: status}) this.emitter.emit('did-change-status', {path: _path, pathStatus: status}) } this.pathStatusCache[relativePath] = status @@ -134,8 +153,37 @@ module.exports = class GitRepositoryAsync { }) } - // Utility functions - // ================= + // Section: Private + // ================ + + subscribeToBuffer (buffer) { + var getBufferPathStatus = () => { + var _path = buffer.getPath() + var bufferSubscriptions = new CompositeDisposable() + + if (_path) { + // We don't need to do anything with this promise, we just want the + // emitted event side effect + this.getPathStatus(_path) + } + + bufferSubscriptions.add( + buffer.onDidSave(getBufferPathStatus), + buffer.onDidReload(getBufferPathStatus), + buffer.onDidChangePath(getBufferPathStatus) + ) + + bufferSubscriptions.add(() => { + buffer.onDidDestroy(() => { + bufferSubscriptions.dispose() + this.subscriptions.remove(bufferSubscriptions) + }) + }) + + this.subscriptions.add(bufferSubscriptions) + return + } + } getCachedPathStatus (_path) { return this.pathStatusCache[this._gitUtilsRepo.relativize(_path)] diff --git a/src/git-repository.coffee b/src/git-repository.coffee index 724142264..a2e28b127 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -76,7 +76,7 @@ class GitRepository unless @repo? throw new Error("No Git repository found searching path: #{path}") - @async = GitRepositoryAsync.open(path) + @async = GitRepositoryAsync.open(path, options) @statuses = {} @upstream = {ahead: 0, behind: 0} @@ -319,6 +319,9 @@ class GitRepository # Returns a {Number} representing the status. This value can be passed to # {::isStatusModified} or {::isStatusNew} to get more information. getPathStatus: (path) -> + # Trigger events emitted on the async repo as well + @async.getPathStatus(path) + repo = @getRepo(path) relativePath = @relativize(path) currentPathStatus = @statuses[relativePath] ? 0 @@ -479,6 +482,7 @@ class GitRepository # Refreshes the current git status in an outside process and asynchronously # updates the relevant properties. refreshStatus: -> + @async.refreshStatus() @handlerPath ?= require.resolve('./repository-status-handler') @statusTask?.terminate()