Files
atom/src/app/text-mate-theme.coffee
Nathan Sobo 3a8fe2b24e Spans have a class for each dot-separated portion of their token's scope
Previously, we were rendering every prefix of the dot-separated scope as its own class. So the scope meta.delimiter.method.period.coffee would make a token w/ classes:

class="meta, meta-delimiter, meta-delimiter-method, meta-delimiter-method-period…"

Now we just give the token each piece of the scope as a class:

class="meta delimiter method period coffee"

We lose a bit of meaning, in that a scope selector method.period.coffee would match this element in CSS even though it *wouldn't* in TextMate. But we also gain the behavior where longer prefixes are more specific by naturally producing more specific css selectors. So '.meta.delimiter.method' is always more specific than '.meta.delimiter', whereas '.meta-delimiter-method' ties with '.meta-delimiter'. 

If prefix ambiguities become a problem later we may need to revisit this approach, but I think it's good enough for now.
2012-09-28 17:00:31 -06:00

111 lines
3.2 KiB
CoffeeScript

_ = require 'underscore'
fs = require 'fs'
plist = require 'plist'
module.exports =
class TextMateTheme
@themesByName: {}
@loadAll: ->
for themePath in fs.list(require.resolve("themes"))
@registerTheme(TextMateTheme.load(themePath))
@load: (path) ->
plistString = fs.read(require.resolve(path))
theme = null
plist.parseString plistString, (err, data) ->
throw new Error("Error loading theme at '#{path}': #{err}") if err
theme = new TextMateTheme(data[0])
theme
@registerTheme: (theme) ->
@themesByName[theme.name] = theme
@getNames: ->
_.keys(@themesByName)
@getTheme: (name) ->
@themesByName[name]
@activate: (name) ->
if theme = @getTheme(name)
theme.activate()
else
throw new Error("No theme with name '#{name}'")
constructor: ({@name, settings}) ->
@rulesets = []
globalSettings = settings[0]
@buildGlobalSettingsRulesets(settings[0])
@buildScopeSelectorRulesets(settings[1..])
activate: ->
applyStylesheet(@name, @getStylesheet())
getStylesheet: ->
lines = []
for {selector, properties} in @getRulesets()
lines.push("#{selector} {")
for name, value of properties
lines.push " #{name}: #{value};"
lines.push("}\n")
lines.join("\n")
getRulesets: -> @rulesets
buildGlobalSettingsRulesets: ({settings}) ->
{ background, foreground, caret, selection } = settings
@rulesets.push
selector: '.editor'
properties:
'background-color': @translateColor(background)
'color': @translateColor(foreground)
@rulesets.push
selector: '.editor.focused .cursor'
properties:
'border-color': @translateColor(caret)
@rulesets.push
selector: '.editor.focused .selection'
properties:
'background-color': @translateColor(selection)
buildScopeSelectorRulesets: (scopeSelectorSettings) ->
for { name, scope, settings } in scopeSelectorSettings
continue unless scope
@rulesets.push
comment: name
selector: @translateScopeSelector(scope)
properties: @translateScopeSelectorSettings(settings)
translateScopeSelector: (textmateScopeSelector) ->
scopes = textmateScopeSelector.split(/\s+/).map (scope) -> '.' + scope
scopes.join(' ')
translateScopeSelectorSettings: ({ foreground, background, fontStyle }) ->
properties = {}
if fontStyle
fontStyles = fontStyle.split(/\s+/)
# properties['font-weight'] = 'bold' if _.contains(fontStyles, 'bold')
# properties['font-style'] = 'italic' if _.contains(fontStyles, 'italic')
properties['text-decoration'] = 'underline' if _.contains(fontStyles, 'underline')
properties['color'] = @translateColor(foreground) if foreground
properties['background-color'] = @translateColor(background) if background
properties
translateColor: (textmateColor) ->
if textmateColor.length <= 7
textmateColor
else
r = parseInt(textmateColor[1..2], 16)
g = parseInt(textmateColor[3..4], 16)
b = parseInt(textmateColor[5..6], 16)
a = parseInt(textmateColor[7..8], 16)
a = Math.round((a / 255.0) * 100) / 100
"rgba(#{r}, #{g}, #{b}, #{a})"