diff --git a/src/htmlContent/themes-settings.html b/src/htmlContent/themes-settings.html
index 6aec089b573..029d4782459 100644
--- a/src/htmlContent/themes-settings.html
+++ b/src/htmlContent/themes-settings.html
@@ -9,7 +9,7 @@
Themes Settings
diff --git a/src/view/ThemeManager.js b/src/view/ThemeManager.js
index afbf3821e31..6f64c63a1cd 100644
--- a/src/view/ThemeManager.js
+++ b/src/view/ThemeManager.js
@@ -42,6 +42,7 @@ define(function (require, exports, module) {
prefs = PreferencesManager.getExtensionPrefs("brackets-themes");
var loadedThemes = {},
+ currentTheme = null,
defaultTheme = "thor-light-theme",
commentRegex = /\/\*([\s\S]*?)\*\//mg,
scrollbarsRegex = /::-webkit-scrollbar(?:[\s\S]*?)\{(?:[\s\S]*?)\}/mg,
@@ -73,7 +74,7 @@ define(function (require, exports, module) {
* dialog, and to properly add a theme into CodeMirror along with the rest of brackets.
*
* @param {File} file for the theme
- * @param {{name: string, className: string, title: string}} options to configure different
+ * @param {{name: string, title: string}} options to configure different
* properties in the theme
*/
function Theme(file, options) {
@@ -86,9 +87,9 @@ define(function (require, exports, module) {
// is a theme1.css and a theme1.less that are entirely different themes...
this.file = file;
- this.name = options.name || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-');
- this.className = options.className || "theme-" + this.name;
- this.displayName = options.title || toDisplayName(fileName);
+ this.name = options.name || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-');
+ this.displayName = options.title || toDisplayName(fileName);
+ this.dark = options.dark === true;
}
@@ -156,7 +157,7 @@ define(function (require, exports, module) {
filename: fixPath(theme.file._path)
});
- parser.parse("." + theme.className + "{" + content + "}", function (err, tree) {
+ parser.parse("#editor-holder {" + content + "}", function (err, tree) {
if (err) {
deferred.reject(err);
} else {
@@ -197,79 +198,83 @@ define(function (require, exports, module) {
/**
- * @private
- * Get all current theme objects
+ * Get all current theme objects that are loaded in the editor.
*
* @return {Array.} collection of the current theme instances
*/
- function getCurrentThemes() {
- return _.map(prefs.get("themes").slice(0), function (item) {
- return loadedThemes[item] || loadedThemes[defaultTheme];
- });
+ function getCurrentTheme() {
+ if (!currentTheme) {
+ currentTheme = loadedThemes[prefs.get("theme")] || loadedThemes[defaultTheme];
+ }
+
+ return currentTheme;
}
/**
- * Provides quick access to all available themes
+ * Gets all available themes that can be loaded in the editor.
* @return {Array.} collection of all available themes
*/
function getAllThemes() {
- return _.map(loadedThemes, function (item) {
- return item;
+ return loadedThemes.map(function (theme) {
+ return theme;
});
}
/**
* @private
- * Loads all current themes
+ * Process and load the current theme into the editor
*
* @return {$.Promise} promise object resolved with the theme object and all
* corresponding new css/less and scrollbar information
*/
- function loadCurrentThemes() {
- var pendingThemes = _.map(getCurrentThemes(), function (theme) {
-
- return theme && FileUtils.readAsText(theme.file)
- .then(function (content) {
- var result = extractScrollbars(content);
- theme.scrollbar = result.scrollbar;
- return result.content;
- })
- .then(function (content) {
- return lessifyTheme(content, theme);
- })
- .then(function (style) {
- return ExtensionUtils.addEmbeddedStyleSheet(style);
- })
- .then(function (styleNode) {
- // Remove after the style has been applied to avoid weird flashes
- if (theme.css) {
- $(theme.css).remove();
- }
+ function loadCurrentTheme() {
+ var theme = getCurrentTheme();
+
+ var pending = theme && FileUtils.readAsText(theme.file)
+ .then(function (content) {
+ var result = extractScrollbars(content);
+ theme.scrollbar = result.scrollbar;
+ return result.content;
+ })
+ .then(function (content) {
+ return lessifyTheme(content, theme);
+ })
+ .then(function (style) {
+ return ExtensionUtils.addEmbeddedStyleSheet(style);
+ })
+ .then(function (styleNode) {
+ // Remove after the style has been applied to avoid weird flashes
+ if (theme.css) {
+ $(theme.css).remove();
+ }
- theme.css = styleNode;
- return theme;
- });
- });
+ theme.css = styleNode;
+ return theme;
+ });
- return $.when.apply(undefined, pendingThemes);
+ return $.when(pending);
}
/**
- * Refresh currently loaded themes
+ * Refresh current theme in the editor
*
- * @param {boolean} force is to force reload the current themes
+ * @param {boolean} force is to force reload the current theme
*/
function refresh(force) {
- $.when(force && loadCurrentThemes()).done(function () {
+ if (force) {
+ currentTheme = null;
+ }
+
+ $.when(force && loadCurrentTheme()).done(function () {
var editor = EditorManager.getActiveEditor();
if (!editor || !editor._codeMirror) {
return;
}
- var cm = editor._codeMirror;
+ var cm = editor._codeMirror;
ThemeView.setDocumentMode(cm);
ThemeView.updateThemes(cm);
});
@@ -285,9 +290,9 @@ define(function (require, exports, module) {
* @return {$.Promise} promise object resolved with the theme to be loaded from fileName
*/
function loadFile(fileName, options) {
- var deferred = new $.Deferred(),
- file = FileSystem.getFileForPath(fileName),
- currentThemes = (prefs.get("themes") || []);
+ var deferred = new $.Deferred(),
+ file = FileSystem.getFileForPath(fileName),
+ currentThemeName = prefs.get("theme");
file.exists(function (err, exists) {
var theme;
@@ -298,9 +303,9 @@ define(function (require, exports, module) {
ThemeSettings._setThemes(loadedThemes);
// For themes that are loaded after ThemeManager has been loaded,
- // we should check if it's the current theme. It is, then we just
+ // we should check if it's the current theme. If it is, then we just
// load it.
- if (currentThemes.indexOf(theme.name) !== -1) {
+ if (currentThemeName === theme.name) {
refresh(true);
}
@@ -317,11 +322,11 @@ define(function (require, exports, module) {
/**
* Loads a theme from an extension package.
*
- * @param {Object} themePackage is a package for the theme to be loaded.
+ * @param {Object} themePackage is a package from the extension manager for the theme to be loaded.
* @return {$.Promise} promise object resolved with the theme to be loaded from the pacakge
*/
function loadPackage(themePackage) {
- var fileName = themePackage.path + "/" + themePackage.metadata.theme;
+ var fileName = themePackage.path + "/" + themePackage.metadata.theme.file;
return loadFile(fileName, themePackage.metadata);
}
@@ -365,31 +370,23 @@ define(function (require, exports, module) {
}
}
- function loadThemesFiles(themes) {
+ function loadThemeFiles(theme) {
// Iterate through each name in the themes and make them theme objects
- var deferred = _.map(themes.files, function (themeFile) {
- return loadFile(themes.path + "/" + themeFile);
+ var deferred = theme.files.forEach(function (themeFile) {
+ return loadFile(theme.path + "/" + themeFile);
});
return $.when.apply(undefined, deferred);
}
FileSystem.getDirectoryForPath(path).getContents(readContent);
- return result.then(loadThemesFiles);
+ return result.then(loadThemeFiles);
}
- prefs.on("change", "themes", function () {
- refresh(true);
- ThemeView.updateScrollbars(getCurrentThemes()[0]);
-
- // Expose event for theme changes
- $(exports).trigger("themeChange", getCurrentThemes());
- });
-
prefs.on("change", "customScrollbars", function () {
refresh();
- ThemeView.updateScrollbars(getCurrentThemes()[0]);
+ ThemeView.updateScrollbars(getCurrentTheme());
});
prefs.on("change", "fontSize", function () {
@@ -421,16 +418,44 @@ define(function (require, exports, module) {
refresh();
});
- refresh(true);
+
+ // We need to postpone setting the prefs on change event handler for themes
+ // to prevent flickering
+ AppInit.appReady(function () {
+ prefs.on("change", "theme", function () {
+ // Save the theme that's about to be replaced to allow for some transition to prevent flicker
+ var oldTheme = getCurrentTheme();
+
+ // Refresh editor with the new theme
+ refresh(true);
+
+ // Process the scrollbars for the editor
+ ThemeView.updateScrollbars(getCurrentTheme());
+
+ // Expose event for theme changes
+ $(exports).trigger("themeChange", getCurrentTheme());
+
+ // Delay removing the old style element to avoid flashes when transitioning between themes
+ if (oldTheme) {
+ setTimeout(function (theme) {
+ if (theme.css) {
+ $(theme.css).remove();
+ }
+ }, 0, oldTheme);
+ }
+ });
+ });
+
+
ThemeView.updateFonts();
ThemeView.updateScrollbars();
- exports.refresh = refresh;
- exports.loadFile = loadFile;
- exports.loadPackage = loadPackage;
- exports.loadDirectory = loadDirectory;
- exports.getCurrentThemes = getCurrentThemes;
- exports.getAllThemes = getAllThemes;
+ exports.refresh = refresh;
+ exports.loadFile = loadFile;
+ exports.loadPackage = loadPackage;
+ exports.loadDirectory = loadDirectory;
+ exports.getCurrentTheme = getCurrentTheme;
+ exports.getAllThemes = getAllThemes;
// Exposed for testing purposes
exports._toDisplayName = toDisplayName;
diff --git a/src/view/ThemeSettings.js b/src/view/ThemeSettings.js
index e11c4f15d61..0688e413710 100644
--- a/src/view/ThemeSettings.js
+++ b/src/view/ThemeSettings.js
@@ -49,7 +49,7 @@ define(function (require, exports, module) {
"lineHeight": 1.25,
"fontFamily": "'SourceCodePro-Medium', MS ゴシック, 'MS Gothic', monospace",
"customScrollbars": true,
- "themes": ["thor-light-theme"]
+ "theme": "thor-light-theme"
};
@@ -81,11 +81,9 @@ define(function (require, exports, module) {
var $template = $(Mustache.render(template, {"settings": currentSettings, "themes": themes, "Strings": Strings}));
// Select the correct theme.
- _.each(currentSettings.themes, function (item) {
- $template
- .find("[value='" + item + "']")
- .attr("selected", "selected");
- });
+ $template
+ .find("[value='" + currentSettings.theme + "']")
+ .attr("selected", "selected");
$template
.find("[data-toggle=tab].default")
@@ -103,16 +101,11 @@ define(function (require, exports, module) {
newSettings[attr] = $target.val();
})
.on("change", function () {
- var items;
var $target = $(":selected", this);
var attr = $target.attr("data-target");
if (attr) {
- items = $target.map(function (i, item) {
- return $(item).val();
- });
-
- prefs.set(attr, items.toArray());
+ prefs.set(attr, $target.val());
}
});
@@ -123,7 +116,7 @@ define(function (require, exports, module) {
});
} else if (id === "cancel") {
// Make sure we revert any changes to theme selection
- prefs.set("themes", currentSettings.themes);
+ prefs.set("theme", currentSettings.theme);
}
});
}
@@ -140,7 +133,7 @@ define(function (require, exports, module) {
* Restores themes to factory settings.
*/
function restore() {
- prefs.set("themes", defaults.themes);
+ prefs.set("theme", defaults.theme);
prefs.set("fontSize", defaults.fontSize + "px");
prefs.set("lineHeight", defaults.lineHeight);
prefs.set("fontFamily", defaults.fontFamily);
@@ -148,7 +141,7 @@ define(function (require, exports, module) {
}
- prefs.definePreference("themes", "array", defaults.themes);
+ prefs.definePreference("theme", "string", defaults.theme);
prefs.definePreference("fontSize", "string", defaults.fontSize + "px");
prefs.definePreference("lineHeight", "number", defaults.lineHeight);
prefs.definePreference("fontFamily", "string", defaults.fontFamily);
diff --git a/src/view/ThemeView.js b/src/view/ThemeView.js
index 26611587279..75f8fc13d5b 100644
--- a/src/view/ThemeView.js
+++ b/src/view/ThemeView.js
@@ -49,24 +49,6 @@ define(function (require, exports, module) {
}
- /**
- * Add theme class to the document to add proper theme styling scoping.
- * New class is added, old class is removed. This basically allows
- * themeSettings to get nicely cleaned up from the DOM.
- */
- function setDocumentTheme() {
- var newThemes = (prefs.get("themes") || []);
- var oldThemes = (currentThemes || []).slice(0);
- currentThemes = newThemes.slice(0);
-
- // We gotta prefix theme names with "theme" because themes that start with a number
- // will not render correctly. Class names that start with a number are invalid
- newThemes = _.map(newThemes, function (theme) { return "theme-" + theme; }).join(" ");
- oldThemes = _.map(oldThemes, function (theme) { return "theme-" + theme; }).join(" ");
- $("html").removeClass(oldThemes).addClass(newThemes);
- }
-
-
function updateLineHeight() {
clearFonts();
var value = prefs.get("lineHeight");
@@ -113,20 +95,17 @@ define(function (require, exports, module) {
* @param {CodeMirror} cm is the CodeMirror instance currently loaded
*/
function updateThemes(cm) {
- var newThemes = (prefs.get("themes") || []).join(" "),
- cmThemes = cm.getOption("theme").replace(/[\s]*/, " "); // Normalize themes string
+ var newTheme = prefs.get("theme"),
+ cmTheme = (cm.getOption("theme") || "").replace(/[\s]*/, ""); // Normalize themes string
// Check if the editor already has the theme applied...
- if (cmThemes === newThemes) {
+ if (cmTheme === newTheme) {
return;
}
// Setup current and further documents to get the new theme...
- CodeMirror.defaults.theme = newThemes;
- cm.setOption("theme", newThemes);
-
- // Make sure to update the document theme if a new theme is being set.
- setDocumentTheme();
+ CodeMirror.defaults.theme = newTheme;
+ cm.setOption("theme", newTheme);
}
@@ -139,7 +118,7 @@ define(function (require, exports, module) {
function setDocumentMode(cm) {
var mode = cm.getDoc().getMode();
var docMode = mode && (mode.helperType || mode.name);
- $("body").removeClass("doctype-" + currentDocMode).addClass("doctype-" + docMode);
+ $("#editor-holder .CodeMirror").removeClass("doctype-" + currentDocMode).addClass("doctype-" + docMode);
currentDocMode = docMode; // Update docMode
return docMode;
}