Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,17 @@ define(function (require, exports, module) {
return CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true })
.done(function () {
_windowGoingAway = true;

// Give everyone a chance to save their state - but don't let any problems block
// us from quitting
try {
$(ProjectManager).triggerHandler("beforeAppClose");
} catch (ex) {
console.error(ex);
}

PreferencesManager.savePreferences();

postCloseHandler();
})
.fail(function () {
Expand Down
51 changes: 15 additions & 36 deletions src/document/DocumentManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,6 @@ define(function (require, exports, module) {
*/
var _documentNavPending = false;

/**
* While true, allow preferences to be saved
* @type {boolean}
*/
var _isProjectChanging = false;

/**
* All documents with refCount > 0. Maps Document.file.fullPath -> Document.
* @private
Expand Down Expand Up @@ -1049,14 +1043,9 @@ define(function (require, exports, module) {

/**
* @private
* Preferences callback. Saves the document file paths for the working set.
* Preferences callback. Saves the state of the working set.
*/
function _savePreferences() {

if (_isProjectChanging) {
return;
}

// save the working set file paths
var files = [],
isActive = false,
Expand All @@ -1071,42 +1060,26 @@ define(function (require, exports, module) {
workingSet.forEach(function (file, index) {
// flag the currently active editor
isActive = currentDoc && (file.fullPath === currentDoc.file.fullPath);


// save editor UI state for just the working set
var viewState = EditorManager._getViewState(file.fullPath);

files.push({
file: file.fullPath,
active: isActive
active: isActive,
viewState: viewState
});
});

// append file root to make file list unique for each project
_prefs.setValue("files_" + projectRoot.fullPath, files);
}

/**
* @private
* Handle beforeProjectClose event
*/
function _beforeProjectClose() {
_savePreferences();

// When app is shutdown via shortcut key, the command goes directly to the
// app shell, so we can't reliably fire the beforeProjectClose event on
// app shutdown. To compensate, we listen for currentDocumentChange,
// workingSetAdd, and workingSetRemove events so that the prefs for
// last project used get updated. But when switching projects, after
// the beforeProjectChange event gets fired, DocumentManager.closeAll()
// causes workingSetRemove event to get fired and update the prefs to an empty
// list. So, temporarily (until projectOpen event) disallow saving prefs.
_isProjectChanging = true;
}

/**
* @private
* Initializes the working set.
*/
function _projectOpen(e) {
_isProjectChanging = false;

// file root is appended for each project
var projectRoot = ProjectManager.getProjectRoot(),
files = _prefs.getValue("files_" + projectRoot.fullPath);
Expand All @@ -1116,6 +1089,7 @@ define(function (require, exports, module) {
}

var filesToOpen = [],
viewStates = {},
activeFile;

// Add all files to the working set without verifying that
Expand All @@ -1125,8 +1099,14 @@ define(function (require, exports, module) {
if (value.active) {
activeFile = value.file;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to clarify here. Is this comment no longer valid about beforeProjectClose being unreliable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As best I can tell, "beforeProjectClose" is never fired on app quit -- only when switching from an old project to a new project. So in that sense it's actually quite reliable :-) I'm assuming the comment just means it's not a reliable point to save preferences... (Unless back in the day we actually did dispatch it on quit sometimes... but I couldn't find anything in the ProjectManager history to suggest that, at least).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterflynn is correct that beforeProjectClose never fired on app quit.

}
if (value.viewState) {
viewStates[value.file] = value.viewState;
}
});
addListToWorkingSet(filesToOpen);

// Allow for restoring saved editor UI state
EditorManager._resetViewStates(viewStates);

// Initialize the active editor
if (!activeFile && _workingSet.length > 0) {
Expand Down Expand Up @@ -1213,12 +1193,11 @@ define(function (require, exports, module) {

// Setup preferences
_prefs = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID);
$(exports).bind("currentDocumentChange workingSetAdd workingSetAddList workingSetRemove workingSetRemoveList fileNameChange workingSetReorder workingSetSort", _savePreferences);

// Performance measurements
PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()");

// Handle project change events
$(ProjectManager).on("projectOpen", _projectOpen);
$(ProjectManager).on("beforeProjectClose", _beforeProjectClose);
$(ProjectManager).on("beforeProjectClose beforeAppClose", _savePreferences);
});
53 changes: 53 additions & 0 deletions src/editor/EditorManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ define(function (require, exports, module) {
*/
var _lastFocusedEditor = null;

/**
* Maps full path to scroll pos & cursor/selection info. Not kept up to date while an editor is current.
* Only updated when switching / closing editor, or when requested explicitly via _getViewState().
* @type {Object<string, {scrollPos:{x:number, y:number}, selection:{start:{line:number, ch:number}, end:{line:number, ch:number}}}>}
*/
var _viewStateCache = {};

/**
* Last known editor area width, used to detect when the window is resized horizontally.
*/
Expand Down Expand Up @@ -394,6 +401,42 @@ define(function (require, exports, module) {
}


/** Updates _viewStateCache from the given editor's actual current state */
function _saveEditorViewState(editor) {
_viewStateCache[editor.document.file.fullPath] = {
selection: editor.getSelection(),
scrollPos: editor.getScrollPos()
};
}

/** Updates the given editor's actual state from _viewStateCache, if any state stored */
function _restoreEditorViewState(editor) {
// We want to ignore the current state of the editor, so don't call _getViewState()
var viewState = _viewStateCache[editor.document.file.fullPath];
if (viewState) {
if (viewState.selection) {
editor.setSelection(viewState.selection.start, viewState.selection.end);
}
if (viewState.scrollPos) {
editor.setScrollPos(viewState.scrollPos.x, viewState.scrollPos.y);
}
}
}

/** Returns up-to-date view state for the given file, or null if file not open and no state cached */
function _getViewState(fullPath) {
if (_currentEditorsDocument && _currentEditorsDocument.file.fullPath === fullPath) {
_saveEditorViewState(_currentEditor);
}
return _viewStateCache[fullPath];
}

/** Removes all cached view state info and replaces it with the given mapping */
function _resetViewStates(viewStates) {
_viewStateCache = viewStates;
}


/**
* @private
* @param {?Editor} current
Expand Down Expand Up @@ -437,23 +480,31 @@ define(function (require, exports, module) {
if (!_currentEditor) {
$("#not-editor").css("display", "none");
} else {
_saveEditorViewState(_currentEditor);
_currentEditor.setVisible(false);
_destroyEditorIfUnneeded(_currentEditorsDocument);
}

// Ensure a main editor exists for this document to show in the UI
var createdNewEditor = false;
if (!document._masterEditor) {
createdNewEditor = true;
// Editor doesn't exist: populate a new Editor with the text
_createFullEditorForDocument(document);
}

_doShow(document);

if (createdNewEditor) {
_restoreEditorViewState(document._masterEditor);
}
}


/** Hide the currently visible editor and show a placeholder UI in its place */
function _showNoEditor() {
if (_currentEditor) {
_saveEditorViewState(_currentEditor);
_currentEditor.setVisible(false);
_destroyEditorIfUnneeded(_currentEditorsDocument);

Expand Down Expand Up @@ -811,6 +862,8 @@ define(function (require, exports, module) {
exports._notifyActiveEditorChanged = _notifyActiveEditorChanged;
exports._createFullEditorForDocument = _createFullEditorForDocument;
exports._destroyEditorIfUnneeded = _destroyEditorIfUnneeded;
exports._getViewState = _getViewState;
exports._resetViewStates = _resetViewStates;
exports._doShow = _doShow;

exports.REFRESH_FORCE = REFRESH_FORCE;
Expand Down
1 change: 1 addition & 0 deletions src/project/ProjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*
* This module dispatches these events:
* - beforeProjectClose -- before _projectRoot changes
* - beforeAppClose -- before Brackets quits entirely
* - projectOpen -- after _projectRoot changes
* - projectFilesChange -- sent if one of the project files has changed--
* added, removed, renamed, etc.
Expand Down
49 changes: 30 additions & 19 deletions src/search/QuickOpen.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,25 @@ define(function (require, exports, module) {
var fileListPromise;

/**
* Remembers the current document that was displayed when showDialog() was called
* The current document is restored if the user presses escape
* @type {string} full path
* Remembers the current document that was displayed when showDialog() was called.
* TODO: in the future, if focusing an item can switch documents, need to restore this on Escape.
* @type {?string} full path
*/
var origDocPath;

/**
* Remembers the selection in the document origDocPath that was present when showDialog() was called.
* Focusing on an item can cause the current and and/or selection to change, so this variable restores it.
* The cursor position is restored if the user presses escape.
* @type ?{start:{line:number, ch:number}, end:{line:number, ch:number}}
* Remembers the selection in origDocPath that was present when showDialog() was called. Focusing on an
* item can change the selection; we restore this original selection if the user presses Escape. Null if
* no document was open when Quick Open was invoked.
* @type {?{start:{line:number, ch:number}, end:{line:number, ch:number}}}
*/
var origSelection;

/**
* Remembers the scroll position in origDocPath when showDialog() was called (see origSelection above).
* @type {?{x:number, y:number}}
*/
var origScrollPos;

/** @type {boolean} */
var dialogOpen = false;
Expand Down Expand Up @@ -343,17 +349,14 @@ define(function (require, exports, module) {
var self = this;
setTimeout(function () {
if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) {
// Restore original document & selection / scroll pos
if (origDocPath) {
CommandManager.execute(Commands.FILE_OPEN, {fullPath: origDocPath})
.done(function () {
if (origSelection) {
EditorManager.getCurrentFullEditor().setSelection(origSelection.start, origSelection.end, true);
}
});
}

self._close();
self._close()
.done(function () {
// Restore original selection / scroll pos
if (origSelection) {
EditorManager.getCurrentFullEditor().setSelection(origSelection.start, origSelection.end);
EditorManager.getCurrentFullEditor().setScrollPos(origScrollPos.x, origScrollPos.y);
}
});

} else if (e.keyCode === KeyEvent.DOM_VK_RETURN) {
self._handleItemSelect(null, $(".smart_autocomplete_highlight").get(0)); // calls _close() too
Expand Down Expand Up @@ -406,10 +409,11 @@ define(function (require, exports, module) {
/**
* Closes the search dialog and notifies all quick open plugins that
* searching is done.
* @return {$.Promise} Resolved when the search bar is entirely closed.
*/
QuickNavigateDialog.prototype._close = function () {
if (!dialogOpen) {
return;
return new $.Deferred().reject();
}
dialogOpen = false;

Expand All @@ -432,14 +436,18 @@ define(function (require, exports, module) {
// (because it's a later handler of the event that just triggered _close()), and that code expects to
// find metadata that it stuffed onto the DOM node earlier. But $.remove() strips that metadata.
// So we wait until after this call chain is complete before actually closing the dialog.
var result = new $.Deferred();
var self = this;
setTimeout(function () {
self.modalBar.close();
result.resolve();
}, 0);

$(".smart_autocomplete_container").remove();

$(window.document).off("mousedown", this._handleDocumentMouseDown);

return result.promise();
};

/**
Expand Down Expand Up @@ -730,12 +738,15 @@ define(function (require, exports, module) {
//JSLintUtils.setEnabled(false);

// Record current document & cursor pos so we can restore it if search is canceled
// We record scroll pos *before* modal bar is opened since we're going to restore it *after* it's closed
var curDoc = DocumentManager.getCurrentDocument();
origDocPath = curDoc ? curDoc.file.fullPath : null;
if (curDoc) {
origSelection = EditorManager.getCurrentFullEditor().getSelection();
origScrollPos = EditorManager.getCurrentFullEditor().getScrollPos();
} else {
origSelection = null;
origScrollPos = null;
}

// Show the search bar ("dialog")
Expand Down
12 changes: 10 additions & 2 deletions src/widgets/ModalBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ define(function (require, exports, module) {
}
EditorManager.resizeEditor();
if (fullEditor) {
fullEditor._codeMirror.scrollTo(scrollPos.x, scrollPos.y + this._$root.outerHeight());
fullEditor._codeMirror.scrollTo(scrollPos.x, scrollPos.y + this.height());
}
}

Expand All @@ -97,11 +97,19 @@ define(function (require, exports, module) {
return $("input[type='text']", this._$root).first();
};

/**
* @return {number} Height of the modal bar in pixels, if open.
*/
ModalBar.prototype.height = function () {
return this._$root.outerHeight();
};

/**
* Closes the modal bar and returns focus to the active editor.
*/
ModalBar.prototype.close = function () {
var barHeight = this._$root.outerHeight();
// Store our height before closing, while we can still measure it
var barHeight = this.height();

if (this._autoClose) {
window.document.body.removeEventListener("focusin", this._handleFocusChange, true);
Expand Down