diff --git a/package.json b/package.json index b15b198a4..cabeae2da 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "tantamount": "0.3.0", "nslog": "0.1.0", "coffeestack": "0.4.0", + "first-mate": "0.1.0", "c-tmbundle": "1.0.0", "coffee-script-tmbundle": "4.0.0", "css-tmbundle": "1.0.0", diff --git a/tasks/convert-theme.coffee b/tasks/convert-theme.coffee new file mode 100644 index 000000000..2d9884ed0 --- /dev/null +++ b/tasks/convert-theme.coffee @@ -0,0 +1,101 @@ +path = require 'path' + +_ = require 'underscore' +plist = require 'plist' +{ScopeSelector} = require 'first-mate' + +module.exports = (grunt) -> + grunt.registerTask 'convert-theme', 'Convert a TextMate theme to an Atom theme', -> + if textMateThemePath = grunt.option('path') + textMateThemePath = path.resolve(textMateThemePath) + if grunt.file.isFile(textMateThemePath) + textMateTheme = new TextMateTheme(textMateThemePath) + atomThemePath = path.join(path.dirname(textMateThemePath), "#{path.basename(textMateThemePath, path.extname(textMateThemePath))}.css") + grunt.file.write(atomThemePath, textMateTheme.getStylesheet()) + grunt.log.ok("Atom theme written to: #{atomThemePath}") + else + grunt.log.error("Theme file at path not found: #{textMateThemePath}") + false + else + grunt.log.error('Must specify --path=') + false + +class TextMateTheme + constructor: (@path) -> + @rulesets = [] + @buildRulesets() + + buildRulesets: -> + {settings} = plist.parseFileSync(@path) + @buildGlobalSettingsRulesets(settings[0]) + @buildScopeSelectorRulesets(settings[1..]) + + getStylesheet: -> + lines = [] + for {selector, properties} in @getRulesets() + lines.push("#{selector} {") + lines.push " #{name}: #{value};" for name, value of properties + lines.push("}\n") + lines.join('\n') + + getRulesets: -> @rulesets + + buildGlobalSettingsRulesets: ({settings}) -> + { background, foreground, caret, selection, lineHighlight } = settings + + @rulesets.push + selector: '.editor, .editor .gutter' + properties: + 'background-color': @translateColor(background) + 'color': @translateColor(foreground) + + @rulesets.push + selector: '.editor.is-focused .cursor' + properties: + 'border-color': @translateColor(caret) + + @rulesets.push + selector: '.editor.is-focused .selection .region' + properties: + 'background-color': @translateColor(selection) + + @rulesets.push + selector: '.editor.is-focused .line-number.cursor-line-no-selection, .editor.is-focused .line.cursor-line' + properties: + 'background-color': @translateColor(lineHighlight) + + buildScopeSelectorRulesets: (scopeSelectorSettings) -> + for { name, scope, settings } in scopeSelectorSettings + continue unless scope + @rulesets.push + comment: name + selector: @translateScopeSelector(scope) + properties: @translateScopeSelectorSettings(settings) + + translateScopeSelector: (textmateScopeSelector) -> + new ScopeSelector(textmateScopeSelector).toCssSelector() + + 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})"