From 314d7ffc633bbe4ea4756781b13ee5b1521c3ef4 Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Fri, 29 May 2015 00:11:36 -0300 Subject: [PATCH 01/10] Support disabling extensions via .disabled file. Extension can also be disabled using `"disable": true` in the package.json. Details: - Rename getPackageJson to getMetadata. - Check for presence of .disabled file in the extension directory and add it to the metadata. - Do not load extension if it is disabled instead, reject with disabled status. - Add DISABLED extension state. - Extension is still gets on record and is enumerated with all its metadata, but it's not started. --- src/extensibility/ExtensionManager.js | 29 +++++++++----- src/utils/ExtensionLoader.js | 14 +++++-- src/utils/ExtensionUtils.js | 54 ++++++++++++++++++++------- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index 9eafb6b6e9b..b4335aabf6a 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -67,6 +67,7 @@ define(function (require, exports, module) { * Extension status constants. */ var ENABLED = "enabled", + DISABLED = "disabled", START_FAILED = "startFailed"; /** @@ -240,8 +241,9 @@ define(function (require, exports, module) { * @param {string} path The local path of the loaded extension's folder. */ function _handleExtensionLoad(e, path) { - function setData(id, metadata) { + function setData(metadata) { var locationType, + id = metadata.name, userExtensionPath = ExtensionLoader.getUserExtensionPath(); if (path.indexOf(userExtensionPath) === 0) { locationType = LOCATION_USER; @@ -265,27 +267,33 @@ define(function (require, exports, module) { metadata: metadata, path: path, locationType: locationType, - status: (e.type === "loadFailed" ? START_FAILED : ENABLED) + status: (e.type === "loadFailed" ? START_FAILED : (metadata.disabled ? DISABLED : ENABLED)) }; synchronizeEntry(id); loadTheme(id); exports.trigger("statusChange", id); } + + function deduceMetadata() { + var match = path.match(/\/([^\/]+)$/), + name = (match && match[1]) || path, + metadata = { name: name, title: name }; + return metadata; + } - ExtensionUtils.loadPackageJson(path) + ExtensionUtils.loadMetadata(path) .done(function (metadata) { - setData(metadata.name, metadata); + setData(metadata); }) - .fail(function () { + .fail(function (disabled) { // If there's no package.json, this is a legacy extension. It was successfully loaded, // but we don't have an official ID or metadata for it, so we just create an id and // "title" for it (which is the last segment of its pathname) // and record that it's enabled. - var match = path.match(/\/([^\/]+)$/), - name = (match && match[1]) || path, - metadata = { name: name, title: name }; - setData(name, metadata); + var metadata = deduceMetadata(); + metadata.disabled = disabled; + setData(metadata); }); } @@ -764,7 +772,8 @@ define(function (require, exports, module) { // Listen to extension load and loadFailed events ExtensionLoader .on("load", _handleExtensionLoad) - .on("loadFailed", _handleExtensionLoad); + .on("loadFailed", _handleExtensionLoad) + .on("disabled", _handleExtensionLoad); EventDispatcher.makeEventDispatcher(exports); diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index 154bd7254e7..240a5a63a86 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -242,7 +242,7 @@ define(function (require, exports, module) { var promise = new $.Deferred(); // Try to load the package.json to figure out if we are loading a theme. - ExtensionUtils.loadPackageJson(config.baseUrl).always(promise.resolve); + ExtensionUtils.loadMetadata(config.baseUrl).always(promise.resolve); return promise .then(function (metadata) { @@ -251,12 +251,20 @@ define(function (require, exports, module) { return; } - return loadExtensionModule(name, config, entryPoint); + if (!metadata.disabled) { + return loadExtensionModule(name, config, entryPoint); + } else { + return new $.Deferred().resolve().reject("disabled"); + } }) .then(function () { exports.trigger("load", config.baseUrl); }, function (err) { - exports.trigger("loadFailed", config.baseUrl); + if (err === "disabled") { + exports.trigger("disabled", config.baseUrl); + } else { + exports.trigger("loadFailed", config.baseUrl); + } }); } diff --git a/src/utils/ExtensionUtils.js b/src/utils/ExtensionUtils.js index 30bd3c64bc1..1f3a8689b1c 100644 --- a/src/utils/ExtensionUtils.js +++ b/src/utils/ExtensionUtils.js @@ -31,7 +31,8 @@ define(function (require, exports, module) { "use strict"; - var FileSystem = require("filesystem/FileSystem"), + var Async = require("utils/Async"), + FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"); /** @@ -233,26 +234,51 @@ define(function (require, exports, module) { } /** - * Loads the package.json file in the given extension folder. + * Loads the package.json file in the given extension folder as well as any additional + * metadata. + * + * If there's a .disabled file in the extension directory, then the content of package.json + * will be augmented with disabled property set to true. It will override whatever value of + * disabled might be set. * * @param {string} folder The extension folder. * @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file, - * or rejected if there is no package.json or the contents are not valid JSON. + * or rejected if there is no package.json with the boolean indicating whether .disabled file exists. */ - function loadPackageJson(folder) { - var file = FileSystem.getFileForPath(folder + "/package.json"), - result = new $.Deferred(); - FileUtils.readAsText(file) - .done(function (text) { + function loadMetadata(folder) { + var packageJSONFile = FileSystem.getFileForPath(folder + "/package.json"), + disabledFile = FileSystem.getFileForPath(folder + "/.disabled"), + result = new $.Deferred(), + jsonPromise = new $.Deferred(), + disabledPromise = new $.Deferred(), + json, + disabled; + FileUtils.readAsText(packageJSONFile) + .then(function (text) { try { - var json = JSON.parse(text); - result.resolve(json); + json = JSON.parse(text); + jsonPromise.resolve(); } catch (e) { - result.reject(); + jsonPromise.reject(); } }) - .fail(function () { - result.reject(); + .fail(jsonPromise.reject); + disabledFile.exists(function (err, exists) { + if (err) { + disabled = false; + } else { + disabled = exists; + } + disabledPromise.resolve(); + }); + Async.waitForAll([jsonPromise, disabledPromise]) + .always(function () { + if (!json) { + result.reject(disabled); + } else { + json.disabled = disabled; + result.resolve(json); + } }); return result.promise(); } @@ -264,5 +290,5 @@ define(function (require, exports, module) { exports.getModuleUrl = getModuleUrl; exports.loadFile = loadFile; exports.loadStyleSheet = loadStyleSheet; - exports.loadPackageJson = loadPackageJson; + exports.loadMetadata = loadMetadata; }); From c8af09f62aba86ea24872097daaf4903d76d6577 Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Sat, 30 May 2015 19:47:34 -0300 Subject: [PATCH 02/10] Support disabling/enabling extensions in UI. --- src/extensibility/ExtensionManager.js | 129 +++++++- src/extensibility/ExtensionManagerDialog.js | 148 +++++---- src/extensibility/ExtensionManagerView.js | 17 +- src/extensibility/Package.js | 59 +++- .../extension-manager-view-item.html | 13 + src/nls/root/strings.js | 9 +- test/spec/ExtensionManager-test.js | 294 +++++++++++++++++- 7 files changed, 588 insertions(+), 81 deletions(-) diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index b4335aabf6a..9c92eb4868f 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -104,8 +104,9 @@ define(function (require, exports, module) { /** * Requested changes to the installed extensions. */ - var _idsToRemove = [], - _idsToUpdate = []; + var _idsToRemove = {}, + _idsToUpdate = {}, + _idsToDisable = {}; PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined); @@ -187,8 +188,9 @@ define(function (require, exports, module) { */ function _reset() { exports.extensions = extensions = {}; - _idsToRemove = []; - _idsToUpdate = []; + _idsToRemove = {}; + _idsToUpdate = {}; + _idsToDisable = {}; } /** @@ -267,7 +269,7 @@ define(function (require, exports, module) { metadata: metadata, path: path, locationType: locationType, - status: (e.type === "loadFailed" ? START_FAILED : (metadata.disabled ? DISABLED : ENABLED)) + status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED)) }; synchronizeEntry(id); @@ -406,6 +408,60 @@ define(function (require, exports, module) { } return result.promise(); } + + /** + * Disables the installed extension with the given id. + * + * @param {string} id The id of the extension to disable. + * @return {$.Promise} A promise that's resolved when the extenion is disabled or + * rejected with an error that prevented the disabling. + */ + function disable(id) { + var result = new $.Deferred(), + extension = extensions[id]; + if (extension && extension.installInfo) { + Package.disable(extension.installInfo.path) + .done(function () { + extension.installInfo.status = DISABLED; + extension.installInfo.metadata.disabled = true; + result.resolve(); + exports.trigger("statusChange", id); + }) + .fail(function (err) { + result.reject(err); + }); + } else { + result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id)); + } + return result.promise(); + } + + /** + * Enables the installed extension with the given id. + * + * @param {string} id The id of the extension to enable. + * @return {$.Promise} A promise that's resolved when the extenion is enabled or + * rejected with an error that prevented the enabling. + */ + function enable(id) { + var result = new $.Deferred(), + extension = extensions[id]; + if (extension && extension.installInfo) { + Package.enable(extension.installInfo.path) + .done(function () { + extension.installInfo.status = ENABLED; + extension.installInfo.metadata.disabled = false; + result.resolve(); + exports.trigger("statusChange", id); + }) + .fail(function (err) { + result.reject(err); + }); + } else { + result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id)); + } + return result.promise(); + } /** * Updates an installed extension with the given package file. @@ -460,7 +516,7 @@ define(function (require, exports, module) { } exports.trigger("statusChange", id); } - + /** * Returns true if an extension is marked for removal. * @param {string} id The id of the extension to check. @@ -469,7 +525,7 @@ define(function (require, exports, module) { function isMarkedForRemoval(id) { return !!(_idsToRemove[id]); } - + /** * Returns true if there are any extensions marked for removal. * @return {boolean} true if there are extensions to remove @@ -478,6 +534,39 @@ define(function (require, exports, module) { return Object.keys(_idsToRemove).length > 0; } + /** + * Marks an extension for disabling later, or unmarks an extension previously marked. + * + * @param {string} id The id of the extension + * @param {boolean} mark Whether to mark or unmark the extension. + */ + function markForDisabling(id, mark) { + if (mark) { + _idsToDisable[id] = true; + } else { + delete _idsToDisable[id]; + } + exports.trigger("statusChange", id); + } + + /** + * Returns true if an extension is mark for disabling. + * + * @param {string} id The id of the extension to check. + * @return {boolean} true if it's been mark for disabling, false otherwise. + */ + function isMarkedForDisabling(id) { + return !!(_idsToDisable[id]); + } + + /** + * Returns true if there are any extensions marked for disabling. + * @return {boolean} true if there are extensions to disable + */ + function hasExtensionsToDisable() { + return Object.keys(_idsToDisable).length > 0; + } + /** * If a downloaded package appears to be an update, mark the extension for update. * If an extension was previously marked for removal, marking for update will @@ -550,6 +639,25 @@ define(function (require, exports, module) { } ); } + + /** + * Disables extensions marked for disabling. + * + * If the return promise is rejected, the argument will contain an array of objects. Each + * element is an object identifying the extension failed with "item" property set to the + * extension id which has failed to be disabled and "error" property set to the error. + * + * @return {$.Promise} A promise that's resolved when all extensions marked for disabling are + * disabled or rejected if one or more extensions can't be disabled. + */ + function disableMarkedExtensions() { + return Async.doInParallel_aggregateErrors( + Object.keys(_idsToDisable), + function (id) { + return disable(id); + } + ); + } /** * Updates extensions previously marked for update. @@ -784,17 +892,23 @@ define(function (require, exports, module) { exports.getExtensionURL = getExtensionURL; exports.remove = remove; exports.update = update; + exports.disable = disable; + exports.enable = enable; exports.extensions = extensions; exports.cleanupUpdates = cleanupUpdates; exports.markForRemoval = markForRemoval; exports.isMarkedForRemoval = isMarkedForRemoval; exports.unmarkAllForRemoval = unmarkAllForRemoval; exports.hasExtensionsToRemove = hasExtensionsToRemove; + exports.markForDisabling = markForDisabling; + exports.isMarkedForDisabling = isMarkedForDisabling; + exports.hasExtensionsToDisable = hasExtensionsToDisable; exports.updateFromDownload = updateFromDownload; exports.removeUpdate = removeUpdate; exports.isMarkedForUpdate = isMarkedForUpdate; exports.hasExtensionsToUpdate = hasExtensionsToUpdate; exports.removeMarkedExtensions = removeMarkedExtensions; + exports.disableMarkedExtensions = disableMarkedExtensions; exports.updateExtensions = updateExtensions; exports.getAvailableUpdates = getAvailableUpdates; exports.cleanAvailableUpdates = cleanAvailableUpdates; @@ -802,6 +916,7 @@ define(function (require, exports, module) { exports.hasDownloadedRegistry = false; exports.ENABLED = ENABLED; + exports.DISABLED = DISABLED; exports.START_FAILED = START_FAILED; exports.LOCATION_DEFAULT = LOCATION_DEFAULT; diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js index b0a6367bf6b..6b7f1d802d6 100644 --- a/src/extensibility/ExtensionManagerDialog.js +++ b/src/extensibility/ExtensionManagerDialog.js @@ -63,17 +63,20 @@ define(function (require, exports, module) { */ function _performChanges() { // If an extension was removed or updated, prompt the user to quit Brackets. - var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(), - hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate(); - if (!hasRemovedExtensions && !hasUpdatedExtensions) { + var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(), + hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate(), + hasDisabledExtensions = ExtensionManager.hasExtensionsToDisable(); + if (!hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) { return; } var buttonLabel = Strings.CHANGE_AND_RELOAD; - if (hasRemovedExtensions && !hasUpdatedExtensions) { + if (hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) { buttonLabel = Strings.REMOVE_AND_RELOAD; - } else if (hasUpdatedExtensions && !hasRemovedExtensions) { + } else if (hasUpdatedExtensions && !hasRemovedExtensions && !hasDisabledExtensions) { buttonLabel = Strings.UPDATE_AND_RELOAD; + } else if (hasDisabledExtensions && !hasRemovedExtensions && !hasUpdatedExtensions) { + buttonLabel = Strings.DISABLE_AND_RELOAD; } var dlg = Dialogs.showModalDialog( @@ -107,57 +110,98 @@ define(function (require, exports, module) { .text(Strings.PROCESSING_EXTENSIONS) .append(""); - ExtensionManager.removeMarkedExtensions() + var removeExtensionsPromise, + updateExtensionsPromise, + disableExtensionsPromise, + removeErrors, + updateErrors, + disableErrors; + + removeExtensionsPromise = ExtensionManager.removeMarkedExtensions(); + removeExtensionsPromise + .fail(function (errorArray) { + removeErrors = errorArray; + }); + updateExtensionsPromise = ExtensionManager.updateExtensions(); + updateExtensionsPromise + .fail(function (errorArray) { + updateErrors = errorArray; + }); + disableExtensionsPromise = ExtensionManager.disableMarkedExtensions(); + disableExtensionsPromise + .fail(function (errorArray) { + disableErrors = errorArray; + }); + + Async.waitForAll([removeExtensionsPromise, updateExtensionsPromise, disableExtensionsPromise]) + .always(function () { + dlg.close(); + }) .done(function () { - ExtensionManager.updateExtensions() - .done(function () { - dlg.close(); + CommandManager.execute(Commands.APP_RELOAD); + }) + .fail(function () { + var ids = [], + dialogs = []; + + function nextDialog() { + var dialog = dialogs.shift(); + if (dialog) { + Dialogs.showModalDialog(dialog.dialog, dialog.title, dialog.message) + .done(nextDialog); + } else { + // Even in case of error condition, we still have to reload CommandManager.execute(Commands.APP_RELOAD); - }) - .fail(function (errorArray) { - dlg.close(); - - // This error case should be very uncommon. - // Just let the user know that we couldn't update - // this extension and log the errors to the console. - var ids = []; - errorArray.forEach(function (errorObj) { - ids.push(errorObj.item); - if (errorObj.error && errorObj.error.forEach) { - console.error("Errors for", errorObj.item); - errorObj.error.forEach(function (error) { - console.error(Package.formatError(error)); - }); - } else { - console.error("Error for", errorObj.item, errorObj); - } - }); - Dialogs.showModalDialog( - DefaultDialogs.DIALOG_ID_ERROR, - Strings.EXTENSION_MANAGER_UPDATE, - StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", ")) - ).done(function () { - // We still have to reload even if some of the removals failed. - CommandManager.execute(Commands.APP_RELOAD); - }); + } + } + + if (removeErrors) { + removeErrors.forEach(function (errorObj) { + ids.push(errorObj.item); }); - }) - .fail(function (errorArray) { - dlg.close(); - ExtensionManager.cleanupUpdates(); + dialogs.push({ + dialog: DefaultDialogs.DIALOG_ID_ERROR, + title: Strings.EXTENSION_MANAGER_REMOVE, + message: StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", ")) + }); + } - var ids = []; - errorArray.forEach(function (errorObj) { - ids.push(errorObj.item); - }); - Dialogs.showModalDialog( - DefaultDialogs.DIALOG_ID_ERROR, - Strings.EXTENSION_MANAGER_REMOVE, - StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", ")) - ).done(function () { - // We still have to reload even if some of the removals failed. - CommandManager.execute(Commands.APP_RELOAD); - }); + if (updateErrors) { + // This error case should be very uncommon. + // Just let the user know that we couldn't update + // this extension and log the errors to the console. + ids.length = 0; + updateErrors.forEach(function (errorObj) { + ids.push(errorObj.item); + if (errorObj.error && errorObj.error.forEach) { + console.error("Errors for", errorObj.item); + errorObj.error.forEach(function (error) { + console.error(Package.formatError(error)); + }); + } else { + console.error("Error for", errorObj.item, errorObj); + } + }); + dialogs.push({ + dialog: DefaultDialogs.DIALOG_ID_ERROR, + title: Strings.EXTENSION_MANAGER_UPDATE, + message: StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", ")) + }); + } + + if (disableErrors) { + ids.length = 0; + disableErrors.forEach(function (errorObj) { + ids.push(errorObj.item); + }); + dialogs.push({ + dialog: DefaultDialogs.DIALOG_ID_ERROR, + title: Strings.EXTENSION_MANAGER_DISABLE, + message: StringUtils.format(Strings.EXTENSION_MANAGER_DISABLE_ERROR, ids.join(", ")) + }); + } + + nextDialog(); }); } else { dlg.close(); diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js index b6781a4a42d..00640258845 100644 --- a/src/extensibility/ExtensionManagerView.js +++ b/src/extensibility/ExtensionManagerView.js @@ -181,6 +181,8 @@ 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.hasClass("undo-disable")) { + ExtensionManager.markForDisabling($target.attr("data-extension-id"), 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") { @@ -195,6 +197,12 @@ define(function (require, exports, module) { }) .on("click", "button.remove", function (e) { ExtensionManager.markForRemoval($(e.target).attr("data-extension-id"), true); + }) + .on("click", "button.disable", function (e) { + ExtensionManager.markForDisabling($(e.target).attr("data-extension-id"), true); + }) + .on("click", "button.enable", function (e) { + ExtensionManager.enable($(e.target).attr("data-extension-id")); }); }; @@ -221,6 +229,7 @@ define(function (require, exports, module) { // arrays as iteration contexts. context.isInstalled = !!entry.installInfo; context.failedToStart = (entry.installInfo && entry.installInfo.status === ExtensionManager.START_FAILED); + context.disabled = (entry.installInfo && entry.installInfo.status === ExtensionManager.DISABLED); context.hasVersionInfo = !!info.versions; if (entry.registryInfo) { @@ -259,7 +268,9 @@ define(function (require, exports, module) { } context.isMarkedForRemoval = ExtensionManager.isMarkedForRemoval(info.metadata.name); + context.isMarkedForDisabling = ExtensionManager.isMarkedForDisabling(info.metadata.name); context.isMarkedForUpdate = ExtensionManager.isMarkedForUpdate(info.metadata.name); + var hasPendingAction = context.isMarkedForDisabling || context.isMarkedForRemoval || context.isMarkedForUpdate; context.showInstallButton = (this.model.source === this.model.SOURCE_REGISTRY || this.model.source === this.model.SOURCE_THEMES) && !context.updateAvailable; context.showUpdateButton = context.updateAvailable && !context.isMarkedForUpdate && !context.isMarkedForRemoval; @@ -314,7 +325,11 @@ define(function (require, exports, module) { } context.removalAllowed = this.model.source === "installed" && - !context.failedToStart && !context.isMarkedForUpdate && !context.isMarkedForRemoval; + !context.failedToStart && !hasPendingAction; + context.disablingAllowed = this.model.source === "installed" && + !context.disabled && !hasPendingAction; + context.enablingAllowed = this.model.source === "installed" && + context.disabled && !hasPendingAction; // Copy over helper functions that we share with the registry app. ["lastVersionDate", "authorInfo"].forEach(function (helper) { diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 6cee87b062c..cc4025f04d0 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -430,6 +430,47 @@ define(function (require, exports, module) { }); } + /** + * Disables the extension at the given path. + * + * @param {string} path The absolute path to the extension to disable. + * @return {$.Promise} A promise that's resolved when the extenion is disabled, or + * rejected if there was an error. + */ + function disable(path) { + var result = new $.Deferred(), + file = FileSystem.getFileForPath(path + "/.disabled"); + file.write("", function (err) { + if (err) { + result.reject(err); + } else { + result.resolve(); + } + }); + return result.promise(); + } + + /** + * Enables the extension at the given path. + * + * @param {string} path The absolute path to the extension to enable. + * @return {$.Promise} A promise that's resolved when the extenion is enable, or + * rejected if there was an error. + */ + function enable(path) { + var result = new $.Deferred(), + file = FileSystem.getFileForPath(path + "/.disabled"); + file.unlink(function (err) { + if (err) { + result.reject(err); + return; + } + ExtensionLoader.loadExtension(FileUtils.getBaseName(path), { baseUrl: path }, "main") + .done(result.resolve) + .fail(result.reject); + }); + } + /** * Install an extension update located at path. * This assumes that the installation was previously attempted @@ -498,12 +539,14 @@ define(function (require, exports, module) { // For unit tests only exports._getNodeConnectionDeferred = _getNodeConnectionDeferred; - exports.installFromURL = installFromURL; - exports.installFromPath = installFromPath; - exports.validate = validate; - exports.install = install; - exports.remove = remove; - exports.installUpdate = installUpdate; - exports.formatError = formatError; - exports.InstallationStatuses = InstallationStatuses; + exports.installFromURL = installFromURL; + exports.installFromPath = installFromPath; + exports.validate = validate; + exports.install = install; + exports.remove = remove; + exports.disable = disable; + exports.enable = enable; + exports.installUpdate = installUpdate; + exports.formatError = formatError; + exports.InstallationStatuses = InstallationStatuses; }); diff --git a/src/htmlContent/extension-manager-view-item.html b/src/htmlContent/extension-manager-view-item.html index c966f681cb3..40e25169475 100644 --- a/src/htmlContent/extension-manager-view-item.html +++ b/src/htmlContent/extension-manager-view-item.html @@ -63,6 +63,16 @@ {{Strings.UPDATE}} {{/showUpdateButton}} + {{#disablingAllowed}} + + {{/disablingAllowed}} + {{#enablingAllowed}} + + {{/enablingAllowed}} {{#removalAllowed}} {{/showUpdateButton}} {{#disablingAllowed}} + {{#showUpdateButton}} +
+ {{/showUpdateButton}} @@ -93,5 +97,6 @@ {{Strings.MARKED_FOR_UPDATE}} ({{Strings.UNDO_UPDATE}}) {{/isMarkedForUpdate}} {{/isInstalled}} +
diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less index 97acf3e0dd7..042098c1a3f 100644 --- a/src/styles/brackets_patterns_override.less +++ b/src/styles/brackets_patterns_override.less @@ -1525,6 +1525,7 @@ input[type="color"], box-shadow: inset 0 1px @bc-highlight-hard; -webkit-font-smoothing: antialiased; text-shadow: none; + margin: 3px; .dark & { background-color: @dark-bc-btn-bg; @@ -1650,7 +1651,6 @@ input[type="color"], color: @bc-text-alt; font-weight: @font-weight-semibold; text-shadow: 0 -1px 0 @bc-shadow-small; - margin-right: 3px; .dark & { background-color: @dark-bc-secondary-btn-bg; From d7b667550cc66a1a0360cfb9e583a2fc949fdd7e Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Thu, 25 Jun 2015 11:48:10 -0300 Subject: [PATCH 08/10] Make .disabled file rule more concise. --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e1d723920f5..d0bce994f57 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ Thumbs.db /src/extensions/disabled +# ignore .disabled file for default extensions +/src/extensions/default/*/.disabled + #OSX .DS_Store files .DS_Store @@ -35,5 +38,3 @@ Thumbs.db # Files that can be automatically downloaded that we don't want to ship with our builds /src/extensibility/node/node_modules/request/tests/ -# .disabled should be ignored, it could be used to disable default extensions -.disabled From 1eef654b68c88832f52dcbe16fc3cccaa5db7f1d Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Mon, 29 Jun 2015 13:33:36 -0300 Subject: [PATCH 09/10] Change the extension manager layout. Remove right margin entirely. --- src/styles/brackets_patterns_override.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less index 042098c1a3f..c6b670b0b26 100644 --- a/src/styles/brackets_patterns_override.less +++ b/src/styles/brackets_patterns_override.less @@ -1525,7 +1525,7 @@ input[type="color"], box-shadow: inset 0 1px @bc-highlight-hard; -webkit-font-smoothing: antialiased; text-shadow: none; - margin: 3px; + margin: 3px 0px 3px 3px; .dark & { background-color: @dark-bc-btn-bg; From 753c84cdd4e8b08044f91954828694fe1dbe52e8 Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Tue, 7 Jul 2015 17:41:51 -0300 Subject: [PATCH 10/10] Use a common function to handle enabling/disabling --- src/extensibility/ExtensionManager.js | 48 +++++++++++++-------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index b754c5e18d7..503bc55fefd 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -410,20 +410,23 @@ define(function (require, exports, module) { } /** - * Disables the installed extension with the given id. + * @private * - * @param {string} id The id of the extension to disable. - * @return {$.Promise} A promise that's resolved when the extenion is disabled or - * rejected with an error that prevented the disabling. + * Disables or enables the installed extensions. + * + * @param {string} id The id of the extension to disable or enable. + * @param {boolean} enable A boolean indicating whether to enable or disable. + * @return {$.Promise} A promise that's resolved when the extension action is + * completed or rejected with an error that prevents the action from completion. */ - function disable(id) { + function _enableOrDisable(id, enable) { var result = new $.Deferred(), extension = extensions[id]; if (extension && extension.installInfo) { - Package.disable(extension.installInfo.path) + Package[(enable ? "enable" : "disable")](extension.installInfo.path) .done(function () { - extension.installInfo.status = DISABLED; - extension.installInfo.metadata.disabled = true; + extension.installInfo.status = enable ? ENABLED : DISABLED; + extension.installInfo.metadata.disabled = !enable; result.resolve(); exports.trigger("statusChange", id); }) @@ -436,6 +439,17 @@ define(function (require, exports, module) { return result.promise(); } + /** + * Disables the installed extension with the given id. + * + * @param {string} id The id of the extension to disable. + * @return {$.Promise} A promise that's resolved when the extenion is disabled or + * rejected with an error that prevented the disabling. + */ + function disable(id) { + return _enableOrDisable(id, false); + } + /** * Enables the installed extension with the given id. * @@ -444,23 +458,7 @@ define(function (require, exports, module) { * rejected with an error that prevented the enabling. */ function enable(id) { - var result = new $.Deferred(), - extension = extensions[id]; - if (extension && extension.installInfo) { - Package.enable(extension.installInfo.path) - .done(function () { - extension.installInfo.status = ENABLED; - extension.installInfo.metadata.disabled = false; - result.resolve(); - exports.trigger("statusChange", id); - }) - .fail(function (err) { - result.reject(err); - }); - } else { - result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id)); - } - return result.promise(); + return _enableOrDisable(id, true); } /**