diff --git a/src/brackets.js b/src/brackets.js index 4af5ad6eb15..d1121b13cbb 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -53,6 +53,7 @@ define(function (require, exports, module) { require("thirdparty/CodeMirror2/addon/edit/closetag"); require("thirdparty/CodeMirror2/addon/scroll/scrollpastend"); require("thirdparty/CodeMirror2/addon/selection/active-line"); + require("thirdparty/CodeMirror2/addon/selection/mark-selection"); require("thirdparty/CodeMirror2/addon/mode/multiplex"); require("thirdparty/CodeMirror2/addon/mode/overlay"); require("thirdparty/CodeMirror2/addon/search/match-highlighter"); @@ -103,6 +104,7 @@ define(function (require, exports, module) { NativeApp = require("utils/NativeApp"), DeprecationWarning = require("utils/DeprecationWarning"), ViewCommandHandlers = require("view/ViewCommandHandlers"), + ThemeManager = require("view/ThemeManager"), _ = require("thirdparty/lodash"); // DEPRECATED: In future we want to remove the global CodeMirror, but for now we diff --git a/src/command/Commands.js b/src/command/Commands.js index 5a35fc4c0df..cdaa8c72b43 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -122,6 +122,7 @@ define(function (require, exports, module) { exports.CMD_REPLACE_IN_SUBTREE = "cmd.replaceInSubtree"; // FindInFilesUI.js _showReplaceBarForSubtree() // VIEW + exports.CMD_THEMES_OPEN_SETTINGS = "view.themesOpenSetting"; // MenuCommands.js Settings.open() exports.VIEW_HIDE_SIDEBAR = "view.hideSidebar"; // SidebarView.js toggle() exports.VIEW_INCREASE_FONT_SIZE = "view.increaseFontSize"; // ViewCommandHandlers.js _handleIncreaseFontSize() exports.VIEW_DECREASE_FONT_SIZE = "view.decreaseFontSize"; // ViewCommandHandlers.js _handleDecreaseFontSize() diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 6fa5eb98ec6..65245bb86e0 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -118,6 +118,8 @@ define(function (require, exports, module) { * View menu */ menu = Menus.addMenu(Strings.VIEW_MENU, Menus.AppMenuBar.VIEW_MENU); + menu.addMenuItem(Commands.CMD_THEMES_OPEN_SETTINGS); + menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_HIDE_SIDEBAR); menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_INCREASE_FONT_SIZE); diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index bf550763e8b..a1de7ecb8d1 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -1,24 +1,24 @@ /* * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. - * + * * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. - * + * */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ @@ -27,7 +27,7 @@ /** * The ExtensionManager fetches/caches the extension registry and provides - * information about the status of installed extensions. ExtensionManager raises the + * information about the status of installed extensions. ExtensionManager raises the * following events: * - statusChange - indicates that an extension has been installed/uninstalled or * its status has otherwise changed. Second parameter is the id of the @@ -37,25 +37,27 @@ */ 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"), + ExtensionUtils = require("utils/ExtensionUtils"), FileSystem = require("filesystem/FileSystem"), Strings = require("strings"), - StringUtils = require("utils/StringUtils"); - + StringUtils = require("utils/StringUtils"), + ThemeManager = require("view/ThemeManager"); + // semver.browser is an AMD-compatible module var semver = require("extensibility/node/node_modules/semver/semver.browser"); - + /** * Extension status constants. */ var ENABLED = "enabled", START_FAILED = "startFailed"; - + /** * Extension location constants. */ @@ -63,13 +65,13 @@ define(function (require, exports, module) { LOCATION_DEV = "dev", LOCATION_USER = "user", LOCATION_UNKNOWN = "unknown"; - + /** * @private * @type {Object.} - * The set of all known extensions, both from the registry and locally installed. - * The keys are either "name" from package.json (for extensions that have package metadata) - * or the last segment of local file paths (for installed legacy extensions + * The set of all known extensions, both from the registry and locally installed. + * The keys are either "name" from package.json (for extensions that have package metadata) + * or the last segment of local file paths (for installed legacy extensions * with no package metadata). The fields of each record are: * registryInfo: object containing the info for this id from the main registry (containing metadata, owner, * and versions). This will be null for legacy extensions. @@ -81,13 +83,13 @@ define(function (require, exports, module) { * status: the current status, one of the status constants above */ var extensions = {}; - + /** * Requested changes to the installed extensions. */ var _idsToRemove = [], _idsToUpdate = []; - + /** * @private * Synchronizes the information between the public registry and the installed @@ -98,12 +100,12 @@ define(function (require, exports, module) { */ function synchronizeEntry(id) { var entry = extensions[id]; - + // Do nothing if we only have one set of data if (!entry || !entry.installInfo || !entry.registryInfo) { return; } - + entry.installInfo.owner = entry.registryInfo.owner; // Assume false @@ -132,6 +134,21 @@ define(function (require, exports, module) { $(exports).triggerHandler("registryUpdate", [id]); } + + /** + * @private + * Verifies if an extension is a theme based on the presence of the field "theme" + * in the package.json. If it is a theme, then the theme file is just loaded by the + * ThemeManager + */ + function loadTheme(id) { + var extension = extensions[id]; + if ( extension.installInfo && extension.installInfo.metadata && extension.installInfo.metadata.theme ) { + ThemeManager.loadPackage(extension.installInfo); + } + } + + /** * @private * Sets our data. For unit testing only. @@ -152,7 +169,7 @@ define(function (require, exports, module) { _idsToRemove = []; _idsToUpdate = []; } - + /** * Downloads the registry of Brackets extensions and stores the information in our * extension info. @@ -183,32 +200,8 @@ define(function (require, exports, module) { }); return result.promise(); } - - /** - * @private - * Loads the package.json file in the given extension folder. - * @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. - */ - function _loadPackageJson(folder) { - var file = FileSystem.getFileForPath(folder + "/package.json"), - result = new $.Deferred(); - FileUtils.readAsText(file) - .done(function (text) { - try { - var json = JSON.parse(text); - result.resolve(json); - } catch (e) { - result.reject(); - } - }) - .fail(function () { - result.reject(); - }); - return result.promise(); - } - + + /** * @private * When an extension is loaded, fetches the package.json and stores the extension in our map. @@ -244,17 +237,18 @@ define(function (require, exports, module) { status: (e.type === "loadFailed" ? START_FAILED : ENABLED) }; synchronizeEntry(id); + loadTheme(id); $(exports).triggerHandler("statusChange", [id]); } - - _loadPackageJson(path) + + ExtensionUtils.loadPackageJson(path) .done(function (metadata) { setData(metadata.name, metadata); }) .fail(function () { // 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) + // "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, @@ -262,7 +256,7 @@ define(function (require, exports, module) { setData(name, metadata); }); } - + /** * Determines if the given versions[] entry is compatible with the given Brackets API version, and if not * specifies why. @@ -296,7 +290,7 @@ define(function (require, exports, module) { } return result; } - + /** * Finds the newest version of the entry that is compatible with the given Brackets API version, if any. * @param {Object} entry The registry entry to check. @@ -315,10 +309,10 @@ define(function (require, exports, module) { } return fallback; } - + var i = entry.versions.length - 1, latestInfo = getCompatibilityInfoForVersion(entry.versions[i], apiVersion); - + if (latestInfo.isCompatible) { latestInfo.isLatestVersion = true; return latestInfo; @@ -332,12 +326,12 @@ define(function (require, exports, module) { return compatInfo; } } - + // No version is compatible, so just return info for the latest version return latestInfo; } } - + /** * Given an extension id and version number, returns the URL for downloading that extension from * the repository. Does not guarantee that the extension exists at that URL. @@ -348,7 +342,7 @@ define(function (require, exports, module) { function getExtensionURL(id, version) { return StringUtils.format(brackets.config.extension_url, id, version); } - + /** * Removes the installed extension with the given id. * @param {string} id The id of the extension to remove. @@ -372,7 +366,7 @@ define(function (require, exports, module) { } return result.promise(); } - + /** * Updates an installed extension with the given package file. * @param {string} id of the extension @@ -397,7 +391,7 @@ define(function (require, exports, module) { }); _idsToUpdate = {}; } - + /** * Unmarks all extensions marked for removal. */ @@ -418,7 +412,7 @@ define(function (require, exports, module) { } $(exports).triggerHandler("statusChange", [id]); } - + /** * Returns true if an extension is marked for removal. * @param {string} id The id of the extension to check. @@ -427,7 +421,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 @@ -435,7 +429,7 @@ define(function (require, exports, module) { function hasExtensionsToRemove() { return Object.keys(_idsToRemove).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 @@ -454,7 +448,7 @@ define(function (require, exports, module) { $(exports).triggerHandler("statusChange", [id]); } } - + /** * Removes the mark for an extension to be updated on restart. Also deletes the * downloaded package file. @@ -471,7 +465,7 @@ define(function (require, exports, module) { delete _idsToUpdate[id]; $(exports).triggerHandler("statusChange", [id]); } - + /** * Returns true if an extension is marked for update. * @param {string} id The id of the extension to check. @@ -480,7 +474,7 @@ define(function (require, exports, module) { function isMarkedForUpdate(id) { return !!(_idsToUpdate[id]); } - + /** * Returns true if there are any extensions marked for update. * @return {boolean} true if there are extensions to update @@ -488,7 +482,7 @@ define(function (require, exports, module) { function hasExtensionsToUpdate() { return Object.keys(_idsToUpdate).length > 0; } - + /** * Removes extensions previously marked for removal. * @return {$.Promise} A promise that's resolved when all extensions are removed, or rejected @@ -504,7 +498,7 @@ define(function (require, exports, module) { } ); } - + /** * Updates extensions previously marked for update. * @return {$.Promise} A promise that's resolved when all extensions are updated, or rejected @@ -521,7 +515,7 @@ define(function (require, exports, module) { } ); } - + /** * Gets an array of extensions that are currently installed and can be updated to a new version * @return {Array.<{id: string, installVersion: string, registryVersion: string}>} @@ -600,10 +594,10 @@ define(function (require, exports, module) { exports.updateExtensions = updateExtensions; exports.getAvailableUpdates = getAvailableUpdates; exports.cleanAvailableUpdates = cleanAvailableUpdates; - + exports.ENABLED = ENABLED; exports.START_FAILED = START_FAILED; - + exports.LOCATION_DEFAULT = LOCATION_DEFAULT; exports.LOCATION_DEV = LOCATION_DEV; exports.LOCATION_USER = LOCATION_USER; diff --git a/src/extensions/default/ThorDarkTheme/main.less b/src/extensions/default/ThorDarkTheme/main.less new file mode 100644 index 00000000000..0b70097e580 --- /dev/null +++ b/src/extensions/default/ThorDarkTheme/main.less @@ -0,0 +1,184 @@ +// Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// Style definitions are from +// https://github.com/chriskempson/tomorrow-theme/tree/master/Brackets + + +// Brackets-specific default font and color definitions +@import url("brackets_colors.less"); + +// Default theme -- all UI styling comes from variables in a theme +// Themes can rely on variables defined above +@import url("brackets_theme_default.less"); + +// Include Codemirror styling overrides so that we can overrides proper values +// for the theme. If you need to customize the tree, you also include jsTree.less +// and even brackets_scrollbars.less +@import url("brackets_codemirror_override.less"); + + +/* + * Brackets Default Theme + * + * Defines all the variables that one can configure in a theme. This should + * contain all variables / mixins for UI styling that we want to be able to + * change in a theme. + * + * Throughout the rest of the LESS files we should _only_ use color + * variable names that are on the LHS of the list below. So, if we + * need a new color for some UI element, we should add a variable + * in this file. + */ + +/* Overall Colors */ +@background: #1d1f21; +@current-line: #000; +@foreground: #c5c8c6; +@comment: #767676; +@orange: #d89333; +@blue: #6c9ef8; +@purple: #b77fdb; +@green: #85a300; +@red: #dc322f; +@aqua: #8abeb7; +@violet: #6c71c4; +@yellow: #f0c674; +@pink: #d85896; + +/* + * Background colors are ordered from least "intense" to most "intense" + * So, if the background is light, then @background-color-3 should be + * lightest, -2 should be darker, and -1 should be darker still. + * + * The opposite is true for a dark background -- background-color-3 should be + * the darkest, -2 should be lighter, and -1 should be lighter still. + */ +@background-color-1: lighten(@background, @bc-color-step-size*2); +@background-color-2: lighten(@background, @bc-color-step-size); +@background-color-3: @background; + +/* + * @content-color-stronger should be should be further away from the + * background color than @content-color (i.e. more contrasty). + * + * @content-color-weaker should be closer to the background color + * than @content-color (i.e. less contrasty). + */ +@content-color: @foreground; +@content-color-stronger: lighten(@foreground, @bc-color-step-size); +@content-color-weaker: darken(@foreground, @bc-color-step-size); + +/* Code Styling */ + +/* code accent colors */ +@accent-keyword: @blue; +@accent-atom: @orange; +@accent-number: @green; +@accent-def: @purple; +@accent-variable: @foreground; +@accent-variable-2: @foreground; +@accent-variable-3: @foreground; +@accent-property: @purple; +@accent-operator: @foreground; +@accent-comment: @comment; +@accent-string: @orange; +@accent-string-2: @orange; +@accent-meta: @foreground; +@accent-error: @red; +@accent-qualifier: @blue; +@accent-builtin: @blue; +@accent-bracket: @foreground; +@accent-tag: @blue; +@accent-attribute: @green; +@accent-header: @pink; +@accent-quote: @blue; +@accent-hr: @orange; +@accent-link: @purple; +@accent-rangeinfo: @violet; +@accent-minus: @red; +@accent-plus: @green; + +/* inline editor colors */ +@inline-background-color-1: lighten(@background, @bc-color-step-size); +@inline-background-color-2: lighten(@background, @bc-color-step-size*2); +@inline-background-color-3: rgba(0,0,0,0); + +@inline-color-1: darken(@foreground, @bc-color-step-size*2); +@inline-color-2: darken(@foreground, @bc-color-step-size); +@inline-color-3: @background; + +/* Selection colors */ +@selection-color-focused: #133c53; +@selection-color-unfocused: #424242; + +@activeline-bgcolor: rgb(49, 49, 49); +@activeline-number-bgcolor: rgb(0, 0, 0); +@matching-bracket: rgba(100, 189, 138, 0.43); + +/* Status Bar Colors */ +@status-bar-background-color: #1C1C1E; +@status-bar-border: rgba(255, 255, 255, 0.08); +@status-bar-text-color: #a1a1a1; +@status-bar-quiet-text-color: #666; + + +/* Extra CSS */ + +.code-cursor() { + // to make a block cursor, use something like this: + // background-color: fadeout(@blue, 50%); + // border: none !important; + + // to make an I-cursor, use something like this: + border-left: 1px solid @content-color !important; +} + +.CodeMirror .CodeMirror-gutters { + // gutter border: + // border-right: 1px solid rgba(255, 255, 255, 0.03); +} + +.CodeMirror-focused .CodeMirror-activeline .CodeMirror-gutter-elt { + color: @comment; +} + +#status-bar { + background: @status-bar-background-color; + border-top: 1px solid @status-bar-border; + color:@status-bar-text-color; +} + +#status-info { + color: @status-bar-text-color; +} + +#status-file { + color: @status-bar-quiet-text-color; +} + +#status-indicators { + background: @status-bar-background-color; + color: @status-bar-text-color; + + > div { + border-left: 1px solid @status-bar-border; + } +} diff --git a/src/extensions/default/ThorDarkTheme/package.json b/src/extensions/default/ThorDarkTheme/package.json new file mode 100644 index 00000000000..2f6ddee63a4 --- /dev/null +++ b/src/extensions/default/ThorDarkTheme/package.json @@ -0,0 +1,12 @@ +{ + "title": "Thor Dark", + "name": "thor-dark-theme", + "description": "Awesome dark theme for Brackets", + "version": "0.0.1", + "author": "Miguel Castillo ", + "engines": { + "brackets": ">=0.39.0" + }, + "theme": "main.less", + "screenshot": "screenshot.png" +} diff --git a/src/extensions/default/ThorLightTheme/main.less b/src/extensions/default/ThorLightTheme/main.less new file mode 100644 index 00000000000..b05bbeb2101 --- /dev/null +++ b/src/extensions/default/ThorLightTheme/main.less @@ -0,0 +1,44 @@ +// Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// Brackets-specific default font and color definitions +@import url("brackets_colors.less"); + +// Default theme -- all UI styling comes from variables in a theme +// Themes can rely on variables defined above +@import url("brackets_theme_default.less"); + +// Include Codemirror styling overrides so that we can overrides proper values +// for the theme. If you need to customize the tree, you also include jsTree.less +// and even brackets_scrollbars.less +@import url("brackets_codemirror_override.less"); + +/* + * Brackets Default Theme + * + * Defines all the variables that one can configure in a theme. This should + * contain all variables / mixins for UI styling that we want to be able to + * change in a theme. + * + * Throughout the rest of the LESS files we should _only_ use color + * variable names that are on the LHS of the list below. So, if we + * need a new color for some UI element, we should add a variable + * in this file. + */ diff --git a/src/extensions/default/ThorLightTheme/package.json b/src/extensions/default/ThorLightTheme/package.json new file mode 100644 index 00000000000..fe4dc603e18 --- /dev/null +++ b/src/extensions/default/ThorLightTheme/package.json @@ -0,0 +1,12 @@ +{ + "title": "Thor Light", + "name": "thor-light-theme", + "description": "Awesome light theme for Brackets", + "version": "0.0.1", + "author": "Miguel Castillo ", + "engines": { + "brackets": ">=0.39.0" + }, + "theme": "main.less", + "screenshot": "screenshot.png" +} diff --git a/src/htmlContent/themes-settings.html b/src/htmlContent/themes-settings.html new file mode 100644 index 00000000000..6246aa50629 --- /dev/null +++ b/src/htmlContent/themes-settings.html @@ -0,0 +1,56 @@ + diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 59c168e377a..64703ff6558 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -353,6 +353,7 @@ define({ "CMD_SORT_WORKINGSET_BY_NAME" : "Sort by Name", "CMD_SORT_WORKINGSET_BY_TYPE" : "Sort by Type", "CMD_SORT_WORKINGSET_AUTO" : "Automatic Sort", + "CMD_THEMES" : "Themes", // Navigate menu Commands "NAVIGATE_MENU" : "Navigate", @@ -415,6 +416,14 @@ define({ "BASEURL_ERROR_INVALID_CHAR" : "Special characters like '{0}' must be %-encoded.", "BASEURL_ERROR_UNKNOWN_ERROR" : "Unknown error parsing Base URL", + // Strings for themes-settings.html and themes-general.html + "CURRENT_THEME" : "Current Theme", + "CUSTOM_SCROLLBARS" : "Custom Scrollbars", + "FONT_SIZE" : "Font Size", + "FONT_FAMILY" : "Font Family", + "LINE_HEIGHT" : "Line Height", + "GENERAL" : "General", + // CSS Quick Edit "BUTTON_NEW_RULE" : "New Rule", diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 7337af5b76f..a35ef1f0a10 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -219,7 +219,6 @@ a, img { #status-overwrite { transition: background-color 3s; background-color: rgba(120, 178, 242, 0); - color: rgb(69, 69, 69); cursor: pointer; } @@ -1626,3 +1625,16 @@ label input { font-weight: 900; font-size: 1.01em; } + +.theme-settings td { + padding: 2px; +} + +.theme-settings .modal-body { + min-height: 300px; + max-height: 300px; +} + +.theme-settings .label { + padding: 4px !important; +} diff --git a/src/styles/brackets_codemirror_override.less b/src/styles/brackets_codemirror_override.less index 8b83445b33e..e44d31c20a0 100644 --- a/src/styles/brackets_codemirror_override.less +++ b/src/styles/brackets_codemirror_override.less @@ -76,111 +76,109 @@ } -.cm-s-default { - span.cm-keyword {color: @accent-keyword;} - span.cm-atom {color: @accent-atom;} - span.cm-number {color: @accent-number;} - span.cm-def {color: @accent-def;} - span.cm-variable {color: @accent-variable;} - span.cm-variable-2 {color: @accent-variable-2;} - span.cm-variable-3 {color: @accent-variable-3;} - span.cm-property {color: @accent-property;} - span.cm-operator {color: @accent-operator;} - span.cm-comment {color: @accent-comment;} - span.cm-string {color: @accent-string;} - span.cm-string-2 {color: @accent-string-2;} - span.cm-meta {color: @accent-meta;} - span.cm-error {color: @accent-error;} - span.cm-qualifier {color: @accent-qualifier;} - span.cm-builtin {color: @accent-builtin;} - span.cm-bracket {color: @accent-bracket;} - span.cm-tag {color: @accent-tag;} - span.cm-attribute {color: @accent-attribute;} - span.cm-header {color: @accent-header;} - span.cm-quote {color: @accent-quote;} - span.cm-hr {color: @accent-hr;} - span.cm-link {color: @accent-link; text-decoration: none;} - span.cm-rangeinfo {color: @accent-rangeinfo;} - span.cm-minus {color: @accent-minus;} - span.cm-plus {color: @accent-plus;} - - span.CodeMirror-matchingbracket {color: @accent-bracket !important; background-color: @matching-bracket;} - span.CodeMirror-nonmatchingbracket {color: @accent-bracket !important;} +span.cm-keyword {color: @accent-keyword;} +span.cm-atom {color: @accent-atom;} +span.cm-number {color: @accent-number;} +span.cm-def {color: @accent-def;} +span.cm-variable {color: @accent-variable;} +span.cm-variable-2 {color: @accent-variable-2;} +span.cm-variable-3 {color: @accent-variable-3;} +span.cm-property {color: @accent-property;} +span.cm-operator {color: @accent-operator;} +span.cm-comment {color: @accent-comment;} +span.cm-string {color: @accent-string;} +span.cm-string-2 {color: @accent-string-2;} +span.cm-meta {color: @accent-meta;} +span.cm-error {color: @accent-error;} +span.cm-qualifier {color: @accent-qualifier;} +span.cm-builtin {color: @accent-builtin;} +span.cm-bracket {color: @accent-bracket;} +span.cm-tag {color: @accent-tag;} +span.cm-attribute {color: @accent-attribute;} +span.cm-header {color: @accent-header;} +span.cm-quote {color: @accent-quote;} +span.cm-hr {color: @accent-hr;} +span.cm-link {color: @accent-link; text-decoration: none;} +span.cm-rangeinfo {color: @accent-rangeinfo;} +span.cm-minus {color: @accent-minus;} +span.cm-plus {color: @accent-plus;} - .CodeMirror-cursors { - .CodeMirror-cursor { - .code-cursor(); - } +span.CodeMirror-matchingbracket {color: @accent-bracket !important; background-color: @matching-bracket;} +span.CodeMirror-nonmatchingbracket {color: @accent-bracket !important;} - /* Ensure the cursor shows up in front of code spans with a background color - * (e.g. matchingbracket). - */ - z-index: 3; +.CodeMirror-cursors { + .CodeMirror-cursor { + .code-cursor(); } - .CodeMirror-lines { - padding: @code-padding 0; - - /* This is necessary for issue #2780. The logic for closing dropdowns depends on "click" events. Now - * that each line has a separate div element, there is a good chance that mouseDown and mouseUp events - * occur on different elements, which means a click event will not be sent. By disabling pointer events here, - * we are guaranteed that the mouse event will be captured by our parent div, and click events will - * be dispatched. - */ - pointer-events: none; - } - - .CodeMirror-linewidget { - /* Re-enable pointer events for line widget. Pointer events are disabled for "CodeMirror-lines", which is the - * parent of line widgets, so they need to be explicitly re-enabled here in order for selection to work. */ - pointer-events: auto; - } - - .CodeMirror-gutters { - background-color: @background-color-3; - border-right: none; + /* Ensure the cursor shows up in front of code spans with a background color + * (e.g. matchingbracket). + */ + z-index: 3; +} + +.CodeMirror-lines { + padding: @code-padding 0; + + /* This is necessary for issue #2780. The logic for closing dropdowns depends on "click" events. Now + * that each line has a separate div element, there is a good chance that mouseDown and mouseUp events + * occur on different elements, which means a click event will not be sent. By disabling pointer events here, + * we are guaranteed that the mouse event will be captured by our parent div, and click events will + * be dispatched. + */ + pointer-events: none; +} + +.CodeMirror-linewidget { + /* Re-enable pointer events for line widget. Pointer events are disabled for "CodeMirror-lines", which is the + * parent of line widgets, so they need to be explicitly re-enabled here in order for selection to work. */ + pointer-events: auto; +} + +.CodeMirror-gutters { + background-color: @background-color-3; + border-right: none; +} + +.platform-mac { + .CodeMirror-scrollbar-filler { + background-image: url(images/scrollbar-mac-corner.png); } - - .platform-mac & { - .CodeMirror-scrollbar-filler { - background-image: url(images/scrollbar-mac-corner.png); - } - .CodeMirror-gutter-filler { - background-image: url(images/scrollbar-mac-bg.png); - } + .CodeMirror-gutter-filler { + background-image: url(images/scrollbar-mac-bg.png); } - .platform-win & { - .CodeMirror-scrollbar-filler, - .CodeMirror-gutter-filler { - background-color: @win-scrollbar-track; - height: 12px !important; - } - .CodeMirror-scrollbar-filler { - width: 12px !important; - } +} +.platform-win { + .CodeMirror-scrollbar-filler, + .CodeMirror-gutter-filler { + background-color: @win-scrollbar-track; + height: 12px !important; } - .platform-linux & { - .CodeMirror-scrollbar-filler, - .CodeMirror-gutter-filler { - background-color: @background-color-3; - height: 12px !important; - } - .CodeMirror-scrollbar-filler { - width: 12px !important; - } + .CodeMirror-scrollbar-filler { + width: 12px !important; } - - .CodeMirror-linenumber { - color: @accent-comment; - min-width: 2.5em; - /*font-size: 0.9em;*/ /* restore after SourceCodePro font fix? */ - padding: 0 @code-padding 0 10px; /* left padding for project panel selection triangle */ +} +.platform-linux { + .CodeMirror-scrollbar-filler, + .CodeMirror-gutter-filler { + background-color: @background-color-3; + height: 12px !important; } - &.CodeMirror-focused .CodeMirror-selected { - background: @selection-color-focused; + .CodeMirror-scrollbar-filler { + width: 12px !important; } } +.CodeMirror-linenumber { + color: @accent-comment; + min-width: 2.5em; + /*font-size: 0.9em;*/ /* restore after SourceCodePro font fix? */ + padding: 0 @code-padding 0 10px; /* left padding for project panel selection triangle */ +} +.CodeMirror-focused .CodeMirror-selected { + background: @selection-color-focused; +} + /* CodeMirror's use of descendant selectors for certain styling causes problems when editors are nested because, for items in the inner editor, the left-hand clause in the selector will now @@ -216,10 +214,10 @@ visibility: visible; } - .CodeMirror.cm-s-default .CodeMirror-selected { + .CodeMirror .CodeMirror-selected { background: @selection-color-unfocused; } - .CodeMirror.cm-s-default.CodeMirror-focused .CodeMirror-selected { + .CodeMirror.CodeMirror-focused .CodeMirror-selected { background: @selection-color-focused; } .CodeMirror .CodeMirror-gutters { diff --git a/src/themes/FontCommandsManager.js b/src/themes/FontCommandsManager.js new file mode 100644 index 00000000000..9df4057cabb --- /dev/null +++ b/src/themes/FontCommandsManager.js @@ -0,0 +1,37 @@ +/** + * Brackets Themes Copyright (c) 2014 Miguel Castillo. + * @author Brad Gearon + * + * Licensed under MIT + */ + +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ +/*global $, define, require */ + +define(function (require, exports, module) { + "use strict"; + + var ThemeSettings = require("view/ThemeSettings"), + ViewCommandHandlers = require("view/ViewCommandHandlers"), + PreferencesManager = require("preferences/PreferencesManager"), + prefs = PreferencesManager.getExtensionPrefs("brackets-themes"); + + function updateThemeFontSize(evt, adjustment, fontSize /*, lineHeight*/) { + prefs.set("fontSize", fontSize); + } + + function updateBracketsFontSize() { + var fontSize = prefs.get("fontSize"), + fontSizeNumeric = Number(fontSize.replace(/px|em/, "")), + fontSizeOffset = fontSizeNumeric - ThemeSettings._defaults.fontSize; + + if (!isNaN(fontSizeOffset)) { + PreferencesManager.setViewState("fontSizeAdjustment", fontSizeOffset); + PreferencesManager.setViewState("fontSizeStyle", fontSize); + } + } + + $(ViewCommandHandlers).on("fontSizeChange", updateThemeFontSize); + prefs.on("change", "fontSize", updateBracketsFontSize); + updateBracketsFontSize(); +}); diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index 0434f9ce01c..95d06f880cf 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -39,11 +39,12 @@ define(function (require, exports, module) { require("utils/Global"); - var _ = require("thirdparty/lodash"), - FileSystem = require("filesystem/FileSystem"), - FileUtils = require("file/FileUtils"), - Async = require("utils/Async"), - UrlParams = require("utils/UrlParams").UrlParams; + var _ = require("thirdparty/lodash"), + FileSystem = require("filesystem/FileSystem"), + FileUtils = require("file/FileUtils"), + Async = require("utils/Async"), + ExtensionUtils = require("utils/ExtensionUtils"), + UrlParams = require("utils/UrlParams").UrlParams; // default async initExtension timeout var INIT_EXTENSION_TIMEOUT = 10000; @@ -156,6 +157,38 @@ define(function (require, exports, module) { * (Note: if extension contains a JS syntax error, promise is resolved not rejected). */ function loadExtension(name, config, entryPoint) { + 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); + + return promise + .then(function(metadata) { + // No special handling for themes... Let the promise propagate into the ExtensionManager + if (metadata && "theme" in metadata) { + return; + } + + return loadExtensionModule(name, config, entryPoint); + }) + .then(function () { + $(exports).triggerHandler("load", config.baseUrl); + }, function (err) { + $(exports).triggerHandler("loadFailed", config.baseUrl); + }); + } + + /** + * Loads the extension module that lives at baseUrl into its own Require.js context + * + * @param {!string} name, used to identify the extension + * @param {!{baseUrl: string}} config object with baseUrl property containing absolute path of extension + * @param {!string} entryPoint, name of the main js file to load + * @return {!$.Promise} A promise object that is resolved when the extension is loaded, or rejected + * if the extension fails to load or throws an exception immediately when loaded. + * (Note: if extension contains a JS syntax error, promise is resolved not rejected). + */ + function loadExtensionModule(name, config, entryPoint) { var extensionConfig = { context: name, baseUrl: config.baseUrl, @@ -215,12 +248,8 @@ define(function (require, exports, module) { // This type has a useful stack (exception thrown by ext code or info on bad getModule() call) console.log(err.stack); } - }).then(function () { - $(exports).triggerHandler("load", config.baseUrl); - }, function (err) { - $(exports).triggerHandler("loadFailed", config.baseUrl); }); - + return promise; } diff --git a/src/utils/ExtensionUtils.js b/src/utils/ExtensionUtils.js index a7e33ca3f1a..7e08c40e950 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 FileSystem = require("filesystem/FileSystem"), + FileUtils = require("file/FileUtils"); /** * Appends a