diff --git a/.jshintrc b/.jshintrc index 6abd671636f..7916402f071 100644 --- a/.jshintrc +++ b/.jshintrc @@ -13,7 +13,7 @@ "regexp" : true, "undef" : true, "strict" : true, - "trailing" : false, + "unused" : "vars", "asi" : false, "boss" : false, @@ -35,15 +35,14 @@ "proto" : false, "regexdash" : false, "scripturl" : false, - "smarttabs" : false, "shadow" : false, "sub" : false, "supernew" : false, "validthis" : false, - "browser" : true, + "browser" : false, "couch" : false, - "devel" : false, + "devel" : true, "dojo" : false, "jquery" : false, "mootools" : false, @@ -52,24 +51,20 @@ "prototypejs" : false, "rhino" : false, "wsh" : false, - - "nomen" : false, - "onevar" : false, - "passfail" : false, - "white" : false, "maxerr" : 100, - "predef" : [ - ], "indent" : 4, - "globals" : [ - "require", - "define", - "brackets", - "$", - "PathUtils", - "window", - "navigator", - "Mustache" - ] -} \ No newline at end of file + "globals" : { + "window": false, + "document": false, + "setTimeout": false, + "require": false, + "define": false, + "brackets": false, + "$": false, + "PathUtils": false, + "window": false, + "navigator": false, + "Mustache": false + } +} diff --git a/Gruntfile.js b/Gruntfile.js index 9cf1ac7f24f..96d96d12f53 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -27,8 +27,6 @@ module.exports = function (grunt) { // load dependencies require('load-grunt-tasks')(grunt, {pattern: ['grunt-contrib-*', 'grunt-targethtml', 'grunt-usemin']}); grunt.loadTasks('tasks'); - - var common = require("./tasks/lib/common")(grunt); // Project configuration. grunt.initConfig({ @@ -312,9 +310,9 @@ module.exports = function (grunt) { grunt.registerTask('test', ['jshint:all', 'jasmine']); // grunt.registerTask('test', ['jshint:all', 'jasmine', 'jasmine_node']); - // task: set-sprint - // Update sprint number in package.json and rewrite src/config.json - grunt.registerTask('set-sprint', ['update-sprint-number', 'write-config']); + // task: set-release + // Update version number in package.json and rewrite src/config.json + grunt.registerTask('set-release', ['update-release-number', 'write-config']); // task: build grunt.registerTask('build', [ diff --git a/src/LiveDevelopment/Agents/DOMAgent.js b/src/LiveDevelopment/Agents/DOMAgent.js index 36cb8fad443..f27ec6f621c 100644 --- a/src/LiveDevelopment/Agents/DOMAgent.js +++ b/src/LiveDevelopment/Agents/DOMAgent.js @@ -41,7 +41,6 @@ define(function DOMAgent(require, exports, module) { var $exports = $(exports); var Inspector = require("LiveDevelopment/Inspector/Inspector"); - var RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"); var EditAgent = require("LiveDevelopment/Agents/EditAgent"); var DOMNode = require("LiveDevelopment/Agents/DOMNode"); var DOMHelpers = require("LiveDevelopment/Agents/DOMHelpers"); @@ -123,14 +122,6 @@ define(function DOMAgent(require, exports, module) { Inspector.DOM.requestChildNodes(node.nodeId); } - /** Resolve a node - * @param {DOMNode} node - */ - function resolveNode(node, callback) { - console.assert(node.nodeId, "Attempted to resolve node without id"); - Inspector.DOM.resolveNode(node.nodeId, callback); - } - /** Eliminate the query string from a URL * @param {string} URL */ @@ -332,4 +323,4 @@ define(function DOMAgent(require, exports, module) { exports.applyChange = applyChange; exports.load = load; exports.unload = unload; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/Agents/DOMHelpers.js b/src/LiveDevelopment/Agents/DOMHelpers.js index 86551f63c90..96121762c93 100644 --- a/src/LiveDevelopment/Agents/DOMHelpers.js +++ b/src/LiveDevelopment/Agents/DOMHelpers.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $ */ +/*global define */ /** * DOMHelpers is a collection of functions used by the DOMAgent exports `eachNode(src, callback)` @@ -230,7 +230,6 @@ define(function DOMHelpersModule(require, exports, module) { function eachNode(src, callback) { var index = 0; var text, range, length, payload; - var x = 0; while (index < src.length) { // find the next tag @@ -267,4 +266,4 @@ define(function DOMHelpersModule(require, exports, module) { // Export public functions exports.extractPayload = extractPayload; exports.eachNode = eachNode; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/Agents/GotoAgent.js b/src/LiveDevelopment/Agents/GotoAgent.js index 0bcdf0e6401..e5eac3a1347 100644 --- a/src/LiveDevelopment/Agents/GotoAgent.js +++ b/src/LiveDevelopment/Agents/GotoAgent.js @@ -40,6 +40,7 @@ define(function GotoAgent(require, exports, module) { var DocumentManager = require("document/DocumentManager"); var EditorManager = require("editor/EditorManager"); + var MainViewManager = require("view/MainViewManager"); /** Return the URL without the query string * @param {string} URL @@ -66,7 +67,6 @@ define(function GotoAgent(require, exports, module) { */ function _makeHTMLTarget(targets, node) { if (node.location) { - var target = {}; var url = DOMAgent.url; var location = node.location; if (node.canHaveChildren()) { @@ -85,7 +85,6 @@ define(function GotoAgent(require, exports, module) { */ function _makeCSSTarget(targets, rule) { if (rule.sourceURL) { - var target = {}; var url = rule.sourceURL; url += ":" + rule.style.range.start; var name = rule.selectorList.text; @@ -101,7 +100,6 @@ define(function GotoAgent(require, exports, module) { function _makeJSTarget(targets, callFrame) { var script = ScriptAgent.scriptWithId(callFrame.location.scriptId); if (script && script.url) { - var target = {}; var url = script.url; url += ":" + callFrame.location.lineNumber + "," + callFrame.location.columnNumber; var name = callFrame.functionName; @@ -120,7 +118,7 @@ define(function GotoAgent(require, exports, module) { // get all css rules that apply to the given node Inspector.CSS.getMatchedStylesForNode(node.nodeId, function onMatchedStyles(res) { - var i, callFrame, name, script, url, rule, targets = []; + var i, targets = []; _makeHTMLTarget(targets, node); for (i in node.trace) { _makeJSTarget(targets, node.trace[i]); @@ -172,7 +170,7 @@ define(function GotoAgent(require, exports, module) { path = decodeURI(path); var promise = DocumentManager.getDocumentForPath(path); promise.done(function onDone(doc) { - DocumentManager.setCurrentDocument(doc); + MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); if (location) { openLocation(location, noFlash); } @@ -220,4 +218,4 @@ define(function GotoAgent(require, exports, module) { exports.open = open; exports.load = load; exports.unload = unload; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/Agents/RemoteAgent.js b/src/LiveDevelopment/Agents/RemoteAgent.js index 6f9d1202552..031768969e4 100644 --- a/src/LiveDevelopment/Agents/RemoteAgent.js +++ b/src/LiveDevelopment/Agents/RemoteAgent.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, XMLHttpRequest, window */ +/*global define, $, window */ /** * RemoteAgent defines and provides an interface for custom remote functions diff --git a/src/LiveDevelopment/Agents/RemoteFunctions.js b/src/LiveDevelopment/Agents/RemoteFunctions.js index 242377d063a..ddda39f76f3 100644 --- a/src/LiveDevelopment/Agents/RemoteFunctions.js +++ b/src/LiveDevelopment/Agents/RemoteFunctions.js @@ -23,7 +23,8 @@ /*jslint vars: true, plusplus: true, browser: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, window, navigator, Node, console */ +/*jshint unused: false */ +/*global window, navigator, Node, console */ /*theseus instrument: false */ /** @@ -814,4 +815,4 @@ function RemoteFunctions(experimental) { "applyDOMEdits" : applyDOMEdits, "getSimpleDOM" : getSimpleDOM }; -} \ No newline at end of file +} diff --git a/src/LiveDevelopment/Agents/ScriptAgent.js b/src/LiveDevelopment/Agents/ScriptAgent.js index 144c9d7622c..26604535d7d 100644 --- a/src/LiveDevelopment/Agents/ScriptAgent.js +++ b/src/LiveDevelopment/Agents/ScriptAgent.js @@ -40,15 +40,6 @@ define(function ScriptAgent(require, exports, module) { var _idToScript; // id -> script info var _insertTrace; // the last recorded trace of a DOM insertion - /** Add a call stack trace to a node - * @param {integer} node id - * @param [{Debugger.CallFrame}] call stack - */ - function _addTraceToNode(nodeId, trace) { - var node = DOMAgent.nodeWithId(nodeId); - node.trace = trace; - } - // TODO: should the parameter to this be an ID rather than a URL? /** Get the script information for a given url * @param {string} url diff --git a/src/LiveDevelopment/Documents/CSSDocument.js b/src/LiveDevelopment/Documents/CSSDocument.js index 23e077cc58c..13df847e5ed 100644 --- a/src/LiveDevelopment/Documents/CSSDocument.js +++ b/src/LiveDevelopment/Documents/CSSDocument.js @@ -176,7 +176,6 @@ define(function CSSDocumentModule(require, exports, module) { CSSDocument.prototype.updateHighlight = function () { if (Inspector.config.highlight && this.editor) { var editor = this.editor, - codeMirror = editor._codeMirror, selectors = []; _.each(this.editor.getSelections(), function (sel) { var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); diff --git a/src/LiveDevelopment/Documents/CSSPreprocessorDocument.js b/src/LiveDevelopment/Documents/CSSPreprocessorDocument.js index 91aeda2d576..5c74ed48cea 100644 --- a/src/LiveDevelopment/Documents/CSSPreprocessorDocument.js +++ b/src/LiveDevelopment/Documents/CSSPreprocessorDocument.js @@ -97,7 +97,6 @@ define(function CSSPreprocessorDocumentModule(require, exports, module) { CSSPreprocessorDocument.prototype.updateHighlight = function () { if (Inspector.config.highlight && this.editor) { var editor = this.editor, - codeMirror = editor._codeMirror, selectors = []; _.each(this.editor.getSelections(), function (sel) { var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); diff --git a/src/LiveDevelopment/Documents/HTMLDocument.js b/src/LiveDevelopment/Documents/HTMLDocument.js index cce9296bb4a..f5ec62e04e9 100644 --- a/src/LiveDevelopment/Documents/HTMLDocument.js +++ b/src/LiveDevelopment/Documents/HTMLDocument.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, brackets */ +/*global define, $ */ /** * HTMLDocument manages a single HTML source document @@ -44,16 +44,13 @@ define(function HTMLDocumentModule(require, exports, module) { "use strict"; - var DocumentManager = require("document/DocumentManager"), - DOMAgent = require("LiveDevelopment/Agents/DOMAgent"), - EditorManager = require("editor/EditorManager"), + var EditorManager = require("editor/EditorManager"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"), HTMLInstrumentation = require("language/HTMLInstrumentation"), Inspector = require("LiveDevelopment/Inspector/Inspector"), LiveDevelopment = require("LiveDevelopment/LiveDevelopment"), PerfUtils = require("utils/PerfUtils"), RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"), - StringUtils = require("utils/StringUtils"), _ = require("thirdparty/lodash"); /** @@ -62,8 +59,6 @@ define(function HTMLDocumentModule(require, exports, module) { * @param {!Editor} editor The editor for this document */ var HTMLDocument = function HTMLDocument(doc, editor) { - var self = this; - this.doc = doc; if (this.doc) { this.doc.addRef(); @@ -155,6 +150,10 @@ define(function HTMLDocumentModule(require, exports, module) { self._onChange(event, editor, change); }); + $(this.editor).on("beforeDestroy.HTMLDocument", function (event, editor) { + self._onDestroy(event, editor); + }); + // Experimental code if (LiveDevelopment.config.experimental) { $(HighlightAgent).on("highlight.HTMLDocument", function (event, node) { @@ -186,7 +185,6 @@ define(function HTMLDocumentModule(require, exports, module) { */ HTMLDocument.prototype.updateHighlight = function () { var editor = this.editor, - codeMirror = editor._codeMirror, ids = []; if (Inspector.config.highlight) { _.each(this.editor.getSelections(), function (sel) { @@ -266,6 +264,18 @@ define(function HTMLDocumentModule(require, exports, module) { }); }; + /** + * Triggered when the editor is being destroyed + * @param {$.Event} event Event + * @param {!Editor} editor The editor being destroyed + */ + HTMLDocument.prototype._onDestroy = function (event, editor) { + if (this.editor === editor) { + this.detachFromEditor(); + } + }; + + /** * Triggered on change by the editor * @param {$.Event} event Event diff --git a/src/LiveDevelopment/Inspector/Inspector.js b/src/LiveDevelopment/Inspector/Inspector.js index 87dcf8a7177..fa36e1bb974 100644 --- a/src/LiveDevelopment/Inspector/Inspector.js +++ b/src/LiveDevelopment/Inspector/Inspector.js @@ -24,7 +24,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, WebSocket, FileError, window, XMLHttpRequest */ +/*global define, $, WebSocket, FileError, XMLHttpRequest */ /** * Inspector manages the connection to Chrome/Chromium's remote debugger. @@ -395,7 +395,7 @@ define(function Inspector(require, exports, module) { var InspectorText = require("text!LiveDevelopment/Inspector/Inspector.json"), InspectorJSON = JSON.parse(InspectorText); - var i, j, domain, domainDef, command; + var i, j, domain, command; for (i in InspectorJSON.domains) { domain = InspectorJSON.domains[i]; exports[domain.domain] = {}; diff --git a/src/LiveDevelopment/LiveDevServerManager.js b/src/LiveDevelopment/LiveDevServerManager.js index dd6c65d1106..f59e6d12e63 100644 --- a/src/LiveDevelopment/LiveDevServerManager.js +++ b/src/LiveDevelopment/LiveDevServerManager.js @@ -45,9 +45,6 @@ define(function (require, exports, module) { "use strict"; - var FileUtils = require("file/FileUtils"), - ProjectManager = require("project/ProjectManager"); - var _serverProviders = []; /** diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index 8ea62eb40a6..342ba5fb853 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -68,38 +68,39 @@ define(function LiveDevelopment(require, exports, module) { var _ = require("thirdparty/lodash"); // Status Codes - var STATUS_ERROR = exports.STATUS_ERROR = -1; - var STATUS_INACTIVE = exports.STATUS_INACTIVE = 0; - var STATUS_CONNECTING = exports.STATUS_CONNECTING = 1; - var STATUS_LOADING_AGENTS = exports.STATUS_LOADING_AGENTS = 2; - var STATUS_ACTIVE = exports.STATUS_ACTIVE = 3; - var STATUS_OUT_OF_SYNC = exports.STATUS_OUT_OF_SYNC = 4; - var STATUS_SYNC_ERROR = exports.STATUS_SYNC_ERROR = 5; - - var Async = require("utils/Async"), - Dialogs = require("widgets/Dialogs"), - DefaultDialogs = require("widgets/DefaultDialogs"), - DocumentManager = require("document/DocumentManager"), - EditorManager = require("editor/EditorManager"), - FileServer = require("LiveDevelopment/Servers/FileServer").FileServer, - FileSystemError = require("filesystem/FileSystemError"), - FileUtils = require("file/FileUtils"), - LiveDevServerManager = require("LiveDevelopment/LiveDevServerManager"), - NativeApp = require("utils/NativeApp"), - PreferencesDialogs = require("preferences/PreferencesDialogs"), - ProjectManager = require("project/ProjectManager"), - Strings = require("strings"), - StringUtils = require("utils/StringUtils"), - UserServer = require("LiveDevelopment/Servers/UserServer").UserServer; + var STATUS_ERROR = exports.STATUS_ERROR = -1; + var STATUS_INACTIVE = exports.STATUS_INACTIVE = 0; + var STATUS_CONNECTING = exports.STATUS_CONNECTING = 1; + var STATUS_LOADING_AGENTS = exports.STATUS_LOADING_AGENTS = 2; + var STATUS_ACTIVE = exports.STATUS_ACTIVE = 3; + var STATUS_OUT_OF_SYNC = exports.STATUS_OUT_OF_SYNC = 4; + var STATUS_SYNC_ERROR = exports.STATUS_SYNC_ERROR = 5; + + var Async = require("utils/Async"), + Dialogs = require("widgets/Dialogs"), + DefaultDialogs = require("widgets/DefaultDialogs"), + DocumentManager = require("document/DocumentManager"), + EditorManager = require("editor/EditorManager"), + FileServer = require("LiveDevelopment/Servers/FileServer").FileServer, + FileSystemError = require("filesystem/FileSystemError"), + FileUtils = require("file/FileUtils"), + LiveDevServerManager = require("LiveDevelopment/LiveDevServerManager"), + MainViewManager = require("view/MainViewManager"), + NativeApp = require("utils/NativeApp"), + PreferencesDialogs = require("preferences/PreferencesDialogs"), + ProjectManager = require("project/ProjectManager"), + Strings = require("strings"), + StringUtils = require("utils/StringUtils"), + UserServer = require("LiveDevelopment/Servers/UserServer").UserServer; // Inspector - var Inspector = require("LiveDevelopment/Inspector/Inspector"); + var Inspector = require("LiveDevelopment/Inspector/Inspector"); // Documents - var CSSDocument = require("LiveDevelopment/Documents/CSSDocument"), + var CSSDocument = require("LiveDevelopment/Documents/CSSDocument"), CSSPreprocessorDocument = require("LiveDevelopment/Documents/CSSPreprocessorDocument"), - HTMLDocument = require("LiveDevelopment/Documents/HTMLDocument"), - JSDocument = require("LiveDevelopment/Documents/JSDocument"); + HTMLDocument = require("LiveDevelopment/Documents/HTMLDocument"), + JSDocument = require("LiveDevelopment/Documents/JSDocument"); // Document errors var SYNC_ERROR_CLASS = "live-preview-sync-error"; @@ -185,7 +186,7 @@ define(function LiveDevelopment(require, exports, module) { * @type {BaseServer} */ var _server; - + function _isPromisePending(promise) { return promise && promise.state() === "pending"; } @@ -266,6 +267,16 @@ define(function LiveDevelopment(require, exports, module) { }); } + /** + * @private + * Make a message to direct users to the troubleshooting page + * @param {string} msg Original message + * @return {string} Original message plus link to troubleshooting page. + */ + function _makeTroubleshootingMessage(msg) { + return msg + " " + StringUtils.format(Strings.LIVE_DEVELOPMENT_TROUBLESHOOTING, brackets.config.troubleshoot_url); + } + /** * @private * Close a live document @@ -326,7 +337,6 @@ define(function LiveDevelopment(require, exports, module) { function _handleLiveDocumentStatusChanged(liveDocument) { var startLine, endLine, - lineInfo, i, lineHandle, status = (liveDocument.errors.length) ? STATUS_SYNC_ERROR : STATUS_ACTIVE; @@ -438,8 +448,7 @@ define(function LiveDevelopment(require, exports, module) { * @param {Document} doc */ function _docIsOutOfSync(doc) { - var docClass = _classForDocument(doc), - liveDoc = _server && _server.get(doc.file.fullPath), + var liveDoc = _server && _server.get(doc.file.fullPath), isLiveEditingEnabled = liveDoc && liveDoc.isLiveEditingEnabled(); return doc.isDirty && !isLiveEditingEnabled; @@ -575,8 +584,6 @@ define(function LiveDevelopment(require, exports, module) { } var result = new $.Deferred(), - promises = [], - enableAgentsPromise, allAgentsPromise; _loadAgentsPromise = result.promise(); @@ -624,7 +631,7 @@ define(function LiveDevelopment(require, exports, module) { Dialogs.showModalDialog( Dialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, - Strings.LIVE_DEV_LOADING_ERROR_MESSAGE + _makeTroubleshootingMessage(Strings.LIVE_DEV_LOADING_ERROR_MESSAGE) ); }) .always(function () { @@ -1092,7 +1099,7 @@ define(function LiveDevelopment(require, exports, module) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, - Strings.LIVE_DEV_LOADING_ERROR_MESSAGE + _makeTroubleshootingMessage(Strings.LIVE_DEV_LOADING_ERROR_MESSAGE) ); }) .done(_onInterstitialPageLoad); @@ -1102,7 +1109,7 @@ define(function LiveDevelopment(require, exports, module) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, - Strings.LIVE_DEV_NEED_HTML_MESSAGE + _makeTroubleshootingMessage(Strings.LIVE_DEV_NEED_HTML_MESSAGE) ); _openDeferred.reject(); } @@ -1111,7 +1118,7 @@ define(function LiveDevelopment(require, exports, module) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, - Strings.LIVE_DEV_SERVER_NOT_READY_MESSAGE + _makeTroubleshootingMessage(Strings.LIVE_DEV_SERVER_NOT_READY_MESSAGE) ); _openDeferred.reject(); } @@ -1133,7 +1140,7 @@ define(function LiveDevelopment(require, exports, module) { var dialogPromise = Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_LIVE_DEVELOPMENT, Strings.LIVE_DEVELOPMENT_RELAUNCH_TITLE, - Strings.LIVE_DEVELOPMENT_ERROR_MESSAGE, + _makeTroubleshootingMessage(Strings.LIVE_DEVELOPMENT_ERROR_MESSAGE), [ { className: Dialogs.DIALOG_BTN_CLASS_LEFT, @@ -1201,15 +1208,10 @@ define(function LiveDevelopment(require, exports, module) { message = StringUtils.format(Strings.ERROR_LAUNCHING_BROWSER, err); } - // Append a message to direct users to the troubleshooting page. - if (message) { - message += " " + StringUtils.format(Strings.LIVE_DEVELOPMENT_TROUBLESHOOTING, brackets.config.troubleshoot_url); - } - Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_LAUNCHING_BROWSER_TITLE, - message + _makeTroubleshootingMessage(message) ); _openDeferred.reject("OPEN_LIVE_BROWSER"); @@ -1321,18 +1323,18 @@ define(function LiveDevelopment(require, exports, module) { } } - // TODO: need to run _onDocumentChange() after load if doc != currentDocument here? Maybe not, since activeEditorChange + // TODO: need to run _onFileChanged() after load if doc != currentDocument here? Maybe not, since activeEditorChange // doesn't trigger it, while inline editors can still cause edits in doc other than currentDoc... _getInitialDocFromCurrent().done(function (doc) { var prepareServerPromise = (doc && _prepareServer(doc)) || new $.Deferred().reject(), otherDocumentsInWorkingFiles; if (doc && !doc._masterEditor) { - otherDocumentsInWorkingFiles = DocumentManager.getWorkingSet().length; - DocumentManager.addToWorkingSet(doc.file); + otherDocumentsInWorkingFiles = MainViewManager.getWorkingSet(MainViewManager.ALL_PANES).length; + MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file); if (!otherDocumentsInWorkingFiles) { - DocumentManager.setCurrentDocument(doc); + MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); } } @@ -1374,9 +1376,9 @@ define(function LiveDevelopment(require, exports, module) { /** * @private - * DocumentManager currentDocumentChange event handler. + * MainViewManager.currentFileChange event handler. */ - function _onDocumentChange() { + function _onFileChanged() { var doc = _getCurrentDocument(); if (!doc || !Inspector.connected()) { @@ -1475,10 +1477,13 @@ define(function LiveDevelopment(require, exports, module) { // We may get interim added/removed events when pushing incremental updates $(CSSAgent).on("styleSheetAdded.livedev", _styleSheetAdded); - $(DocumentManager).on("currentDocumentChange", _onDocumentChange) + $(MainViewManager) + .on("currentFileChange", _onFileChanged); + $(DocumentManager) .on("documentSaved", _onDocumentSaved) .on("dirtyFlagChange", _onDirtyFlagChange); - $(ProjectManager).on("beforeProjectClose beforeAppClose", close); + $(ProjectManager) + .on("beforeProjectClose beforeAppClose", close); // Register user defined server provider LiveDevServerManager.registerServer({ create: _createUserServer }, 99); diff --git a/src/LiveDevelopment/Servers/BaseServer.js b/src/LiveDevelopment/Servers/BaseServer.js index ca3ad675516..bf14064e1aa 100644 --- a/src/LiveDevelopment/Servers/BaseServer.js +++ b/src/LiveDevelopment/Servers/BaseServer.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, brackets, window */ +/*global define, $ */ define(function (require, exports, module) { "use strict"; @@ -62,7 +62,6 @@ define(function (require, exports, module) { */ BaseServer.prototype._setDocInfo = function (liveDocument) { var parentUrl, - rootUrl, matches, doc = liveDocument.doc; @@ -97,8 +96,7 @@ define(function (require, exports, module) { * Returns null if the path is not a descendant of the project root. */ BaseServer.prototype.pathToUrl = function (path) { - var url = null, - baseUrl = this.getBaseUrl(), + var baseUrl = this.getBaseUrl(), relativePath = this._pathResolver(path); // See if base url has been specified and path is within project @@ -229,4 +227,4 @@ define(function (require, exports, module) { }; exports.BaseServer = BaseServer; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/Servers/FileServer.js b/src/LiveDevelopment/Servers/FileServer.js index 66c08bdb859..4f9a64b8412 100644 --- a/src/LiveDevelopment/Servers/FileServer.js +++ b/src/LiveDevelopment/Servers/FileServer.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, brackets, window */ +/*global define, brackets */ define(function (require, exports, module) { "use strict"; @@ -89,4 +89,4 @@ define(function (require, exports, module) { }; exports.FileServer = FileServer; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/Servers/UserServer.js b/src/LiveDevelopment/Servers/UserServer.js index 6b8215da8ee..45494fcecab 100644 --- a/src/LiveDevelopment/Servers/UserServer.js +++ b/src/LiveDevelopment/Servers/UserServer.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, brackets, window */ +/*global define */ define(function (require, exports, module) { "use strict"; @@ -73,4 +73,4 @@ define(function (require, exports, module) { }; exports.UserServer = UserServer; -}); \ No newline at end of file +}); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 2ab7fdf1003..0c8c7f68eb0 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global brackets, define, $, less, window */ +/*global define, $, less, window */ /** * main integrates LiveDevelopment into Brackets @@ -64,7 +64,6 @@ define(function main(require, exports, module) { showInfo: true } }; - var _checkMark = "✓"; // Check mark character // Status labels/styles are ordered: error, not connected, progress1, progress2, connected. var _statusTooltip = [ Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, @@ -80,7 +79,6 @@ define(function main(require, exports, module) { var _allStatusStyles = _statusStyle.join(" "); var _$btnGoLive; // reference to the GoLive button - var _$btnHighlight; // reference to the HighlightButton /** Load Live Development LESS Style */ function _loadStyles() { diff --git a/src/brackets.js b/src/brackets.js index 577b14d2767..62b1c3f1e5e 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global require, define, brackets: true, $, window, navigator, Mustache */ +/*global define, brackets: true, $, window, navigator, Mustache */ // TODO: (issue #264) break out the definition of brackets into a separate module from the application controller logic @@ -61,16 +61,13 @@ define(function (require, exports, module) { require("thirdparty/CodeMirror2/keymap/sublime"); // Load dependent modules - var Global = require("utils/Global"), - AppInit = require("utils/AppInit"), + var AppInit = require("utils/AppInit"), LanguageManager = require("language/LanguageManager"), ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), - CSSInlineEditor = require("editor/CSSInlineEditor"), JSUtils = require("language/JSUtils"), WorkingSetView = require("project/WorkingSetView"), - WorkingSetSort = require("project/WorkingSetSort"), DocumentCommandHandlers = require("document/DocumentCommandHandlers"), FileViewController = require("project/FileViewController"), FileSyncManager = require("project/FileSyncManager"), @@ -80,32 +77,38 @@ define(function (require, exports, module) { CodeHintManager = require("editor/CodeHintManager"), PerfUtils = require("utils/PerfUtils"), FileSystem = require("filesystem/FileSystem"), - QuickOpen = require("search/QuickOpen"), Menus = require("command/Menus"), - FileUtils = require("file/FileUtils"), MainViewHTML = require("text!htmlContent/main-view.html"), Strings = require("strings"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), ExtensionLoader = require("utils/ExtensionLoader"), - SidebarView = require("project/SidebarView"), Async = require("utils/Async"), UpdateNotification = require("utils/UpdateNotification"), UrlParams = require("utils/UrlParams").UrlParams, PreferencesManager = require("preferences/PreferencesManager"), - Resizer = require("utils/Resizer"), - LiveDevelopmentMain = require("LiveDevelopment/main"), - NodeConnection = require("utils/NodeConnection"), - NodeDomain = require("utils/NodeDomain"), ExtensionUtils = require("utils/ExtensionUtils"), DragAndDrop = require("utils/DragAndDrop"), - ColorUtils = require("utils/ColorUtils"), CodeInspection = require("language/CodeInspection"), NativeApp = require("utils/NativeApp"), DeprecationWarning = require("utils/DeprecationWarning"), ViewCommandHandlers = require("view/ViewCommandHandlers"), - ThemeManager = require("view/ThemeManager"), - _ = require("thirdparty/lodash"); + MainViewManager = require("view/MainViewManager"); + + // load modules for later use + require("utils/Global"); + require("editor/CSSInlineEditor"); + require("project/WorkingSetSort"); + require("search/QuickOpen"); + require("file/FileUtils"); + require("project/SidebarView"); + require("utils/Resizer"); + require("LiveDevelopment/main"); + require("utils/NodeConnection"); + require("utils/NodeDomain"); + require("utils/ColorUtils"); + require("view/ThemeManager"); + require("thirdparty/lodash"); // DEPRECATED: In future we want to remove the global CodeMirror, but for now we // expose our required CodeMirror globally so as to avoid breaking extensions in the @@ -139,6 +142,9 @@ define(function (require, exports, module) { require("file/NativeFileSystem"); require("file/NativeFileError"); + // Compatibility shim for PanelManager to WorkspaceManager migration + require("view/PanelManager"); + PerfUtils.addMeasurement("brackets module dependencies resolved"); // Local variables @@ -188,6 +194,8 @@ define(function (require, exports, module) { LanguageManager : LanguageManager, LiveDevelopment : require("LiveDevelopment/LiveDevelopment"), LiveDevServerManager : require("LiveDevelopment/LiveDevServerManager"), + MainViewManager : MainViewManager, + MainViewFactory : require("view/MainViewFactory"), Menus : Menus, MultiRangeInlineEditor : require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, NativeApp : NativeApp, @@ -198,7 +206,6 @@ define(function (require, exports, module) { ScrollTrackMarkers : require("search/ScrollTrackMarkers"), UpdateNotification : require("utils/UpdateNotification"), WorkingSetView : WorkingSetView, - doneLoading : false }; @@ -213,8 +220,6 @@ define(function (require, exports, module) { function _onReady() { PerfUtils.addMeasurement("window.document Ready"); - EditorManager.setEditorHolder($("#editor-holder")); - // Let the user know Brackets doesn't run in a web browser yet if (brackets.inBrowser) { Dialogs.showModalDialog( @@ -248,6 +253,9 @@ define(function (require, exports, module) { // Load the initial project after extensions have loaded extensionLoaderPromise.always(function () { + // Signal that extensions are loaded + AppInit._dispatchReady(AppInit.EXTENSIONS_LOADED); + // Finish UI initialization ViewCommandHandlers.restoreFontSize(); var initialProjectPath = ProjectManager.getInitialProjectPath(); @@ -265,7 +273,7 @@ define(function (require, exports, module) { if (ProjectManager.isWelcomeProjectPath(initialProjectPath)) { FileSystem.resolve(initialProjectPath + "index.html", function (err, file) { if (!err) { - var promise = CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, { fullPath: file.fullPath }); + var promise = CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, { fullPath: file.fullPath }); promise.then(deferred.resolve, deferred.reject); } else { deferred.reject(); @@ -299,8 +307,8 @@ define(function (require, exports, module) { // See if any startup files were passed to the application if (brackets.app.getPendingFilesToOpen) { - brackets.app.getPendingFilesToOpen(function (err, files) { - DragAndDrop.openDroppedFiles(files); + brackets.app.getPendingFilesToOpen(function (err, paths) { + DragAndDrop.openDroppedFiles(paths); }); } }); @@ -373,9 +381,9 @@ define(function (require, exports, module) { if (event.originalEvent.dataTransfer.files) { event.stopPropagation(); event.preventDefault(); - brackets.app.getDroppedFiles(function (err, files) { + brackets.app.getDroppedFiles(function (err, paths) { if (!err) { - DragAndDrop.openDroppedFiles(files); + DragAndDrop.openDroppedFiles(paths); } }); } @@ -409,7 +417,7 @@ define(function (require, exports, module) { $target.is("input:not([type])") || // input with no type attribute defaults to text $target.is("textarea") || $target.is("select"); - + if (!isFormElement) { e.preventDefault(); } diff --git a/src/command/Commands.js b/src/command/Commands.js index cdaa8c72b43..82035ea82a7 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -30,24 +30,6 @@ define(function (require, exports, module) { var DeprecationWarning = require("utils/DeprecationWarning"); - /** - * @private - * Create a deprecation warning and action for updated Command constants - * @param {!string} oldConstant - * @param {!string} newConstant - */ - function _deprecateCommand(oldConstant, newConstant) { - var warning = "Use Commands." + newConstant + " instead of Commands." + oldConstant, - newValue = exports[newConstant]; - - Object.defineProperty(exports, oldConstant, { - get: function () { - DeprecationWarning.deprecationWarning(warning, true); - return newValue; - } - }); - } - /** * List of constants for global command IDs. */ @@ -56,7 +38,7 @@ define(function (require, exports, module) { exports.FILE_NEW_UNTITLED = "file.newDoc"; // DocumentCommandHandlers.js handleFileNew() exports.FILE_NEW = "file.newFile"; // DocumentCommandHandlers.js handleFileNewInProject() exports.FILE_NEW_FOLDER = "file.newFolder"; // DocumentCommandHandlers.js handleNewFolderInProject() - exports.FILE_OPEN = "file.open"; // DocumentCommandHandlers.js handleFileOpen() + exports.FILE_OPEN = "file.open"; // DocumentCommandHandlers.js handleDocumentOpen() exports.FILE_OPEN_FOLDER = "file.openFolder"; // ProjectManager.js openProject() exports.FILE_SAVE = "file.save"; // DocumentCommandHandlers.js handleFileSave() exports.FILE_SAVE_ALL = "file.saveAll"; // DocumentCommandHandlers.js handleFileSaveAll() @@ -64,7 +46,6 @@ define(function (require, exports, module) { exports.FILE_CLOSE = "file.close"; // DocumentCommandHandlers.js handleFileClose() exports.FILE_CLOSE_ALL = "file.close_all"; // DocumentCommandHandlers.js handleFileCloseAll() exports.FILE_CLOSE_LIST = "file.close_list"; // DocumentCommandHandlers.js handleFileCloseList() - exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet"; // DocumentCommandHandlers.js handleFileAddToWorkingSet() exports.FILE_OPEN_DROPPED_FILES = "file.openDroppedFiles"; // DragAndDrop.js openDroppedFiles() exports.FILE_LIVE_FILE_PREVIEW = "file.liveFilePreview"; // LiveDevelopment/main.js _handleGoLiveCommand() exports.CMD_RELOAD_LIVE_PREVIEW = "file.reloadLivePreview"; // LiveDevelopment/main.js _handleReloadLivePreviewCommand() @@ -133,10 +114,9 @@ define(function (require, exports, module) { exports.TOGGLE_LINE_NUMBERS = "view.toggleLineNumbers"; // EditorOptionHandlers.js _getToggler() exports.TOGGLE_ACTIVE_LINE = "view.toggleActiveLine"; // EditorOptionHandlers.js _getToggler() exports.TOGGLE_WORD_WRAP = "view.toggleWordWrap"; // EditorOptionHandlers.js _getToggler() - exports.SORT_WORKINGSET_BY_ADDED = "view.sortWorkingSetByAdded"; // WorkingSetSort.js _handleSortWorkingSetByAdded() - exports.SORT_WORKINGSET_BY_NAME = "view.sortWorkingSetByName"; // WorkingSetSort.js _handleSortWorkingSetByName() - exports.SORT_WORKINGSET_BY_TYPE = "view.sortWorkingSetByType"; // WorkingSetSort.js _handleSortWorkingSetByType() - exports.SORT_WORKINGSET_AUTO = "view.sortWorkingSetAuto"; // WorkingSetSort.js _handleAutomaticSort() + + exports.CMD_OPEN = "cmd.open"; + exports.CMD_ADD_TO_WORKINGSET_AND_OPEN = "cmd.addToWorkingSetAndOpen"; // DocumentCommandHandlers.js handleOpenDocumentInNewPane() // NAVIGATE exports.NAVIGATE_NEXT_DOC = "navigate.nextDoc"; // DocumentCommandHandlers.js handleGoNextDoc() @@ -165,6 +145,17 @@ define(function (require, exports, module) { exports.HELP_HOMEPAGE = "help.homepage"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_TWITTER = "help.twitter"; // HelpCommandHandlers.js _handleLinkMenuItem() + // Working Set Configuration + exports.CMD_WORKINGSET_SORT_BY_ADDED = "cmd.sortWorkingSetByAdded"; // WorkingSetSort.js _handleSort() + exports.CMD_WORKINGSET_SORT_BY_NAME = "cmd.sortWorkingSetByName"; // WorkingSetSort.js _handleSort() + exports.CMD_WORKINGSET_SORT_BY_TYPE = "cmd.sortWorkingSetByType"; // WorkingSetSort.js _handleSort() + exports.CMD_WORKING_SORT_TOGGLE_AUTO = "cmd.sortWorkingSetToggleAuto"; // WorkingSetSort.js _handleToggleAutoSort() + + // Split View + exports.CMD_SPLITVIEW_NONE = "cmd.splitViewNone"; // SidebarView.js _handleSplitNone() + exports.CMD_SPLITVIEW_VERTICAL = "cmd.splitViewVertical"; // SidebarView.js _handleSplitVertical() + exports.CMD_SPLITVIEW_HORIZONTAL = "cmd.splitViewHorizontal"; // SidebarView.js _handleSplitHorizontal() + // File shell callbacks - string must MATCH string in native code (appshell/command_callbacks.h) exports.HELP_ABOUT = "help.about"; // HelpCommandHandlers.js _handleAboutDialog() @@ -176,15 +167,24 @@ define(function (require, exports, module) { exports.APP_ABORT_QUIT = "app.abort_quit"; // DocumentCommandHandlers.js handleAbortQuit() exports.APP_BEFORE_MENUPOPUP = "app.before_menupopup"; // DocumentCommandHandlers.js handleBeforeMenuPopup() + // ADD_TO_WORKING_SET is deprectated but we need a handler for it because the new command doesn't return the same result as the legacy command + exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet"; // Deprecated through DocumentCommandHandlers.js handleFileAddToWorkingSet + + // DEPRECATED: Working Set Commands + DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_ADDED", "CMD_WORKINGSET_SORT_BY_ADDED"); + DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_NAME", "CMD_WORKINGSET_SORT_BY_NAME"); + DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_TYPE", "CMD_WORKINGSET_SORT_BY_TYPE"); + DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_AUTO", "CMD_WORKING_SORT_TOGGLE_AUTO"); + // DEPRECATED: Edit commands that were moved from the Edit Menu to the Find Menu - _deprecateCommand("EDIT_FIND", "CMD_FIND"); - _deprecateCommand("EDIT_FIND_IN_SELECTED", "CMD_FIND_IN_SELECTED"); - _deprecateCommand("EDIT_FIND_IN_SUBTREE", "CMD_FIND_IN_SUBTREE"); - _deprecateCommand("EDIT_FIND_NEXT", "CMD_FIND_NEXT"); - _deprecateCommand("EDIT_FIND_PREVIOUS", "CMD_FIND_PREVIOUS"); - _deprecateCommand("EDIT_FIND_ALL_AND_SELECT", "CMD_FIND_ALL_AND_SELECT"); - _deprecateCommand("EDIT_ADD_NEXT_MATCH", "CMD_ADD_NEXT_MATCH"); - _deprecateCommand("EDIT_SKIP_CURRENT_MATCH", "CMD_SKIP_CURRENT_MATCH"); - _deprecateCommand("EDIT_REPLACE", "CMD_REPLACE"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND", "CMD_FIND"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND_IN_SELECTED", "CMD_FIND_IN_SELECTED"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND_IN_SUBTREE", "CMD_FIND_IN_SUBTREE"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND_NEXT", "CMD_FIND_NEXT"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND_PREVIOUS", "CMD_FIND_PREVIOUS"); + DeprecationWarning.deprecateConstant(exports, "EDIT_FIND_ALL_AND_SELECT", "CMD_FIND_ALL_AND_SELECT"); + DeprecationWarning.deprecateConstant(exports, "EDIT_ADD_NEXT_MATCH", "CMD_ADD_NEXT_MATCH"); + DeprecationWarning.deprecateConstant(exports, "EDIT_SKIP_CURRENT_MATCH", "CMD_SKIP_CURRENT_MATCH"); + DeprecationWarning.deprecateConstant(exports, "EDIT_REPLACE", "CMD_REPLACE"); }); diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 5e32d650d52..c9ee991a102 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -33,7 +33,6 @@ define(function (require, exports, module) { var AppInit = require("utils/AppInit"), Commands = require("command/Commands"), - ContextMenu = require("command/Menus"), EditorManager = require("editor/EditorManager"), Menus = require("command/Menus"), Strings = require("strings"); @@ -204,10 +203,39 @@ define(function (require, exports, module) { if (hasAboutItem) { menu.addMenuItem(Commands.HELP_ABOUT); } - + + /* * Context Menus */ + + // WorkingSet context menu - Unlike most context menus, we can't attach + // listeners here because the DOM nodes for each pane's working set are + // created dynamically. Each WorkingSetView attaches its own listeners. + var workingset_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU); + workingset_cmenu.addMenuItem(Commands.FILE_SAVE); + workingset_cmenu.addMenuItem(Commands.FILE_SAVE_AS); + workingset_cmenu.addMenuItem(Commands.FILE_RENAME); + workingset_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE); + workingset_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS); + workingset_cmenu.addMenuDivider(); + workingset_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE); + workingset_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE); + workingset_cmenu.addMenuDivider(); + workingset_cmenu.addMenuItem(Commands.FILE_CLOSE); + + var workingset_configuration_menu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_CONFIG_MENU); + workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_ADDED); + workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_NAME); + workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_TYPE); + workingset_configuration_menu.addMenuDivider(); + workingset_configuration_menu.addMenuItem(Commands.CMD_WORKING_SORT_TOGGLE_AUTO); + + var splitview_menu = Menus.registerContextMenu(Menus.ContextMenuIds.SPLITVIEW_MENU); + splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_NONE); + splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_VERTICAL); + splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_HORIZONTAL); + var project_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.PROJECT_MENU); project_cmenu.addMenuItem(Commands.FILE_NEW); project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER); @@ -219,27 +247,7 @@ define(function (require, exports, module) { project_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE); project_cmenu.addMenuDivider(); project_cmenu.addMenuItem(Commands.FILE_REFRESH); - - var working_set_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_MENU); - working_set_cmenu.addMenuItem(Commands.FILE_SAVE); - working_set_cmenu.addMenuItem(Commands.FILE_SAVE_AS); - working_set_cmenu.addMenuItem(Commands.FILE_RENAME); - working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE); - working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS); - working_set_cmenu.addMenuDivider(); - working_set_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE); - working_set_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE); - working_set_cmenu.addMenuDivider(); - working_set_cmenu.addMenuItem(Commands.FILE_CLOSE); - - var working_set_settings_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_SETTINGS_MENU); - working_set_settings_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_ADDED); - working_set_settings_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_NAME); - working_set_settings_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_TYPE); - working_set_settings_cmenu.addMenuDivider(); - working_set_settings_cmenu.addMenuItem(Commands.SORT_WORKINGSET_AUTO); - var editor_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.EDITOR_MENU); // editor_cmenu.addMenuItem(Commands.NAVIGATE_JUMPTO_DEFINITION); editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT); @@ -294,20 +302,17 @@ define(function (require, exports, module) { }); /** - * Context menus for folder tree & working set list + * Context menu for folder tree */ $("#project-files-container").on("contextmenu", function (e) { project_cmenu.open(e); }); - $("#open-files-container").on("contextmenu", function (e) { - working_set_cmenu.open(e); - }); + // Dropdown menu for workspace sorting + Menus.ContextMenu.assignContextMenuToSelector(".working-set-option-btn", workingset_configuration_menu); - /** - * Dropdown menu for workspace sorting - */ - Menus.ContextMenu.assignContextMenuToSelector("#working-set-option-btn", working_set_settings_cmenu); + // Dropdown menu for view splitting + Menus.ContextMenu.assignContextMenuToSelector(".working-set-splitview-btn", splitview_menu); // Prevent the browser context menu since Brackets creates a custom context menu $(window).contextmenu(function (e) { diff --git a/src/command/Menus.js b/src/command/Menus.js index ed9a3a02fd5..702dc2d1c9d 100644 --- a/src/command/Menus.js +++ b/src/command/Menus.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, brackets, window, MouseEvent */ +/*global define, $, brackets, window */ define(function (require, exports, module) { "use strict"; @@ -31,8 +31,7 @@ define(function (require, exports, module) { var _ = require("thirdparty/lodash"); // Load dependent modules - var Global = require("utils/Global"), - Commands = require("command/Commands"), + var Commands = require("command/Commands"), KeyBindingManager = require("command/KeyBindingManager"), StringUtils = require("utils/StringUtils"), CommandManager = require("command/CommandManager"), @@ -40,6 +39,9 @@ define(function (require, exports, module) { ViewUtils = require("utils/ViewUtils"), DeprecationWarning = require("utils/DeprecationWarning"); + // make sure the global brackets variable is loaded + require("utils/Global"); + /** * Brackets Application Menu Constants * @enum {string} @@ -58,14 +60,14 @@ define(function (require, exports, module) { * @enum {string} */ var ContextMenuIds = { - EDITOR_MENU: "editor-context-menu", - INLINE_EDITOR_MENU: "inline-editor-context-menu", - PROJECT_MENU: "project-context-menu", - WORKING_SET_MENU: "working-set-context-menu", - WORKING_SET_SETTINGS_MENU: "working-set-settings-context-menu" + EDITOR_MENU: "editor-context-menu", + INLINE_EDITOR_MENU: "inline-editor-context-menu", + PROJECT_MENU: "project-context-menu", + WORKING_SET_CONTEXT_MENU: "workingset-context-menu", + WORKING_SET_CONFIG_MENU: "workingset-configuration-menu", + SPLITVIEW_MENU: "splitview-menu" }; - /** * Brackets Application Menu Section Constants * It is preferred that plug-ins specify the location of new MenuItems @@ -552,7 +554,6 @@ define(function (require, exports, module) { var menuID = this.id, id, $menuItem, - $link, menuItem, name, commandID; @@ -1176,6 +1177,10 @@ define(function (require, exports, module) { return cmenu; } + // Deprecated menu ids + DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_MENU", "WORKING_SET_CONTEXT_MENU"); + DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_SETTINGS_MENU", "WORKING_SET_CONFIG_MENU"); + // Define public API exports.AppMenuBar = AppMenuBar; exports.ContextMenuIds = ContextMenuIds; diff --git a/src/dependencies.js b/src/dependencies.js index 1a2ae341a82..e0a9a432204 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, evil:true */ -/*global window, document:true, CollectionUtils:true */ +/*global window, document */ /** * Check for missing dependencies @@ -51,4 +51,4 @@ window.setTimeout(function () { document.write("
git submodule update --init
"); document.write("

If you're still having problems, please contact us via one of the channels mentioned at the bottom of the README.

"); document.write("

Reload Brackets

"); -}, 1000); \ No newline at end of file +}, 1000); diff --git a/src/document/Document.js b/src/document/Document.js index 13591eb3884..337136b0942 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -227,7 +227,7 @@ define(function (require, exports, module) { */ Document.prototype._ensureMasterEditor = function () { if (!this._masterEditor) { - EditorManager._createFullEditorForDocument(this); + EditorManager._createUnattachedMasterEditor(this); } }; diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index dfc9312ac93..3524682db66 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -34,8 +34,10 @@ define(function (require, exports, module) { var AppInit = require("utils/AppInit"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), + DeprecationWarning = require("utils/DeprecationWarning"), ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), + MainViewManager = require("view/MainViewManager"), EditorManager = require("editor/EditorManager"), FileSystem = require("filesystem/FileSystem"), FileSystemError = require("filesystem/FileSystemError"), @@ -49,14 +51,13 @@ define(function (require, exports, module) { Strings = require("strings"), PopUpManager = require("widgets/PopUpManager"), PreferencesManager = require("preferences/PreferencesManager"), - DragAndDrop = require("utils/DragAndDrop"), PerfUtils = require("utils/PerfUtils"), KeyEvent = require("utils/KeyEvent"), - LanguageManager = require("language/LanguageManager"), Inspector = require("LiveDevelopment/Inspector/Inspector"), Menus = require("command/Menus"), UrlParams = require("utils/UrlParams").UrlParams, - StatusBar = require("widgets/StatusBar"); + StatusBar = require("widgets/StatusBar"), + WorkspaceManager = require("view/WorkspaceManager"); /** * Handlers for commands related to document handling (opening, saving, etc.) @@ -126,10 +127,14 @@ define(function (require, exports, module) { * @type {function} */ var handleFileSaveAs; - - function updateTitle() { + + /** + * Updates the title bar with new file title or dirty indicator + * @private + */ + function _updateTitle() { var currentDoc = DocumentManager.getCurrentDocument(), - currentlyViewedPath = EditorManager.getCurrentlyViewedPath(), + currentlyViewedPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE), windowTitle = brackets.config.app_title; if (!brackets.nativeMenus) { @@ -161,7 +166,7 @@ define(function (require, exports, module) { var newToolbarHeight = _$titleContainerToolbar.height(); if (_lastToolbarHeight !== newToolbarHeight) { _lastToolbarHeight = newToolbarHeight; - EditorManager.resizeEditor(); + WorkspaceManager.recomputeLayout(); } } @@ -184,7 +189,7 @@ define(function (require, exports, module) { /** * Returns a short title for a given document. * - * @param {Document} doc + * @param {Document} doc - the document to compute the short title for * @return {string} - a short title for doc. */ function _shortTitleForDocument(doc) { @@ -200,36 +205,36 @@ define(function (require, exports, module) { } } - function updateDocumentTitle() { - var newDocument = DocumentManager.getCurrentDocument(); - - // TODO: This timer is causing a "Recursive tests with the same name are not supported" - // exception. This code should be removed (if not needed), or updated with a unique - // timer name (if needed). - // var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath)); + /** + * Handles currentFileChange and filenameChanged events and updates the titlebar + */ + function handleCurrentFileChange() { + var newFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); - if (newDocument) { - _currentTitlePath = _shortTitleForDocument(newDocument); - } else { - var currentlyViewedFilePath = EditorManager.getCurrentlyViewedPath(); - if (currentlyViewedFilePath) { - _currentTitlePath = ProjectManager.makeProjectRelativeIfPossible(currentlyViewedFilePath); + if (newFile) { + var newDocument = DocumentManager.getOpenDocumentForPath(newFile.fullPath); + + if (newDocument) { + _currentTitlePath = _shortTitleForDocument(newDocument); } else { - _currentTitlePath = null; + _currentTitlePath = ProjectManager.makeProjectRelativeIfPossible(newFile.fullPath); } + } else { + _currentTitlePath = null; } // Update title text & "dirty dot" display - updateTitle(); - - // PerfUtils.addMeasurement(perfTimerName); + _updateTitle(); } + /** + * Handles dirtyFlagChange event and updates the title bar if necessary + */ function handleDirtyChange(event, changedDoc) { var currentDoc = DocumentManager.getCurrentDocument(); if (currentDoc && changedDoc.file.fullPath === currentDoc.file.fullPath) { - updateTitle(); + _updateTitle(); } } @@ -238,83 +243,59 @@ define(function (require, exports, module) { * Creates a document and displays an editor for the specified file path. * @param {!string} fullPath * @param {boolean=} silent If true, don't show error message + * @param {string=} paneId, the id oi the pane in which to open the file. Can be undefined, a valid pane id or ACTIVE_PANE. * @return {$.Promise} a jQuery promise that will either - * - be resolved with a document for the specified file path or - * - be resolved without document, i.e. when an image is displayed or - * - be rejected if the file can not be read. + * - be resolved with a file for the specified file path or + * - be rejected with FileSystemError if the file can not be read. + * If paneId is undefined, the ACTIVE_PANE constant */ - function doOpen(fullPath, silent) { + function _doOpen(fullPath, silent, paneId) { var result = new $.Deferred(); // workaround for https://github.com/adobe/brackets/issues/6001 // TODO should be removed once bug is closed. // if we are already displaying a file do nothing but resolve immediately. // this fixes timing issues in test cases. - if (EditorManager.getCurrentlyViewedPath() === fullPath) { - result.resolve(DocumentManager.getCurrentDocument()); + if (MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE) === fullPath) { + result.resolve(MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE)); return result.promise(); } - function _cleanup(fullFilePath) { - if (!fullFilePath || EditorManager.showingCustomViewerForPath(fullFilePath)) { - // We get here only after the user renames a file that makes it no longer belong to a - // custom viewer but the file is still showing in the current custom viewer. This only - // occurs on Mac since opening a non-text file always fails on Mac and triggers an error - // message that in turn calls _cleanup() after the user clicks OK in the message box. - // So we need to explicitly close the currently viewing image file whose filename is - // no longer valid. Calling notifyPathDeleted will close the image vieer and then select - // the previously opened text file or show no-editor if none exists. - EditorManager.notifyPathDeleted(fullFilePath); - } else { - // For performance, we do lazy checking of file existence, so it may be in working set - DocumentManager.removeFromWorkingSet(FileSystem.getFileForPath(fullFilePath)); - EditorManager.focusEditor(); + function _cleanup(fileError, fullFilePath) { + if (fullFilePath) { + // For performance, we do lazy checking of file existence, so it may be in workingset + MainViewManager._removeView(paneId, FileSystem.getFileForPath(fullFilePath)); + MainViewManager.focusActivePane(); } - result.reject(); + result.reject(fileError); } function _showErrorAndCleanUp(fileError, fullFilePath) { if (silent) { - _cleanup(fullFilePath); + _cleanup(fileError, fullFilePath); } else { FileUtils.showFileOpenError(fileError, fullFilePath).done(function () { - _cleanup(fullFilePath); + _cleanup(fileError, fullFilePath); }); } } if (!fullPath) { - console.error("doOpen() called without fullPath"); - result.reject(); + throw new Error("_doOpen() called without fullPath"); } else { var perfTimerName = PerfUtils.markStart("Open File:\t" + fullPath); result.always(function () { PerfUtils.addMeasurement(perfTimerName); }); - var viewProvider = EditorManager.getCustomViewerForPath(fullPath); - if (viewProvider) { - var file = FileSystem.getFileForPath(fullPath); - file.exists(function (fileError, fileExists) { - if (fileExists) { - EditorManager._showCustomViewer(viewProvider, fullPath); - result.resolve(); - } else { - fileError = fileError || FileSystemError.NOT_FOUND; - _showErrorAndCleanUp(fileError); - } + var file = FileSystem.getFileForPath(fullPath); + MainViewManager._open(paneId, file) + .done(function () { + result.resolve(file); + }) + .fail(function (fileError) { + _showErrorAndCleanUp(fileError, fullPath); + result.reject(); }); - - } else { - // Load the file if it was never open before, and then switch to it in the UI - DocumentManager.getDocumentForPath(fullPath) - .done(function (doc) { - DocumentManager.setCurrentDocument(doc); - result.resolve(doc); - }) - .fail(function (fileError) { - _showErrorAndCleanUp(fileError, fullPath); - }); - } } return result.promise(); @@ -328,16 +309,17 @@ define(function (require, exports, module) { /** * @private - * Creates a document and displays an editor for the specified file path. + * Opens a file and displays its view (editor, image view, etc...) for the specified path. * If no path is specified, a file prompt is provided for input. * @param {?string} fullPath - The path of the file to open; if it's null we'll prompt for it * @param {boolean=} silent - If true, don't show error message - * @return {$.Promise} a jQuery promise that will be resolved with a new - * document for the specified file path or be resolved without document, i.e. when an image is displayed, - * or rejected if the file can not be read. + * @param {string=} paneId - the pane in which to open the file. Can be undefined, a valid pane id or ACTIVE_PANE + * @return {$.Promise} a jQuery promise resolved with a Document object or + * rejected with an err */ - function _doOpenWithOptionalPath(fullPath, silent) { + function _doOpenWithOptionalPath(fullPath, silent, paneId) { var result; + paneId = paneId || MainViewManager.ACTIVE_PANE; if (!fullPath) { // Create placeholder deferred result = new $.Deferred(); @@ -350,24 +332,21 @@ define(function (require, exports, module) { FileSystem.showOpenDialog(true, false, Strings.OPEN_FILE, _defaultOpenDialogFullPath, null, function (err, paths) { if (!err) { if (paths.length > 0) { - // Add all files to the working set without verifying that + // Add all files to the workingset without verifying that // they still exist on disk (for faster opening) - var filesToOpen = [], - filteredPaths = DragAndDrop.filterFilesToOpen(paths); + var filesToOpen = []; - filteredPaths.forEach(function (file) { - filesToOpen.push(FileSystem.getFileForPath(file)); + paths.forEach(function (path) { + filesToOpen.push(FileSystem.getFileForPath(path)); }); - DocumentManager.addListToWorkingSet(filesToOpen); + MainViewManager.addListToWorkingSet(paneId, filesToOpen); - doOpen(filteredPaths[filteredPaths.length - 1], silent) - .done(function (doc) { - // doc may be null, i.e. if an image has been opened. - // Then we do not add the opened file to the working set. - if (doc) { - DocumentManager.addToWorkingSet(doc.file); - } - _defaultOpenDialogFullPath = FileUtils.getDirectoryPath(EditorManager.getCurrentlyViewedPath()); + _doOpen(paths[paths.length - 1], silent, paneId) + .done(function (file) { + _defaultOpenDialogFullPath = + FileUtils.getDirectoryPath( + MainViewManager.getCurrentlyViewedPath(paneId) + ); }) // Send the resulting document that was opened .then(result.resolve, result.reject); @@ -378,7 +357,7 @@ define(function (require, exports, module) { } }); } else { - result = doOpen(fullPath, silent); + result = _doOpen(fullPath, silent, paneId); } return result.promise(); @@ -410,28 +389,54 @@ define(function (require, exports, module) { } /** - * Opens the given file and makes it the current document. Does NOT add it to the working set. - * @param {!{fullPath:string}} Params for FILE_OPEN command; - * the fullPath string is of the form "path[:lineNumber[:columnNumber]]" - * lineNumber and columnNumber are 1-origin: the very first line is line 1, and the very first column is column 1. + * @typedef {{fullPath:?string=, silent:boolean=, paneId:string=}} FileCommandData + * fullPath: is in the form "path[:lineNumber[:columnNumber]]" + * lineNumber and columnNumber are 1-origin: lines and columns are 1-based + */ + + /** + * @typedef {{fullPath:?string=, index:number=, silent:boolean=, forceRedraw:boolean=, paneId:string=}} PaneCommandData + * fullPath: is in the form "path[:lineNumber[:columnNumber]]" + * lineNumber and columnNumber are 1-origin: lines and columns are 1-based + */ + + /** + * Opens the given file and makes it the current file. Does NOT add it to the workingset. + * @param {FileCommandData=} commandData - record with the following properties: + * fullPath: File to open; + * silent: optional flag to suppress error messages; + * paneId: optional PaneId (defaults to active pane) + * @return {$.Promise} a jQuery promise that will be resolved with a file object */ function handleFileOpen(commandData) { var fileInfo = _parseDecoratedPath(commandData ? commandData.fullPath : null), - silent = commandData ? commandData.silent : false; - return _doOpenWithOptionalPath(fileInfo.path, silent) - .always(function () { + silent = (commandData && commandData.silent) || false, + paneId = (commandData && commandData.paneId) || MainViewManager.ACTIVE_PANE, + result = new $.Deferred(); + + _doOpenWithOptionalPath(fileInfo.path, silent, paneId) + .done(function (file) { + MainViewManager.setActivePaneId(paneId); + // If a line and column number were given, position the editor accordingly. if (fileInfo.line !== null) { if (fileInfo.column === null || (fileInfo.column <= 0)) { fileInfo.column = 1; } + // setCursorPos expects line/column numbers as 0-origin, so we subtract 1 - EditorManager.getCurrentFullEditor().setCursorPos(fileInfo.line - 1, fileInfo.column - 1, true); + EditorManager.getCurrentFullEditor().setCursorPos(fileInfo.line - 1, + fileInfo.column - 1, + true); } - // Give the editor focus - EditorManager.focusEditor(); + result.resolve(file); + }) + .fail(function (err) { + result.reject(err); }); + + return result; // Testing notes: here are some recommended manual tests for handleFileOpen, on macintosh. // Do all tests with brackets already running, and also with brackets not already running. // @@ -446,22 +451,83 @@ define(function (require, exports, module) { } /** - * Opens the given file, makes it the current document, AND adds it to the working set - * only if the file does not have a custom viewer. - * @param {!{fullPath:string, index:number=, forceRedraw:boolean}} commandData File to open; optional position in - * working set list (defaults to last); optional flag to force working set redraw + * Opens the given file, makes it the current file, does NOT add it to the workingset + * @param {FileCommandData} commandData + * fullPath: File to open; + * silent: optional flag to suppress error messages; + * paneId: optional PaneId (defaults to active pane) + * @return {$.Promise} a jQuery promise that will be resolved with @type {Document} */ - function handleFileAddToWorkingSet(commandData) { - return handleFileOpen(commandData).done(function (doc) { - // addToWorkingSet is synchronous - // When opening a file with a custom viewer, we get a null doc. - // So check it before we add it to the working set. - if (doc) { - DocumentManager.addToWorkingSet(doc.file, commandData.index, commandData.forceRedraw); - } + function handleDocumentOpen(commandData) { + var result = new $.Deferred(); + handleFileOpen(commandData) + .done(function (file) { + // if we succeeded with an open file + // then we need to resolve that to a document. + // getOpenDocumentForPath will return null if there isn't a + // supporting document for that file (e.g. an image) + var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + result.resolve(doc); + }) + .fail(function (err) { + result.reject(err); + }); + + return result.promise(); + + } + + /** + * Opens the given file, makes it the current file, AND adds it to the workingset + * @param {!PaneCommandData} commandData - record with the following properties: + * fullPath: File to open; + * index: optional index to position in workingset (defaults to last); + * silent: optional flag to suppress error messages; + * forceRedraw: flag to force the working set view redraw; + * paneId: optional PaneId (defaults to active pane) + * @return {$.Promise} a jQuery promise that will be resolved with a @type {File} + */ + function handleFileAddToWorkingSetAndOpen(commandData) { + return handleFileOpen(commandData).done(function (file) { + var paneId = (commandData && commandData.paneId) || MainViewManager.ACTIVE_PANE; + MainViewManager.addToWorkingSet(paneId, file, commandData.index, commandData.forceRedraw); }); } + /** + * @deprecated + * Opens the given file, makes it the current document, AND adds it to the workingset + * @param {!PaneCommandData} commandData - record with the following properties: + * fullPath: File to open; + * index: optional index to position in workingset (defaults to last); + * silent: optional flag to suppress error messages; + * forceRedraw: flag to force the working set view redraw; + * paneId: optional PaneId (defaults to active pane) + * @return {$.Promise} a jQuery promise that will be resolved with @type {File} + */ + function handleFileAddToWorkingSet(commandData) { + // This is a legacy deprecated command that + // will use the new command and resolve with a document + // as the legacy command would only support. + DeprecationWarning.deprecationWarning("Commands.FILE_ADD_TO_WORKING_SET has been deprecated. Use Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN instead."); + var result = new $.Deferred(); + + handleFileAddToWorkingSetAndOpen(commandData) + .done(function (file) { + // if we succeeded with an open file + // then we need to resolve that to a document. + // getOpenDocumentForPath will return null if there isn't a + // supporting document for that file (e.g. an image) + var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + result.resolve(doc); + }) + .fail(function (err) { + result.reject(err); + }); + + return result.promise(); + } + /** * @private * Ensures the suggested file name doesn't already exit. @@ -507,6 +573,8 @@ define(function (require, exports, module) { /** * Bottleneck function for creating new files and folders in the project tree. + * @private + * @param {boolean} isFolder - true if creating a new folder, false if creating a new file */ function _handleNewItemInProject(isFolder) { if (fileNewInProgress) { @@ -519,7 +587,7 @@ define(function (require, exports, module) { // If a file is currently selected in the tree, put it next to it. // If a directory is currently selected in the tree, put it in it. // If an Untitled document is selected or nothing is selected in the tree, put it at the root of the project. - // (Note: 'selected' may be an item that's selected in the working set and not the tree; but in that case + // (Note: 'selected' may be an item that's selected in the workingset and not the tree; but in that case // ProjectManager.createNewItem() ignores the baseDir we give it and falls back to the project root on its own) var baseDirEntry, selected = ProjectManager.getSelectedItem(); @@ -545,7 +613,7 @@ define(function (require, exports, module) { } /** - * Create a new untitled document in the working set, and make it the current document. + * Create a new untitled document in the workingset, and make it the current document. * Promise is resolved (synchronously) with the newly-created Document. */ function handleFileNew() { @@ -556,8 +624,7 @@ define(function (require, exports, module) { var defaultExtension = ""; // disable preference setting for now var doc = DocumentManager.createUntitledDocument(_nextUntitledIndexToUse++, defaultExtension); - DocumentManager.setCurrentDocument(doc); - EditorManager.focusEditor(); + MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); return new $.Deferred().resolve(doc).promise(); } @@ -669,8 +736,6 @@ define(function (require, exports, module) { } if (docToSave.isDirty) { - var writeError = false; - if (docToSave.keepChangesTime) { // The user has decided to keep conflicting changes in the editor. Check to make sure // the file hasn't changed since they last decided to do that. @@ -692,7 +757,7 @@ define(function (require, exports, module) { result.resolve(file); } result.always(function () { - EditorManager.focusEditor(); + MainViewManager.focusActivePane(); }); return result.promise(); } @@ -700,6 +765,7 @@ define(function (require, exports, module) { /** * Reverts the Document to the current contents of its file on disk. Discards any unsaved changes * in the Document. + * @private * @param {Document} doc * @param {boolean=} suppressError If true, then a failure to read the file will be ignored and the * resulting promise will be resolved rather than rejected. @@ -707,7 +773,7 @@ define(function (require, exports, module) { * rejected with a FileSystemError if the file cannot be read (after showing an error * dialog to the user). */ - function doRevert(doc, suppressError) { + function _doRevert(doc, suppressError) { var result = new $.Deferred(); FileUtils.readAsText(doc.file) @@ -763,21 +829,23 @@ define(function (require, exports, module) { result.resolve(newFile); } - // Replace old document with new one in open editor & working set + // Replace old document with new one in open editor & workingset function openNewFile() { var fileOpenPromise; if (FileViewController.getFileSelectionFocus() === FileViewController.PROJECT_MANAGER) { - // If selection is in the tree, leave working set unchanged - even if orig file is in the list + // If selection is in the tree, leave workingset unchanged - even if orig file is in the list fileOpenPromise = FileViewController .openAndSelectDocument(path, FileViewController.PROJECT_MANAGER); } else { - // If selection is in working set, replace orig item in place with the new file - var index = DocumentManager.findInWorkingSet(doc.file.fullPath); - // Remove old file from working set; no redraw yet since there's a pause before the new file is opened - DocumentManager.removeFromWorkingSet(doc.file, true); - // Add new file to working set, and ensure we now redraw (even if index hasn't changed) - fileOpenPromise = handleFileAddToWorkingSet({fullPath: path, index: index, forceRedraw: true}); + // If selection is in workingset, replace orig item in place with the new file + var info = MainViewManager.findInAllWorkingSets(doc.file.fullPath).shift(); + + // Remove old file from workingset; no redraw yet since there's a pause before the new file is opened + MainViewManager._removeView(info.paneId, doc.file, true); + + // Add new file to workingset, and ensure we now redraw (even if index hasn't changed) + fileOpenPromise = handleFileAddToWorkingSetAndOpen({fullPath: path, paneId: info.paneId, index: info.index, forceRedraw: true}); } // always configure editor after file is opened @@ -805,12 +873,12 @@ define(function (require, exports, module) { .done(function () { // If there were unsaved changes before Save As, they don't stay with the old // file anymore - so must revert the old doc to match disk content. - // Only do this if the doc was dirty: doRevert on a file that is not dirty and - // not in the working set has the side effect of adding it to the working set. + // Only do this if the doc was dirty: _doRevert on a file that is not dirty and + // not in the workingset has the side effect of adding it to the workingset. if (doc.isDirty && !(doc.isUntitled())) { - // if the file is dirty it must be in the working set - // doRevert is side effect free in this case - doRevert(doc).always(openNewFile); + // if the file is dirty it must be in the workingset + // _doRevert is side effect free in this case + _doRevert(doc).always(openNewFile); } else { openNewFile(); } @@ -834,7 +902,11 @@ define(function (require, exports, module) { // (Issue #4489) if we're saving an untitled document, go ahead and switch to this document // in the editor, so that if we're, for example, saving several files (ie. Save All), // then the user can visually tell which document we're currently prompting them to save. - DocumentManager.setCurrentDocument(doc); + var info = MainViewManager.findInAllWorkingSets(origPath).shift(); + + if (info) { + MainViewManager._open(info.paneId, doc.file); + } // If the document is untitled, default to project root. saveAsDefaultPath = ProjectManager.getProjectRoot().fullPath; @@ -931,7 +1003,7 @@ define(function (require, exports, module) { }); return savePromise; } else { - // working set entry that was never actually opened - ignore + // workingset entry that was never actually opened - ignore filesAfterSave.push(file); return (new $.Deferred()).resolve().promise(); } @@ -947,7 +1019,7 @@ define(function (require, exports, module) { * @return {$.Promise} */ function saveAll() { - return _saveFileList(DocumentManager.getWorkingSet()); + return _saveFileList(MainViewManager.getWorkingSet(MainViewManager.ALL_PANES)); } /** @@ -986,7 +1058,7 @@ define(function (require, exports, module) { } /** - * Closes the specified file: removes it from the working set, and closes the main editor if one + * Closes the specified file: removes it from the workingset, and closes the main editor if one * is open. Prompts user about saving changes first, if document is dirty. * * @param {?{file: File, promptOnly:boolean}} commandData Optional bag of arguments: @@ -1002,58 +1074,28 @@ define(function (require, exports, module) { function handleFileClose(commandData) { var file, promptOnly, - _forceClose; + _forceClose, + paneId = MainViewManager.ACTIVE_PANE; if (commandData) { file = commandData.file; promptOnly = commandData.promptOnly; _forceClose = commandData._forceClose; + paneId = commandData.paneId || paneId; } - // utility function for handleFileClose: closes document & removes from working set + // utility function for handleFileClose: closes document & removes from workingset function doClose(file) { if (!promptOnly) { - // This selects a different document if the working set has any other options - DocumentManager.closeFullEditor(file); - - EditorManager.focusEditor(); + MainViewManager._close(paneId, file); } } var result = new $.Deferred(), promise = result.promise(); - function doCloseCustomViewer() { - if (!promptOnly) { - var nextFile = DocumentManager.getNextPrevFile(1); - if (nextFile) { - // opening a text file will automatically close the custom viewer. - // This is done in the currentDocumentChange handler in EditorManager - doOpen(nextFile.fullPath).always(function () { - EditorManager.focusEditor(); - result.resolve(); - }); - } else { - EditorManager._closeCustomViewer(); - result.resolve(); - } - } - } - - // Close custom viewer if, either - // - a custom viewer is currently displayed and no file specified in command data - // - a custom viewer is currently displayed and the file specified in command data - // is the file in the custom viewer - if (!DocumentManager.getCurrentDocument()) { - if ((EditorManager.getCurrentlyViewedPath() && !file) || - (file && file.fullPath === EditorManager.getCurrentlyViewedPath())) { - doCloseCustomViewer(); - return promise; - } - } - // Default to current document if doc is null - if (!file && DocumentManager.getCurrentDocument()) { - file = DocumentManager.getCurrentDocument().file; + if (!file) { + file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); } // No-op if called when nothing is open; TODO: (issue #273) should command be grayed out instead? @@ -1123,30 +1165,30 @@ define(function (require, exports, module) { // we want to ignore errors during the revert, since we don't want a failed revert // to throw a dialog if the document isn't actually open in the UI. var suppressError = !DocumentManager.getOpenDocumentForPath(file.fullPath); - doRevert(doc, suppressError) + _doRevert(doc, suppressError) .then(result.resolve, result.reject); } } }); result.always(function () { - EditorManager.focusEditor(); + MainViewManager.focusActivePane(); }); } else { // File is not open, or IS open but Document not dirty: close immediately doClose(file); - EditorManager.focusEditor(); + MainViewManager.focusActivePane(); result.resolve(); } return promise; } /** - * @param {!Array.} list - * @param {boolean} promptOnly - * @param {boolean} clearCurrentDoc + * @param {!Array.} list - the list of files to close + * @param {boolean} promptOnly - true to just prompt for saving documents with actually closing them. * @param {boolean} _forceClose Whether to force all the documents to close even if they have unsaved changes. For unit testing only. + * @return {jQuery.Promise} promise that is resolved or rejected when the function finishes. */ - function _closeList(list, promptOnly, clearCurrentDoc, _forceClose) { + function _closeList(list, promptOnly, _forceClose) { var result = new $.Deferred(), unsavedDocs = []; @@ -1222,7 +1264,7 @@ define(function (require, exports, module) { result.done(function (listAfterSave) { listAfterSave = listAfterSave || list; if (!promptOnly) { - DocumentManager.removeListFromWorkingSet(listAfterSave, clearCurrentDoc); + MainViewManager._closeList(MainViewManager.ALL_PANES, listAfterSave); } }); @@ -1230,7 +1272,7 @@ define(function (require, exports, module) { } /** - * Closes all open documents; equivalent to calling handleFileClose() for each document, except + * Closes all open files; equivalent to calling handleFileClose() for each document, except * that unsaved changes are confirmed once, in bulk. * @param {?{promptOnly: boolean, _forceClose: boolean}} * If promptOnly is true, only displays the relevant confirmation UI and does NOT @@ -1241,20 +1283,24 @@ define(function (require, exports, module) { * @return {$.Promise} a promise that is resolved when all files are closed */ function handleFileCloseAll(commandData) { - return _closeList(DocumentManager.getWorkingSet(), - (commandData && commandData.promptOnly), true, (commandData && commandData._forceClose)).done(function () { - if (!DocumentManager.getCurrentDocument()) { - EditorManager._closeCustomViewer(); - } - }); + return _closeList(MainViewManager.getAllOpenFiles(), + (commandData && commandData.promptOnly), (commandData && commandData._forceClose)); } + + /** + * Closes a list of open files; equivalent to calling handleFileClose() for each document, except + * that unsaved changes are confirmed once, in bulk. + * @param {?{promptOnly: boolean, _forceClose: boolean}} + * If promptOnly is true, only displays the relevant confirmation UI and does NOT + * actually close any documents. This is useful when chaining close-all together with + * other user prompts that may be cancelable. + * If _forceClose is true, forces the files to close with no confirmation even if dirty. + * Should only be used for unit test cleanup. + * @return {$.Promise} a promise that is resolved when all files are closed + */ function handleFileCloseList(commandData) { - return _closeList(commandData.fileList, false, false).done(function () { - if (!DocumentManager.getCurrentDocument()) { - EditorManager._closeCustomViewer(); - } - }); + return _closeList(commandData.fileList); } /** @@ -1266,7 +1312,10 @@ define(function (require, exports, module) { * @private * Common implementation for close/quit/reload which all mostly * the same except for the final step - */ + * @param {Object} commandData - (not referenced) + * @param {!function()} postCloseHandler - called after close + * @param {!function()} failHandler - called when the save fails to cancel closing the window + */ function _handleWindowGoingAway(commandData, postCloseHandler, failHandler) { if (_windowGoingAway) { //if we get called back while we're closing, then just return @@ -1313,7 +1362,10 @@ define(function (require, exports, module) { $(PopUpManager).triggerHandler("beforeMenuPopup"); } - /** Confirms any unsaved changes, then closes the window */ + /** + * Confirms any unsaved changes, then closes the window + * @param {Object} command data + */ function handleFileCloseWindow(commandData) { return _handleWindowGoingAway( commandData, @@ -1332,9 +1384,8 @@ define(function (require, exports, module) { // Prefer selected sidebar item (which could be a folder) var entry = ProjectManager.getSelectedItem(); if (!entry) { - // Else use current file (not selected in ProjectManager if not visible in tree or working set) - var doc = DocumentManager.getCurrentDocument(); - entry = doc && doc.file; + // Else use current file (not selected in ProjectManager if not visible in tree or workingset) + entry = MainViewManager.getCurrentlyViewedFile(); } if (entry) { ProjectManager.renameItemInline(entry); @@ -1368,8 +1419,7 @@ define(function (require, exports, module) { */ function detectDocumentNavEnd(event) { if (event.keyCode === KeyEvent.DOM_VK_CONTROL) { // Ctrl key - DocumentManager.finalizeDocumentNavigation(); - + MainViewManager.endTraversal(); _addedNavKeyHandler = false; $(window.document.body).off("keyup", detectDocumentNavEnd); } @@ -1377,10 +1427,14 @@ define(function (require, exports, module) { /** Navigate to the next/previous (MRU) document. Don't update MRU order yet */ function goNextPrevDoc(inc) { - var file = DocumentManager.getNextPrevFile(inc); - if (file) { - DocumentManager.beginDocumentNavigation(); - CommandManager.execute(Commands.FILE_OPEN, { fullPath: file.fullPath }); + var result = MainViewManager.traverseToNextViewByMRU(inc); + if (result) { + var file = result.file, + paneId = result.paneId; + + MainViewManager.beginTraversal(); + CommandManager.execute(Commands.FILE_OPEN, {fullPath: file.fullPath, + paneId: paneId }); // Listen for ending of Ctrl+Tab sequence if (!_addedNavKeyHandler) { @@ -1390,17 +1444,22 @@ define(function (require, exports, module) { } } + /** Next Doc command handler **/ function handleGoNextDoc() { goNextPrevDoc(+1); + } + /** Previous Doc command handler **/ function handleGoPrevDoc() { goNextPrevDoc(-1); } + /** Show in File Tree command handler **/ function handleShowInTree() { - ProjectManager.showInTree(DocumentManager.getCurrentDocument().file); + ProjectManager.showInTree(MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE)); } + /** Delete file command handler **/ function handleFileDelete() { var entry = ProjectManager.getSelectedItem(); if (entry.isDirectory) { @@ -1434,7 +1493,7 @@ define(function (require, exports, module) { } } - /** Show the selected sidebar (tree or working set) item in Finder/Explorer */ + /** Show the selected sidebar (tree or workingset) item in Finder/Explorer */ function handleShowInOS() { var entry = ProjectManager.getSelectedItem(); if (entry) { @@ -1522,16 +1581,27 @@ define(function (require, exports, module) { _isReloading = false; }); } - - function handleReload() { + + /** + * Restarts brackets Handler + * @param {boolean=} loadWithoutExtensions - true to restart without extensions, + * otherwise extensions are loadeed as it is durning a typical boot + */ + function handleReload(loadWithoutExtensions) { var href = window.location.href, params = new UrlParams(); // Make sure the Reload Without User Extensions parameter is removed params.parse(); - if (params.get("reloadWithoutUserExts")) { - params.remove("reloadWithoutUserExts"); + if (loadWithoutExtensions) { + if (!params.get("reloadWithoutUserExts")) { + params.put("reloadWithoutUserExts", true); + } + } else { + if (params.get("reloadWithoutUserExts")) { + params.remove("reloadWithoutUserExts"); + } } if (href.indexOf("?") !== -1) { @@ -1549,29 +1619,11 @@ define(function (require, exports, module) { }, 100); } - function handleReloadWithoutExts() { - var href = window.location.href, - params = new UrlParams(); - - params.parse(); - - if (!params.get("reloadWithoutUserExts")) { - params.put("reloadWithoutUserExts", true); - } - - if (href.indexOf("?") !== -1) { - href = href.substring(0, href.indexOf("?")); - } - - href += "?" + params.toString(); - - // Give Mac native menus extra time to update shortcut highlighting. - // Prevents the menu highlighting from getting messed up after reload. - window.setTimeout(function () { - browserReload(href); - }, 100); - } + /** Reload Without Extensions commnad handler **/ + var handleReloadWithoutExts = _.partial(handleReload, true); + + /** Do some initialization when the DOM is ready **/ AppInit.htmlReady(function () { // If in Reload Without User Extensions mode, update UI and log console message var params = new UrlParams(), @@ -1607,27 +1659,37 @@ define(function (require, exports, module) { showInOS = Strings.CMD_SHOW_IN_FINDER; } - // Register global commands - CommandManager.register(Strings.CMD_FILE_OPEN, Commands.FILE_OPEN, handleFileOpen); - CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet); - CommandManager.register(Strings.CMD_FILE_NEW_UNTITLED, Commands.FILE_NEW_UNTITLED, handleFileNew); - CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject); - CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject); - CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave); - CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll); - CommandManager.register(Strings.CMD_FILE_SAVE_AS, Commands.FILE_SAVE_AS, handleFileSaveAs); - CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename); - CommandManager.register(Strings.CMD_FILE_DELETE, Commands.FILE_DELETE, handleFileDelete); - - CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose); - CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll); - CommandManager.register(Strings.CMD_FILE_CLOSE_LIST, Commands.FILE_CLOSE_LIST, handleFileCloseList); - CommandManager.register(quitString, Commands.FILE_QUIT, handleFileQuit); - - CommandManager.register(Strings.CMD_NEXT_DOC, Commands.NAVIGATE_NEXT_DOC, handleGoNextDoc); - CommandManager.register(Strings.CMD_PREV_DOC, Commands.NAVIGATE_PREV_DOC, handleGoPrevDoc); - CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree); - CommandManager.register(showInOS, Commands.NAVIGATE_SHOW_IN_OS, handleShowInOS); + // Deprecated commands + CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet); + CommandManager.register(Strings.CMD_FILE_OPEN, Commands.FILE_OPEN, handleDocumentOpen); + + // New commands + CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, handleFileAddToWorkingSetAndOpen); + CommandManager.register(Strings.CMD_FILE_OPEN, Commands.CMD_OPEN, handleFileOpen); + + // File Commands + CommandManager.register(Strings.CMD_FILE_NEW_UNTITLED, Commands.FILE_NEW_UNTITLED, handleFileNew); + CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject); + CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject); + CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave); + CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll); + CommandManager.register(Strings.CMD_FILE_SAVE_AS, Commands.FILE_SAVE_AS, handleFileSaveAs); + CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename); + CommandManager.register(Strings.CMD_FILE_DELETE, Commands.FILE_DELETE, handleFileDelete); + + // Close Commands + CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose); + CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll); + CommandManager.register(Strings.CMD_FILE_CLOSE_LIST, Commands.FILE_CLOSE_LIST, handleFileCloseList); + + // Traversal + CommandManager.register(Strings.CMD_NEXT_DOC, Commands.NAVIGATE_NEXT_DOC, handleGoNextDoc); + CommandManager.register(Strings.CMD_PREV_DOC, Commands.NAVIGATE_PREV_DOC, handleGoPrevDoc); + + // Special Commands + CommandManager.register(showInOS, Commands.NAVIGATE_SHOW_IN_OS, handleShowInOS); + CommandManager.register(quitString, Commands.FILE_QUIT, handleFileQuit); + CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree); // These commands have no UI representation and are only used internally CommandManager.registerInternal(Commands.APP_ABORT_QUIT, handleAbortQuit); @@ -1638,8 +1700,8 @@ define(function (require, exports, module) { // Listen for changes that require updating the editor titlebar $(DocumentManager).on("dirtyFlagChange", handleDirtyChange); - $(DocumentManager).on("fileNameChange", updateDocumentTitle); - $(EditorManager).on("currentlyViewedFileChange", updateDocumentTitle); + $(DocumentManager).on("fileNameChange", handleCurrentFileChange); + $(MainViewManager).on("currentFileChange", handleCurrentFileChange); // Reset the untitled document counter before changing projects $(ProjectManager).on("beforeProjectClose", function () { _nextUntitledIndexToUse = 1; }); diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index ca4ecedd354..af108ccab92 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -26,8 +26,8 @@ /*global define, $ */ /** - * DocumentManager maintains a list of currently 'open' Documents. It also owns the list of files in - * the working set, and the notion of which Document is currently shown in the main editor UI area. + * DocumentManager maintains a list of currently 'open' Documents. The DocumentManager is responsible + * for coordinating document operations and dispatching certain document events. * * Document is the model for a file's contents; it dispatches events whenever those contents change. * To transiently inspect a file's content, simply get a Document and call getText() on it. However, @@ -40,9 +40,7 @@ * Secretly, a Document may use an Editor instance to act as the model for its internal state. (This * is unavoidable because CodeMirror does not separate its model from its UI). Documents are not * modifiable until they have a backing 'master Editor'. Creation of the backing Editor is owned by - * EditorManager. A Document only gets a backing Editor if it becomes the currentDocument, or if edits - * occur in any Editor (inline or full-sized) bound to the Document; there is currently no other way - * to ensure a Document is modifiable. + * EditorManager. A Document only gets a backing Editor if it opened in an editor. * * A non-modifiable Document may still dispatch change notifications, if the Document was changed * externally on disk. @@ -59,26 +57,19 @@ * - documentRefreshed -- When a Document's contents have been reloaded from disk. The 2nd arg to the * listener is the Document that has been refreshed. * - * - currentDocumentChange -- When the value of getCurrentDocument() changes. 2nd argument to the listener - * is the current document and 3rd argument is the previous document. + * NOTE: WorkingSet APIs have been deprecated and have moved to MainViewManager as WorkingSet APIs + * Some WorkingSet APIs that have been identified as being used by 3rd party extensions will + * emit deprecation warnings and call the WorkingSet APIS to maintain backwards compatibility * - * To listen for working set changes, you must listen to *all* of these events: - * - workingSetAdd -- When a file is added to the working set (see getWorkingSet()). The 2nd arg - * to the listener is the added File, and the 3rd arg is the index it was inserted at. - * - workingSetAddList -- When multiple files are added to the working set (e.g. project open, multiple file open). - * The 2nd arg to the listener is the array of added File objects. - * - workingSetRemove -- When a file is removed from the working set (see getWorkingSet()). The - * 2nd arg to the listener is the removed File. - * - workingSetRemoveList -- When multiple files are removed from the working set (e.g. project close). - * The 2nd arg to the listener is the array of removed File objects. - * - workingSetSort -- When the workingSet array is reordered without additions or removals. - * Listener receives no arguments. - * - * - workingSetDisableAutoSorting -- Dispatched in addition to workingSetSort when the reorder was caused - * by manual dragging and dropping. Listener receives no arguments. + * - currentDocumentChange -- Deprecated: use EditorManager activeEditorChange (which covers all editors, + * not just full-sized editors) or MainViewManager currentFileChange (which covers full-sized views + * only, but is also triggered for non-editor views e.g. image files). * * - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name. - * The 3rd arg is the new name. + * The 3rd arg is the new name. Generally, however, file objects have already been changed by the + * time this event is dispatched so code that relies on matching the filename to a file object + * will need to compare the newname. + * * - pathDeleted -- When a file or folder has been deleted. The 2nd arg is the path that was deleted. * * These are jQuery events, so to listen for them you do something like this: @@ -91,37 +82,23 @@ define(function (require, exports, module) { var _ = require("thirdparty/lodash"); - var DocumentModule = require("document/Document"), - ProjectManager = require("project/ProjectManager"), - EditorManager = require("editor/EditorManager"), + var AppInit = require("utils/AppInit"), + DocumentModule = require("document/Document"), + DeprecationWarning = require("utils/DeprecationWarning"), + MainViewManager = require("view/MainViewManager"), + MainViewFactory = require("view/MainViewFactory"), FileSyncManager = require("project/FileSyncManager"), FileSystem = require("filesystem/FileSystem"), PreferencesManager = require("preferences/PreferencesManager"), FileUtils = require("file/FileUtils"), InMemoryFile = require("document/InMemoryFile"), CommandManager = require("command/CommandManager"), - Async = require("utils/Async"), - PerfUtils = require("utils/PerfUtils"), Commands = require("command/Commands"), + PerfUtils = require("utils/PerfUtils"), LanguageManager = require("language/LanguageManager"), Strings = require("strings"); - /** - * @private - * @see DocumentManager.getCurrentDocument() - */ - var _currentDocument = null; - - /** - * Returns the Document that is currently open in the editor UI. May be null. - * When this changes, DocumentManager dispatches a "currentDocumentChange" event. The current - * document always has a backing Editor (Document._masterEditor != null) and is thus modifiable. - * @return {?Document} - */ - function getCurrentDocument() { - return _currentDocument; - } - + /** * @private * Random path prefix for untitled documents @@ -129,81 +106,75 @@ define(function (require, exports, module) { var _untitledDocumentPath = "/_brackets_" + _.random(10000000, 99999999); /** + * All documents with refCount > 0. Maps Document.file.id -> Document. * @private - * @type {Array.} - * @see DocumentManager.getWorkingSet() - */ - var _workingSet = []; - - /** - * @private - * Contains the same set of items as _workingSet, but ordered by how recently they were _currentDocument (0 = most recent). - * @type {Array.} - */ - var _workingSetMRUOrder = []; - - /** - * @private - * Contains the same set of items as _workingSet, but ordered in the way they where added to _workingSet (0 = last added). - * @type {Array.} + * @type {Object.} */ - var _workingSetAddedOrder = []; - + var _openDocuments = {}; + /** - * While true, the MRU order is frozen - * @type {boolean} + * Returns the existing open Document for the given file, or null if the file is not open ('open' + * means referenced by the UI somewhere). If you will hang onto the Document, you must addRef() + * it; see {@link getDocumentForPath()} for details. + * @param {!string} fullPath + * @return {?Document} */ - var _documentNavPending = false; + function getOpenDocumentForPath(fullPath) { + var id; + + // Need to walk all open documents and check for matching path. We can't + // use getFileForPath(fullPath).id since the file it returns won't match + // an Untitled document's InMemoryFile. + for (id in _openDocuments) { + if (_openDocuments.hasOwnProperty(id)) { + if (_openDocuments[id].file.fullPath === fullPath) { + return _openDocuments[id]; + } + } + } + return null; + } /** - * All documents with refCount > 0. Maps Document.file.id -> Document. - * @private - * @type {Object.} + * Returns the Document that is currently open in the editor UI. May be null. + * @return {?Document} */ - var _openDocuments = {}; + function getCurrentDocument() { + var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); + + if (file) { + return getOpenDocumentForPath(file.fullPath); + } + + return null; + } + /** * Returns a list of items in the working set in UI list order. May be 0-length, but never null. - * - * When a file is added this list, DocumentManager dispatches a "workingSetAdd" event. - * When a file is removed from list, DocumentManager dispatches a "workingSetRemove" event. - * To listen for ALL changes to this list, you must listen for both events. - * - * Which items belong in the working set is managed entirely by DocumentManager. Callers cannot - * (yet) change this collection on their own. - * + * @deprecated Use MainViewManager.getWorkingSet() instead * @return {Array.} */ function getWorkingSet() { - return _.clone(_workingSet); + DeprecationWarning.deprecationWarning("Use MainViewManager.getViews() instead of DocumentManager.getWorkingSet()", true); + return MainViewManager.getWorkingSet(MainViewManager.ALL_PANES) + .filter(function (file) { + // Legacy didn't allow for files with custom viewers + return !MainViewFactory.findSuitableFactoryForPath(file.fullPath); + }); } /** * Returns the index of the file matching fullPath in the working set. - * Returns -1 if not found. + * @deprecated Use MainViewManager.findInWorkingSet() instead * @param {!string} fullPath - * @param {Array.=} list Pass this arg to search a different array of files. Internal - * use only. - * @return {number} index + * @return {number} index, -1 if not found */ - function findInWorkingSet(fullPath, list) { - list = list || _workingSet; - - return _.findIndex(list, function (file, i) { - return file.fullPath === fullPath; - }); + function findInWorkingSet(fullPath) { + DeprecationWarning.deprecationWarning("Use MainViewManager.findInWorkingSet() instead of DocumentManager.findInWorkingSet()", true); + return MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, fullPath); } - /** - * Returns the index of the file matching fullPath in _workingSetAddedOrder. - * Returns -1 if not found. - * @param {!string} fullPath - * @return {number} index - */ - function findInWorkingSetAddedOrder(fullPath) { - return findInWorkingSet(fullPath, _workingSetAddedOrder); - } - /** * Returns all Documents that are 'open' in the UI somewhere (for now, this means open in an * inline editor and/or a full-size editor). Only these Documents can be modified, and only @@ -223,60 +194,20 @@ define(function (require, exports, module) { /** - * Adds the given file to the end of the working set list, if it is not already in the list - * and it does not have a custom viewer. - * Does not change which document is currently open in the editor. Completes synchronously. + * Adds the given file to the end of the working set list. + * @deprecated Use MainViewManager.addToWorkingSet() instead * @param {!File} file * @param {number=} index Position to add to list (defaults to last); -1 is ignored * @param {boolean=} forceRedraw If true, a working set change notification is always sent * (useful if suppressRedraw was used with removeFromWorkingSet() earlier) */ function addToWorkingSet(file, index, forceRedraw) { - var indexRequested = (index !== undefined && index !== null && index !== -1); - - // If the file has a custom viewer, then don't add it to the working set. - if (EditorManager.getCustomViewerForPath(file.fullPath)) { - return; - } - - // If doc is already in working set, don't add it again - var curIndex = findInWorkingSet(file.fullPath); - if (curIndex !== -1) { - // File is in working set, but not at the specifically requested index - only need to reorder - if (forceRedraw || (indexRequested && curIndex !== index)) { - var entry = _workingSet.splice(curIndex, 1)[0]; - _workingSet.splice(index, 0, entry); - $(exports).triggerHandler("workingSetSort"); - } - return; - } - - if (!indexRequested) { - // If no index is specified, just add the file to the end of the working set. - _workingSet.push(file); - } else { - // If specified, insert into the working set list at this 0-based index - _workingSet.splice(index, 0, file); - } - - // Add to MRU order: either first or last, depending on whether it's already the current doc or not - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { - _workingSetMRUOrder.unshift(file); - } else { - _workingSetMRUOrder.push(file); - } - - // Add first to Added order - _workingSetAddedOrder.unshift(file); - - // Dispatch event - if (!indexRequested) { - index = _workingSet.length - 1; - } - $(exports).triggerHandler("workingSetAdd", [file, index]); + DeprecationWarning.deprecationWarning("Use MainViewManager.addToWorkingSet() instead of DocumentManager.addToWorkingSet()", true); + MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, file, index, forceRedraw); } /** + * @deprecated Use MainViewManager.addListToWorkingSet() instead * Adds the given file list to the end of the working set list. * If a file in the list has its own custom viewer, then it * is not added into the working set. @@ -286,330 +217,80 @@ define(function (require, exports, module) { * @param {!Array.} fileList */ function addListToWorkingSet(fileList) { - var uniqueFileList = []; - - // Process only files not already in working set - fileList.forEach(function (file, index) { - // If doc has a custom viewer, then don't add it to the working set. - // Or if doc is already in working set, don't add it again. - if (!EditorManager.getCustomViewerForPath(file.fullPath) && - findInWorkingSet(file.fullPath) === -1) { - uniqueFileList.push(file); - - // Add - _workingSet.push(file); - - // Add to MRU order: either first or last, depending on whether it's already the current doc or not - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { - _workingSetMRUOrder.unshift(file); - } else { - _workingSetMRUOrder.push(file); - } - - // Add first to Added order - _workingSetAddedOrder.splice(index, 1, file); - } - }); - - - // Dispatch event - $(exports).triggerHandler("workingSetAddList", [uniqueFileList]); + DeprecationWarning.deprecationWarning("Use MainViewManager.addListToWorkingSet() instead of DocumentManager.addListToWorkingSet()", true); + MainViewManager.addListToWorkingSet(MainViewManager.ACTIVE_PANE, fileList); } + /** - * Warning: low level API - use FILE_CLOSE command in most cases. - * Removes the given file from the working set list, if it was in the list. Does not change - * the current editor even if it's for this file. Does not prompt for unsaved changes. - * @param {!File} file - * @param {boolean=} true to suppress redraw after removal */ - function removeFromWorkingSet(file, suppressRedraw) { - // If doc isn't in working set, do nothing - var index = findInWorkingSet(file.fullPath); - if (index === -1) { - return; - } - - // Remove - _workingSet.splice(index, 1); - _workingSetMRUOrder.splice(findInWorkingSet(file.fullPath, _workingSetMRUOrder), 1); - _workingSetAddedOrder.splice(findInWorkingSet(file.fullPath, _workingSetAddedOrder), 1); - - // Dispatch event - $(exports).triggerHandler("workingSetRemove", [file, suppressRedraw]); + function removeListFromWorkingSet(list) { + throw new Error("removeListFromWorkingSet() has been deprecated. Use Command.FILE_CLOSE_LIST instead."); } - + /** - * Removes all files from the working set list. + * closes all open files + * @deprecated Use MainViewManager._closeAll() instead + * Calling this discards any unsaved changes, so the UI should confirm with the user before calling this. */ - function _removeAllFromWorkingSet() { - var fileList = _workingSet; - - // Remove all - _workingSet = []; - _workingSetMRUOrder = []; - _workingSetAddedOrder = []; - - // Dispatch event - $(exports).triggerHandler("workingSetRemoveList", [fileList]); + function closeAll() { + DeprecationWarning.deprecationWarning("Use MainViewManager._closeAll() instead of DocumentManager.closeAll()", true); + CommandManager.execute(Commands.FILE_CLOSE_ALL, {PaneId: MainViewManager.ALL_PANES}); } /** - * Moves document to the front of the MRU list, IF it's in the working set; no-op otherwise. - * @param {!Document} - */ - function _markMostRecent(doc) { - var mruI = findInWorkingSet(doc.file.fullPath, _workingSetMRUOrder); - if (mruI !== -1) { - _workingSetMRUOrder.splice(mruI, 1); - _workingSetMRUOrder.unshift(doc.file); - } - } - - - /** - * Mutually exchanges the files at the indexes passed by parameters. - * @param {number} index Old file index - * @param {number} index New file index + * closes the specified file file + * @deprecated use MainViewManager._close() instead + * @param {!File} file */ - function swapWorkingSetIndexes(index1, index2) { - var length = _workingSet.length - 1; - var temp; - - if (index1 >= 0 && index2 <= length && index1 >= 0 && index2 <= length) { - temp = _workingSet[index1]; - _workingSet[index1] = _workingSet[index2]; - _workingSet[index2] = temp; - - $(exports).triggerHandler("workingSetSort"); - $(exports).triggerHandler("workingSetDisableAutoSorting"); - } + function closeFullEditor(file) { + DeprecationWarning.deprecationWarning("Use MainViewManager._close() instead of DocumentManager.closeFullEditor()", true); + CommandManager.execute(Commands.FILE_CLOSE, {File: file}); } /** - * Sorts _workingSet using the compare function - * @param {function(File, File): number} compareFn The function that will be used inside JavaScript's - * sort function. The return a value should be >0 (sort a to a lower index than b), =0 (leaves a and b - * unchanged with respect to each other) or <0 (sort b to a lower index than a) and must always returns - * the same value when given a specific pair of elements a and b as its two arguments. - * Documentation: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort + * opens the specified document for editing in the currently active pane + * @deprecated use MainViewManager._edit() instead + * @param {!Document} document The Document to make current. */ - function sortWorkingSet(compareFn) { - _workingSet.sort(compareFn); - $(exports).triggerHandler("workingSetSort"); + function setCurrentDocument(doc) { + DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.CMD_OPEN) instead of DocumentManager.setCurrentDocument()", true); + CommandManager.execute(Commands.CMD_OPEN, {fullPath: doc.file.fullPath}); } - + /** - * Indicate that changes to currentDocument are temporary for now, and should not update the MRU - * ordering of the working set. Useful for next/previous keyboard navigation (until Ctrl is released) - * or for incremental-search style document preview like Quick Open will eventually have. - * Can be called any number of times, and ended by a single finalizeDocumentNavigation() call. + * freezes the Working Set MRU list + * @deprecated use MainViewManager.beginTraversal() instead */ function beginDocumentNavigation() { - _documentNavPending = true; + DeprecationWarning.deprecationWarning("Use MainViewManager.beginTraversal() instead of DocumentManager.beginDocumentNavigation()", true); + MainViewManager.beginTraversal(); } /** - * Un-freezes the MRU list after one or more beginDocumentNavigation() calls. Whatever document is - * current is bumped to the front of the MRU list. + * ends document navigation and moves the current file to the front of the MRU list in the Working Set + * @deprecated use MainViewManager.endTraversal() instead */ function finalizeDocumentNavigation() { - if (_documentNavPending) { - _documentNavPending = false; - - _markMostRecent(_currentDocument); - } + DeprecationWarning.deprecationWarning("Use MainViewManager.endTraversal() instead of DocumentManager.finalizeDocumentNavigation()", true); + MainViewManager.endTraversal(); } - /** * Get the next or previous file in the working set, in MRU order (relative to currentDocument). May * return currentDocument itself if working set is length 1. - * @param {number} inc -1 for previous, +1 for next; no other values allowed - * @return {?File} null if working set empty + * @deprecated use MainViewManager.traverseToNextViewByMRU() instead */ function getNextPrevFile(inc) { - if (inc !== -1 && inc !== +1) { - console.error("Illegal argument: inc = " + inc); - return null; + DeprecationWarning.deprecationWarning("Use MainViewManager.traverseToNextViewByMRU() instead of DocumentManager.getNextPrevFile()", true); + var result = MainViewManager.traverseToNextViewByMRU(inc); + if (result) { + return result.file; } - - if (EditorManager.getCurrentlyViewedPath()) { - var mruI = findInWorkingSet(EditorManager.getCurrentlyViewedPath(), _workingSetMRUOrder); - if (mruI === -1) { - // If doc not in working set, return most recent working set item - if (_workingSetMRUOrder.length > 0) { - return _workingSetMRUOrder[0]; - } - } else { - // If doc is in working set, return next/prev item with wrap-around - var newI = mruI + inc; - if (newI >= _workingSetMRUOrder.length) { - newI = 0; - } else if (newI < 0) { - newI = _workingSetMRUOrder.length - 1; - } - - return _workingSetMRUOrder[newI]; - } - } - - // If no doc open or working set empty, there is no "next" file return null; } - - /** - * Changes currentDocument to the given Document, firing currentDocumentChange, which in turn - * causes this Document's main editor UI to be shown in the editor pane, updates the selection - * in the file tree / working set UI, etc. This call may also add the item to the working set. - * - * @param {!Document} document The Document to make current. May or may not already be in the - * working set. - */ - function setCurrentDocument(doc) { - - // If this doc is already current, do nothing - if (_currentDocument === doc) { - return; - } - - var perfTimerName = PerfUtils.markStart("setCurrentDocument:\t" + doc.file.fullPath); - - if (_currentDocument) { - $(_currentDocument).off("languageChanged.DocumentManager"); - } - - // If file is untitled or otherwise not within project tree, add it to - // working set right now (don't wait for it to become dirty) - if (doc.isUntitled() || !ProjectManager.isWithinProject(doc.file.fullPath)) { - addToWorkingSet(doc.file); - } - - // Adjust MRU working set ordering (except while in the middle of a Ctrl+Tab sequence) - if (!_documentNavPending) { - _markMostRecent(doc); - } - - // Make it the current document - var previousDocument = _currentDocument; - _currentDocument = doc; - - // Proxy this doc's languageChange events as long as it's current - $(_currentDocument).on("languageChanged.DocumentManager", function (data) { - $(exports).trigger("currentDocumentLanguageChanged", data); - }); - - $(exports).triggerHandler("currentDocumentChange", [_currentDocument, previousDocument]); - // (this event triggers EditorManager to actually switch editors in the UI) - - PerfUtils.addMeasurement(perfTimerName); - } - - - /** Changes currentDocument to null, causing no full Editor to be shown in the UI */ - function _clearCurrentDocument() { - // If editor already blank, do nothing - if (!_currentDocument) { - return; - } else { - // Change model & dispatch event - var previousDocument = _currentDocument; - _currentDocument = null; - // (this event triggers EditorManager to actually clear the editor UI) - $(exports).triggerHandler("currentDocumentChange", [_currentDocument, previousDocument]); - } - } - - - - /** - * Warning: low level API - use FILE_CLOSE command in most cases. - * Closes the full editor for the given file (if there is one), and removes it from the working - * set. Any other editors for this Document remain open. Discards any unsaved changes without prompting. - * - * Changes currentDocument if this file was the current document (may change to null). - * - * This is a subset of notifyFileDeleted(). Use this for the user-facing Close command. - * - * @param {!File} file - * @param {boolean} skipAutoSelect - if true, don't automatically open and select the next document - */ - function closeFullEditor(file, skipAutoSelect) { - // If this was the current document shown in the editor UI, we're going to switch to a - // different document (or none if working set has no other options) - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { - // Get next most recent doc in the MRU order - var nextFile = getNextPrevFile(1); - if (nextFile && nextFile.fullPath === _currentDocument.file.fullPath) { - // getNextPrevFile() might return the file we're about to close if it's the only one open (due to wraparound) - nextFile = null; - } - - // Switch editor to next document (or blank it out) - if (nextFile && !skipAutoSelect) { - CommandManager.execute(Commands.FILE_OPEN, { fullPath: nextFile.fullPath }) - .done(function () { - // (Now we're guaranteed that the current document is not the one we're closing) - console.assert(!(_currentDocument && _currentDocument.file.fullPath === file.fullPath)); - }) - .fail(function () { - // File chosen to be switched to could not be opened, and the original file - // is still in editor. Close it again so code will try to open the next file, - // or empty the editor if there are no other files. - closeFullEditor(file); - }); - } else { - _clearCurrentDocument(); - } - } - - // Remove closed doc from working set, if it was in there - // This happens regardless of whether the document being closed was the current one or not - removeFromWorkingSet(file); - - // Note: EditorManager will dispose the closed document's now-unneeded editor either in - // response to the editor-swap call above, or the removeFromWorkingSet() call, depending on - // circumstances. See notes in EditorManager for more. - } - - /** - * Equivalent to calling closeFullEditor() for all Documents. Same caveat: this discards any - * unsaved changes, so the UI should confirm with the user before calling this. - */ - function closeAll() { - _clearCurrentDocument(); - _removeAllFromWorkingSet(); - } - - function removeListFromWorkingSet(list, clearCurrentDocument) { - var fileList = [], index; - - if (!list) { - return; - } - - if (clearCurrentDocument) { - _clearCurrentDocument(); - } - - list.forEach(function (file) { - index = findInWorkingSet(file.fullPath); - - if (index !== -1) { - fileList.push(_workingSet[index]); - - _workingSet.splice(index, 1); - _workingSetMRUOrder.splice(findInWorkingSet(file.fullPath, _workingSetMRUOrder), 1); - _workingSetAddedOrder.splice(findInWorkingSet(file.fullPath, _workingSetAddedOrder), 1); - } - }); - - $(exports).triggerHandler("workingSetRemoveList", [fileList]); - } - - /** * Cleans up any loose Documents whose only ref is its own master Editor, and that Editor is not * rooted in the UI anywhere. This can happen if the Editor is auto-created via Document APIs that @@ -621,33 +302,11 @@ define(function (require, exports, module) { // Is the only ref to this document its own master Editor? if (doc._refCount === 1 && doc._masterEditor) { // Destroy the Editor if it's not being kept alive by the UI - EditorManager._destroyEditorIfUnneeded(doc); + MainViewManager._destroyEditorIfNotNeeded(doc); } }); } - /** - * Returns the existing open Document for the given file, or null if the file is not open ('open' - * means referenced by the UI somewhere). If you will hang onto the Document, you must addRef() - * it; see {@link getDocumentForPath()} for details. - * @param {!string} fullPath - * @return {?Document} - */ - function getOpenDocumentForPath(fullPath) { - var id; - - // Need to walk all open documents and check for matching path. We can't - // use getFileForPath(fullPath).id since the file it returns won't match - // an Untitled document's InMemoryFile. - for (id in _openDocuments) { - if (_openDocuments.hasOwnProperty(id)) { - if (_openDocuments[id].file.fullPath === fullPath) { - return _openDocuments[id]; - } - } - } - return null; - } /** * Gets an existing open Document for the given file, or creates a new one if the Document is @@ -809,10 +468,9 @@ define(function (require, exports, module) { * @param {boolean} skipAutoSelect - if true, don't automatically open/select the next document */ function notifyFileDeleted(file, skipAutoSelect) { - // First ensure it's not currentDocument, and remove from working set - closeFullEditor(file, skipAutoSelect); - - // Notify all other editors to close as well + // Notify all editors to close as well + $(exports).triggerHandler("pathDeleted", file.fullPath); + var doc = getOpenDocumentForPath(file.fullPath); if (doc) { $(doc).triggerHandler("deleted"); @@ -820,100 +478,22 @@ define(function (require, exports, module) { // At this point, all those other views SHOULD have released the Doc if (doc && doc._refCount > 0) { - console.log("WARNING: deleted Document still has " + doc._refCount + " references. Did someone addRef() without listening for 'deleted'?"); + console.warn("Deleted " + file.fullPath + " Document still has " + doc._refCount + " references. Did someone addRef() without listening for 'deleted'?"); } } - - - /** - * @private - * Preferences callback. Saves the state of the working set. - */ - function _savePreferences() { - // save the working set file paths - var files = [], - isActive = false, - workingSet = getWorkingSet(), - currentDoc = getCurrentDocument(), - projectRoot = ProjectManager.getProjectRoot(), - context = { location : { scope: "user", - layer: "project", - layerID: projectRoot.fullPath } }; - - if (!projectRoot) { - return; - } - - workingSet.forEach(function (file, index) { - // Do not persist untitled document paths - if (!(file instanceof InMemoryFile)) { - // 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, - viewState: viewState - }); - } - }); - - // Writing out working set files using the project layer specified in 'context'. - PreferencesManager.setViewState("project.files", files, context); - } /** - * @private - * Initializes the working set. + * Called after a file or folder has been deleted. This function is responsible + * for updating underlying model data and notifying all views of the change. + * + * @param {string} path The path of the file/folder that has been deleted */ - function _projectOpen(e) { - // file root is appended for each project - var projectRoot = ProjectManager.getProjectRoot(), - files = [], - context = { location : { scope: "user", - layer: "project" } }; - - files = PreferencesManager.getViewState("project.files", context); - - console.assert(Object.keys(_openDocuments).length === 0); // no files leftover from prev proj - - if (!files) { - return; - } - - var filesToOpen = [], - viewStates = {}, - activeFile; - - // Add all files to the working set without verifying that - // they still exist on disk (for faster project switching) - files.forEach(function (value, index) { - filesToOpen.push(FileSystem.getFileForPath(value.file)); - if (value.active) { - activeFile = value.file; - } - 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) { - activeFile = _workingSet[0].fullPath; - } - - if (activeFile) { - var promise = CommandManager.execute(Commands.FILE_OPEN, { fullPath: activeFile }); - // Add this promise to the event's promises to signal that this handler isn't done yet - e.promises.push(promise); - } + function notifyPathDeleted(path) { + /* FileSyncManager.syncOpenDocuments() does all the work of closing files + in the working set and notifying the user of any unsaved changes. */ + FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE); + // Send a "pathDeleted" event. This will trigger the views to update. + $(exports).triggerHandler("pathDeleted", path); } /** @@ -937,20 +517,6 @@ define(function (require, exports, module) { $(exports).triggerHandler("fileNameChange", [oldName, newName]); } - /** - * Called after a file or folder has been deleted. This function is responsible - * for updating underlying model data and notifying all views of the change. - * - * @param {string} path The path of the file/folder that has been deleted - */ - function notifyPathDeleted(path) { - /* FileSyncManager.syncOpenDocuments() does all the work of closing files - in the working set and notifying the user of any unsaved changes. */ - FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE); - - // Send a "pathDeleted" event. This will trigger the views to update. - $(exports).triggerHandler("pathDeleted", path); - } /** * @private @@ -1007,7 +573,7 @@ define(function (require, exports, module) { .on("_dirtyFlagChange", function (event, doc) { $(exports).triggerHandler("dirtyFlagChange", doc); if (doc.isDirty) { - addToWorkingSet(doc.file); + MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file); } }) .on("_documentSaved", function (event, doc) { @@ -1035,6 +601,40 @@ define(function (require, exports, module) { return null; } + /** + * Creates a deprecation warning event handler + * @param {!string} eventName - the event being deprecated. + * The Event Name doesn't change just which object dispatches it + */ + function _deprecateEvent(eventName) { + DeprecationWarning.deprecateEvent(exports, + MainViewManager, + eventName, + eventName, + "DocumentManager." + eventName, + "MainViewManager." + eventName); + } + + /* + * Setup an extensionsLoaded handler to register deprecated events. + * We do this so these events are added to the end of the event + * handler chain which gives the system a chance to process them + * before they are dispatched to extensions. + * + * Extensions that listen to the new MainViewManager working set events + * are always added to the end so this effectively puts the legacy events + * at the end of the event list. This prevents extensions from + * handling the event too soon. (e.g. workingSetListView needs to + * process these events before the Extension Highlighter extension) + */ + AppInit.extensionsLoaded(function () { + _deprecateEvent("workingSetAdd"); + _deprecateEvent("workingSetAddList"); + _deprecateEvent("workingSetRemove"); + _deprecateEvent("workingSetRemoveList"); + _deprecateEvent("workingSetSort"); + }); + PreferencesManager.convertPreferences(module, {"files_": "user"}, true, _checkPreferencePrefix); // Handle file saves that may affect preferences @@ -1042,33 +642,60 @@ define(function (require, exports, module) { PreferencesManager.fileChanged(doc.file.fullPath); }); - // For unit tests and internal use only - exports._clearCurrentDocument = _clearCurrentDocument; + $(MainViewManager).on("currentFileChange", function (e, newFile, newPaneId, oldFile, oldPaneId) { + var newDoc = null, + oldDoc = null; + + if (newFile) { + newDoc = getOpenDocumentForPath(newFile.fullPath); + } + + if (oldFile) { + oldDoc = getOpenDocumentForPath(oldFile.fullPath); + } + + if (oldDoc) { + $(oldDoc).off("languageChanged.DocumentManager"); + } + + if (newDoc !== oldDoc) { + var count = DeprecationWarning.getEventHandlerCount(exports, "currentDocumentChange"); + if (count > 0) { + DeprecationWarning.deprecationWarning("The Event 'DocumentManager.currentDocumentChange' has been deprecated. Please use 'MainViewManager.currentFileChange' instead.", true); + } + + $(exports).triggerHandler("currentDocumentChange", [newDoc, oldDoc]); + } + + if (newDoc) { + $(newDoc).on("languageChanged.DocumentManager", function (data) { + $(exports).trigger("currentDocumentLanguageChanged", data); + }); + } + + }); + + // Deprecated APIs + exports.getWorkingSet = getWorkingSet; + exports.findInWorkingSet = findInWorkingSet; + exports.addToWorkingSet = addToWorkingSet; + exports.addListToWorkingSet = addListToWorkingSet; + exports.removeListFromWorkingSet = removeListFromWorkingSet; + exports.getCurrentDocument = getCurrentDocument; + exports.beginDocumentNavigation = beginDocumentNavigation; + exports.finalizeDocumentNavigation = finalizeDocumentNavigation; + exports.getNextPrevFile = getNextPrevFile; + exports.setCurrentDocument = setCurrentDocument; + exports.closeFullEditor = closeFullEditor; + exports.closeAll = closeAll; // Define public API exports.Document = DocumentModule.Document; - exports.getCurrentDocument = getCurrentDocument; - exports._clearCurrentDocument = _clearCurrentDocument; exports.getDocumentForPath = getDocumentForPath; exports.getOpenDocumentForPath = getOpenDocumentForPath; exports.getDocumentText = getDocumentText; exports.createUntitledDocument = createUntitledDocument; - exports.getWorkingSet = getWorkingSet; - exports.findInWorkingSet = findInWorkingSet; - exports.findInWorkingSetAddedOrder = findInWorkingSetAddedOrder; exports.getAllOpenDocuments = getAllOpenDocuments; - exports.setCurrentDocument = setCurrentDocument; - exports.addToWorkingSet = addToWorkingSet; - exports.addListToWorkingSet = addListToWorkingSet; - exports.removeFromWorkingSet = removeFromWorkingSet; - exports.removeListFromWorkingSet = removeListFromWorkingSet; - exports.getNextPrevFile = getNextPrevFile; - exports.swapWorkingSetIndexes = swapWorkingSetIndexes; - exports.sortWorkingSet = sortWorkingSet; - exports.beginDocumentNavigation = beginDocumentNavigation; - exports.finalizeDocumentNavigation = finalizeDocumentNavigation; - exports.closeFullEditor = closeFullEditor; - exports.closeAll = closeAll; exports.notifyFileDeleted = notifyFileDeleted; exports.notifyPathNameChanged = notifyPathNameChanged; exports.notifyPathDeleted = notifyPathDeleted; @@ -1076,11 +703,6 @@ define(function (require, exports, module) { // Performance measurements PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()"); - // Handle project change events - var $ProjectManager = $(ProjectManager); - $ProjectManager.on("projectOpen", _projectOpen); - $ProjectManager.on("beforeProjectClose beforeAppClose", _savePreferences); - // Handle Language change events $(LanguageManager).on("languageAdded", _handleLanguageAdded); $(LanguageManager).on("languageModified", _handleLanguageModified); diff --git a/src/document/InMemoryFile.js b/src/document/InMemoryFile.js index a62f3a8a763..01eab4876c7 100644 --- a/src/document/InMemoryFile.js +++ b/src/document/InMemoryFile.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define */ /** * Represents a file that will never exist on disk - a placeholder backing file for untitled Documents. NO ONE diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index 542490dacdd..18524091f20 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -23,7 +23,7 @@ /*jslint regexp: true, vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, window, Mustache */ +/*global define, $ */ define(function (require, exports, module) { "use strict"; diff --git a/src/editor/CodeHintList.js b/src/editor/CodeHintList.js index a1e54edce50..c43e1303058 100644 --- a/src/editor/CodeHintList.js +++ b/src/editor/CodeHintList.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, window, brackets, Mustache */ +/*global define, $, window, Mustache */ define(function (require, exports, module) { "use strict"; diff --git a/src/editor/CodeHintManager.js b/src/editor/CodeHintManager.js index 51270da1159..6363ed2306d 100644 --- a/src/editor/CodeHintManager.js +++ b/src/editor/CodeHintManager.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, brackets */ +/*global define, $ */ /* * __CodeHintManager Overview:__ @@ -318,9 +318,7 @@ define(function (require, exports, module) { * to all languages. */ function _removeHintProvider(provider, targetLanguageId) { - var languageId, - languages, - index, + var index, providers, targetLanguageIdArr; diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 2682195b7a6..c426ed7b450 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -54,6 +54,8 @@ * - optionChange -- Triggered when an option for the editor is changed. The 2nd arg to the listener * is a string containing the editor option that is changing. The 3rd arg, which can be any * data type, is the new value for the editor option. + * - beforeDestroy - Triggered before the object is about to dispose of all its internal state data + * so that listeners can cache things like scroll pos, etc... * * The Editor also dispatches "change" events internally, but you should listen for those on * Documents, not Editors. @@ -71,7 +73,6 @@ define(function (require, exports, module) { PerfUtils = require("utils/PerfUtils"), PopUpManager = require("widgets/PopUpManager"), PreferencesManager = require("preferences/PreferencesManager"), - Strings = require("strings"), TextRange = require("document/TextRange").TextRange, TokenUtils = require("utils/TokenUtils"), ValidationUtils = require("utils/ValidationUtils"), @@ -79,18 +80,19 @@ define(function (require, exports, module) { _ = require("thirdparty/lodash"); /** Editor preferences */ - var CLOSE_BRACKETS = "closeBrackets", - CLOSE_TAGS = "closeTags", - HIGHLIGHT_MATCHES = "highlightMatches", - SCROLL_PAST_END = "scrollPastEnd", - SHOW_LINE_NUMBERS = "showLineNumbers", - SMART_INDENT = "smartIndent", - SOFT_TABS = "softTabs", - SPACE_UNITS = "spaceUnits", - STYLE_ACTIVE_LINE = "styleActiveLine", - TAB_SIZE = "tabSize", - WORD_WRAP = "wordWrap", - USE_TAB_CHAR = "useTabChar"; + var CLOSE_BRACKETS = "closeBrackets", + CLOSE_TAGS = "closeTags", + HIGHLIGHT_MATCHES = "highlightMatches", + SCROLL_PAST_END = "scrollPastEnd", + SHOW_CURSOR_SELECT = "showCursorWhenSelecting", + SHOW_LINE_NUMBERS = "showLineNumbers", + SMART_INDENT = "smartIndent", + SOFT_TABS = "softTabs", + SPACE_UNITS = "spaceUnits", + STYLE_ACTIVE_LINE = "styleActiveLine", + TAB_SIZE = "tabSize", + WORD_WRAP = "wordWrap", + USE_TAB_CHAR = "useTabChar"; var cmOptions = {}; @@ -110,6 +112,7 @@ define(function (require, exports, module) { cmOptions[CLOSE_TAGS] = "autoCloseTags"; cmOptions[HIGHLIGHT_MATCHES] = "highlightSelectionMatches"; cmOptions[SCROLL_PAST_END] = "scrollPastEnd"; + cmOptions[SHOW_CURSOR_SELECT] = "showCursorWhenSelecting"; cmOptions[SHOW_LINE_NUMBERS] = "lineNumbers"; cmOptions[SMART_INDENT] = "smartIndent"; cmOptions[SPACE_UNITS] = "indentUnit"; @@ -118,22 +121,23 @@ define(function (require, exports, module) { cmOptions[USE_TAB_CHAR] = "indentWithTabs"; cmOptions[WORD_WRAP] = "lineWrapping"; - PreferencesManager.definePreference(CLOSE_BRACKETS, "boolean", false); - PreferencesManager.definePreference(CLOSE_TAGS, "Object", { whenOpening: true, whenClosing: true, indentTags: [] }); - PreferencesManager.definePreference(HIGHLIGHT_MATCHES, "boolean", false); - PreferencesManager.definePreference(SCROLL_PAST_END, "boolean", false); - PreferencesManager.definePreference(SHOW_LINE_NUMBERS, "boolean", true); - PreferencesManager.definePreference(SMART_INDENT, "boolean", true); - PreferencesManager.definePreference(SOFT_TABS, "boolean", true); - PreferencesManager.definePreference(SPACE_UNITS, "number", DEFAULT_SPACE_UNITS, { + PreferencesManager.definePreference(CLOSE_BRACKETS, "boolean", false); + PreferencesManager.definePreference(CLOSE_TAGS, "Object", { whenOpening: true, whenClosing: true, indentTags: [] }); + PreferencesManager.definePreference(HIGHLIGHT_MATCHES, "boolean", false); + PreferencesManager.definePreference(SCROLL_PAST_END, "boolean", false); + PreferencesManager.definePreference(SHOW_CURSOR_SELECT, "boolean", false); + PreferencesManager.definePreference(SHOW_LINE_NUMBERS, "boolean", true); + PreferencesManager.definePreference(SMART_INDENT, "boolean", true); + PreferencesManager.definePreference(SOFT_TABS, "boolean", true); + PreferencesManager.definePreference(SPACE_UNITS, "number", DEFAULT_SPACE_UNITS, { validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_SPACE_UNITS, MAX_SPACE_UNITS) }); - PreferencesManager.definePreference(STYLE_ACTIVE_LINE, "boolean", false); - PreferencesManager.definePreference(TAB_SIZE, "number", DEFAULT_TAB_SIZE, { + PreferencesManager.definePreference(STYLE_ACTIVE_LINE, "boolean", false); + PreferencesManager.definePreference(TAB_SIZE, "number", DEFAULT_TAB_SIZE, { validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_TAB_SIZE, MAX_TAB_SIZE) }); - PreferencesManager.definePreference(USE_TAB_CHAR, "boolean", false); - PreferencesManager.definePreference(WORD_WRAP, "boolean", true); + PreferencesManager.definePreference(USE_TAB_CHAR, "boolean", false); + PreferencesManager.definePreference(WORD_WRAP, "boolean", true); var editorOptions = Object.keys(cmOptions); @@ -144,7 +148,7 @@ define(function (require, exports, module) { * @type {boolean} */ var _duringFocus = false; - + /** * Constant: ignore upper boundary when centering text * @type {number} @@ -194,7 +198,7 @@ define(function (require, exports, module) { * @param {!boolean} makeMasterEditor If true, this Editor will set itself as the (secret) "master" * Editor for the Document. If false, this Editor will attach to the Document as a "slave"/ * secondary editor. - * @param {!jQueryObject} container Container to add the editor to. + * @param {!jQueryObject|DomNode} container Container to add the editor to. * @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document * to display in this editor. Inclusive. */ @@ -207,6 +211,13 @@ define(function (require, exports, module) { this.document = document; document.addRef(); + if (container.jquery) { + // CodeMirror wants a DOM element, not a jQuery wrapper + container = container.get(0); + } + + var $container = $(container); + if (range) { // attach this first: want range updated before we process a change this._visibleRange = new TextRange(document, range.startLine, range.endLine); } @@ -226,6 +237,7 @@ define(function (require, exports, module) { this._inlineWidgets = []; this._inlineWidgetQueues = {}; this._hideMarks = []; + this._lastEditorWidth = null; this._$messagePopover = null; @@ -253,7 +265,10 @@ define(function (require, exports, module) { self.removeAllInlineWidgets(); } }, - "Cmd-Left": "goLineStartSmart" + "Home": "goLineLeftSmart", + "Cmd-Left": "goLineLeftSmart", + "End": "goLineRight", + "Cmd-Right": "goLineRight" }; var currentOptions = this._currentOptions = _.zipObject( @@ -263,6 +278,13 @@ define(function (require, exports, module) { }) ); + // When panes are created *after* the showLineNumbers option has been turned off + // we need to apply the show-line-padding class or the text will be juxtaposed + // to the edge of the editor which makes it not easy to read. The code below to handle + // that the option change only applies the class to panes that have already been created + // This line ensures that the class is applied to any editor created after the fact + $container.toggleClass("show-line-padding", Boolean(!this._getOption("showLineNumbers"))); + // Create the CodeMirror instance // (note: CodeMirror doesn't actually require using 'new', but jslint complains without it) this._codeMirror = new CodeMirror(container, { @@ -280,6 +302,7 @@ define(function (require, exports, module) { lineWrapping : currentOptions[WORD_WRAP], matchBrackets : { maxScanLineLength: 50000, maxScanLines: 1000 }, matchTags : { bothTags: true }, + showCursorWhenSelecting : currentOptions[SHOW_CURSOR_SELECT], scrollPastEnd : !range && currentOptions[SCROLL_PAST_END], smartIndent : currentOptions[SMART_INDENT], styleActiveLine : currentOptions[STYLE_ACTIVE_LINE], @@ -328,6 +351,13 @@ define(function (require, exports, module) { return this._codeMirror.getScrollInfo().top; } }); + + // Add an $el getter for Pane Views + Object.defineProperty(this, "$el", { + get: function () { + return $(this.getRootElement()); + } + }); } /** @@ -336,6 +366,8 @@ define(function (require, exports, module) { * a read-only string-backed mode. */ Editor.prototype.destroy = function () { + $(this).triggerHandler("beforeDestroy", [this]); + // CodeMirror docs for getWrapperElement() say all you have to do is "Remove this from your // tree to delete an editor instance." $(this.getRootElement()).remove(); @@ -488,7 +520,6 @@ define(function (require, exports, module) { /** * @private * Handle Tab key press. - * @param {!CodeMirror} instance CodeMirror instance. */ Editor.prototype._handleTabKey = function () { // Tab key handling is done as follows: @@ -762,8 +793,6 @@ define(function (require, exports, module) { * the document an editor change that originated with us */ Editor.prototype._handleDocumentChange = function (event, doc, changeList) { - var change; - // we're currently syncing to the Document, so don't echo back FROM the Document if (this._duringSync) { return; @@ -886,6 +915,15 @@ define(function (require, exports, module) { PerfUtils.addMeasurement(perfTimerName); }; + + /** + * Gets the file associated with this editor + * This is a required Pane-View interface method + * @return {!File} the file associated with this editor + */ + Editor.prototype.getFile = function () { + return this.document.file; + }; /** * Gets the current cursor position within the editor. @@ -896,7 +934,7 @@ define(function (require, exports, module) { * selection that moves when you press shift+arrow), or "anchor" (the * fixed side of the selection). Omitting the argument is the same as * passing "head". A {line, ch} object will be returned.) - * @return !{line:number, ch:number} + * @return {!{line:number, ch:number}} */ Editor.prototype.getCursorPos = function (expandTabs, which) { // Translate "start" and "end" to the official CM names (it actually @@ -997,6 +1035,7 @@ define(function (require, exports, module) { this._codeMirror.setSize(width, height); }; + /** @const */ var CENTERING_MARGIN = 0.15; /** @@ -1364,6 +1403,7 @@ define(function (require, exports, module) { Editor.prototype.getRootElement = function () { return this._codeMirror.getWrapperElement(); }; + /** * Gets the lineSpace element within the editor (the container around the individual lines of code). @@ -1384,6 +1424,15 @@ define(function (require, exports, module) { return { x: scrollInfo.left, y: scrollInfo.top }; }; + /** + * Restores and adjusts the current scroll position of the editor. + * @param {{x:number, y:number}} scrollPos - The x,y scroll position in pixels + * @param {!number} heightDelta - The amount of delta H to apply to the scroll position + */ + Editor.prototype.adjustScrollPos = function (scrollPos, heightDelta) { + this._codeMirror.scrollTo(scrollPos.x, scrollPos.y + heightDelta); + }; + /** * Sets the current scroll position of the editor. * @param {number} x scrollLeft position in pixels @@ -1490,8 +1539,6 @@ define(function (require, exports, module) { } if (!inlineWidget.closePromise) { - var lineNum = this._getInlineWidgetLineNumber(inlineWidget); - // Remove the inline widget from our internal list immediately, so // everyone external to us knows it's essentially already gone. We // don't want to wait until it's done animating closed (but we do want @@ -1575,7 +1622,7 @@ define(function (require, exports, module) { inlineWidget.isClosed = true; } }; - + /** * Returns a list of all inline widgets currently open in this editor. Each entry contains the * inline's id, and the data parameter that was passed to addInlineWidget(). @@ -1584,6 +1631,22 @@ define(function (require, exports, module) { Editor.prototype.getInlineWidgets = function () { return this._inlineWidgets; }; + + /** + * Returns the currently focused inline widget, if any. + * @return {?InlineWidget} + */ + Editor.prototype.getFocusedInlineWidget = function () { + var result = null; + + this.getInlineWidgets().forEach(function (widget) { + if (widget.hasFocus()) { + result = widget; + } + }); + + return result; + }; /** * Display temporary popover message at current cursor position. Display message above @@ -1595,7 +1658,6 @@ define(function (require, exports, module) { var arrowBelow, cursorPos, cursorCoord, popoverRect, top, left, clip, arrowCenter, arrowLeft, self = this, - $editorHolder = $("#editor-holder"), POPOVER_MARGIN = 10, POPOVER_ARROW_HALF_WIDTH = 10, POPOVER_ARROW_HALF_BASE = POPOVER_ARROW_HALF_WIDTH + 3; // 3 is border radius @@ -1667,7 +1729,7 @@ define(function (require, exports, module) { }; // See if popover is clipped on any side - clip = ViewUtils.getElementClipSize($editorHolder, popoverRect); + clip = ViewUtils.getElementClipSize($("#editor-holder"), popoverRect); // Prevent horizontal clipping if (clip.left > 0) { @@ -1821,6 +1883,40 @@ define(function (require, exports, module) { return this._focused; }; + /* + * @typedef {scrollPos:{x:number, y:number},Array.<{start:{line:number, ch:number},end:{line:number, ch:number}}>} EditorViewState + */ + + /* + * returns the view state for the editor + * @return {!EditorViewState} + */ + Editor.prototype.getViewState = function () { + return { + selections: this.getSelections(), + scrollPos: this.getScrollPos() + }; + + }; + + /* + * Restores the view state + * @param {!EditorViewState} viewState - the view state object to restore + */ + Editor.prototype.restoreViewState = function (viewState) { + if (viewState.selection) { + // We no longer write out single-selection, but there might be some view state + // from an older version. + this.setSelection(viewState.selection.start, viewState.selection.end); + } + if (viewState.selections) { + this.setSelections(viewState.selections); + } + if (viewState.scrollPos) { + this.setScrollPos(viewState.scrollPos.x, viewState.scrollPos.y); + } + }; + /** * Re-renders the editor UI * @param {boolean=} handleResize true if this is in response to resizing the editor. Default false. @@ -1859,13 +1955,12 @@ define(function (require, exports, module) { }; /** - * Shows or hides the editor within its parent. Does not force its ancestors to - * become visible. + * View API Visibility Change Notification handler. This is also + * called by the native "setVisible" API which refresh can be optimized * @param {boolean} show true to show the editor, false to hide it * @param {boolean} refresh true (default) to refresh the editor, false to skip refreshing it */ - Editor.prototype.setVisible = function (show, refresh) { - $(this.getRootElement()).css("display", (show ? "" : "none")); + Editor.prototype.notifyVisibilityChange = function (show, refresh) { if (show && (refresh || refresh === undefined)) { this.refresh(); } @@ -1876,6 +1971,17 @@ define(function (require, exports, module) { } }; + /** + * Shows or hides the editor within its parent. Does not force its ancestors to + * become visible. + * @param {boolean} show true to show the editor, false to hide it + * @param {boolean} refresh true (default) to refresh the editor, false to skip refreshing it + */ + Editor.prototype.setVisible = function (show, refresh) { + this.$el.css("display", (show ? "" : "none")); + this.notifyVisibilityChange(show, refresh); + }; + /** * Returns true if the editor is fully visible--i.e., is in the DOM, all ancestors are * visible, and has a non-zero width/height. @@ -1896,7 +2002,7 @@ define(function (require, exports, module) { * the start and end. * @return {?(Object|string)} Name of syntax-highlighting mode, or object containing a "name" property * naming the mode along with configuration options required by the mode. - * See {@link LanguageManager#getLanguageForPath()} and {@link Language#getMode()}. + * @see {@link LanguageManager#getLanguageForPath()} and {@link Language#getMode()}. */ Editor.prototype.getModeForRange = function (start, end, knownMixed) { var outerMode = this._codeMirror.getMode(), @@ -1922,7 +2028,7 @@ define(function (require, exports, module) { * * @return {?(Object|string)} Name of syntax-highlighting mode, or object containing a "name" property * naming the mode along with configuration options required by the mode. - * See {@link LanguageManager#getLanguageForPath()} and {@link Language#getMode()}. + * @see {@link LanguageManager#getLanguageForPath()} and {@link Language#getMode()}. */ Editor.prototype.getModeForSelection = function () { // Check for mixed mode info @@ -1964,6 +2070,10 @@ define(function (require, exports, module) { } }; + /* + * gets the language for the selection. (Javascript selected from an HTML document or CSS selected from an HTML document, etc...) + * @return {!Language} + */ Editor.prototype.getLanguageForSelection = function () { return this.document.getLanguage().getLanguageForMode(this.getModeForSelection()); }; @@ -1976,12 +2086,22 @@ define(function (require, exports, module) { Editor.prototype.getModeForDocument = function () { return this._codeMirror.getOption("mode"); }; - + /** * The Document we're bound to * @type {!Document} */ Editor.prototype.document = null; + + + /** + * The Editor's last known width. + * Used in conjunction with updateLayout to recompute the layout + * if the the parent container changes its size since our last layout update. + * @type {?number} + */ + Editor.prototype._lastEditorWidth = null; + /** * If true, we're in the middle of syncing to/from the Document. Used to ignore spurious change @@ -1989,7 +2109,7 @@ define(function (require, exports, module) { * @type {!boolean} */ Editor.prototype._duringSync = false; - + /** * @private * NOTE: this is actually "semi-private": EditorManager also accesses this field... as well as @@ -2088,6 +2208,38 @@ define(function (require, exports, module) { this._codeMirror.setOption("styleActiveLine", this._currentOptions[STYLE_ACTIVE_LINE]); } }; + + /** + * resizes the editor to fill its parent container + * should not be used on inline editors + * @param {boolean=} forceRefresh - forces the editor to update its layout + * even if it already matches the container's height / width + */ + Editor.prototype.updateLayout = function (forceRefresh) { + var curRoot = this.getRootElement(), + curWidth = $(curRoot).width(), + $editorHolder = this.$el.parent(), + editorAreaHt = $editorHolder.height(); + + if (!curRoot.style.height || $(curRoot).height() !== editorAreaHt) { + // Call setSize() instead of $.height() to allow CodeMirror to + // check for options like line wrapping + this.setSize(null, editorAreaHt); + if (forceRefresh === undefined) { + forceRefresh = true; + } + } else if (curWidth !== this._lastEditorWidth) { + if (forceRefresh === undefined) { + forceRefresh = true; + } + } + this._lastEditorWidth = curWidth; + + if (forceRefresh) { + this.refreshAll(forceRefresh); + } + }; + // Global settings that affect Editor instances that share the same preference locations @@ -2245,7 +2397,18 @@ define(function (require, exports, module) { * @param {boolean} showLinePadding */ Editor._toggleLinePadding = function (showLinePadding) { - $("#editor-holder").toggleClass("show-line-padding", showLinePadding); + // apply class to all pane DOM nodes + var $holders = []; + _instances.forEach(function (editor) { + var $editorHolder = editor.$el.parent(); + if ($holders.indexOf($editorHolder) === -1) { + $holders.push($editorHolder); + } + }); + + _.each($holders, function ($holder) { + $holder.toggleClass("show-line-padding", Boolean(showLinePadding)); + }); }; // Set up listeners for preference changes diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 3933fac7bd5..ea907f78f84 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -201,8 +201,7 @@ define(function (require, exports, module) { // Are there any non-blank lines that aren't commented out? (We ignore blank lines because // some editors like Sublime don't comment them out) var i, line, prefix, commentI, - containsNotLineComment = _containsNotLineComment(editor, startLine, endLine, lineExp), - updateSelection = false; + containsNotLineComment = _containsNotLineComment(editor, startLine, endLine, lineExp); if (containsNotLineComment) { // Comment out - prepend the first prefix to each line @@ -553,10 +552,7 @@ define(function (require, exports, module) { * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ function _getLineCommentPrefixSuffixEdit(editor, prefix, suffix, lineSel) { - var sel = lineSel.selectionForEdit, - selStart = sel.start, - selEnd = sel.end, - edit; + var sel = lineSel.selectionForEdit; // For one-line selections, we shrink the selection to exclude the trailing newline. if (sel.end.line === sel.start.line + 1 && sel.end.ch === 0) { @@ -727,8 +723,7 @@ define(function (require, exports, module) { edits = []; _.each(lineSelections, function (lineSel, index) { - var sel = lineSel.selectionForEdit, - selStartLine = sel.start.line; + var sel = lineSel.selectionForEdit; from = sel.start; to = sel.end; // this is already at the beginning of the line after the last selected line diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 4536e270e9c..8e54549a339 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -35,12 +35,18 @@ * must have some knowledge about Document's internal state (we access its _editor property). * * This module dispatches the following events: - * - activeEditorChange -- Fires after the active editor (full or inline) changes and size/visibility - * are complete. Doesn't fire when editor temporarily loses focus to a non-editor - * control (e.g. search toolbar or modal dialog, or window deactivation). Does - * fire when focus moves between inline editor and its full-size container. - * This event tracks `getActiveEditor()` changes, while DocumentManager's - * `currentDocumentChange` tracks `getCurrentFullEditor()` changes. + * - activeEditorChange -- Fires after the active editor (full or inline). + * + * Doesn't fire when editor temporarily loses focus to a non-editor + * control (e.g. search toolbar or modal dialog, or window deactivation). + * + * Does fire when focus moves between inline editor and its full-size container. + * + * This event tracks `MainViewManagers's `currentFileChange` event and all editor + * objects "focus" event. + * + * (e, editorGainingFocus:editor, editorLosingFocus:editor) + * * The 2nd arg to the listener is which Editor became active; the 3rd arg is * which Editor is deactivated as a result. Either one may be null. * NOTE (#1257): `getFocusedEditor()` sometimes lags behind this event. Listeners @@ -52,99 +58,99 @@ define(function (require, exports, module) { // Load dependent modules var Commands = require("command/Commands"), - PanelManager = require("view/PanelManager"), + WorkspaceManager = require("view/WorkspaceManager"), PreferencesManager = require("preferences/PreferencesManager"), CommandManager = require("command/CommandManager"), DocumentManager = require("document/DocumentManager"), + MainViewManager = require("view/MainViewManager"), + ViewStateManager = require("view/ViewStateManager"), PerfUtils = require("utils/PerfUtils"), Editor = require("editor/Editor").Editor, InlineTextEditor = require("editor/InlineTextEditor").InlineTextEditor, Strings = require("strings"), - LanguageManager = require("language/LanguageManager"); + LanguageManager = require("language/LanguageManager"), + DeprecationWarning = require("utils/DeprecationWarning"); - /** - * DOM node that contains all editors (visible and hidden alike) - * @type {jQueryObject} - */ - var _editorHolder = null; - - /** - * Currently visible full-size Editor, or null if no editors open - * @type {?Editor} - */ - var _currentEditor = null; - - /** - * Document in current editor - * @type {?Document} - */ - var _currentEditorsDocument = null; - - /** - * full path to file - * @type {?string} - */ - var _currentlyViewedPath = null; - - /** - * DOM node representing UI of custom view - * @type {?JQuery} - */ - var _$currentCustomViewer = null; - - /** - * view provider - * @type {?Object} - */ - var _currentViewProvider = null; - - /** - * view provider registry - * @type {?Object} - */ - var _customViewerRegistry = {}; /** * Currently focused Editor (full-size, inline, or otherwise) * @type {?Editor} + * @private */ 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} - */ - var _viewStateCache = {}; - - /** - * Last known editor area width, used to detect when the window is resized horizontally. - */ - var _lastEditorWidth = null; - /** * Registered inline-editor widget providers sorted descending by priority. - * See {@link #registerInlineEditProvider()}. + * @see {@link #registerInlineEditProvider()}. * @type {Array.<{priority:number, provider:function(...)}>} + * @private */ var _inlineEditProviders = []; /** * Registered inline documentation widget providers sorted descending by priority. - * See {@link #registerInlineDocsProvider()}. + * @see {@link #registerInlineDocsProvider()}. * @type {Array.<{priority:number, provider:function(...)}>} + * @private */ var _inlineDocsProviders = []; /** - * Registered jump-to-definition providers. See {@link #registerJumpToDefProvider()}. + * Registered jump-to-definition providers. + * @see {@link #registerJumpToDefProvider()}. + * @private * @type {Array.} */ var _jumpToDefProviders = []; + + /** + * DOM element to house any hidden editors created soley for inline widgets + * @private + * @type {jQuery} + */ + var _$hiddenEditorsContainer; + + + /** + * Retrieves the visible full-size Editor for the currently opened file in the ACTIVE_PANE + * @return {?Editor} editor of the current view or null + */ + function getCurrentFullEditor() { + var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE), + doc = currentPath && DocumentManager.getOpenDocumentForPath(currentPath); + return doc && doc._masterEditor; + } + + + + /** + * Updates _viewStateCache from the given editor's actual current state + * @private + * @param {!Editor} editor - editor to cache data for + */ + function _saveEditorViewState(editor) { + ViewStateManager.updateViewState(editor); + } + + /** + * Updates _viewStateCache from the given editor's actual current state + * @param {!Editor} editor - editor restore cached data + * @private + */ + function _restoreEditorViewState(editor) { + // We want to ignore the current state of the editor, so don't call __getViewState() + var viewState = ViewStateManager.getViewState(editor.document.file); + if (viewState) { + editor.restoreViewState(viewState); + } + } + + /** + * Editor focus handler to change the currently active editor * @private - * @param {?Editor} current + * @param {?Editor} current - the editor that will be the active editor */ function _notifyActiveEditorChanged(current) { // Skip if the Editor that gained focus was already the most recently focused editor. @@ -158,9 +164,27 @@ define(function (require, exports, module) { $(exports).triggerHandler("activeEditorChange", [current, previous]); } + /** + * Current File Changed handler + * MainViewManager dispatches a "currentFileChange" event whenever the currently viewed + * file changes. Which could mean that the previously viewed file has been closed or a + * non-editor view (image) has been given focus. _notifyAcitveEditorChanged is also hooked + * up to editor.focus to handle focus events for editors which handles changing focus between + * two editors but, because editormanager maintains a "_lastFocusedEditor" state, we have to + * "nullify" that state whenever the focus goes to a non-editor or when the current editor is closed + * @private + * @param {!jQuery.Event} e - event + * @param {?File} file - current file (can be null) + */ + function _handleCurrentFileChange(e, file) { + var doc = file && DocumentManager.getOpenDocumentForPath(file.fullPath); + _notifyActiveEditorChanged(doc && doc._masterEditor); + } + /** * Creates a new Editor bound to the given Document. * The editor is appended to the given container as a visible child. + * @private * @param {!Document} doc Document for the Editor's content * @param {!boolean} makeMasterEditor If true, the Editor will set itself as the private "master" * Editor for the Document. If false, the Editor will attach to the Document as a "slave." @@ -176,6 +200,12 @@ define(function (require, exports, module) { _notifyActiveEditorChanged(this); }); + $(editor).on("beforeDestroy", function () { + if (editor.$el.is(":visible")) { + _saveEditorViewState(editor); + } + }); + return editor; } @@ -249,9 +279,52 @@ define(function (require, exports, module) { return result.promise(); } + /** - * Inserts a prioritized provider object into the array in sorted (descending) order. + * Closes any focused inline widget. Else, asynchronously asks providers to create one. * + * @param {Array.<{priority:number, provider:function(...)}>} providers + * prioritized list of providers + * @param {string=} errorMsg Default message to display if no providers return non-null + * @return {!Promise} A promise resolved with true if an inline widget is opened or false + * when closed. Rejected if there is neither an existing widget to close nor a provider + * willing to create a widget (or if no editor is open). + */ + function _toggleInlineWidget(providers, errorMsg) { + var result = new $.Deferred(); + + var currentEditor = getCurrentFullEditor(); + + if (currentEditor) { + var inlineWidget = currentEditor.getFocusedInlineWidget(); + + if (inlineWidget) { + // an inline widget's editor has focus, so close it + PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE); + inlineWidget.close().done(function () { + PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE); + // return a resolved promise to CommandManager + result.resolve(false); + }); + } else { + // main editor has focus, so create an inline editor + _openInlineWidget(currentEditor, providers, errorMsg).done(function () { + result.resolve(true); + }).fail(function () { + result.reject(); + }); + } + } else { + // Can not open an inline editor without a host editor + result.reject(); + } + + return result.promise(); + } + + /** + * Inserts a prioritized provider object into the array in sorted (descending) order. + * @private * @param {Array.<{priority:number, provider:function(...)}>} array * @param {number} priority * @param {function(...)} provider @@ -272,6 +345,26 @@ define(function (require, exports, module) { array.splice(index, 0, prioritizedProvider); } + + /** + * Creates a hidden, unattached master editor that is needed when a document is created for the + * sole purpose of creating an inline editor so operations that require a master editor can be performed + * Only called from Document._ensureMasterEditor() + * The editor view is placed in a hidden part of the DOM but can later be moved to a visible pane + * when the document is opened using pane.addView() + * @param {!Document} doc - document to create a hidden editor for + */ + function _createUnattachedMasterEditor(doc) { + // attach to the hidden containers DOM node if necessary + if (!_$hiddenEditorsContainer) { + _$hiddenEditorsContainer = $("#hidden-editors"); + } + // Create an editor + var editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer); + // and hide it + editor.setVisible(false); + } + /** * Removes the given widget UI from the given hostEditor (agnostic of what the widget's content * is). The widget's onClosed() callback will be run as a result. @@ -373,24 +466,19 @@ define(function (require, exports, module) { /** * @private * Creates a new "full-size" (not inline) Editor for the given Document, and sets it as the - * Document's master backing editor. The editor is not yet visible; to show it, use - * DocumentManager.setCurrentDocument(). + * Document's master backing editor. The editor is not yet visible; * Semi-private: should only be called within this module or by Document. * @param {!Document} document Document whose main/full Editor to create + * @param {!Pane} pane Pane in which the editor will be hosted */ - function _createFullEditorForDocument(document) { + function _createFullEditorForDocument(document, pane) { // Create editor; make it initially invisible - var container = _editorHolder.get(0); - var editor = _createEditorForDocument(document, true, container); + var editor = _createEditorForDocument(document, true, pane.$content); editor.setVisible(false); + pane.addView(editor); + $(exports).triggerHandler("_fullEditorCreatedForDocument", [document, editor, pane.id]); } - - /** Returns the visible full-size Editor corresponding to DocumentManager.getCurrentDocument() */ - function getCurrentFullEditor() { - // This *should* always be equivalent to DocumentManager.getCurrentDocument()._masterEditor - return _currentEditor; - } - + /** * Creates a new inline Editor instance for the given Document. @@ -415,46 +503,6 @@ define(function (require, exports, module) { return { content: inlineContent, editor: inlineEditor }; } - - - /** - * Disposes the given Document's full-size editor if the doc is no longer "open" from the user's - * standpoint - not in the working set and not currentDocument). - * - * Destroying the full-size editor releases ONE ref to the Document; if inline editors or other - * UI elements are still referencing the Document it will still be 'open' (kept alive) from - * DocumentManager's standpoint. However, destroying the full-size editor does remove the backing - * "master" editor from the Document, rendering it immutable until either inline-editor edits or - * currentDocument change triggers `_createFullEditorForDocument()` full-size editor again. - * - * In certain edge cases, this is called directly by DocumentManager; see `_gcDocuments()` for details. - * - * @param {!Document} document Document whose "master" editor we may destroy - */ - function _destroyEditorIfUnneeded(document) { - var editor = document._masterEditor; - - if (!editor) { - if (!(document instanceof DocumentManager.Document)) { - throw new Error("_destroyEditorIfUnneeded() should be passed a Document"); - } - return; - } - - // If outgoing editor is no longer needed, dispose it - var isCurrentDocument = (DocumentManager.getCurrentDocument() === document); - var isInWorkingSet = (DocumentManager.findInWorkingSet(document.file.fullPath) !== -1); - if (!isCurrentDocument && !isInWorkingSet) { - // Destroy the editor widget (which un-refs the Document and reverts it to read-only mode) - editor.destroy(); - - // Our callers should really ensure this, but just for safety... - if (_currentEditor === editor) { - _currentEditorsDocument = null; - _currentEditor = null; - } - } - } /** * Returns focus to the last visible editor that had focus. If no editor visible, does nothing. @@ -462,148 +510,31 @@ define(function (require, exports, module) { * removed. For example, after a dialog with editable text is closed. */ function focusEditor() { - if (_lastFocusedEditor) { - _lastFocusedEditor.focus(); - } + DeprecationWarning.deprecationWarning("Use MainViewManager.focusActivePane() instead of EditorManager.focusEditor().", true); + MainViewManager.focusActivePane(); } - - /** - * Flag for `_onEditorAreaResize()` to always force refresh. - * @const - * @type {string} - */ - var REFRESH_FORCE = "force"; - - /** - * Flag for `_onEditorAreaResize()` to never refresh. - * @const - * @type {string} - */ - var REFRESH_SKIP = "skip"; - /** - * Must be called whenever the size/visibility of editor area siblings is changed without going through - * PanelManager or Resizer. Resizable panels created via PanelManager do not require this manual call. + * @deprecated + * resizes the editor */ function resizeEditor() { - if (!_editorHolder) { - return; // still too early during init - } - // PanelManager computes the correct editor-holder size & calls us back with it, via _onEditorAreaResize() - PanelManager._notifyLayoutChange(); - } - - /** - * Update the current CodeMirror editor's size. Must be called any time the contents of the editor area - * are swapped or any time the editor-holder area has changed height. EditorManager calls us in the swap - * case. PanelManager calls us in the most common height-change cases (panel and/or window resize), but - * some other cases are handled by external code calling `resizeEditor()` (e.g. ModalBar hide/show). - * - * @param {number} editorAreaHt - * @param {string=} refreshFlag For internal use. Set to "force" to ensure the editor will refresh, - * "skip" to ensure the editor does not refresh, or leave undefined to let `_onEditorAreaResize()` - * determine whether it needs to refresh. - */ - function _onEditorAreaResize(event, editorAreaHt, refreshFlag) { - if (_currentEditor) { - var curRoot = _currentEditor.getRootElement(), - curWidth = $(curRoot).width(); - if (!curRoot.style.height || $(curRoot).height() !== editorAreaHt) { - // Call setSize() instead of $.height() to allow CodeMirror to - // check for options like line wrapping - _currentEditor.setSize(null, editorAreaHt); - if (refreshFlag === undefined) { - refreshFlag = REFRESH_FORCE; - } - } else if (curWidth !== _lastEditorWidth) { - if (refreshFlag === undefined) { - refreshFlag = REFRESH_FORCE; - } - } - _lastEditorWidth = curWidth; - - if (refreshFlag === REFRESH_FORCE) { - _currentEditor.refreshAll(true); - } - } - } - - /** Updates _viewStateCache from the given editor's actual current state */ - function _saveEditorViewState(editor) { - _viewStateCache[editor.document.file.fullPath] = { - selections: editor.getSelections(), - 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) { - // We no longer write out single-selection, but there might be some view state - // from an older version. - editor.setSelection(viewState.selection.start, viewState.selection.end); - } - if (viewState.selections) { - editor.setSelections(viewState.selections); - } - 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; + DeprecationWarning.deprecationWarning("Use WorkspaceManager.recomputeLayout() instead of EditorManager.resizeEditor().", true); + WorkspaceManager.recomputeLayout(); } /** + * Create and/or show the editor for the specified document + * @param {!Document} document - document to edit + * @param {!Pane} pane - pane to show it in * @private */ - function _doShow(document) { - // Show new editor - _currentEditorsDocument = document; - _currentEditor = document._masterEditor; - - // Skip refreshing the editor since we're going to refresh it more explicitly below - _currentEditor.setVisible(true, false); - _currentEditor.focus(); - - // Resize and refresh the editor, since it might have changed size or had other edits applied - // since it was last visible. - PanelManager._notifyLayoutChange(REFRESH_FORCE); - } - - /** - * Make the given document's editor visible in the UI, hiding whatever was - * visible before. Creates a new editor if none is assigned. - * @param {!Document} document - */ - function _showEditor(document) { - // Hide whatever was visible before - if (!_currentEditor) { - $("#not-editor").css("display", "none"); - } else { - _saveEditorViewState(_currentEditor); - _currentEditor.setVisible(false); - _destroyEditorIfUnneeded(_currentEditorsDocument); - } - + function _showEditor(document, pane) { // Ensure a main editor exists for this document to show in the UI - var createdNewEditor = false; - if (!document._masterEditor) { + var createdNewEditor = false, + editor = document._masterEditor; + + if (!editor) { createdNewEditor = true; // Performance (see #4757) Chrome wastes time messing with selection @@ -613,286 +544,86 @@ define(function (require, exports, module) { } // Editor doesn't exist: populate a new Editor with the text - _createFullEditorForDocument(document); + _createFullEditorForDocument(document, pane); + } else if (editor.$el.parent() !== pane.$el) { + // editor does exist but is not a child of the pane so add it to the + // pane (which will switch the view's container as well) + pane.addView(editor); } - - _doShow(document); - + + // show the view + pane.showView(document._masterEditor); + + // give it focus + document._masterEditor.focus(); + if (createdNewEditor) { _restoreEditorViewState(document._masterEditor); } } - + /** - * Resets editor state to make sure `getFocusedEditor()`, `getActiveEditor()`, - * and `getCurrentFullEditor()` return null when an image or the NoEditor - * placeholder is displayed. + * @deprecated use MainViewManager.getCurrentlyViewedFile() instead + * @return {string=} path of the file currently viewed in the active, full sized editor or null when there is no active editor */ - function _nullifyEditor() { - if (_currentEditor) { - _saveEditorViewState(_currentEditor); - - // This is a hack to deal with #5589. The issue is that CodeMirror's logic for polling its - // hidden input field relies on whether there's a selection in the input field or not. When - // we hide the editor, the input field loses its selection. Somehow, CodeMirror's readInput() - // poll can get called before the resulting blur event is asynchronously sent. (Our guess is - // that if the setTimeout() that the poll is on is overdue, it gets serviced before the backlog - // of asynchronous events is flushed.) That means that readInput() thinks CM still has focus, - // but that the hidden input has lost its selection, meaning the user has typed something, which - // causes it to replace the editor selection (with the same text), leading to the erroneous - // change event and selection change. To work around this, we simply blur CM's input field - // before hiding the editor, which forces the blur event to be sent synchronously, before the - // next readInput() triggers. - // - // Note that we only need to do this here, not in _showEditor(), because _showEditor() - // ends up synchronously setting focus to another editor, which has the effect of - // forcing a synchronous blur event as well. - _currentEditor._codeMirror.getInputField().blur(); - - _currentEditor.setVisible(false); - _destroyEditorIfUnneeded(_currentEditorsDocument); - - _currentEditorsDocument = null; - _currentEditor = null; - _currentlyViewedPath = null; - - // No other Editor is gaining focus, so in this one special case we must trigger event manually - _notifyActiveEditorChanged(null); - } - } - - /** Hide the currently visible editor and show a placeholder UI in its place */ - function _showNoEditor() { - $("#not-editor").css("display", ""); - _nullifyEditor(); - } - function getCurrentlyViewedPath() { - return _currentlyViewedPath; - } - - function _clearCurrentlyViewedPath() { - _currentlyViewedPath = null; - $(exports).triggerHandler("currentlyViewedFileChange"); - } - - function _setCurrentlyViewedPath(fullPath) { - _currentlyViewedPath = fullPath; - $(exports).triggerHandler("currentlyViewedFileChange"); - } - - /** Remove existing custom view if present */ - function _removeCustomViewer() { + DeprecationWarning.deprecationWarning("Use MainViewManager.getCurrentlyViewedFile() instead of EditorManager.getCurrentlyViewedPath().", true); - if (_$currentCustomViewer) { - _$currentCustomViewer.remove(); - if (_currentViewProvider.onRemove) { - _currentViewProvider.onRemove(); - } - } - _$currentCustomViewer = null; - _currentViewProvider = null; - } - - /** - * Closes the customViewer currently displayed, shows the NoEditor view - * and notifies the ProjectManager to update the file selection - */ - function _closeCustomViewer() { - _removeCustomViewer(); - _setCurrentlyViewedPath(); - _showNoEditor(); - } - - /** - * Append custom view to editor-holder - * @param {!Object} provider custom view provider - * @param {!string} fullPath path to the file displayed in the custom view - */ - function _showCustomViewer(provider, fullPath) { - // Don't show the same custom view again if file path - // and view provider are still the same. - if (_currentlyViewedPath === fullPath && - _currentViewProvider === provider) { - return; - } + // We only want to return a path of a document object + // not other things like images, etc... + var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE), + doc; - // Clean up currently viewing document or custom viewer - DocumentManager._clearCurrentDocument(); - _removeCustomViewer(); - - // Hide the not-editor or reset current editor - $("#not-editor").css("display", "none"); - _nullifyEditor(); - - _currentViewProvider = provider; + if (currentPath) { + doc = DocumentManager.getOpenDocumentForPath(currentPath); + } - // add path, dimensions and file size to the view after loading image - _$currentCustomViewer = provider.render(fullPath, $("#editor-holder")); + if (doc) { + return currentPath; + } - _setCurrentlyViewedPath(fullPath); - } - - /** - * Check whether the given file is currently open in a custom viewer. - * - * @param {!string} fullPath file path to check - * @return {boolean} true if we have a custom viewer showing and the given file - * path matches the one in the custom viewer, false otherwise. - */ - function showingCustomViewerForPath(fullPath) { - return (_currentViewProvider && _currentlyViewedPath === fullPath); + return null; } /** - * Registers a new custom viewer provider. To create an extension - * that enables Brackets to view files that cannot be shown as - * text such as binary files, use this method to register a CustomViewer. - * - * A CustomViewer, such as ImageViewer in Brackets core needs to - * implement and export two methods: - * - render - * @param {!string} fullPath Path to the image file - * @param {!jQueryObject} $editorHolder The DOM element to append the view to. - * - onRemove - * signs off listeners and performs any required clean up when editor manager closes - * the custom viewer - * - * By registering a CustomViewer with EditorManager Brackets is - * enabled to view files for one or more given file extensions. - * The first argument defines a so called languageId which bundles - * file extensions to be handled by the custom viewer, see more - * in LanguageManager JSDocs. - * The second argument is an instance of the custom viewer that is ready to display - * files. - * - * @param {!String} languageId, i.e. string such as image, audio, etc to - * identify a language known to LanguageManager - * @param {!Object} provider custom view provider instance + * @deprecated There is no equivelent API moving forward. + * Use MainViewManager._initialize() from a unit test to create a Main View attached to a specific DOM element */ - function registerCustomViewer(langId, provider) { - if (!_customViewerRegistry[langId]) { - _customViewerRegistry[langId] = provider; - } else { - console.error("There already is a custom viewer registered for language id \"" + langId + "\""); - } + function setEditorHolder() { + throw new Error("EditorManager.setEditorHolder() has been removed."); } /** - * Update file name if necessary + * @deprecated Register a View Factory instead + * @see MainViewManager.registerViewFactory() */ - function _onFileNameChange(e, oldName, newName) { - if (_currentlyViewedPath === oldName) { - _setCurrentlyViewedPath(newName); - } + function registerCustomViewer() { + throw new Error("EditorManager.registerCustomViewer() has been removed."); } /** - * Return the provider of a custom viewer for the given path if one exists. - * Otherwise, return null. - * - * @param {!string} fullPath - file path to be checked for a custom viewer - * @return {?Object} + * Determines if the file can be opened in an editor + * @param {!string} fullPath - file to be opened + * @return {boolean} true if the file can be opened in an editor, false if not */ - function getCustomViewerForPath(fullPath) { - var lang = LanguageManager.getLanguageForPath(fullPath); - - return _customViewerRegistry[lang.getId()]; + function canOpenPath(fullPath) { + return !LanguageManager.getLanguageForPath(fullPath).isBinary(); } /** - * Clears custom viewer for a file with a given path and displays - * an alternate file or the no editor view. - * If no param fullpath is passed an alternate file will be opened - * regardless of the current value of _currentlyViewedPath. - * If param fullpath is provided then only if fullpath matches - * the currently viewed file an alternate file will be opened. - * @param {?string} fullPath - file path of deleted file. + * Opens the specified document in the given pane + * @param {!Document} doc - the document to open + * @param {!Pane} pane - the pane to open the document in + * @return {boolean} true if the file can be opened, false if not */ - function notifyPathDeleted(fullPath) { - function openAlternateFile() { - var fileToOpen = DocumentManager.getNextPrevFile(1); - if (fileToOpen) { - CommandManager.execute(Commands.FILE_OPEN, {fullPath: fileToOpen.fullPath}); - } else { - _removeCustomViewer(); - _showNoEditor(); - _setCurrentlyViewedPath(); - } - } - if (!fullPath || _currentlyViewedPath === fullPath) { - openAlternateFile(); - } - } - - /** Handles changes to DocumentManager.getCurrentDocument() */ - function _onCurrentDocumentChange() { - var doc = DocumentManager.getCurrentDocument(), - container = _editorHolder.get(0); - - var perfTimerName = PerfUtils.markStart("EditorManager._onCurrentDocumentChange():\t" + (!doc || doc.file.fullPath)); - - // When the document or file in view changes clean up. - _removeCustomViewer(); - // Update the UI to show the right editor (or nothing), and also dispose old editor if no - // longer needed. - if (doc) { - _showEditor(doc); - _setCurrentlyViewedPath(doc.file.fullPath); - } else { - _clearCurrentlyViewedPath(); - _showNoEditor(); - } + function openDocument(doc, pane) { + var perfTimerName = PerfUtils.markStart("EditorManager.openDocument():\t" + (!doc || doc.file.fullPath)); - PerfUtils.addMeasurement(perfTimerName); - } - - /** Handles removals from DocumentManager's working set list */ - function _onWorkingSetRemove(event, removedFile) { - // There's one case where an editor should be disposed even though the current document - // didn't change: removing a document from the working set (via the "X" button). (This may - // also cover the case where the document WAS current, if the editor-swap happens before the - // removal from the working set. - var doc = DocumentManager.getOpenDocumentForPath(removedFile.fullPath); - if (doc) { - _destroyEditorIfUnneeded(doc); + if (doc && pane) { + _showEditor(doc, pane); } - // else, file was listed in working set but never shown in the editor - ignore - } - - function _onWorkingSetRemoveList(event, removedFiles) { - removedFiles.forEach(function (removedFile) { - _onWorkingSetRemove(event, removedFile); - }); - } - - /** - * Note: there are several paths that can lead to an editor getting destroyed - * - file was in working set, but not in current editor; then closed (via working set "X" button) - * --> handled by _onWorkingSetRemove() - * - file was in current editor, but not in working set; then navigated away from - * --> handled by _onCurrentDocumentChange() - * - file was in current editor, but not in working set; then closed (via File > Close) (and thus - * implicitly navigated away from) - * --> handled by _onCurrentDocumentChange() - * - file was in current editor AND in working set; then closed (via File > Close OR working set - * "X" button) (and thus implicitly navigated away from) - * --> handled by _onWorkingSetRemove() currently, but could be _onCurrentDocumentChange() - * just as easily (depends on the order of events coming from DocumentManager) - * Designates the DOM node that will contain the currently active editor instance. EditorManager - * will own the content of this DOM node. - * @param {!jQueryObject} holder - */ - function setEditorHolder(holder) { - if (_currentEditor) { - console.error("Cannot change editor area after an editor has already been created!"); - return; - } - - _editorHolder = holder; - - resizeEditor(); // if no files open at startup, we won't get called back later to resize the "no-editor" placeholder + PerfUtils.addMeasurement(perfTimerName); } /** @@ -900,17 +631,11 @@ define(function (require, exports, module) { * @return {?InlineWidget} */ function getFocusedInlineWidget() { - var result = null; - - if (_currentEditor) { - _currentEditor.getInlineWidgets().forEach(function (widget) { - if (widget.hasFocus()) { - result = widget; - } - }); + var currentEditor = getCurrentFullEditor(); + if (currentEditor) { + return currentEditor.getFocusedInlineWidget(); } - - return result; + return null; } /** @@ -935,7 +660,8 @@ define(function (require, exports, module) { * @return {?Editor} */ function getFocusedEditor() { - if (_currentEditor) { + var currentEditor = getCurrentFullEditor(); + if (currentEditor) { // See if any inlines have focus var focusedInline = _getFocusedInlineEditor(); @@ -944,8 +670,8 @@ define(function (require, exports, module) { } // otherwise, see if full-sized editor has focus - if (_currentEditor.hasFocus()) { - return _currentEditor; + if (currentEditor.hasFocus()) { + return currentEditor; } } @@ -962,49 +688,9 @@ define(function (require, exports, module) { function getActiveEditor() { return _lastFocusedEditor; } + - - /** - * Closes any focused inline widget. Else, asynchronously asks providers to create one. - * - * @param {Array.<{priority:number, provider:function(...)}>} providers - * prioritized list of providers - * @param {string=} errorMsg Default message to display if no providers return non-null - * @return {!Promise} A promise resolved with true if an inline widget is opened or false - * when closed. Rejected if there is neither an existing widget to close nor a provider - * willing to create a widget (or if no editor is open). - */ - function _toggleInlineWidget(providers, errorMsg) { - var result = new $.Deferred(); - - if (_currentEditor) { - var inlineWidget = getFocusedInlineWidget(); - - if (inlineWidget) { - // an inline widget's editor has focus, so close it - PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE); - inlineWidget.close().done(function () { - PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE); - // return a resolved promise to CommandManager - result.resolve(false); - }); - } else { - // main editor has focus, so create an inline editor - _openInlineWidget(_currentEditor, providers, errorMsg).done(function () { - result.resolve(true); - }).fail(function () { - result.reject(); - }); - } - } else { - // Can not open an inline editor without a host editor - result.reject(); - } - - return result.promise(); - } - - /** + /** * Asynchronously asks providers to handle jump-to-definition. * @return {!Promise} Resolved when the provider signals that it's done; rejected if no * provider responded or the provider that responded failed. @@ -1016,6 +702,7 @@ define(function (require, exports, module) { result = new $.Deferred(); var editor = getActiveEditor(); + if (editor) { var pos = editor.getCursorPos(); @@ -1050,6 +737,33 @@ define(function (require, exports, module) { return result.promise(); } + + /** + * file removed from pane handler. + * @param {jQuery.Event} e + * @param {File|Array.} removedFiles - file, path or array of files or paths that are being removed + */ + function _handleRemoveFromPaneView(e, removedFiles) { + var handleFileRemoved = function (file) { + var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + + if (doc) { + MainViewManager._destroyEditorIfNotNeeded(doc); + } + }; + + // when files are removed from a pane then + // we should destroy any unnecssary views + if ($.isArray(removedFiles)) { + removedFiles.forEach(function (removedFile) { + handleFileRemoved(removedFile); + }); + } else { + handleFileRemoved(removedFiles); + } + } + + // File-based preferences handling $(exports).on("activeEditorChange", function (e, current) { if (current && current.document && current.document.file) { @@ -1069,47 +783,40 @@ define(function (require, exports, module) { // Create PerfUtils measurement PerfUtils.createPerfMeasurement("JUMP_TO_DEFINITION", "Jump-To-Definiiton"); - // Initialize: register listeners - $(DocumentManager).on("currentDocumentChange", _onCurrentDocumentChange); - $(DocumentManager).on("workingSetRemove", _onWorkingSetRemove); - $(DocumentManager).on("workingSetRemoveList", _onWorkingSetRemoveList); - $(DocumentManager).on("fileNameChange", _onFileNameChange); - $(PanelManager).on("editorAreaResize", _onEditorAreaResize); - + $(MainViewManager).on("currentFileChange", _handleCurrentFileChange); + $(MainViewManager).on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView); + // For unit tests and internal use only - exports._openInlineWidget = _openInlineWidget; exports._createFullEditorForDocument = _createFullEditorForDocument; - exports._destroyEditorIfUnneeded = _destroyEditorIfUnneeded; - exports._getViewState = _getViewState; - exports._resetViewStates = _resetViewStates; - exports._doShow = _doShow; exports._notifyActiveEditorChanged = _notifyActiveEditorChanged; - exports._showCustomViewer = _showCustomViewer; - exports._closeCustomViewer = _closeCustomViewer; - - - - exports.REFRESH_FORCE = REFRESH_FORCE; - exports.REFRESH_SKIP = REFRESH_SKIP; + + // Internal Use only + exports._saveEditorViewState = _saveEditorViewState; + exports._createUnattachedMasterEditor = _createUnattachedMasterEditor; // Define public API - exports.setEditorHolder = setEditorHolder; - exports.getCurrentFullEditor = getCurrentFullEditor; exports.createInlineEditorForDocument = createInlineEditorForDocument; - exports.focusEditor = focusEditor; - exports.getFocusedEditor = getFocusedEditor; - exports.getActiveEditor = getActiveEditor; - exports.getCurrentlyViewedPath = getCurrentlyViewedPath; exports.getFocusedInlineWidget = getFocusedInlineWidget; - exports.resizeEditor = resizeEditor; + exports.getInlineEditors = getInlineEditors; + exports.closeInlineWidget = closeInlineWidget; + exports.openDocument = openDocument; + exports.canOpenPath = canOpenPath; + + // Convenience Methods + exports.getActiveEditor = getActiveEditor; + exports.getCurrentFullEditor = getCurrentFullEditor; + exports.getFocusedEditor = getFocusedEditor; + + exports.registerInlineEditProvider = registerInlineEditProvider; exports.registerInlineDocsProvider = registerInlineDocsProvider; exports.registerJumpToDefProvider = registerJumpToDefProvider; - exports.getInlineEditors = getInlineEditors; - exports.closeInlineWidget = closeInlineWidget; + + // Deprecated exports.registerCustomViewer = registerCustomViewer; - exports.getCustomViewerForPath = getCustomViewerForPath; - exports.notifyPathDeleted = notifyPathDeleted; - exports.showingCustomViewerForPath = showingCustomViewerForPath; + exports.resizeEditor = resizeEditor; + exports.focusEditor = focusEditor; + exports.getCurrentlyViewedPath = getCurrentlyViewedPath; + exports.setEditorHolder = setEditorHolder; }); diff --git a/src/editor/EditorOptionHandlers.js b/src/editor/EditorOptionHandlers.js index 75dbde476cf..1af9cfa5bf3 100644 --- a/src/editor/EditorOptionHandlers.js +++ b/src/editor/EditorOptionHandlers.js @@ -22,19 +22,18 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define */ define(function (require, exports, module) { "use strict"; - var AppInit = require("utils/AppInit"), - Editor = require("editor/Editor").Editor, - EditorManager = require("editor/EditorManager"), - Commands = require("command/Commands"), - CommandManager = require("command/CommandManager"), - PreferencesManager = require("preferences/PreferencesManager"), - Strings = require("strings"), - _ = require("thirdparty/lodash"); + var AppInit = require("utils/AppInit"), + Editor = require("editor/Editor").Editor, + Commands = require("command/Commands"), + CommandManager = require("command/CommandManager"), + PreferencesManager = require("preferences/PreferencesManager"), + Strings = require("strings"), + _ = require("thirdparty/lodash"); // Constants for the preferences referred to in this file var SHOW_LINE_NUMBERS = "showLineNumbers", diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 7cf5520c4cb..0b742992699 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -37,6 +37,7 @@ define(function (require, exports, module) { AppInit = require("utils/AppInit"), DropdownButton = require("widgets/DropdownButton").DropdownButton, EditorManager = require("editor/EditorManager"), + MainViewManager = require("view/MainViewManager"), Editor = require("editor/Editor").Editor, FileUtils = require("file/FileUtils"), KeyEvent = require("utils/KeyEvent"), @@ -184,7 +185,7 @@ define(function (require, exports, module) { $indentWidthInput.off("blur keyup"); // restore focus to the editor - EditorManager.focusEditor(); + MainViewManager.focusActivePane(); var valInt = parseInt(value, 10); if (Editor.getUseTabChar(fullPath)) { @@ -260,10 +261,10 @@ define(function (require, exports, module) { } if (!current) { - StatusBar.hide(); // calls resizeEditor() if needed + StatusBar.hideAllPanes(); } else { var fullPath = current.document.file.fullPath; - StatusBar.show(); // calls resizeEditor() if needed + StatusBar.showAllPanes(); $(current).on("cursorActivity.statusbar", _updateCursorInfo); $(current).on("optionChange.statusbar", function () { @@ -396,8 +397,6 @@ define(function (require, exports, module) { }); $statusOverwrite.on("click", _updateEditorOverwriteMode); - - _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); } // Initialize: status bar focused listener @@ -408,5 +407,7 @@ define(function (require, exports, module) { // Populate language switcher with all languages after startup; update it later if this set changes _populateLanguageDropdown(); $(LanguageManager).on("languageAdded languageModified", _populateLanguageDropdown); + _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); + StatusBar.show(); }); }); diff --git a/src/editor/ImageViewer.js b/src/editor/ImageViewer.js index 1d914b0d6fd..499067b3398 100644 --- a/src/editor/ImageViewer.js +++ b/src/editor/ImageViewer.js @@ -28,172 +28,177 @@ define(function (require, exports, module) { "use strict"; var DocumentManager = require("document/DocumentManager"), - EditorManager = require("editor/EditorManager"), - ImageHolderTemplate = require("text!htmlContent/image-holder.html"), - PanelManager = require("view/PanelManager"), + ImageViewTemplate = require("text!htmlContent/image-view.html"), ProjectManager = require("project/ProjectManager"), + LanguageManager = require("language/LanguageManager"), + MainViewFactory = require("view/MainViewFactory"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), FileSystem = require("filesystem/FileSystem"), - FileUtils = require("file/FileUtils"); + FileUtils = require("file/FileUtils"), + _ = require("thirdparty/lodash"); - var _naturalWidth = 0, - _naturalHeight = 0, - _scale = 100, - _scaleDivInfo = null; // coordinates of hidden scale sticker - /** Update the scale element, i.e. on resize - * @param {!string} currentWidth actual width of image in view - */ - function _updateScale(currentWidth) { - if (currentWidth && currentWidth < _naturalWidth) { - _scale = currentWidth / _naturalWidth * 100; - $("#img-scale").text(Math.floor(_scale) + "%") - // Keep the position of the image scale div relative to the image. - .css("left", $("#img-preview").position().left + 5) - .show(); - } else { - // Reset everything related to the image scale sticker before hiding it. - _scale = 100; - _scaleDivInfo = null; - $("#img-scale").text("").hide(); - } - } - - function _hideGuidesAndTip() { - $("#img-tip").hide(); - $(".img-guide").hide(); - } - - /** handle editor resize event, i.e. update scale sticker */ - function _onEditorAreaResize() { - _hideGuidesAndTip(); - _updateScale($("#img-preview").width()); - } + var _viewers = {}; /** - * Update file name if necessary + * ImageView objects are constructed when an image is opened + * @see {@link Pane} for more information about where ImageViews are rendered + * + * @constructor + * @param {!File} file - The image file object to render + * @param {!jQuery} container - The container to render the image view in */ - function _onFileNameChange(e, oldName, newName) { - var oldRelPath = ProjectManager.makeProjectRelativeIfPossible(oldName), - currentPath = $("#img-path").text(); + function ImageView(file, $container) { + this.file = file; + this.$el = $(Mustache.render(ImageViewTemplate, {fullPath: file.fullPath, + now: new Date().valueOf()})); + + $container.append(this.$el); - if (currentPath === oldRelPath) { - var newRelName = ProjectManager.makeProjectRelativeIfPossible(newName); - $("#img-path").text(newRelName) - .attr("title", newRelName); - } + this._naturalWidth = 0; + this._naturalHeight = 0; + this._scale = 100; // 100% + this._scaleDivInfo = null; // coordinates of hidden scale sticker + + this.relPath = ProjectManager.makeProjectRelativeIfPossible(this.file.fullPath); + + this.$imagePath = this.$el.find(".image-path"); + this.$imagePreview = this.$el.find(".image-preview"); + this.$imageData = this.$el.find(".image-data"); + + this.$image = this.$el.find(".image"); + this.$imageTip = this.$el.find(".image-tip"); + this.$imageGuides = this.$el.find(".image-guide"); + this.$imageScale = this.$el.find(".image-scale"); + this.$x_value = this.$el.find(".x-value"); + this.$y_value = this.$el.find(".y-value"); + this.$horzGuide = this.$el.find(".horz-guide"); + this.$vertGuide = this.$el.find(".vert-guide"); + + this.$imagePath.text(this.relPath).attr("title", this.relPath); + this.$imagePreview.on("load", _.bind(this._onImageLoaded, this)); + + _viewers[file.fullPath] = this; } /** - * Check mouse entering/exiting the scale sticker. - * Hide it when entering and show it again when exiting. - * - * @param {number} offsetX mouse offset from the left of the previewing image - * @param {number} offsetY mouseoffset from the top of the previewing image + * DocumentManger.fileNameChange handler - when an image is renamed, we must + * update the view + * + * @param {jQuery.Event} e - event + * @param {!string} oldPath - the name of the file that's changing changing + * @param {!string} newPath - the name of the file that's changing changing + * @private */ - function _handleMouseEnterOrExitScaleSticker(offsetX, offsetY) { - var imagePos = $("#img-preview").position(), - scaleDivPos = $("#img-scale").position(), - imgWidth = $("#img-preview").width(), - imgHeight = $("#img-preview").height(), - scaleDivLeft, - scaleDivTop, - scaleDivRight, - scaleDivBottom; + ImageView.prototype._onFilenameChange = function (e, oldPath, newPath) { + /* + * File objects are already updated when the event is triggered + * so we just need to see if the file has the same path as our image + */ + if (this.file.fullPath === newPath) { + this.relPath = ProjectManager.makeProjectRelativeIfPossible(newPath); + this.$imagePath.text(this.relPath).attr("title", this.relPath); + } + }; + + /** + * .on("load") handler - updates content of the image view + * initializes computed values + * installs event handlers + * @param {Event} e - event + * @private + */ + ImageView.prototype._onImageLoaded = function (e) { + // add dimensions and size + this._naturalWidth = e.currentTarget.naturalWidth; + this._naturalHeight = e.currentTarget.naturalHeight; - if (_scaleDivInfo) { - scaleDivLeft = _scaleDivInfo.left; - scaleDivTop = _scaleDivInfo.top; - scaleDivRight = _scaleDivInfo.right; - scaleDivBottom = _scaleDivInfo.bottom; - - if ((imgWidth + imagePos.left) < scaleDivRight) { - scaleDivRight = imgWidth + imagePos.left; - } - - if ((imgHeight + imagePos.top) < scaleDivBottom) { - scaleDivBottom = imgHeight + imagePos.top; - } - - } else { - scaleDivLeft = scaleDivPos.left; - scaleDivTop = scaleDivPos.top; - scaleDivRight = $("#img-scale").width() + scaleDivLeft; - scaleDivBottom = $("#img-scale").height() + scaleDivTop; + var extension = FileUtils.getFileExtension(this.file.fullPath); + var dimensionString = this._naturalWidth + " × " + this._naturalHeight + " " + Strings.UNIT_PIXELS; + + if (extension === "ico") { + dimensionString += " (" + Strings.IMAGE_VIEWER_LARGEST_ICON + ")"; } - if (_scaleDivInfo) { - // See whether the cursor is no longer inside the hidden scale div. - // If so, show it again. - if ((offsetX < scaleDivLeft || offsetX > scaleDivRight) || - (offsetY < scaleDivTop || offsetY > scaleDivBottom)) { - _scaleDivInfo = null; - $("#img-scale").show(); - } - } else if ((offsetX >= scaleDivLeft && offsetX <= scaleDivRight) && - (offsetY >= scaleDivTop && offsetY <= scaleDivBottom)) { - // Handle mouse inside image scale div. - // But hide it only if the pixel under mouse is also in the image. - if (offsetX < (imagePos.left + imgWidth) && - offsetY < (imagePos.top + imgHeight)) { - // Remember image scale div coordinates before hiding it. - _scaleDivInfo = {left: scaleDivPos.left, - top: scaleDivPos.top, - right: scaleDivRight, - bottom: scaleDivBottom}; - $("#img-scale").hide(); + // get image size + var self = this; + + this.file.stat(function (err, stat) { + if (err) { + self.$imageData.html(dimensionString); + } else { + var sizeString = ""; + if (stat.size) { + sizeString = " — " + StringUtils.prettyPrintBytes(stat.size, 2); + } + var dimensionAndSize = dimensionString + sizeString; + self.$imageData.html(dimensionAndSize) + .attr("title", dimensionAndSize + .replace("×", "x") + .replace("—", "-")); } - } - } + }); + + // make sure we always show the right file name + $(DocumentManager).on("fileNameChange", _.bind(this._onFilenameChange, this)); + + this.$imageTip.hide(); + this.$imageGuides.hide(); + + this.$image.on("mousemove.ImageView", ".image-preview", _.bind(this._showImageTip, this)) + .on("mouseleave.ImageView", ".image-preview", _.bind(this._hideImageTip, this)); + + this._updateScale(); + }; /** - * Hide image coordinates info tip - * - * @param {MouseEvent} e mouse leave event + * Update the scale element + * @private */ - function _hideImageTip(e) { - var $target = $(e.target), - targetPos = $target.position(), - imagePos = $("#img-preview").position(), - right = imagePos.left + $("#img-preview").width(), - bottom = imagePos.top + $("#img-preview").height(), - x = targetPos.left + e.offsetX, - y = targetPos.top + e.offsetY; + ImageView.prototype._updateScale = function () { + var currentWidth = this.$imagePreview.width(); - // Hide image tip and guides only if the cursor is outside of the image. - if (x < imagePos.left || x >= right || - y < imagePos.top || y >= bottom) { - _hideGuidesAndTip(); - if (_scaleDivInfo) { - _scaleDivInfo = null; - $("#img-scale").show(); - } + if (currentWidth && currentWidth < this._naturalWidth) { + this._scale = currentWidth / this._naturalWidth * 100; + this.$imageScale.text(Math.floor(this._scale) + "%") + // Keep the position of the image scale div relative to the image. + .css("left", this.$imagePreview.position().left + 5) + .show(); + } else { + // Reset everything related to the image scale sticker before hiding it. + this._scale = 100; + this._scaleDivInfo = null; + this.$imageScale.text("").hide(); } - } - + }; + + /** * Show image coordinates under the mouse cursor - * - * @param {MouseEvent} e mouse move event + * @param {Event} e - event + * @private */ - function _showImageTip(e) { - // Don't show image tip if _scale is close to zero. + ImageView.prototype._showImageTip = function (e) { + // Don't show image tip if this._scale is close to zero. // since we won't have enough room to show tip anyway. - if (Math.floor(_scale) === 0) { + if (Math.floor(this._scale) === 0) { return; } - var x = Math.round(e.offsetX * 100 / _scale), - y = Math.round(e.offsetY * 100 / _scale), - $target = $(e.target), - imagePos = $("#img-preview").position(), + var x = Math.round(e.offsetX * 100 / this._scale), + y = Math.round(e.offsetY * 100 / this._scale), + imagePos = this.$imagePreview.position(), left = e.offsetX + imagePos.left, top = e.offsetY + imagePos.top, - width = $("#img-preview").width(), - height = $("#img-preview").height(), + width = this.$imagePreview.width(), + height = this.$imagePreview.height(), windowWidth = $(window).width(), - fourDigitImageWidth = _naturalWidth.toString().length === 4, + fourDigitImageWidth = this._naturalWidth.toString().length === 4, + + // @todo -- seems a bit strange that we're computing sizes + // using magic numbers + infoWidth1 = 112, // info div width 96px + vertical toolbar width 16px infoWidth2 = 120, // info div width 104px (for 4-digit image width) + vertical toolbar width 16px tipOffsetX = 10, // adjustment for info div left from x coordinate of cursor @@ -205,15 +210,15 @@ define(function (require, exports, module) { // or the rounding calculation above for a scaled image. For example, if an image is 120 px wide, // we should get mousemove events in the range of 0 <= x < 120, but not 120 or more. If we get // a value beyond the range, then simply handle the event as if it were a mouseleave. - if (x < 0 || x >= _naturalWidth || y < 0 || y >= _naturalHeight) { - _hideImageTip(e); - $("#img-preview").css("cursor", "auto"); + if (x < 0 || x >= this._naturalWidth || y < 0 || y >= this._naturalHeight) { + this._hideImageTip(e); + this.$imagePreview.css("cursor", "auto"); return; } - $("#img-preview").css("cursor", "none"); + this.$imagePreview.css("cursor", "none"); - _handleMouseEnterOrExitScaleSticker(left, top); + this._handleMouseEnterOrExitScaleSticker(left, top); // Check whether to show the image tip on the left. if ((e.pageX + infoWidth1) > windowWidth || @@ -221,106 +226,255 @@ define(function (require, exports, module) { tipOffsetX = fourDigitImageWidth ? tipMinusOffsetX2 : tipMinusOffsetX1; } - $("#x-value").text(x + "px"); - $("#y-value").text(y + "px"); + this.$x_value.text(x + "px"); + this.$y_value.text(y + "px"); - $("#img-tip").css({ + this.$imageTip.css({ left: left + tipOffsetX, top: top + tipOffsetY }).show(); - $("#horiz-guide").css({ + this.$horzGuide.css({ left: imagePos.left, top: top, width: width - 1 }).show(); - $("#vert-guide").css({ + this.$vertGuide.css({ left: left, top: imagePos.top, height: height - 1 }).show(); - } + }; + + /** + * Hide image coordinates info tip + * @param {Event} e - event + * @private + */ + ImageView.prototype._hideImageTip = function (e) { + var $target = $(e.target), + targetPos = $target.position(), + imagePos = this.$imagePreview.position(), + right = imagePos.left + this.$imagePreview.width(), + bottom = imagePos.top + this.$imagePreview.height(), + x = targetPos.left + e.offsetX, + y = targetPos.top + e.offsetY; + // Hide image tip and guides only if the cursor is outside of the image. + if (x < imagePos.left || x >= right || + y < imagePos.top || y >= bottom) { + this._hideGuidesAndTip(); + if (this._scaleDivInfo) { + this._scaleDivInfo = null; + this.$imageScale.show(); + } + } + }; + /** - * sign off listeners when editor manager closes - * the image viewer + * Hides both guides and the tip + * @private */ - function onRemove() { - $(PanelManager).off("editorAreaResize", _onEditorAreaResize); - $(DocumentManager).off("fileNameChange", _onFileNameChange); - $("#img").off("mousemove", "#img-preview", _showImageTip) - .off("mouseleave", "#img-preview", _hideImageTip); - } - + ImageView.prototype._hideGuidesAndTip = function () { + this.$imageTip.hide(); + this.$imageGuides.hide(); + }; + /** - * Perform decorations on the view that require loading the image in the browser, - * i.e. getting actual and natural width and height andplacing the scale sticker - * @param {!string} fullPath Path to the image file - * @param {!jQueryObject} $editorHolder The DOM element to append the view to. + * Check mouse entering/exiting the scale sticker. + * Hide it when entering and show it again when exiting. + * + * @param {number} offsetX mouse offset from the left of the previewing image + * @param {number} offsetY mouseoffset from the top of the previewing image + * @private */ - function render(fullPath, $editorHolder) { - var relPath = ProjectManager.makeProjectRelativeIfPossible(fullPath), - $customViewer = $(Mustache.render(ImageHolderTemplate, {fullPath: fullPath})); - - // place DOM node to hold image - $editorHolder.append($customViewer); - - _scale = 100; // initialize to 100 - _scaleDivInfo = null; + ImageView.prototype._handleMouseEnterOrExitScaleSticker = function (offsetX, offsetY) { + var imagePos = this.$imagePreview.position(), + scaleDivPos = this.$imageScale.position(), + imgWidth = this.$imagePreview.width(), + imgHeight = this.$imagePreview.height(), + scaleDivLeft, + scaleDivTop, + scaleDivRight, + scaleDivBottom; - $("#img-path").text(relPath) - .attr("title", relPath); - $("#img-preview").on("load", function () { - // add dimensions and size - _naturalWidth = this.naturalWidth; - _naturalHeight = this.naturalHeight; - var ext = FileUtils.getFileExtension(fullPath); - var dimensionString = _naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS; - if (ext === "ico") { - dimensionString += " (" + Strings.IMAGE_VIEWER_LARGEST_ICON + ")"; + if (this._scaleDivInfo) { + scaleDivLeft = this._scaleDivInfo.left; + scaleDivTop = this._scaleDivInfo.top; + scaleDivRight = this._scaleDivInfo.right; + scaleDivBottom = this._scaleDivInfo.bottom; + + if ((imgWidth + imagePos.left) < scaleDivRight) { + scaleDivRight = imgWidth + imagePos.left; + } + + if ((imgHeight + imagePos.top) < scaleDivBottom) { + scaleDivBottom = imgHeight + imagePos.top; } - // get image size - var file = FileSystem.getFileForPath(fullPath); - file.stat(function (err, stat) { - if (err) { - $("#img-data").html(dimensionString); - } else { - var sizeString = ""; - if (stat.size) { - sizeString = " — " + StringUtils.prettyPrintBytes(stat.size, 2); - } - var dimensionAndSize = dimensionString + sizeString; - $("#img-data").html(dimensionAndSize) - .attr("title", dimensionAndSize - .replace("×", "x") - .replace("—", "-")); - } - }); - $("#image-holder").show(); - // listen to resize to update the scale sticker - $(PanelManager).on("editorAreaResize", _onEditorAreaResize); + } else { + scaleDivLeft = scaleDivPos.left; + scaleDivTop = scaleDivPos.top; + scaleDivRight = this.$imageScale.width() + scaleDivLeft; + scaleDivBottom = this.$imageScale.height() + scaleDivTop; + } + + if (this._scaleDivInfo) { + // See whether the cursor is no longer inside the hidden scale div. + // If so, show it again. + if ((offsetX < scaleDivLeft || offsetX > scaleDivRight) || + (offsetY < scaleDivTop || offsetY > scaleDivBottom)) { + this._scaleDivInfo = null; + this.$imageScale.show(); + } + } else if ((offsetX >= scaleDivLeft && offsetX <= scaleDivRight) && + (offsetY >= scaleDivTop && offsetY <= scaleDivBottom)) { + // Handle mouse inside image scale div. + // But hide it only if the pixel under mouse is also in the image. + if (offsetX < (imagePos.left + imgWidth) && + offsetY < (imagePos.top + imgHeight)) { + // Remember image scale div coordinates before hiding it. + this._scaleDivInfo = {left: scaleDivPos.left, + top: scaleDivPos.top, + right: scaleDivRight, + bottom: scaleDivBottom}; + this.$imageScale.hide(); + } + } + }; + + /** + * View Interface functions + */ + + /* + * Retrieves the file object for this view + * return {!File} the file object for this view + */ + ImageView.prototype.getFile = function () { + return this.file; + }; + + /* + * Updates the layout of the view + */ + ImageView.prototype.updateLayout = function () { + this._hideGuidesAndTip(); + + var $container = this.$el.parent(); + + var pos = $container.position(), + iWidth = $container.innerWidth(), + iHeight = $container.innerHeight(), + oWidth = $container.outerWidth(), + oHeight = $container.outerHeight(); - // make sure we always show the right file name - $(DocumentManager).on("fileNameChange", _onFileNameChange); + // $view is "position:absolute" so + // we have to update the height, width and position + this.$el.css({top: pos.top + ((oHeight - iHeight) / 2), + left: pos.left + ((oWidth - iWidth) / 2), + width: iWidth, + height: iHeight}); + this._updateScale(); + }; + + /* + * Destroys the view + */ + ImageView.prototype.destroy = function () { + delete _viewers[this.file.fullPath]; + $(DocumentManager).off("fileNameChange", _.bind(this._onFilenameChange, this)); + this.$image.off(".ImageView"); + this.$el.remove(); + }; + + /* + * Refreshes the image preview with what's on disk + */ + ImageView.prototype.refresh = function () { + var noCacheUrl = this.$imagePreview.attr("src"), + now = new Date().valueOf(), + index = noCacheUrl.indexOf("?"); - $("#img-tip").hide(); - $(".img-guide").hide(); - $("#img").on("mousemove", "#img-preview", _showImageTip) - .on("mouseleave", "#img-preview", _hideImageTip); + // strip the old param off + if (index > 0) { + noCacheUrl = noCacheUrl.slice(0, index); + } + + // add a new param which will force chrome to + // re-read the image from disk + noCacheUrl = noCacheUrl + "?ver=" + now; + - _updateScale($(this).width()); + // Update the DOM node with the src URL + this.$imagePreview.attr("src", noCacheUrl); + }; + + /* + * Creates an image view object and adds it to the specified pane + * @param {!File} file - the file to create an image of + * @param {!Pane} pane - the pane in which to host the view + * @return {jQuery.Promise} + */ + function _createImageView(file, pane) { + var view = pane.getViewForPath(file.fullPath); + + if (view) { + pane.showView(view); + } else { + view = new ImageView(file, pane.$content); + pane.addView(view, true); + } + return new $.Deferred().resolve().promise(); + } + + /** + * Handles file system change events so we can refresh + * image viewers for the files that changed on disk due to external editors + * @param {jQuery.event} event - event object + * @param {?File} file - file object that changed + * @param {Array.=} added If entry is a Directory, contains zero or more added children + * @param {Array.=} removed If entry is a Directory, contains zero or more removed children + */ + function _handleFileSystemChange(event, entry, added, removed) { + // this may have been called because files were added + // or removed to the file system. We don't care about those + if (!entry || entry.isDirectory) { + return; + } + + // Look for a viewer for the changed file + var viewer = _viewers[entry.fullPath]; - }); - return $customViewer; + // viewer found, call its refresh method + if (viewer) { + viewer.refresh(); + } } - EditorManager.registerCustomViewer("image", { - render: render, - onRemove: onRemove + /* + * Install an event listener to receive all file system change events + * so we can refresh the view when changes are made to the image in an external editor + */ + FileSystem.on("change", _handleFileSystemChange); + + /* + * Initialization, register our view factory + */ + MainViewFactory.registerViewFactory({ + canOpenFile: function (fullPath) { + var lang = LanguageManager.getLanguageForPath(fullPath); + return (lang.getId() === "image"); + }, + openFile: function (file, pane) { + return _createImageView(file, pane); + } }); - exports.render = render; - exports.onRemove = onRemove; + /* + * This is for extensions that want to create a + * view factory based on ImageViewer + */ + exports.ImageView = ImageView; }); diff --git a/src/editor/InlineTextEditor.js b/src/editor/InlineTextEditor.js index ca6cc60cbf2..2d9595ff4dd 100644 --- a/src/editor/InlineTextEditor.js +++ b/src/editor/InlineTextEditor.js @@ -38,14 +38,6 @@ define(function (require, exports, module) { InlineWidget = require("editor/InlineWidget").InlineWidget, KeyEvent = require("utils/KeyEvent"); - /** - * Returns editor holder width (not CodeMirror's width). - * @private - */ - function _editorHolderWidth() { - return $("#editor-holder").width(); - } - /** * Shows or hides the dirty indicator * @private @@ -300,10 +292,6 @@ define(function (require, exports, module) { * @param {Editor} editor */ InlineTextEditor.prototype._updateLineRange = function (editor) { - var oldStartLine = this._startLine, - oldEndLine = this._endLine, - oldLineCount = this._lineCount; - this._startLine = editor.getFirstVisibleLine(); this._endLine = editor.getLastVisibleLine(); this._lineCount = this._endLine - this._startLine; diff --git a/src/editor/MultiRangeInlineEditor.js b/src/editor/MultiRangeInlineEditor.js index e1714fdc43b..9356faebc27 100644 --- a/src/editor/MultiRangeInlineEditor.js +++ b/src/editor/MultiRangeInlineEditor.js @@ -46,8 +46,7 @@ define(function (require, exports, module) { EditorManager = require("editor/EditorManager"), Commands = require("command/Commands"), Strings = require("strings"), - CommandManager = require("command/CommandManager"), - PerfUtils = require("utils/PerfUtils"); + CommandManager = require("command/CommandManager"); var _prevMatchCmd, _nextMatchCmd; @@ -193,7 +192,6 @@ define(function (require, exports, module) { this.$rangeList = $("
    ").appendTo(this.$related); // create range list & add listeners for range textrange changes - var rangeItemText; this._ranges.forEach(this._createListItem, this); if (this._ranges.length > 1) { // attach to main container @@ -234,8 +232,6 @@ define(function (require, exports, module) { * @override */ MultiRangeInlineEditor.prototype.onAdded = function () { - var self = this; - // Before setting the inline widget height, force a height on the // floating related-container in order for CodeMirror to layout and // compute scrollbars diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index d3b1a91e182..a679c3a0e38 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, window, $, brackets, semver */ +/*global define, $, brackets */ /*unittests: ExtensionManager*/ /** @@ -39,7 +39,6 @@ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), - FileUtils = require("file/FileUtils"), Package = require("extensibility/Package"), Async = require("utils/Async"), ExtensionLoader = require("utils/ExtensionLoader"), @@ -601,31 +600,6 @@ define(function (require, exports, module) { }, []); } - /** - * Toggles between truncated and full length extension descriptions - * @param {string} id The id of the extension clicked - * @param {JQueryElement} $element The DOM element of the extension clicked - * @param {boolean} showFull true if full length description should be shown, false for shorten version. - */ - function toggleDescription(id, $element, showFull) { - var description, linkTitle, - entry = extensions[id]; - - // Toggle between appropriate descriptions and link title, - // depending on if extension is installed or not - if (showFull) { - description = entry.installInfo ? entry.installInfo.metadata.description : entry.registryInfo.metadata.description; - linkTitle = Strings.VIEW_TRUNCATED_DESCRIPTION; - } else { - description = entry.installInfo ? entry.installInfo.metadata.shortdescription : entry.registryInfo.metadata.shortdescription; - linkTitle = Strings.VIEW_COMPLETE_DESCRIPTION; - } - - $element.attr("data-toggle-desc", showFull ? "trunc-desc" : "expand-desc") - .attr("title", linkTitle) - .prev(".ext-full-description").html(description); - } - // Listen to extension load and loadFailed events $(ExtensionLoader) .on("load", _handleExtensionLoad) @@ -651,7 +625,6 @@ define(function (require, exports, module) { exports.updateExtensions = updateExtensions; exports.getAvailableUpdates = getAvailableUpdates; exports.cleanAvailableUpdates = cleanAvailableUpdates; - exports.toggleDescription = toggleDescription; exports.ENABLED = ENABLED; exports.START_FAILED = START_FAILED; diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js index 854593ef92c..8008dbc3755 100644 --- a/src/extensibility/ExtensionManagerDialog.js +++ b/src/extensibility/ExtensionManagerDialog.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global brackets, define, $, Mustache, window */ +/*global brackets, define, $, Mustache */ define(function (require, exports, module) { "use strict"; @@ -40,6 +40,7 @@ define(function (require, exports, module) { InstallExtensionDialog = require("extensibility/InstallExtensionDialog"), AppInit = require("utils/AppInit"), Async = require("utils/Async"), + KeyEvent = require("utils/KeyEvent"), ExtensionManager = require("extensibility/ExtensionManager"), ExtensionManagerView = require("extensibility/ExtensionManagerView").ExtensionManagerView, ExtensionManagerViewModel = require("extensibility/ExtensionManagerViewModel"); @@ -304,16 +305,36 @@ define(function (require, exports, module) { $dlg = dialog.getElement(); $search = $(".search", $dlg); $searchClear = $(".search-clear", $dlg); - + + function setActiveTab($tab) { + models[_activeTabIndex].scrollPos = $(".modal-body", $dlg).scrollTop(); + $tab.tab("show"); + $(".modal-body", $dlg).scrollTop(models[_activeTabIndex].scrollPos || 0); + $searchClear.click(); + } + // Dialog tabs $dlg.find(".nav-tabs a") .on("click", function (event) { - models[_activeTabIndex].scrollPos = $(".modal-body", $dlg).scrollTop(); - $(this).tab("show"); - $(".modal-body", $dlg).scrollTop(models[_activeTabIndex].scrollPos || 0); - $searchClear.click(); + setActiveTab($(this)); }); - + + // navigate through tabs via Ctrl-(Shift)-Tab + $dlg.on("keyup", function (event) { + if (event.keyCode === KeyEvent.DOM_VK_TAB && event.ctrlKey) { + var $tabs = $(".nav-tabs a", $dlg), + tabIndex = _activeTabIndex; + + if (event.shiftKey) { + tabIndex--; + } else { + tabIndex++; + } + tabIndex %= $tabs.length; + setActiveTab($tabs.eq(tabIndex)); + } + }); + // Update & hide/show the notification overlay on a tab's icon, based on its model's notifyCount function updateNotificationIcon(index) { var model = models[index], diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js index 40ab53b8f29..082b9509602 100644 --- a/src/extensibility/ExtensionManagerView.js +++ b/src/extensibility/ExtensionManagerView.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */ -/*global define, window, $, brackets, Mustache */ +/*global define, $, brackets, Mustache */ /*unittests: ExtensionManager*/ define(function (require, exports, module) { @@ -111,6 +111,31 @@ define(function (require, exports, module) { */ ExtensionManagerView.prototype._itemViews = null; + /** + * Toggles between truncated and full length extension descriptions + * @param {string} id The id of the extension clicked + * @param {JQueryElement} $element The DOM element of the extension clicked + * @param {boolean} showFull true if full length description should be shown, false for shortened version. + */ + ExtensionManagerView.prototype._toggleDescription = function (id, $element, showFull) { + var description, linkTitle, + info = this.model._getEntry(id); + + // Toggle between appropriate descriptions and link title, + // depending on if extension is installed or not + if (showFull) { + description = info.metadata.description; + linkTitle = Strings.VIEW_TRUNCATED_DESCRIPTION; + } else { + description = info.metadata.shortdescription; + linkTitle = Strings.VIEW_COMPLETE_DESCRIPTION; + } + + $element.data("toggle-desc", showFull ? "trunc-desc" : "expand-desc") + .attr("title", linkTitle) + .prev(".ext-full-description").text(description); + }; + /** * @private * Attaches our event handlers. We wait to do this until we've fully fetched the extension list. @@ -154,12 +179,12 @@ define(function (require, exports, module) { ExtensionManager.markForRemoval($target.attr("data-extension-id"), true); } else if ($target.hasClass("undo-update")) { ExtensionManager.removeUpdate($target.attr("data-extension-id")); - } else if ($target.attr("data-toggle-desc") === "expand-desc") { - ExtensionManager.toggleDescription($target.attr("data-extension-id"), $target, true); - } else if ($target.attr("data-toggle-desc") === "trunc-desc") { - ExtensionManager.toggleDescription($target.attr("data-extension-id"), $target, false); + } else if ($target.data("toggle-desc") === "expand-desc") { + this._toggleDescription($target.attr("data-extension-id"), $target, true); + } else if ($target.data("toggle-desc") === "trunc-desc") { + this._toggleDescription($target.attr("data-extension-id"), $target, false); } - }) + }.bind(this)) .on("click", "button.install", function (e) { self._installUsingDialog($(e.target).attr("data-extension-id")); }) @@ -211,6 +236,22 @@ define(function (require, exports, module) { context.isCompatible = context.isCompatibleLatest = true; } + // Check if extension metadata contains localized content. + var lang = brackets.getLocale(), + shortLang = lang.split("-")[0]; + if (info.metadata["package-i18n"]) { + [shortLang, lang].forEach(function (locale) { + if (info.metadata["package-i18n"].hasOwnProperty(locale)) { + // only overlay specific properties with the localized values + ["title", "description", "homepage", "keywords"].forEach(function (prop) { + if (info.metadata["package-i18n"][locale].hasOwnProperty(prop)) { + info.metadata[prop] = info.metadata["package-i18n"][locale][prop]; + } + }); + } + }); + } + if (info.metadata.description !== undefined) { info.metadata.shortdescription = StringUtils.truncate(info.metadata.description, 200); } @@ -224,9 +265,6 @@ define(function (require, exports, module) { context.allowInstall = context.isCompatible && !context.isInstalled; if (Array.isArray(info.metadata.i18n) && info.metadata.i18n.length > 0) { - var lang = brackets.getLocale(), - shortLang = lang.split("-")[0]; - context.translated = true; context.translatedLangs = info.metadata.i18n.map(function (value) { @@ -309,8 +347,7 @@ define(function (require, exports, module) { * new items for entries that haven't yet been rendered, but will not re-render existing items. */ ExtensionManagerView.prototype._render = function () { - var self = this, - $item; + var self = this; this._$table.empty(); this._updateMessage(); diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js index 107fc29642e..d40e0a73529 100644 --- a/src/extensibility/ExtensionManagerViewModel.js +++ b/src/extensibility/ExtensionManagerViewModel.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */ -/*global define, window, $, brackets, Mustache */ +/*global define, $ */ /*unittests: ExtensionManager*/ define(function (require, exports, module) { @@ -31,7 +31,6 @@ define(function (require, exports, module) { var _ = require("thirdparty/lodash"); var ExtensionManager = require("extensibility/ExtensionManager"), - Package = require("extensibility/Package"), registry_utils = require("extensibility/registry_utils"), Strings = require("strings"); diff --git a/src/extensibility/InstallExtensionDialog.js b/src/extensibility/InstallExtensionDialog.js index c86cde63219..07b29cb9643 100644 --- a/src/extensibility/InstallExtensionDialog.js +++ b/src/extensibility/InstallExtensionDialog.js @@ -32,8 +32,6 @@ define(function (require, exports, module) { File = require("filesystem/File"), StringUtils = require("utils/StringUtils"), Strings = require("strings"), - Commands = require("command/Commands"), - CommandManager = require("command/CommandManager"), FileSystem = require("filesystem/FileSystem"), KeyEvent = require("utils/KeyEvent"), Package = require("extensibility/Package"), diff --git a/src/extensibility/node/ExtensionManagerDomain.js b/src/extensibility/node/ExtensionManagerDomain.js index e2f489e23ee..70d310bbc7a 100644 --- a/src/extensibility/node/ExtensionManagerDomain.js +++ b/src/extensibility/node/ExtensionManagerDomain.js @@ -29,9 +29,7 @@ indent: 4, maxerr: 50 */ var semver = require("semver"), path = require("path"), - http = require("http"), request = require("request"), - os = require("os"), fs = require("fs-extra"), temp = require("temp"), validate = require("./package-validator").validate; @@ -92,9 +90,7 @@ function _removeFailedInstallation(installDirectory) { */ function _performInstall(packagePath, installDirectory, validationResult, callback) { validationResult.installedTo = installDirectory; - - var callbackCalled = false; - + fs.mkdirs(installDirectory, function (err) { if (err) { callback(err); diff --git a/src/extensibility/node/package-validator.js b/src/extensibility/node/package-validator.js index 9bdf4a5434f..a44d172e0da 100644 --- a/src/extensibility/node/package-validator.js +++ b/src/extensibility/node/package-validator.js @@ -30,9 +30,6 @@ indent: 4, maxerr: 50, regexp: true */ var DecompressZip = require("decompress-zip"), semver = require("semver"), path = require("path"), - http = require("http"), - request = require("request"), - os = require("os"), temp = require("temp"), fs = require("fs-extra"); @@ -269,10 +266,6 @@ function validatePackageJSON(path, packageJSON, options, callback) { * @param {function(Error, {errors: Array, metadata: Object, commonPrefix: string, extractDir: string})} callback function to call with the result */ function extractAndValidateFiles(zipPath, extractDir, options, callback) { - var callbackCalled = false; - var metadata; - var foundMainIn = null; - var unzipper = new DecompressZip(zipPath); unzipper.on("error", function (err) { // General error to report for problems reading the file diff --git a/src/extensibility/node/spec/Installation.spec.js b/src/extensibility/node/spec/Installation.spec.js index 68ac59cb575..9d24ba435cf 100644 --- a/src/extensibility/node/spec/Installation.spec.js +++ b/src/extensibility/node/spec/Installation.spec.js @@ -147,8 +147,6 @@ describe("Package Installation", function () { it("should successfully update an extension", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { - var extensionDirectory = path.join(installDirectory, "basic-valid-extension"); - expect(err).toBeNull(); ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); @@ -327,4 +325,4 @@ describe("Package Installation", function () { done(); }); }); -}); \ No newline at end of file +}); diff --git a/src/extensions/default/CSSCodeHints/main.js b/src/extensions/default/CSSCodeHints/main.js index 932f6c067c8..35963692bd0 100644 --- a/src/extensions/default/CSSCodeHints/main.js +++ b/src/extensions/default/CSSCodeHints/main.js @@ -22,22 +22,21 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */ -/*global define, brackets, $, window */ +/*global define, brackets, $ */ define(function (require, exports, module) { "use strict"; - var _ = brackets.getModule("thirdparty/lodash"), - AppInit = brackets.getModule("utils/AppInit"), - ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), - CodeHintManager = brackets.getModule("editor/CodeHintManager"), - CSSUtils = brackets.getModule("language/CSSUtils"), - HTMLUtils = brackets.getModule("language/HTMLUtils"), - LanguageManager = brackets.getModule("language/LanguageManager"), - TokenUtils = brackets.getModule("utils/TokenUtils"), - StringMatch = brackets.getModule("utils/StringMatch"), - CSSProperties = require("text!CSSProperties.json"), - properties = JSON.parse(CSSProperties); + var AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + CodeHintManager = brackets.getModule("editor/CodeHintManager"), + CSSUtils = brackets.getModule("language/CSSUtils"), + HTMLUtils = brackets.getModule("language/HTMLUtils"), + LanguageManager = brackets.getModule("language/LanguageManager"), + TokenUtils = brackets.getModule("utils/TokenUtils"), + StringMatch = brackets.getModule("utils/StringMatch"), + CSSProperties = require("text!CSSProperties.json"), + properties = JSON.parse(CSSProperties); // Context of the last request for hints: either CSSUtils.PROP_NAME, // CSSUtils.PROP_VALUE or null. @@ -144,8 +143,7 @@ define(function (require, exports, module) { */ CssPropHints.prototype.hasHints = function (editor, implicitChar) { this.editor = editor; - var cursor = this.editor.getCursorPos(), - textAfterCursor; + var cursor = this.editor.getCursorPos(); lastContext = null; this.info = CSSUtils.getInfoAtPos(editor, cursor); diff --git a/src/extensions/default/CSSCodeHints/unittests.js b/src/extensions/default/CSSCodeHints/unittests.js index 754ec03d039..67da96a8c64 100644 --- a/src/extensions/default/CSSCodeHints/unittests.js +++ b/src/extensions/default/CSSCodeHints/unittests.js @@ -28,9 +28,6 @@ define(function (require, exports, module) { "use strict"; var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), - CodeHintManager = brackets.getModule("editor/CodeHintManager"), - DocumentManager = brackets.getModule("document/DocumentManager"), - FileUtils = brackets.getModule("file/FileUtils"), testContentCSS = require("text!unittest-files/regions.css"), testContentHTML = require("text!unittest-files/region-template.html"), CSSCodeHints = require("main"); @@ -481,7 +478,7 @@ define(function (require, exports, module) { var expectedString = "shape-inside:polygon()"; testEditor.setCursorPos({ line: 1, ch: 15 }); // after shape-inside - var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + expectHints(CSSCodeHints.cssPropHintProvider); selectHint(CSSCodeHints.cssPropHintProvider, "polygon()"); expect(testDocument.getLine(1).length).toBe(expectedString.length); expect(testDocument.getLine(1)).toBe(expectedString); diff --git a/src/extensions/default/CloseOthers/main.js b/src/extensions/default/CloseOthers/main.js index e7e5a0711cd..37aadfd345c 100644 --- a/src/extensions/default/CloseOthers/main.js +++ b/src/extensions/default/CloseOthers/main.js @@ -22,18 +22,18 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $, brackets, window, document */ +/*global define, $, brackets */ define(function (require, exports, module) { "use strict"; - var Menus = brackets.getModule("command/Menus"), - CommandManager = brackets.getModule("command/CommandManager"), - Commands = brackets.getModule("command/Commands"), - DocumentManager = brackets.getModule("document/DocumentManager"), - Strings = brackets.getModule("strings"), - workingSetCmenu = Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_MENU), - PreferencesManager = brackets.getModule("preferences/PreferencesManager"); + var Menus = brackets.getModule("command/Menus"), + CommandManager = brackets.getModule("command/CommandManager"), + Commands = brackets.getModule("command/Commands"), + MainViewManager = brackets.getModule("view/MainViewManager"), + Strings = brackets.getModule("strings"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + workingSetListCmenu = Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU); // Constants var closeOthers = "file.close_others", @@ -54,20 +54,17 @@ define(function (require, exports, module) { * @param {string} mode */ function handleClose(mode) { - var targetIndex = DocumentManager.findInWorkingSet(DocumentManager.getCurrentDocument().file.fullPath), - workingSet = DocumentManager.getWorkingSet().slice(0), - start = (mode === closeBelow) ? (targetIndex + 1) : 0, - end = (mode === closeAbove) ? (targetIndex) : (workingSet.length), - files = [], + var targetIndex = MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE)), + workingSetList = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE), + start = (mode === closeBelow) ? (targetIndex + 1) : 0, + end = (mode === closeAbove) ? (targetIndex) : (workingSetList.length), + files = [], i; - - if (mode === closeOthers) { - end--; - workingSet.splice(targetIndex, 1); - } - + for (i = start; i < end; i++) { - files.push(workingSet[i]); + if ((mode === closeOthers && i !== targetIndex) || (mode !== closeOthers)) { + files.push(workingSetList[i]); + } } CommandManager.execute(Commands.FILE_CLOSE_LIST, {fileList: files}); @@ -77,25 +74,25 @@ define(function (require, exports, module) { * Enable/Disable the menu items depending on which document is selected in the working set */ function contextMenuOpenHandler() { - var doc = DocumentManager.getCurrentDocument(); + var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); - if (doc) { - var docIndex = DocumentManager.findInWorkingSet(doc.file.fullPath), - workingSet = DocumentManager.getWorkingSet().slice(0); + if (file) { + var targetIndex = MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, file.fullPath), + workingSetListSize = MainViewManager.getWorkingSetSize(MainViewManager.ACTIVE_PANE); - if (docIndex === workingSet.length - 1) { // hide "Close Others Below" if the last file in Working Files is selected + if (targetIndex === workingSetListSize - 1) { // hide "Close Others Below" if the last file in Working Files is selected CommandManager.get(closeBelow).setEnabled(false); } else { CommandManager.get(closeBelow).setEnabled(true); } - if (workingSet.length === 1) { // hide "Close Others" if there is only one file in Working Files + if (workingSetListSize === 1) { // hide "Close Others" if there is only one file in Working Files CommandManager.get(closeOthers).setEnabled(false); } else { CommandManager.get(closeOthers).setEnabled(true); } - if (docIndex === 0) { // hide "Close Others Above" if the first file in Working Files is selected + if (targetIndex === 0) { // hide "Close Others Above" if the first file in Working Files is selected CommandManager.get(closeAbove).setEnabled(false); } else { CommandManager.get(closeAbove).setEnabled(true); @@ -126,25 +123,25 @@ define(function (require, exports, module) { if (prefs.closeBelow !== menuEntriesShown.closeBelow) { if (prefs.closeBelow) { - workingSetCmenu.addMenuItem(closeBelow, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeBelow, "", Menus.AFTER, Commands.FILE_CLOSE); } else { - workingSetCmenu.removeMenuItem(closeBelow); + workingSetListCmenu.removeMenuItem(closeBelow); } } if (prefs.closeOthers !== menuEntriesShown.closeOthers) { if (prefs.closeOthers) { - workingSetCmenu.addMenuItem(closeOthers, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeOthers, "", Menus.AFTER, Commands.FILE_CLOSE); } else { - workingSetCmenu.removeMenuItem(closeOthers); + workingSetListCmenu.removeMenuItem(closeOthers); } } if (prefs.closeAbove !== menuEntriesShown.closeAbove) { if (prefs.closeAbove) { - workingSetCmenu.addMenuItem(closeAbove, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeAbove, "", Menus.AFTER, Commands.FILE_CLOSE); } else { - workingSetCmenu.removeMenuItem(closeAbove); + workingSetListCmenu.removeMenuItem(closeAbove); } } @@ -168,13 +165,13 @@ define(function (require, exports, module) { }); if (prefs.closeBelow) { - workingSetCmenu.addMenuItem(closeBelow, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeBelow, "", Menus.AFTER, Commands.FILE_CLOSE); } if (prefs.closeOthers) { - workingSetCmenu.addMenuItem(closeOthers, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeOthers, "", Menus.AFTER, Commands.FILE_CLOSE); } if (prefs.closeAbove) { - workingSetCmenu.addMenuItem(closeAbove, "", Menus.AFTER, Commands.FILE_CLOSE); + workingSetListCmenu.addMenuItem(closeAbove, "", Menus.AFTER, Commands.FILE_CLOSE); } menuEntriesShown = prefs; } @@ -184,7 +181,7 @@ define(function (require, exports, module) { initializeCommands(); // Add a context menu open handler - $(workingSetCmenu).on("beforeContextMenuOpen", contextMenuOpenHandler); + $(workingSetListCmenu).on("beforeContextMenuOpen", contextMenuOpenHandler); prefs.on("change", prefChangeHandler); }); diff --git a/src/extensions/default/CloseOthers/unittests.js b/src/extensions/default/CloseOthers/unittests.js index 99754ba2c19..b5022464280 100644 --- a/src/extensions/default/CloseOthers/unittests.js +++ b/src/extensions/default/CloseOthers/unittests.js @@ -34,6 +34,7 @@ define(function (require, exports, module) { Dialogs, EditorManager, DocumentManager, + MainViewManager, FileSystem; describe("CloseOthers", function () { @@ -86,6 +87,7 @@ define(function (require, exports, module) { $ = testWindow.$; brackets = testWindow.brackets; DocumentManager = testWindow.brackets.test.DocumentManager; + MainViewManager = testWindow.brackets.test.MainViewManager; CommandManager = testWindow.brackets.test.CommandManager; EditorManager = testWindow.brackets.test.EditorManager; Dialogs = testWindow.brackets.test.Dialogs; @@ -127,16 +129,21 @@ define(function (require, exports, module) { function runCloseOthers() { - var ws = DocumentManager.getWorkingSet(), + var ws = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE), promise; if (ws.length > docSelectIndex) { DocumentManager.getDocumentForPath(ws[docSelectIndex].fullPath).done(function (doc) { - DocumentManager.setCurrentDocument(doc); + MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); }); - promise = CommandManager.execute(cmdToRun); - waitsForDone(promise, cmdToRun); + runs(function () { + promise = CommandManager.execute(cmdToRun); + waitsForDone(promise, cmdToRun); + }); + runs(function () { + expect(MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE)).toEqual(ws[docSelectIndex].fullPath, "Path of document in editor after close others command should be the document that was selected"); + }); } } @@ -144,10 +151,10 @@ define(function (require, exports, module) { docSelectIndex = 2; cmdToRun = "file.close_others"; - runs(runCloseOthers); + runCloseOthers(); runs(function () { - expect(DocumentManager.getWorkingSet().length).toEqual(1); + expect(MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE).length).toEqual(1); }); }); @@ -155,10 +162,10 @@ define(function (require, exports, module) { docSelectIndex = 2; cmdToRun = "file.close_above"; - runs(runCloseOthers); + runCloseOthers(); runs(function () { - expect(DocumentManager.getWorkingSet().length).toEqual(3); + expect(MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE).length).toEqual(3); }); }); @@ -166,10 +173,10 @@ define(function (require, exports, module) { docSelectIndex = 1; cmdToRun = "file.close_below"; - runs(runCloseOthers); + runCloseOthers(); runs(function () { - expect(DocumentManager.getWorkingSet().length).toEqual(2); + expect(MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE).length).toEqual(2); }); }); }); diff --git a/src/extensions/default/DarkTheme/main.less b/src/extensions/default/DarkTheme/main.less index a2dc9e168d3..b40225671e1 100644 --- a/src/extensions/default/DarkTheme/main.less +++ b/src/extensions/default/DarkTheme/main.less @@ -159,11 +159,11 @@ /* Non-editor styling */ -#image-holder, -#not-editor { +.image-view, +.not-editor { background-color: @background; } -#image-holder { +.view-pane .image-view { color: @foreground; } diff --git a/src/extensions/default/DebugCommands/ErrorNotification.js b/src/extensions/default/DebugCommands/ErrorNotification.js index 5af4fafff41..e75fcbc52ba 100644 --- a/src/extensions/default/DebugCommands/ErrorNotification.js +++ b/src/extensions/default/DebugCommands/ErrorNotification.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $, brackets, window, document */ +/*global define, $, brackets, window */ define(function (require, exports, module) { "use strict"; diff --git a/src/extensions/default/DebugCommands/NodeDebugUtils.js b/src/extensions/default/DebugCommands/NodeDebugUtils.js index c268c0daead..36fbace5957 100644 --- a/src/extensions/default/DebugCommands/NodeDebugUtils.js +++ b/src/extensions/default/DebugCommands/NodeDebugUtils.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, brackets, window */ +/*global define, $, brackets */ define(function (require, exports, module) { "use strict"; @@ -130,4 +130,4 @@ define(function (require, exports, module) { exports.restartNode = restartNode; exports.enableDebugger = enableDebugger; -}); \ No newline at end of file +}); diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index efdc4ea01ea..6641569266c 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -294,8 +294,7 @@ define(function (require, exports, module) { var pos = editor.getCursorPos(), tokenType, offset, - query, - textAfterCursor; + query; this.editor = editor; this.tagInfo = HTMLUtils.getTagInfo(editor, pos); @@ -385,8 +384,7 @@ define(function (require, exports, module) { query = {queryStr: null}, tokenType, offset, - result = [], - textAfterCursor; + result = []; this.tagInfo = HTMLUtils.getTagInfo(this.editor, cursor); tokenType = this.tagInfo.position.tokenType; @@ -570,4 +568,4 @@ define(function (require, exports, module) { exports.tagHintProvider = tagHints; exports.attrHintProvider = attrHints; }); -}); \ No newline at end of file +}); diff --git a/src/extensions/default/HTMLCodeHints/unittests.js b/src/extensions/default/HTMLCodeHints/unittests.js index 0a0e5cc55a0..39d76ced899 100644 --- a/src/extensions/default/HTMLCodeHints/unittests.js +++ b/src/extensions/default/HTMLCodeHints/unittests.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, brackets, waitsForDone */ +/*global define, describe, it, xit, expect, beforeEach, afterEach, $, brackets */ define(function (require, exports, module) { "use strict"; @@ -31,7 +31,6 @@ define(function (require, exports, module) { // Modules from the SpecRunner window var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), Editor = brackets.getModule("editor/Editor").Editor, - CodeHintManager = brackets.getModule("editor/CodeHintManager"), HTMLCodeHints = require("main"); describe("HTML Code Hinting", function () { @@ -447,7 +446,7 @@ define(function (require, exports, module) { // Expect no filtering - however, we offer some attributes (including first in the list) that // are specific to the tag, so we can't use the default "no filtering" empty arg here. - // (This smart filtering isn't officially part of the sprint, so no unit tests specifically + // (This smart filtering isn't officially part of the release, so no unit tests specifically // targeting that functionality yet). verifyAttrHints(hintList, "accept"); }); diff --git a/src/extensions/default/HtmlEntityCodeHints/unittests.js b/src/extensions/default/HtmlEntityCodeHints/unittests.js index 268743ccf4a..13ee46d4545 100644 --- a/src/extensions/default/HtmlEntityCodeHints/unittests.js +++ b/src/extensions/default/HtmlEntityCodeHints/unittests.js @@ -23,15 +23,13 @@ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, brackets, waitsForDone */ +/*global define, describe, it, expect, beforeEach, afterEach, brackets */ define(function (require, exports, module) { "use strict"; // Modules from the SpecRunner window var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), - Editor = brackets.getModule("editor/Editor").Editor, - CodeHintManager = brackets.getModule("editor/CodeHintManager"), HTMLEntityHints = require("main").SpecialCharHints, defaultContent = require("text!unittest-files/default.html"); diff --git a/src/extensions/default/InlineColorEditor/InlineColorEditor.js b/src/extensions/default/InlineColorEditor/InlineColorEditor.js index 25e8836c392..06fc801a7d7 100644 --- a/src/extensions/default/InlineColorEditor/InlineColorEditor.js +++ b/src/extensions/default/InlineColorEditor/InlineColorEditor.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, nomen: true, regexp: true, maxerr: 50 */ -/*global define, brackets, $, window */ +/*global define, brackets, $ */ define(function (require, exports, module) { "use strict"; diff --git a/src/extensions/default/InlineColorEditor/main.js b/src/extensions/default/InlineColorEditor/main.js index 74fbdd9892b..5b205d9c0b7 100644 --- a/src/extensions/default/InlineColorEditor/main.js +++ b/src/extensions/default/InlineColorEditor/main.js @@ -22,13 +22,12 @@ */ /*jslint vars: true, plusplus: true, nomen: true, regexp: true, maxerr: 50 */ -/*global define, brackets, $, document */ +/*global define, brackets, $ */ define(function (require, exports, module) { "use strict"; var EditorManager = brackets.getModule("editor/EditorManager"), - ProjectManager = brackets.getModule("project/ProjectManager"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), InlineColorEditor = require("InlineColorEditor").InlineColorEditor, ColorUtils = brackets.getModule("utils/ColorUtils"); @@ -43,8 +42,7 @@ define(function (require, exports, module) { * @return {?{color:String, start:TextMarker, end:TextMarker}} */ function prepareEditorForProvider(hostEditor, pos) { - var colorPicker, colorRegEx, cursorLine, inlineColorEditor, match, result, - sel, start, end, startBookmark, endBookmark; + var colorRegEx, cursorLine, match, sel, start, end, startBookmark, endBookmark; sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { diff --git a/src/extensions/default/InlineColorEditor/unittests.js b/src/extensions/default/InlineColorEditor/unittests.js index d97dd065dbb..0f5733fb51b 100644 --- a/src/extensions/default/InlineColorEditor/unittests.js +++ b/src/extensions/default/InlineColorEditor/unittests.js @@ -23,16 +23,13 @@ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, brackets, waitsForDone, spyOn, tinycolor, KeyEvent */ +/*global define, describe, it, expect, beforeEach, afterEach, waits, runs, $, brackets, waitsForDone, spyOn, tinycolor */ define(function (require, exports, module) { "use strict"; // Modules from the SpecRunner window var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), - Editor = brackets.getModule("editor/Editor").Editor, - DocumentManager = brackets.getModule("document/DocumentManager"), - Strings = brackets.getModule("strings"), KeyEvent = brackets.getModule("utils/KeyEvent"), testContentCSS = require("text!unittest-files/unittests.css"), testContentHTML = require("text!unittest-files/unittests.html"), diff --git a/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js index 353424382df..532eaac66a9 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js +++ b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js @@ -27,19 +27,16 @@ define(function (require, exports, module) { "use strict"; - var EditorManager = brackets.getModule("editor/EditorManager"), - KeyEvent = brackets.getModule("utils/KeyEvent"), + var KeyEvent = brackets.getModule("utils/KeyEvent"), Strings = brackets.getModule("strings"); - var TimingFunctionUtils = require("TimingFunctionUtils"), - InlineTimingFunctionEditor = require("InlineTimingFunctionEditor").InlineTimingFunctionEditor; + var TimingFunctionUtils = require("TimingFunctionUtils"); /** Mustache template that forms the bare DOM structure of the UI */ - var BezierCurveEditorTemplate = require("text!BezierCurveEditorTemplate.html"); + var BezierCurveEditorTemplate = require("text!BezierCurveEditorTemplate.html"); /** @const @type {number} */ - var STEP_MULTIPLIER = 5, - HEIGHT_ABOVE = 75, // extra height above main grid + var HEIGHT_ABOVE = 75, // extra height above main grid HEIGHT_BELOW = 75, // extra height below main grid HEIGHT_MAIN = 150, // height of main grid WIDTH_MAIN = 150; // width of main grid @@ -134,9 +131,7 @@ define(function (require, exports, module) { offsetsToCoordinates: function (element) { var p = this.padding, w = this.canvas.width, - h = this.canvas.height * 0.5, - x, - y; + h = this.canvas.height * 0.5; // Convert padding percentage to actual padding p = p.map(function (a, i) { diff --git a/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js b/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js index 02cb07852be..8a21766223a 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js +++ b/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js @@ -27,23 +27,17 @@ define(function (require, exports, module) { "use strict"; - var EditorManager = brackets.getModule("editor/EditorManager"), - KeyEvent = brackets.getModule("utils/KeyEvent"), - Strings = brackets.getModule("strings"); + var KeyEvent = brackets.getModule("utils/KeyEvent"), + Strings = brackets.getModule("strings"); - var TimingFunctionUtils = require("TimingFunctionUtils"), - InlineTimingFunctionEditor = require("InlineTimingFunctionEditor").InlineTimingFunctionEditor; + var TimingFunctionUtils = require("TimingFunctionUtils"); /** Mustache template that forms the bare DOM structure of the UI */ var StepEditorTemplate = require("text!StepEditorTemplate.html"); /** @const @type {number} */ - var STEP_LINE = 1, - DASH_LINE = 2, - HEIGHT_MAIN = 150, // height of main grid - WIDTH_MAIN = 150; // width of main grid - - var animationRequest = null; + var STEP_LINE = 1, + DASH_LINE = 2; /** * StepParameters object constructor diff --git a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js b/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js index 687239f8309..5a980da2412 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js +++ b/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, brackets, $ */ +/*global define, brackets */ /** * Utilities functions related to color matching @@ -60,15 +60,15 @@ define(function (require, exports, module) { * If string is a number, then convert it. * * @param {string} str value parsed from page. - * @return { isNumber: boolean, value: number } + * @return { isNumber: boolean, value: ?number } */ function _convertToNumber(str) { - if (typeof (str) !== "string") { - return { isNumber: false }; + if (typeof str !== "string") { + return { isNumber: false, value: null }; } - var val = parseFloat(str, 10), - isNum = (typeof (val) === "number") && !isNaN(val) && + var val = parseFloat(+str, 10), + isNum = (typeof val === "number") && !isNaN(val) && (val !== Infinity) && (val !== -Infinity); return { @@ -173,8 +173,7 @@ define(function (require, exports, module) { def = [ "5", "end" ], params = def, oldIndex = match.index, // we need to store the old match.index to re-set the index afterwards - originalString = match[0], - i; + originalString = match[0]; if (match) { match = match[1].split(","); diff --git a/src/extensions/default/InlineTimingFunctionEditor/main.js b/src/extensions/default/InlineTimingFunctionEditor/main.js index e9cffa1ad09..2ed79471921 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/main.js +++ b/src/extensions/default/InlineTimingFunctionEditor/main.js @@ -41,7 +41,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, brackets, $, window, Mustache */ +/*global define, brackets, $, Mustache */ define(function (require, exports, module) { "use strict"; diff --git a/src/extensions/default/InlineTimingFunctionEditor/unittests.js b/src/extensions/default/InlineTimingFunctionEditor/unittests.js index cc85c37a23b..d24defa52b2 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/unittests.js +++ b/src/extensions/default/InlineTimingFunctionEditor/unittests.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, brackets, waitsForDone, spyOn, KeyEvent */ +/*global define, describe, it, expect, beforeEach, afterEach, runs, $, brackets, waitsForDone */ define(function (require, exports, module) { "use strict"; @@ -215,6 +215,9 @@ define(function (require, exports, module) { it("should correct cubic-bezier function with 5 parameters", function () { testInvalidBezier("cubic-bezier(0, 0, 1, 1, 1)", ["cubic-bezier(0, 0, 1, 1)", "0", "0", "1", "1"]); }); + it("should correct cubic-bezier function with trailing comma", function () { + testInvalidBezier("cubic-bezier(.42, 0, .58, .5,)", ["cubic-bezier(.42, 0, .58, .5)", ".42", "0", ".58", ".5"]); + }); // Real invalid cubic-beziers - they should NOT be corrected automatically it("should not match cubic-bezier function with invalid whitespace", function () { diff --git a/src/extensions/default/JSLint/main.js b/src/extensions/default/JSLint/main.js index e1c5d21b429..cb1e77a96ba 100644 --- a/src/extensions/default/JSLint/main.js +++ b/src/extensions/default/JSLint/main.js @@ -22,7 +22,7 @@ */ -/*global define, $, JSLINT, brackets */ +/*global define, JSLINT, brackets */ /** * Provides JSLint results via the core linting extension point diff --git a/src/extensions/default/JSLint/unittests.js b/src/extensions/default/JSLint/unittests.js index e0197beb8bc..9b8d2a3e3a7 100644 --- a/src/extensions/default/JSLint/unittests.js +++ b/src/extensions/default/JSLint/unittests.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, expect, beforeEach, afterEach, waitsFor, runs, brackets, waitsForDone, spyOn */ +/*global define, describe, it, expect, beforeEach, afterEach, runs, brackets, waitsForDone, spyOn */ define(function (require, exports, module) { "use strict"; diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js b/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js index a5b99048b2a..abe8c50c37b 100644 --- a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js @@ -35,15 +35,13 @@ define(function (require, exports, module) { Menus = brackets.getModule("command/Menus"), Strings = brackets.getModule("strings"), HintsUtils2 = require("HintUtils2"), - ScopeManager = require("ScopeManager"), - Session = require("Session"); + ScopeManager = require("ScopeManager"); /** @const {string} Show Function Hint command ID */ var SHOW_PARAMETER_HINT_CMD_ID = "showParameterHint", // string must MATCH string in native code (brackets_extensions) PUSH_EXISTING_HINT = true, OVERWRITE_EXISTING_HINT = false, - PRESERVE_FUNCTION_STACK = true, hintContainerHTML = require("text!ParameterHintTemplate.html"), KeyboardPrefs = JSON.parse(require("text!keyboard.json")); @@ -136,8 +134,7 @@ define(function (require, exports, module) { * of the function call. */ function formatHint(functionInfo) { - var hints = session.getParameterHint(functionInfo.functionCallPos), - pendingOptional = false; + var hints = session.getParameterHint(functionInfo.functionCallPos); $hintContent.empty(); $hintContent.addClass("brackets-js-hints"); @@ -440,4 +437,4 @@ define(function (require, exports, module) { exports.startCursorTracking = startCursorTracking; exports.stopCursorTracking = stopCursorTracking; -}); \ No newline at end of file +}); diff --git a/src/extensions/default/JavaScriptCodeHints/Preferences.js b/src/extensions/default/JavaScriptCodeHints/Preferences.js index 9605fa9c1f0..75fc119fdc9 100644 --- a/src/extensions/default/JavaScriptCodeHints/Preferences.js +++ b/src/extensions/default/JavaScriptCodeHints/Preferences.js @@ -61,7 +61,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */ -/*global define, brackets, $ */ +/*global define, brackets */ define(function (require, exports, module) { "use strict"; diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 146f590bf70..80f76d24b59 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -44,7 +44,6 @@ define(function (require, exports, module) { ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), FileSystem = brackets.getModule("filesystem/FileSystem"), FileUtils = brackets.getModule("file/FileUtils"), - globmatch = brackets.getModule("thirdparty/globmatch"), LanguageManager = brackets.getModule("language/LanguageManager"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), ProjectManager = brackets.getModule("project/ProjectManager"), @@ -1137,7 +1136,6 @@ define(function (require, exports, module) { var file = document.file, path = file.fullPath, dir = file.parentPath, - files = [], pr; var addFilesDeferred = $.Deferred(); diff --git a/src/extensions/default/JavaScriptCodeHints/Session.js b/src/extensions/default/JavaScriptCodeHints/Session.js index a269a93923b..7cb0280114f 100644 --- a/src/extensions/default/JavaScriptCodeHints/Session.js +++ b/src/extensions/default/JavaScriptCodeHints/Session.js @@ -30,7 +30,6 @@ define(function (require, exports, module) { var StringMatch = brackets.getModule("utils/StringMatch"), LanguageManager = brackets.getModule("language/LanguageManager"), HTMLUtils = brackets.getModule("language/HTMLUtils"), - TokenUtils = brackets.getModule("utils/TokenUtils"), HintUtils = require("HintUtils"), ScopeManager = require("ScopeManager"), Acorn = require("thirdparty/acorn/acorn"), @@ -549,7 +548,7 @@ define(function (require, exports, module) { * * @param {Array} hints - array of hints * @param {StringMatcher} matcher - * @returns {Array} - array of matching hints. + * @return {Array} - array of matching hints. */ function filterWithQueryAndMatcher(hints, matcher) { var matchResults = $.map(hints, function (hint) { @@ -750,8 +749,6 @@ define(function (require, exports, module) { // HTML file - need to send back only the bodies of the //