mirror of
https://github.com/atom/atom.git
synced 2026-02-07 05:05:02 -05:00
Merge pull request #16025 from atom/decaf-theme-manager
☠☕ Decaffeinate `src/theme-manager.coffee`
This commit is contained in:
@@ -1,322 +0,0 @@
|
||||
path = require 'path'
|
||||
_ = require 'underscore-plus'
|
||||
{Emitter, CompositeDisposable} = require 'event-kit'
|
||||
{File} = require 'pathwatcher'
|
||||
fs = require 'fs-plus'
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
|
||||
# Extended: Handles loading and activating available themes.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.themes` global.
|
||||
module.exports =
|
||||
class ThemeManager
|
||||
constructor: ({@packageManager, @config, @styleManager, @notificationManager, @viewRegistry}) ->
|
||||
@emitter = new Emitter
|
||||
@styleSheetDisposablesBySourcePath = {}
|
||||
@lessCache = null
|
||||
@initialLoadComplete = false
|
||||
@packageManager.registerPackageActivator(this, ['theme'])
|
||||
@packageManager.onDidActivateInitialPackages =>
|
||||
@onDidChangeActiveThemes => @packageManager.reloadActivePackageStyleSheets()
|
||||
|
||||
initialize: ({@resourcePath, @configDirPath, @safeMode, devMode}) ->
|
||||
@lessSourcesByRelativeFilePath = null
|
||||
if devMode or typeof snapshotAuxiliaryData is 'undefined'
|
||||
@lessSourcesByRelativeFilePath = {}
|
||||
@importedFilePathsByRelativeImportPath = {}
|
||||
else
|
||||
@lessSourcesByRelativeFilePath = snapshotAuxiliaryData.lessSourcesByRelativeFilePath
|
||||
@importedFilePathsByRelativeImportPath = snapshotAuxiliaryData.importedFilePathsByRelativeImportPath
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke `callback` when style sheet changes associated with
|
||||
# updating the list of active themes have completed.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
onDidChangeActiveThemes: (callback) ->
|
||||
@emitter.on 'did-change-active-themes', callback
|
||||
|
||||
###
|
||||
Section: Accessing Available Themes
|
||||
###
|
||||
|
||||
getAvailableNames: ->
|
||||
# TODO: Maybe should change to list all the available themes out there?
|
||||
@getLoadedNames()
|
||||
|
||||
###
|
||||
Section: Accessing Loaded Themes
|
||||
###
|
||||
|
||||
# Public: Returns an {Array} of {String}s of all the loaded theme names.
|
||||
getLoadedThemeNames: ->
|
||||
theme.name for theme in @getLoadedThemes()
|
||||
|
||||
# Public: Returns an {Array} of all the loaded themes.
|
||||
getLoadedThemes: ->
|
||||
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
|
||||
|
||||
###
|
||||
Section: Accessing Active Themes
|
||||
###
|
||||
|
||||
# Public: Returns an {Array} of {String}s all the active theme names.
|
||||
getActiveThemeNames: ->
|
||||
theme.name for theme in @getActiveThemes()
|
||||
|
||||
# Public: Returns an {Array} of all the active themes.
|
||||
getActiveThemes: ->
|
||||
pack for pack in @packageManager.getActivePackages() when pack.isTheme()
|
||||
|
||||
activatePackages: -> @activateThemes()
|
||||
|
||||
###
|
||||
Section: Managing Enabled Themes
|
||||
###
|
||||
|
||||
warnForNonExistentThemes: ->
|
||||
themeNames = @config.get('core.themes') ? []
|
||||
themeNames = [themeNames] unless _.isArray(themeNames)
|
||||
for themeName in themeNames
|
||||
unless themeName and typeof themeName is 'string' and @packageManager.resolvePackagePath(themeName)
|
||||
console.warn("Enabled theme '#{themeName}' is not installed.")
|
||||
|
||||
# Public: Get the enabled theme names from the config.
|
||||
#
|
||||
# Returns an array of theme names in the order that they should be activated.
|
||||
getEnabledThemeNames: ->
|
||||
themeNames = @config.get('core.themes') ? []
|
||||
themeNames = [themeNames] unless _.isArray(themeNames)
|
||||
themeNames = themeNames.filter (themeName) =>
|
||||
if themeName and typeof themeName is 'string'
|
||||
return true if @packageManager.resolvePackagePath(themeName)
|
||||
false
|
||||
|
||||
# Use a built-in syntax and UI theme any time the configured themes are not
|
||||
# available.
|
||||
if themeNames.length < 2
|
||||
builtInThemeNames = [
|
||||
'atom-dark-syntax'
|
||||
'atom-dark-ui'
|
||||
'atom-light-syntax'
|
||||
'atom-light-ui'
|
||||
'base16-tomorrow-dark-theme'
|
||||
'base16-tomorrow-light-theme'
|
||||
'solarized-dark-syntax'
|
||||
'solarized-light-syntax'
|
||||
]
|
||||
themeNames = _.intersection(themeNames, builtInThemeNames)
|
||||
if themeNames.length is 0
|
||||
themeNames = ['atom-dark-syntax', 'atom-dark-ui']
|
||||
else if themeNames.length is 1
|
||||
if _.endsWith(themeNames[0], '-ui')
|
||||
themeNames.unshift('atom-dark-syntax')
|
||||
else
|
||||
themeNames.push('atom-dark-ui')
|
||||
|
||||
# Reverse so the first (top) theme is loaded after the others. We want
|
||||
# the first/top theme to override later themes in the stack.
|
||||
themeNames.reverse()
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Resolve and apply the stylesheet specified by the path.
|
||||
#
|
||||
# This supports both CSS and Less stylesheets.
|
||||
#
|
||||
# * `stylesheetPath` A {String} path to the stylesheet that can be an absolute
|
||||
# path or a relative path that will be resolved against the load path.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to remove the
|
||||
# required stylesheet.
|
||||
requireStylesheet: (stylesheetPath, priority, skipDeprecatedSelectorsTransformation) ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content, priority, skipDeprecatedSelectorsTransformation)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
unwatchUserStylesheet: ->
|
||||
@userStylesheetSubscriptions?.dispose()
|
||||
@userStylesheetSubscriptions = null
|
||||
@userStylesheetFile = null
|
||||
@userStyleSheetDisposable?.dispose()
|
||||
@userStyleSheetDisposable = null
|
||||
|
||||
loadUserStylesheet: ->
|
||||
@unwatchUserStylesheet()
|
||||
|
||||
userStylesheetPath = @styleManager.getUserStyleSheetPath()
|
||||
return unless fs.isFileSync(userStylesheetPath)
|
||||
|
||||
try
|
||||
@userStylesheetFile = new File(userStylesheetPath)
|
||||
@userStylesheetSubscriptions = new CompositeDisposable()
|
||||
reloadStylesheet = => @loadUserStylesheet()
|
||||
@userStylesheetSubscriptions.add(@userStylesheetFile.onDidChange(reloadStylesheet))
|
||||
@userStylesheetSubscriptions.add(@userStylesheetFile.onDidRename(reloadStylesheet))
|
||||
@userStylesheetSubscriptions.add(@userStylesheetFile.onDidDelete(reloadStylesheet))
|
||||
catch error
|
||||
message = """
|
||||
Unable to watch path: `#{path.basename(userStylesheetPath)}`. Make sure
|
||||
you have permissions to `#{userStylesheetPath}`.
|
||||
|
||||
On linux there are currently problems with watch sizes. See
|
||||
[this document][watches] for more info.
|
||||
[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
|
||||
"""
|
||||
@notificationManager.addError(message, dismissable: true)
|
||||
|
||||
try
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
|
||||
catch
|
||||
return
|
||||
|
||||
@userStyleSheetDisposable = @styleManager.addStyleSheet(userStylesheetContents, sourcePath: userStylesheetPath, priority: 2)
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom', -2, true)
|
||||
|
||||
stylesheetElementForId: (id) ->
|
||||
escapedId = id.replace(/\\/g, '\\\\')
|
||||
document.head.querySelector("atom-styles style[source-path=\"#{escapedId}\"]")
|
||||
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fs.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
loadStylesheet: (stylesheetPath, importFallbackVariables) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
else
|
||||
fs.readFileSync(stylesheetPath, 'utf8')
|
||||
|
||||
loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) ->
|
||||
@lessCache ?= new LessCompileCache({
|
||||
@resourcePath,
|
||||
@lessSourcesByRelativeFilePath,
|
||||
@importedFilePathsByRelativeImportPath,
|
||||
importPaths: @getImportPaths()
|
||||
})
|
||||
|
||||
try
|
||||
if importFallbackVariables
|
||||
baseVarImports = """
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";
|
||||
"""
|
||||
relativeFilePath = path.relative(@resourcePath, lessStylesheetPath)
|
||||
lessSource = @lessSourcesByRelativeFilePath[relativeFilePath]
|
||||
if lessSource?
|
||||
content = lessSource.content
|
||||
digest = lessSource.digest
|
||||
else
|
||||
content = baseVarImports + '\n' + fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
digest = null
|
||||
|
||||
@lessCache.cssForFile(lessStylesheetPath, content, digest)
|
||||
else
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch error
|
||||
error.less = true
|
||||
if error.line?
|
||||
# Adjust line numbers for import fallbacks
|
||||
error.line -= 2 if importFallbackVariables
|
||||
|
||||
message = "Error compiling Less stylesheet: `#{lessStylesheetPath}`"
|
||||
detail = """
|
||||
Line number: #{error.line}
|
||||
#{error.message}
|
||||
"""
|
||||
else
|
||||
message = "Error loading Less stylesheet: `#{lessStylesheetPath}`"
|
||||
detail = error.message
|
||||
|
||||
@notificationManager.addError(message, {detail, dismissable: true})
|
||||
throw error
|
||||
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
@styleSheetDisposablesBySourcePath[stylesheetPath]?.dispose()
|
||||
|
||||
applyStylesheet: (path, text, priority, skipDeprecatedSelectorsTransformation) ->
|
||||
@styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet(
|
||||
text,
|
||||
{
|
||||
priority,
|
||||
skipDeprecatedSelectorsTransformation,
|
||||
sourcePath: path
|
||||
}
|
||||
)
|
||||
|
||||
activateThemes: ->
|
||||
new Promise (resolve) =>
|
||||
# @config.observe runs the callback once, then on subsequent changes.
|
||||
@config.observe 'core.themes', =>
|
||||
@deactivateThemes().then =>
|
||||
@warnForNonExistentThemes()
|
||||
@refreshLessCache() # Update cache for packages in core.themes config
|
||||
|
||||
promises = []
|
||||
for themeName in @getEnabledThemeNames()
|
||||
if @packageManager.resolvePackagePath(themeName)
|
||||
promises.push(@packageManager.activatePackage(themeName))
|
||||
else
|
||||
console.warn("Failed to activate theme '#{themeName}' because it isn't installed.")
|
||||
|
||||
Promise.all(promises).then =>
|
||||
@addActiveThemeClasses()
|
||||
@refreshLessCache() # Update cache again now that @getActiveThemes() is populated
|
||||
@loadUserStylesheet()
|
||||
@reloadBaseStylesheets()
|
||||
@initialLoadComplete = true
|
||||
@emitter.emit 'did-change-active-themes'
|
||||
resolve()
|
||||
|
||||
deactivateThemes: ->
|
||||
@removeActiveThemeClasses()
|
||||
@unwatchUserStylesheet()
|
||||
results = @getActiveThemes().map((pack) => @packageManager.deactivatePackage(pack.name))
|
||||
Promise.all(results.filter((r) -> typeof r?.then is 'function'))
|
||||
|
||||
isInitialLoadComplete: -> @initialLoadComplete
|
||||
|
||||
addActiveThemeClasses: ->
|
||||
if workspaceElement = @viewRegistry.getView(@workspace)
|
||||
for pack in @getActiveThemes()
|
||||
workspaceElement.classList.add("theme-#{pack.name}")
|
||||
return
|
||||
|
||||
removeActiveThemeClasses: ->
|
||||
workspaceElement = @viewRegistry.getView(@workspace)
|
||||
for pack in @getActiveThemes()
|
||||
workspaceElement.classList.remove("theme-#{pack.name}")
|
||||
return
|
||||
|
||||
refreshLessCache: ->
|
||||
@lessCache?.setImportPaths(@getImportPaths())
|
||||
|
||||
getImportPaths: ->
|
||||
activeThemes = @getActiveThemes()
|
||||
if activeThemes.length > 0
|
||||
themePaths = (theme.getStylesheetsPath() for theme in activeThemes when theme)
|
||||
else
|
||||
themePaths = []
|
||||
for themeName in @getEnabledThemeNames()
|
||||
if themePath = @packageManager.resolvePackagePath(themeName)
|
||||
deprecatedPath = path.join(themePath, 'stylesheets')
|
||||
if fs.isDirectorySync(deprecatedPath)
|
||||
themePaths.push(deprecatedPath)
|
||||
else
|
||||
themePaths.push(path.join(themePath, 'styles'))
|
||||
|
||||
themePaths.filter (themePath) -> fs.isDirectorySync(themePath)
|
||||
401
src/theme-manager.js
Normal file
401
src/theme-manager.js
Normal file
@@ -0,0 +1,401 @@
|
||||
/* global snapshotAuxiliaryData */
|
||||
|
||||
const path = require('path')
|
||||
const _ = require('underscore-plus')
|
||||
const {Emitter, CompositeDisposable} = require('event-kit')
|
||||
const {File} = require('pathwatcher')
|
||||
const fs = require('fs-plus')
|
||||
const LessCompileCache = require('./less-compile-cache')
|
||||
|
||||
// Extended: Handles loading and activating available themes.
|
||||
//
|
||||
// An instance of this class is always available as the `atom.themes` global.
|
||||
module.exports =
|
||||
class ThemeManager {
|
||||
constructor ({packageManager, config, styleManager, notificationManager, viewRegistry}) {
|
||||
this.packageManager = packageManager
|
||||
this.config = config
|
||||
this.styleManager = styleManager
|
||||
this.notificationManager = notificationManager
|
||||
this.viewRegistry = viewRegistry
|
||||
this.emitter = new Emitter()
|
||||
this.styleSheetDisposablesBySourcePath = {}
|
||||
this.lessCache = null
|
||||
this.initialLoadComplete = false
|
||||
this.packageManager.registerPackageActivator(this, ['theme'])
|
||||
this.packageManager.onDidActivateInitialPackages(() => {
|
||||
this.onDidChangeActiveThemes(() => this.packageManager.reloadActivePackageStyleSheets())
|
||||
})
|
||||
}
|
||||
|
||||
initialize ({resourcePath, configDirPath, safeMode, devMode}) {
|
||||
this.resourcePath = resourcePath
|
||||
this.configDirPath = configDirPath
|
||||
this.safeMode = safeMode
|
||||
this.lessSourcesByRelativeFilePath = null
|
||||
if (devMode || (typeof snapshotAuxiliaryData === 'undefined')) {
|
||||
this.lessSourcesByRelativeFilePath = {}
|
||||
this.importedFilePathsByRelativeImportPath = {}
|
||||
} else {
|
||||
this.lessSourcesByRelativeFilePath = snapshotAuxiliaryData.lessSourcesByRelativeFilePath
|
||||
this.importedFilePathsByRelativeImportPath = snapshotAuxiliaryData.importedFilePathsByRelativeImportPath
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Event Subscription
|
||||
*/
|
||||
|
||||
// Essential: Invoke `callback` when style sheet changes associated with
|
||||
// updating the list of active themes have completed.
|
||||
//
|
||||
// * `callback` {Function}
|
||||
onDidChangeActiveThemes (callback) {
|
||||
return this.emitter.on('did-change-active-themes', callback)
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Accessing Available Themes
|
||||
*/
|
||||
|
||||
getAvailableNames () {
|
||||
// TODO: Maybe should change to list all the available themes out there?
|
||||
return this.getLoadedNames()
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Accessing Loaded Themes
|
||||
*/
|
||||
|
||||
// Public: Returns an {Array} of {String}s of all the loaded theme names.
|
||||
getLoadedThemeNames () {
|
||||
return this.getLoadedThemes().map((theme) => theme.name)
|
||||
}
|
||||
|
||||
// Public: Returns an {Array} of all the loaded themes.
|
||||
getLoadedThemes () {
|
||||
return this.packageManager.getLoadedPackages().filter((pack) => pack.isTheme())
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Accessing Active Themes
|
||||
*/
|
||||
|
||||
// Public: Returns an {Array} of {String}s of all the active theme names.
|
||||
getActiveThemeNames () {
|
||||
return this.getActiveThemes().map((theme) => theme.name)
|
||||
}
|
||||
|
||||
// Public: Returns an {Array} of all the active themes.
|
||||
getActiveThemes () {
|
||||
return this.packageManager.getActivePackages().filter((pack) => pack.isTheme())
|
||||
}
|
||||
|
||||
activatePackages () {
|
||||
return this.activateThemes()
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Managing Enabled Themes
|
||||
*/
|
||||
|
||||
warnForNonExistentThemes () {
|
||||
let themeNames = this.config.get('core.themes') || []
|
||||
if (!_.isArray(themeNames)) { themeNames = [themeNames] }
|
||||
for (let themeName of themeNames) {
|
||||
if (!themeName || (typeof themeName !== 'string') || !this.packageManager.resolvePackagePath(themeName)) {
|
||||
console.warn(`Enabled theme '${themeName}' is not installed.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public: Get the enabled theme names from the config.
|
||||
//
|
||||
// Returns an array of theme names in the order that they should be activated.
|
||||
getEnabledThemeNames () {
|
||||
let themeNames = this.config.get('core.themes') || []
|
||||
if (!_.isArray(themeNames)) { themeNames = [themeNames] }
|
||||
themeNames = themeNames.filter((themeName) =>
|
||||
(typeof themeName === 'string') && this.packageManager.resolvePackagePath(themeName)
|
||||
)
|
||||
|
||||
// Use a built-in syntax and UI theme any time the configured themes are not
|
||||
// available.
|
||||
if (themeNames.length < 2) {
|
||||
const builtInThemeNames = [
|
||||
'atom-dark-syntax',
|
||||
'atom-dark-ui',
|
||||
'atom-light-syntax',
|
||||
'atom-light-ui',
|
||||
'base16-tomorrow-dark-theme',
|
||||
'base16-tomorrow-light-theme',
|
||||
'solarized-dark-syntax',
|
||||
'solarized-light-syntax'
|
||||
]
|
||||
themeNames = _.intersection(themeNames, builtInThemeNames)
|
||||
if (themeNames.length === 0) {
|
||||
themeNames = ['atom-dark-syntax', 'atom-dark-ui']
|
||||
} else if (themeNames.length === 1) {
|
||||
if (_.endsWith(themeNames[0], '-ui')) {
|
||||
themeNames.unshift('atom-dark-syntax')
|
||||
} else {
|
||||
themeNames.push('atom-dark-ui')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse so the first (top) theme is loaded after the others. We want
|
||||
// the first/top theme to override later themes in the stack.
|
||||
return themeNames.reverse()
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Private
|
||||
*/
|
||||
|
||||
// Resolve and apply the stylesheet specified by the path.
|
||||
//
|
||||
// This supports both CSS and Less stylesheets.
|
||||
//
|
||||
// * `stylesheetPath` A {String} path to the stylesheet that can be an absolute
|
||||
// path or a relative path that will be resolved against the load path.
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to remove the
|
||||
// required stylesheet.
|
||||
requireStylesheet (stylesheetPath, priority, skipDeprecatedSelectorsTransformation) {
|
||||
let fullPath = this.resolveStylesheet(stylesheetPath)
|
||||
if (fullPath) {
|
||||
const content = this.loadStylesheet(fullPath)
|
||||
return this.applyStylesheet(fullPath, content, priority, skipDeprecatedSelectorsTransformation)
|
||||
} else {
|
||||
throw new Error(`Could not find a file at path '${stylesheetPath}'`)
|
||||
}
|
||||
}
|
||||
|
||||
unwatchUserStylesheet () {
|
||||
if (this.userStylesheetSubscriptions != null) this.userStylesheetSubscriptions.dispose()
|
||||
this.userStylesheetSubscriptions = null
|
||||
this.userStylesheetFile = null
|
||||
if (this.userStyleSheetDisposable != null) this.userStyleSheetDisposable.dispose()
|
||||
this.userStyleSheetDisposable = null
|
||||
}
|
||||
|
||||
loadUserStylesheet () {
|
||||
this.unwatchUserStylesheet()
|
||||
|
||||
const userStylesheetPath = this.styleManager.getUserStyleSheetPath()
|
||||
if (!fs.isFileSync(userStylesheetPath)) { return }
|
||||
|
||||
try {
|
||||
this.userStylesheetFile = new File(userStylesheetPath)
|
||||
this.userStylesheetSubscriptions = new CompositeDisposable()
|
||||
const reloadStylesheet = () => this.loadUserStylesheet()
|
||||
this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidChange(reloadStylesheet))
|
||||
this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidRename(reloadStylesheet))
|
||||
this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidDelete(reloadStylesheet))
|
||||
} catch (error) {
|
||||
const message = `\
|
||||
Unable to watch path: \`${path.basename(userStylesheetPath)}\`. Make sure
|
||||
you have permissions to \`${userStylesheetPath}\`.
|
||||
|
||||
On linux there are currently problems with watch sizes. See
|
||||
[this document][watches] for more info.
|
||||
[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path\
|
||||
`
|
||||
this.notificationManager.addError(message, {dismissable: true})
|
||||
}
|
||||
|
||||
let userStylesheetContents
|
||||
try {
|
||||
userStylesheetContents = this.loadStylesheet(userStylesheetPath, true)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.userStyleSheetDisposable = this.styleManager.addStyleSheet(userStylesheetContents, {sourcePath: userStylesheetPath, priority: 2})
|
||||
}
|
||||
|
||||
loadBaseStylesheets () {
|
||||
this.reloadBaseStylesheets()
|
||||
}
|
||||
|
||||
reloadBaseStylesheets () {
|
||||
this.requireStylesheet('../static/atom', -2, true)
|
||||
}
|
||||
|
||||
stylesheetElementForId (id) {
|
||||
const escapedId = id.replace(/\\/g, '\\\\')
|
||||
return document.head.querySelector(`atom-styles style[source-path="${escapedId}"]`)
|
||||
}
|
||||
|
||||
resolveStylesheet (stylesheetPath) {
|
||||
if (path.extname(stylesheetPath).length > 0) {
|
||||
return fs.resolveOnLoadPath(stylesheetPath)
|
||||
} else {
|
||||
return fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
}
|
||||
}
|
||||
|
||||
loadStylesheet (stylesheetPath, importFallbackVariables) {
|
||||
if (path.extname(stylesheetPath) === '.less') {
|
||||
return this.loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
} else {
|
||||
return fs.readFileSync(stylesheetPath, 'utf8')
|
||||
}
|
||||
}
|
||||
|
||||
loadLessStylesheet (lessStylesheetPath, importFallbackVariables = false) {
|
||||
if (this.lessCache == null) {
|
||||
this.lessCache = new LessCompileCache({
|
||||
resourcePath: this.resourcePath,
|
||||
lessSourcesByRelativeFilePath: this.lessSourcesByRelativeFilePath,
|
||||
importedFilePathsByRelativeImportPath: this.importedFilePathsByRelativeImportPath,
|
||||
importPaths: this.getImportPaths()
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
if (importFallbackVariables) {
|
||||
const baseVarImports = `\
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";\
|
||||
`
|
||||
const relativeFilePath = path.relative(this.resourcePath, lessStylesheetPath)
|
||||
const lessSource = this.lessSourcesByRelativeFilePath[relativeFilePath]
|
||||
|
||||
let content, digest
|
||||
if (lessSource != null) {
|
||||
({ content } = lessSource);
|
||||
({ digest } = lessSource)
|
||||
} else {
|
||||
content = baseVarImports + '\n' + fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
digest = null
|
||||
}
|
||||
|
||||
return this.lessCache.cssForFile(lessStylesheetPath, content, digest)
|
||||
} else {
|
||||
return this.lessCache.read(lessStylesheetPath)
|
||||
}
|
||||
} catch (error) {
|
||||
let detail, message
|
||||
error.less = true
|
||||
if (error.line != null) {
|
||||
// Adjust line numbers for import fallbacks
|
||||
if (importFallbackVariables) { error.line -= 2 }
|
||||
|
||||
message = `Error compiling Less stylesheet: \`${lessStylesheetPath}\``
|
||||
detail = `Line number: ${error.line}\n${error.message}`
|
||||
} else {
|
||||
message = `Error loading Less stylesheet: \`${lessStylesheetPath}\``
|
||||
detail = error.message
|
||||
}
|
||||
|
||||
this.notificationManager.addError(message, {detail, dismissable: true})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
removeStylesheet (stylesheetPath) {
|
||||
if (this.styleSheetDisposablesBySourcePath[stylesheetPath] != null) {
|
||||
this.styleSheetDisposablesBySourcePath[stylesheetPath].dispose()
|
||||
}
|
||||
}
|
||||
|
||||
applyStylesheet (path, text, priority, skipDeprecatedSelectorsTransformation) {
|
||||
this.styleSheetDisposablesBySourcePath[path] = this.styleManager.addStyleSheet(
|
||||
text,
|
||||
{
|
||||
priority,
|
||||
skipDeprecatedSelectorsTransformation,
|
||||
sourcePath: path
|
||||
}
|
||||
)
|
||||
|
||||
return this.styleSheetDisposablesBySourcePath[path]
|
||||
}
|
||||
|
||||
activateThemes () {
|
||||
return new Promise(resolve => {
|
||||
// @config.observe runs the callback once, then on subsequent changes.
|
||||
this.config.observe('core.themes', () => {
|
||||
this.deactivateThemes().then(() => {
|
||||
this.warnForNonExistentThemes()
|
||||
this.refreshLessCache() // Update cache for packages in core.themes config
|
||||
|
||||
const promises = []
|
||||
for (const themeName of this.getEnabledThemeNames()) {
|
||||
if (this.packageManager.resolvePackagePath(themeName)) {
|
||||
promises.push(this.packageManager.activatePackage(themeName))
|
||||
} else {
|
||||
console.warn(`Failed to activate theme '${themeName}' because it isn't installed.`)
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this.addActiveThemeClasses()
|
||||
this.refreshLessCache() // Update cache again now that @getActiveThemes() is populated
|
||||
this.loadUserStylesheet()
|
||||
this.reloadBaseStylesheets()
|
||||
this.initialLoadComplete = true
|
||||
this.emitter.emit('did-change-active-themes')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deactivateThemes () {
|
||||
this.removeActiveThemeClasses()
|
||||
this.unwatchUserStylesheet()
|
||||
const results = this.getActiveThemes().map(pack => this.packageManager.deactivatePackage(pack.name))
|
||||
return Promise.all(results.filter((r) => (r != null) && (typeof r.then === 'function')))
|
||||
}
|
||||
|
||||
isInitialLoadComplete () {
|
||||
return this.initialLoadComplete
|
||||
}
|
||||
|
||||
addActiveThemeClasses () {
|
||||
const workspaceElement = this.viewRegistry.getView(this.workspace)
|
||||
if (workspaceElement) {
|
||||
for (const pack of this.getActiveThemes()) {
|
||||
workspaceElement.classList.add(`theme-${pack.name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeActiveThemeClasses () {
|
||||
const workspaceElement = this.viewRegistry.getView(this.workspace)
|
||||
for (const pack of this.getActiveThemes()) {
|
||||
workspaceElement.classList.remove(`theme-${pack.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
refreshLessCache () {
|
||||
if (this.lessCache) this.lessCache.setImportPaths(this.getImportPaths())
|
||||
}
|
||||
|
||||
getImportPaths () {
|
||||
let themePaths
|
||||
const activeThemes = this.getActiveThemes()
|
||||
if (activeThemes.length > 0) {
|
||||
themePaths = (activeThemes.filter((theme) => theme).map((theme) => theme.getStylesheetsPath()))
|
||||
} else {
|
||||
themePaths = []
|
||||
for (const themeName of this.getEnabledThemeNames()) {
|
||||
const themePath = this.packageManager.resolvePackagePath(themeName)
|
||||
if (themePath) {
|
||||
const deprecatedPath = path.join(themePath, 'stylesheets')
|
||||
if (fs.isDirectorySync(deprecatedPath)) {
|
||||
themePaths.push(deprecatedPath)
|
||||
} else {
|
||||
themePaths.push(path.join(themePath, 'styles'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return themePaths.filter(themePath => fs.isDirectorySync(themePath))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user