/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
release-1.13
src/view/ThemeManager.js
373 строки
13 KB
GABINJUNG
promsie -> promise (#13914)
10 янв 2018, 23:43
10 янв 2018, 23:43
9dc7c5a
Код
Авторство
О чём код?
/** * Brackets Themes Copyright (c) 2014 Miguel Castillo. * * 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. * */ /*jslint regexp: true */ /*global less */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), EditorManager = require("editor/EditorManager"), ExtensionUtils = require("utils/ExtensionUtils"), ThemeSettings = require("view/ThemeSettings"), ThemeView = require("view/ThemeView"), PreferencesManager = require("preferences/PreferencesManager"), prefs = PreferencesManager.getExtensionPrefs("themes"); var loadedThemes = {}, currentTheme = null, styleNode = $(ExtensionUtils.addEmbeddedStyleSheet("")), defaultTheme = "thor-light-theme", commentRegex = /\/\*([\s\S]*?)\*\//mg, scrollbarsRegex = /((?:[^}|,]*)::-webkit-scrollbar(?:[^{]*)[{](?:[^}]*?)[}])/mgi, stylesPath = FileUtils.getNativeBracketsDirectoryPath() + "/styles/"; /** * @private * Takes all dashes and converts them to white spaces. Then takes all first letters * and capitalizes them. * * @param {string} name is what needs to be procseed to generate a display name * @return {string} theme name properly formatted for display */ function toDisplayName(name) { var extIndex = name.lastIndexOf('.'); name = name.substring(0, extIndex !== -1 ? extIndex : undefined).replace(/-/g, ' '); return name.split(" ").map(function (part) { return part[0].toUpperCase() + part.substring(1); }).join(" "); } /** * @constructor * Theme contains all the essential bit to load a theme from disk, display a theme in the settings * dialog, and to properly add a theme into CodeMirror along with the rest of brackets. * * @param {File} file for the theme * @param {{name: string, title: string}} options to configure different * properties in the theme */ function Theme(file, options) { options = options || {}; var fileName = file.name; // If no options.name is provided, then we derive the name of the theme from whichever we find // first, the options.title or the filename. if (!options.name) { if (options.title) { options.name = options.title; } else { // Remove the file extension when the filename is used as the theme name. This is to // follow CodeMirror conventions where themes are just a CSS file and the filename // (without the extension) is used to build CSS rules. Also handle removing .min // in case the ".min" is part of the file name. options.name = FileUtils.getFilenameWithoutExtension(fileName).replace(/\.min$/, ""); } // We do a bit of string treatment here to make sure we generate theme names that can be // used as a CSS class name by CodeMirror. options.name = options.name.toLocaleLowerCase().replace(/[\W]/g, '-'); } this.file = file; this.name = options.name; this.displayName = options.title || toDisplayName(fileName); this.dark = options.theme !== undefined && options.theme.dark === true; this.addModeClass = options.theme !== undefined && options.theme.addModeClass === true; } /** * @private * Extracts the scrollbar text from the css/less content so that it can be treated * as a separate styling component that can be anabled/disabled independently from * the theme. * * @param {string} content is the css/less input string to be processed * @return {{content: string, scrollbar: Array.<string>}} content is the new css/less content * with the scrollbar rules extracted out and put in scrollbar */ function extractScrollbars(content) { var scrollbar = []; // Go through and extract out scrollbar customizations so that we can // enable/disable via settings. content = content .replace(scrollbarsRegex, function (match) { scrollbar.push(match); return ""; }); return { content: content, scrollbar: scrollbar }; } /** * @private * Function will process a string and figure out if it looks like window path with a * a drive. If that's the case, then we lower case everything. * --- NOTE: There is a bug in less that only checks for lowercase in order to handle * the rootPath configuration... Hopefully a PR will be coming their way soon. * * @param {string} path is a string to search for drive letters that need to be converted * to lower case. * * @return {string} Windows Drive letter in lowercase. */ function fixPath(path) { return path.replace(/^([A-Z]+:)?\//, function (match) { return match.toLocaleLowerCase(); }); } /** * @private * Takes the content of a file and feeds it through the less processor in order * to provide support for less files. * * @param {string} content is the css/less string to be processed * @param {Theme} theme is the object the css/less corresponds to * @return {$.Promise} promise with the processed css/less as the resolved value */ function lessifyTheme(content, theme) { var deferred = new $.Deferred(); less.render("#editor-holder {" + content + "\n}", { rootpath: fixPath(stylesPath), filename: fixPath(theme.file._path) }, function (err, tree) { if (err) { deferred.reject(err); } else { deferred.resolve(tree.css); } }); return deferred.promise(); } /** * @private * Will search all loaded themes for one the matches the file passed in * * @param {File} file is the search criteria * @return {Theme} theme that matches the file */ function getThemeByFile(file) { var path = file._path; return _.find(loadedThemes, function (item) { return item.file._path === path; }); } /** * Get current theme object that is loaded in the editor. * * @return {Theme} the current theme instance */ function getCurrentTheme() { if (!currentTheme) { currentTheme = loadedThemes[prefs.get("theme")] || loadedThemes[defaultTheme]; } return currentTheme; } /** * Gets all available themes * @return {Array.<Theme>} collection of all available themes */ function getAllThemes() { return _.map(loadedThemes, function (theme) { return theme; }); } /** * @private * 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 loadCurrentTheme() { var theme = getCurrentTheme(); var pending = theme && FileUtils.readAsText(theme.file) .then(function (lessContent) { return lessifyTheme(lessContent.replace(commentRegex, ""), theme); }) .then(function (content) { var result = extractScrollbars(content); theme.scrollbar = result.scrollbar; return result.content; }) .then(function (cssContent) { $("body").toggleClass("dark", theme.dark); styleNode.text(cssContent); return theme; }); return $.when(pending); } /** * Refresh current theme in the editor * * @param {boolean} force Forces a reload of the current theme. It reloads the theme file. */ function refresh(force) { if (force) { currentTheme = null; } $.when(force && loadCurrentTheme()).done(function () { var editor = EditorManager.getActiveEditor(); if (!editor || !editor._codeMirror) { return; } var cm = editor._codeMirror; ThemeView.updateThemes(cm); // currentTheme can be undefined, so watch out cm.setOption("addModeClass", !!(currentTheme && currentTheme.addModeClass)); }); } /** * Loads a theme from a file. * * @param {string} fileName is the full path to the file to be opened * @param {Object} options is an optional parameter to specify metadata * for the theme. * @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), currentThemeName = prefs.get("theme"); file.exists(function (err, exists) { var theme; if (exists) { theme = new Theme(file, options); loadedThemes[theme.name] = theme; ThemeSettings._setThemes(loadedThemes); // For themes that are loaded after ThemeManager has been loaded, // we should check if it's the current theme. If it is, then we just // load it. if (currentThemeName === theme.name) { refresh(true); } deferred.resolve(theme); } else if (err || !exists) { deferred.reject(err); } }); return deferred.promise(); } /** * Loads a theme from an extension package. * * @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.file; return loadFile(fileName, themePackage.metadata); } prefs.on("change", "theme", function () { // Make sure we don't reprocess a theme that's already loaded if (currentTheme && currentTheme.name === prefs.get("theme")) { return; } // 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()); }); prefs.on("change", "themeScrollbars", function () { refresh(); ThemeView.updateScrollbars(getCurrentTheme()); }); // Monitor file changes. If the file that has changed is actually the currently loaded // theme, then we just reload the theme. This allows to live edit the theme FileSystem.on("change", function (evt, file) { if (!file || file.isDirectory) { return; } if (getThemeByFile(file)) { refresh(true); } }); EditorManager.on("activeEditorChange", function () { refresh(); }); EventDispatcher.makeEventDispatcher(exports); exports.refresh = refresh; exports.loadFile = loadFile; exports.loadPackage = loadPackage; exports.getCurrentTheme = getCurrentTheme; exports.getAllThemes = getAllThemes; // Exposed for testing purposes exports._toDisplayName = toDisplayName; exports._extractScrollbars = extractScrollbars; });