diff --git a/native/atom_application.mm b/native/atom_application.mm index c2f2ed2ab..8eab6bfc5 100644 --- a/native/atom_application.mm +++ b/native/atom_application.mm @@ -139,7 +139,7 @@ - (void)open:(NSString *)path pidToKillWhenWindowCloses:(NSNumber *)pid { for (NSWindow *window in [self windows]) { - if ([window isVisible] && ![window isExcludedFromWindowsMenu]) { + if (![window isExcludedFromWindowsMenu]) { AtomWindowController *controller = [window windowController]; if ([path isEqualToString:controller.pathToOpen]) { [window makeKeyAndOrderFront:nil]; diff --git a/native/v8_extensions/git.js b/native/v8_extensions/git.js index a3f772682..1f32038d3 100644 --- a/native/v8_extensions/git.js +++ b/native/v8_extensions/git.js @@ -9,6 +9,7 @@ var $git = {}; native function checkoutHead(path); native function getDiffStats(path); native function isSubmodule(path); + native function refreshIndex(); function GitRepository(path) { var repo = getRepository(path); @@ -24,5 +25,6 @@ var $git = {}; GitRepository.prototype.checkoutHead = checkoutHead; GitRepository.prototype.getDiffStats = getDiffStats; GitRepository.prototype.isSubmodule = isSubmodule; + GitRepository.prototype.refreshIndex = refreshIndex; this.GitRepository = GitRepository; })(); diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm index c68f23a49..8a334ed02 100644 --- a/native/v8_extensions/git.mm +++ b/native/v8_extensions/git.mm @@ -69,12 +69,6 @@ public: return CefV8Value::CreateInt(0); } - git_index* index; - if (git_repository_index(&index, repo) == GIT_OK) { - git_index_read(index); - git_index_free(index); - } - unsigned int status = 0; if (git_status_file(&status, repo, path) == GIT_OK) { return CefV8Value::CreateInt(status); @@ -100,12 +94,7 @@ public: int result = git_checkout_head(repo, &options); free(copiedPath); - if (result == GIT_OK) { - return CefV8Value::CreateBool(true); - } - else { - return CefV8Value::CreateBool(false); - } + return CefV8Value::CreateBool(result == GIT_OK); } CefRefPtr GetDiffStats(const char *path) { @@ -192,7 +181,6 @@ public: BOOL isSubmodule = false; git_index* index; if (git_repository_index(&index, repo) == GIT_OK) { - git_index_read(index); const git_index_entry *entry = git_index_get_bypath(index, path, 0); isSubmodule = entry != NULL && (entry->mode & S_IFMT) == GIT_FILEMODE_COMMIT; git_index_free(index); @@ -201,6 +189,14 @@ public: return CefV8Value::CreateBool(isSubmodule); } + void RefreshIndex() { + git_index* index; + if (exists && git_repository_index(&index, repo) == GIT_OK) { + git_index_read(index); + git_index_free(index); + } + } + IMPLEMENT_REFCOUNTING(GitRepository); }; @@ -264,6 +260,12 @@ bool Git::Execute(const CefString& name, return true; } + if (name == "refreshIndex") { + GitRepository *userData = (GitRepository *)object->GetUserData().get(); + userData->RefreshIndex(); + return true; + } + return false; } diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index fed7b5e59..1542a6641 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -2042,3 +2042,38 @@ describe "Editor", -> event.shiftKey = true editor.underlayer.trigger event expect(editor.getSelection().getScreenRange()).toEqual [[0,0], [12,2]] + + describe ".destroyEditSessionIndex(index)", -> + it "prompts to save dirty buffers before closing", -> + editor.setText("I'm dirty") + rootView.open('sample.txt') + expect(editor.getEditSessions().length).toBe 2 + spyOn(atom, "confirm") + editor.destroyEditSessionIndex(0) + expect(atom.confirm).toHaveBeenCalled() + expect(editor.getEditSessions().length).toBe 2 + expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy() + + describe ".destroyInactiveEditSessions()", -> + it "destroys every edit session except the active one", -> + rootView.open('sample.txt') + cssSession = rootView.open('css.css') + rootView.open('coffee.coffee') + rootView.open('hello.rb') + expect(editor.getEditSessions().length).toBe 5 + editor.setActiveEditSessionIndex(2) + editor.destroyInactiveEditSessions() + expect(editor.getActiveEditSessionIndex()).toBe 0 + expect(editor.getEditSessions().length).toBe 1 + expect(editor.getEditSessions()[0]).toBe cssSession + + it "prompts to save dirty buffers before destroying", -> + editor.setText("I'm dirty") + dirtySession = editor.activeEditSession + rootView.open('sample.txt') + expect(editor.getEditSessions().length).toBe 2 + spyOn(atom, "confirm") + editor.destroyInactiveEditSessions() + expect(atom.confirm).toHaveBeenCalled() + expect(editor.getEditSessions().length).toBe 2 + expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy() diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 9cd9103b2..2471d6cc3 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -2,8 +2,11 @@ $ = require 'jquery' fs = require 'fs' describe "Window", -> + [rootView] = [] + beforeEach -> window.attachRootView(require.resolve('fixtures')) + rootView = window.rootView afterEach -> window.shutdown() @@ -11,11 +14,16 @@ describe "Window", -> $(window).off 'beforeunload' describe ".close()", -> - it "is triggered by the 'close' event", -> + it "is triggered by the 'core:close' event", -> spyOn window, 'close' $(window).trigger 'core:close' expect(window.close).toHaveBeenCalled() + it "is triggered by the 'window:close event'", -> + spyOn window, 'close' + $(window).trigger 'window:close' + expect(window.close).toHaveBeenCalled() + describe ".reload()", -> it "returns false when no buffers are modified", -> spyOn($native, "reload") @@ -63,7 +71,7 @@ describe "Window", -> expect(atom.getRootViewStateForPath(window.rootView.project.getPath())).toBeUndefined() expectedState = JSON.parse(JSON.stringify(window.rootView.serialize())) # JSON.stringify removes keys with undefined values $(window).trigger 'beforeunload' - expect(atom.getRootViewStateForPath(window.rootView.project.getPath())).toEqual expectedState + expect(atom.getRootViewStateForPath(rootView.project.getPath())).toEqual expectedState it "unsubscribes from all buffers", -> rootView.open('sample.js') @@ -74,3 +82,12 @@ describe "Window", -> $(window).trigger 'beforeunload' expect(editor1.getBuffer().subscriptionCount()).toBe 0 + + describe ".shutdown()", -> + it "only deactivates the RootView the first time it is called", -> + deactivateSpy = spyOn(rootView, "deactivate").andCallThrough() + window.shutdown() + expect(rootView.deactivate).toHaveBeenCalled() + deactivateSpy.reset() + window.shutdown() + expect(rootView.deactivate).not.toHaveBeenCalled() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index b4a88884f..e6864ad60 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -148,7 +148,7 @@ class Editor extends View 'core:select-down': @selectDown 'core:select-to-top': @selectToTop 'core:select-to-bottom': @selectToBottom - 'core:close': @close + 'core:close': @destroyActiveEditSession 'editor:save': @save 'editor:newline-below': @insertNewlineBelow 'editor:toggle-soft-tabs': @toggleSoftTabs @@ -176,6 +176,7 @@ class Editor extends View 'editor:toggle-line-comments': @toggleLineCommentsInSelection 'editor:log-cursor-scope': @logCursorScope 'editor:checkout-head-revision': @checkoutHead + 'editor:close-other-editors': @destroyInactiveEditSessions documentation = {} for name, method of editorBindings @@ -446,17 +447,29 @@ class Editor extends View destroyActiveEditSession: -> @destroyEditSessionIndex(@getActiveEditSessionIndex()) - destroyEditSessionIndex: (index) -> - if @editSessions.length == 1 - @remove() - else - editSession = @editSessions[index] - if index is @getActiveEditSessionIndex() + destroyEditSessionIndex: (index, callback) -> + return if @mini + + editSession = @editSessions[index] + destroySession = => + if index is @getActiveEditSessionIndex() and @editSessions.length > 1 @loadPreviousEditSession() _.remove(@editSessions, editSession) editSession.destroy() @trigger 'editor:edit-session-removed', [editSession, index] + @remove() if @editSessions.length is 0 + callback(index) if callback + if editSession.buffer.isModified() + @promptToSaveDirtySession(editSession, destroySession) + else + destroySession(editSession) + + destroyInactiveEditSessions: -> + destroyIndex = (index) => + index++ if @activeEditSession is @editSessions[index] + @destroyEditSessionIndex(index, destroyIndex) if @editSessions[index] + destroyIndex(0) loadNextEditSession: -> nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length @setActiveEditSessionIndex(nextIndex) @@ -625,14 +638,14 @@ class Editor extends View @removeClass 'soft-wrap' $(window).off 'resize', @_setSoftWrapColumn - save: (onSuccess) -> + save: (session=@activeEditSession, onSuccess) -> if @getPath() - @activeEditSession.save() + session.save() onSuccess?() else atom.showSaveDialog (path) => if path - @activeEditSession.saveAs(path) + session.saveAs(path) onSuccess?() autosave: -> @@ -670,19 +683,16 @@ class Editor extends View rootView: -> @parents('#root-view').view() - close: -> - return if @mini - if @getBuffer().isModified() - filename = if @getPath() then fs.base(@getPath()) else "untitled buffer" - atom.confirm( - "'#{filename}' has changes, do you want to save them?" - "Your changes will be lost if you don't save them" - "Save", (=> @save(=> @destroyActiveEditSession())), - "Cancel", null - "Don't save", (=> @destroyActiveEditSession()) - ) - else - @destroyActiveEditSession() + promptToSaveDirtySession: (session, callback) -> + path = session.getPath() + filename = if path then fs.base(path) else "untitled buffer" + atom.confirm( + "'#{filename}' has changes, do you want to save them?" + "Your changes will be lost if you don't save them" + "Save", => @save(session, callback), + "Cancel", null + "Don't save", callback + ) remove: (selector, keepData) -> return super if keepData diff --git a/src/app/git.coffee b/src/app/git.coffee index 44c69a135..ea2dc1a0f 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -1,3 +1,5 @@ +$ = require 'jquery' + module.exports = class Git @@ -15,6 +17,9 @@ class Git constructor: (path) -> @repo = new GitRepository(path) + $(window).on 'focus', => @refreshIndex() + + refreshIndex: -> @repo.refreshIndex() getPath: -> @repo.getPath() @@ -31,19 +36,25 @@ class Git isPathIgnored: (path) -> @repo.isIgnored(@relativize(path)) - isPathModified: (path) -> + isStatusModified: (status) -> modifiedFlags = @statusFlags.working_dir_modified | @statusFlags.working_dir_delete | @statusFlags.working_dir_typechange | @statusFlags.index_modified | @statusFlags.index_deleted | @statusFlags.index_typechange - (@getPathStatus(path) & modifiedFlags) > 0 + (status & modifiedFlags) > 0 - isPathNew: (path) -> + isPathModified: (path) -> + @isStatusModified(@getPathStatus(path)) + + isStatusNew: (status) -> newFlags = @statusFlags.working_dir_new | @statusFlags.index_new - (@getPathStatus(path) & newFlags) > 0 + (status & newFlags) > 0 + + isPathNew: (path) -> + @isStatusNew(@getPathStatus(path)) relativize: (path) -> workingDirectory = @getWorkingDirectory() diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index afbd7f049..76cc697c6 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -23,6 +23,7 @@ 'pagedown': 'core:page-down' 'meta-S': 'window:save-all' + 'meta-W': 'window:close' 'meta-+': 'window:increase-font-size' 'meta--': 'window:decrease-font-size' 'ctrl-w w': 'window:focus-next-pane' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index cb3675501..78be6a273 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -4,7 +4,6 @@ 'meta-enter': 'editor:newline-below' 'tab': 'editor:indent' 'meta-d': 'editor:delete-line' - 'alt-meta-w': 'editor:toggle-soft-wrap' 'ctrl-[': 'editor:fold-current-row' 'ctrl-]': 'editor:unfold-current-row' 'ctrl-{': 'editor:fold-all' @@ -33,3 +32,4 @@ 'meta-alt-p': 'editor:log-cursor-scope' 'meta-u': 'editor:upper-case' 'meta-U': 'editor:lower-case' + 'alt-meta-w': 'editor:close-other-editors' diff --git a/src/app/window.coffee b/src/app/window.coffee index 7e306f44f..85077e84a 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -29,6 +29,7 @@ windowAdditions = @pasteboard = new Pasteboard $(window).on 'core:close', => @close() + $(window).command 'window:close', => @close() # This method is intended only to be run when starting a normal application # Note: RootView assigns itself on window on initialization so that @@ -46,7 +47,8 @@ windowAdditions = false shutdown: -> - @rootView.deactivate() + @rootView?.deactivate() + @rootView = null $(window).unbind('focus') $(window).unbind('blur') $(window).off('before') diff --git a/src/packages/event-palette/spec/event-palette-spec.coffee b/src/packages/event-palette/spec/event-palette-spec.coffee index 53e8448ee..44dd5bb6b 100644 --- a/src/packages/event-palette/spec/event-palette-spec.coffee +++ b/src/packages/event-palette/spec/event-palette-spec.coffee @@ -30,6 +30,20 @@ describe "EventPalette", -> else expect(eventLi).not.toExist() + it "displays all events registerd on the window", -> + editorEvents = rootView.getActiveEditor().events() + windowEvents = $(window).events() + expect(_.isEmpty(windowEvents)).toBeFalsy() + for eventName, description of windowEvents + eventLi = palette.list.children("[data-event-name='#{eventName}']") + description = editorEvents[eventName] unless description + if description + expect(eventLi).toExist() + expect(eventLi.find('.event-name')).toHaveText(eventName) + expect(eventLi.find('.event-description')).toHaveText(description) + else + expect(eventLi).not.toExist() + it "focuses the mini-editor and selects the first event", -> expect(palette.miniEditor.isFocused).toBeTruthy() expect(palette.find('.event:first')).toHaveClass 'selected' diff --git a/src/packages/event-palette/src/event-palette.coffee b/src/packages/event-palette/src/event-palette.coffee index 071f67edc..9e34b797e 100644 --- a/src/packages/event-palette/src/event-palette.coffee +++ b/src/packages/event-palette/src/event-palette.coffee @@ -28,7 +28,7 @@ class EventPalette extends SelectList @keyBindings = _.losslessInvert(keymap.bindingsForElement(@previouslyFocusedElement)) events = [] - for eventName, eventDescription of @previouslyFocusedElement.events() + for eventName, eventDescription of _.extend($(window).events(), @previouslyFocusedElement.events()) events.push({eventName, eventDescription}) if eventDescription events = _.sortBy events, (e) -> e.eventDescription diff --git a/src/packages/status-bar/src/status-bar.coffee b/src/packages/status-bar/src/status-bar.coffee index 841a0bce5..a43302b25 100644 --- a/src/packages/status-bar/src/status-bar.coffee +++ b/src/packages/status-bar/src/status-bar.coffee @@ -76,7 +76,10 @@ class StatusBar extends View @gitStatusIcon.addClass('git-status octicons') git = @buffer.getRepo() - if git?.isPathModified(path) + return unless git + + status = git.getPathStatus(path) + if git.isStatusModified(status) @gitStatusIcon.addClass('modified-status-icon') stats = git.getDiffStats(path) if stats.added and stats.deleted @@ -87,7 +90,7 @@ class StatusBar extends View @gitStatusIcon.text("-#{stats.deleted}") else @gitStatusIcon.text('') - else if git?.isPathNew(path) + else if git.isStatusNew(status) @gitStatusIcon.addClass('new-status-icon') @gitStatusIcon.text("+#{@buffer.getLineCount()}") diff --git a/src/packages/tree-view/src/directory-view.coffee b/src/packages/tree-view/src/directory-view.coffee index 55a2eb1ee..3614f79da 100644 --- a/src/packages/tree-view/src/directory-view.coffee +++ b/src/packages/tree-view/src/directory-view.coffee @@ -23,17 +23,15 @@ class DirectoryView extends View @disclosureArrow.on 'click', => @toggleExpansion() repo = @project.repo + iconClass = 'directory-icon' if repo? path = @directory.getPath() - @directoryName.addClass('ignored') if repo.isPathIgnored(path) - if path is repo.getWorkingDirectory() - @directoryName.addClass('repository-icon') - else if repo.isSubmodule(path) - @directoryName.addClass('submodule-icon') + if parent + @directoryName.addClass('ignored') if repo.isPathIgnored(path) + iconClass = 'submodule-icon' if repo.isSubmodule(path) else - @directoryName.addClass('directory-icon') - else - @directoryName.addClass('directory-icon') + iconClass = 'repository-icon' if path is repo.getWorkingDirectory() + @directoryName.addClass(iconClass) getPath: -> @directory.path diff --git a/src/packages/tree-view/src/file-view.coffee b/src/packages/tree-view/src/file-view.coffee index c0c7db492..ea75356da 100644 --- a/src/packages/tree-view/src/file-view.coffee +++ b/src/packages/tree-view/src/file-view.coffee @@ -36,10 +36,12 @@ class FileView extends View path = @getPath() if repo.isPathIgnored(path) @addClass('ignored') - else if repo.isPathModified(path) - @addClass('modified') - else if repo.isPathNew(path) - @addClass('new') + else + status = repo.getPathStatus(path) + if repo.isStatusModified(status) + @addClass('modified') + else if repo.isStatusNew(status) + @addClass('new') getPath: -> @file.path