tm2deftheme 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ # tm2deftheme
2
+
3
+ Convert TextMate/SublimeText .tmTheme to Emacs 24 deftheme .el
4
+
5
+ ### Install
6
+
7
+ gem install tm2deftheme
8
+
9
+ ### Usage:
10
+
11
+ tm2deftheme [options] [themefile.tmTheme]
12
+
13
+ options:
14
+
15
+ -f ouput Emacs 24 deftheme to file
16
+ e.g. Birds of Paradise.tmTheme
17
+
18
+ becomes:
19
+
20
+ birds-of-paradise-theme.el
21
+
22
+ -s when used with -f silence output
23
+ -o when used with -f overwrite existing file
24
+
25
+ --debug debugging output
26
+
27
+ When run without options converted theme is sent to `STDOUT`
28
+
29
+ ### Notes
30
+
31
+ Note that Emacs syntax highlighting is managed differently to
32
+ SublimeText / TextMate. Various Emacs modes provide additional
33
+ highlighing support, but `tm2deftheme` (currently) only maps to core
34
+ `font-lock` faces. So while things won't look like a one-to-one copy,
35
+ the results are still pretty good.
36
+
37
+ Linum, fringe and modeline colours are derived from the base foreground
38
+ and background colors. Support for [Rainbow Delimiters](http://www.emacswiki.org/emacs/RainbowDelimiters)
39
+ is provided automatically.
40
+
41
+ The imported foreground colors which constrast most from the background
42
+ are averaged, from this average colour, 9 tint colors are generated and
43
+ assigned to the `rainbow-delimiters-depth-n-face` collection.
44
+
45
+ I'll be adding additional support for `js3-mode`, git-gutter, flyspell,
46
+ flymake, flycheck, isearch and more.
47
+
48
+ ### Demo
49
+
50
+ See for yourself, here's a handful of converted themes, shown in their
51
+ original format (here rendered by the excellent
52
+ [http://tmtheme-editor.herokuapp.com/](http://tmtheme-editor.herokuapp.com/))
53
+ and then shown in Emacs 24 after conversion.
54
+
55
+ ![](https://raw.githubusercontent.com/emacsfodder/tmtheme-to-deftheme/master/slides.gif)
56
+
57
+ ### Dependencies
58
+
59
+ Ruby 1.9 or later required.
60
+
61
+ Development, clone and run `bundle install` in the project folder.
62
+
63
+ ### Contribution / Development
64
+
65
+ If you'd like to contribute, the best place to do so is in mapping
66
+ SublimeText / TextMate scopes to Emacs faces. Although any issue
67
+ posting, bug fixing, feature pull-requests, etc. are welcome.
68
+
69
+ Avoid using `test.sh` and `install-generated-themes.el`, they're not
70
+ tuned for general use.
71
+
72
+ PLEASE NOTE: `test.sh` will forceably remove all folders matching
73
+ `~/.emacs.d/elpa/*-theme-140*`.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tmtheme-to-deftheme'
3
+ require 'slop'
4
+
5
+ Slop.parse do
6
+
7
+ banner <<-EOF
8
+ TextMate/SublimeText .tmTheme to Emacs 24 deftheme .el
9
+
10
+ Usage: tm2deftheme [themefile.tmTheme] [options]
11
+ EOF
12
+
13
+ on :f, 'ouput Emacs 24 deftheme to file'
14
+ on :s, 'when used with -f silence output'
15
+ on :o, 'when used with -f overwrite existing file'
16
+ on :debug, 'show debugging output'
17
+
18
+ run do |opts, args|
19
+ if args.first.nil?
20
+ $stderr.puts opts
21
+ exit 1
22
+ end
23
+
24
+ theme_filename = args.first
25
+
26
+ unless File.exist? theme_filename
27
+ $stderr.puts "#{theme_filename} : file doesn't exist"
28
+ exit 1
29
+ end
30
+
31
+ TmthemeToDeftheme::Main.new theme_filename, opts
32
+ end
33
+
34
+ end
@@ -0,0 +1,86 @@
1
+ # Pairs of TMate scopes and equivalent Emacs faces
2
+
3
+ - - entity
4
+ - font-lock-type-face
5
+ - - entity.name.class
6
+ - font-lock-type-face
7
+ - - class
8
+ - font-lock-type-face
9
+ - - comment
10
+ - font-lock-comment-face
11
+ - - constant
12
+ - font-lock-constant-face
13
+ - - constant.language
14
+ - font-lock-builtin-face
15
+ - - entity.name.function
16
+ - font-lock-function-name-face
17
+ - - meta.preprocessor.c
18
+ - font-lock-preprocessor-face
19
+ - - string
20
+ - font-lock-string-face
21
+ - - variable
22
+ - font-lock-variable-name-face
23
+ - - keyword
24
+ - font-lock-keyword-face
25
+ - - diff.header
26
+ - diff-file-header
27
+ - - markup.changed
28
+ - diff-changed
29
+ - - markup.deleted
30
+ - diff-removed
31
+ - - markup.inserted
32
+ - diff-added
33
+ - - invalid.deprecated
34
+ - font-lock-warning-face
35
+ - - invalid.illegal
36
+ - error
37
+
38
+ # js3 and js2 mode
39
+
40
+ - - keyword.other.documentation.param.javadoc
41
+ - js3-jsdoc-tag-face
42
+ - js2-jsdoc-tag
43
+ - - variable.parameter
44
+ - js3-function-param-face
45
+ - js2-function-param
46
+ - - variable.other.external-symbol
47
+ - js3-external-variable-face
48
+ - js2-external-variable
49
+
50
+ # GitGutter
51
+
52
+ - - markup.deleted.git_gutter
53
+ - git-gutter:deleted
54
+ - - markup.inserted.git_gutter
55
+ - git-gutter:added
56
+ - - markup.changed.git_gutter
57
+ - git-gutter:modified
58
+ - - markup.ignored.git_gutter
59
+ - git-gutter:untracked
60
+
61
+ # Markdown
62
+
63
+ - - punctuation.definition.italic.markdown
64
+ - markdown-italic-face
65
+ - - punctuation.definition.bold.markdown
66
+ - markdown-bold-face
67
+ - - meta.separator.markdown
68
+ - markdown-header-rule-face
69
+ - - entity.name.section.markdown
70
+ - markdown-header-face
71
+ - - markup.raw.inline.markdown
72
+ - markdown-inline-code-face
73
+ - - punctuation.definition.list_item.markdown
74
+ - markdown-list-face
75
+ - - punctuation.definition.blockquote.markdown
76
+ - markdown-blockquote-face
77
+ - - markup.raw.block.markdown
78
+ - markdown-pre-face
79
+ - - meta.link.inline.markdown
80
+ - markdown-url-face
81
+ - - string.other.link.title.markdown
82
+ - markdown-link-title-face
83
+
84
+ # Blank lines are ignored
85
+
86
+ # See YAML Docs at http://yaml.org/spec/1.2/spec.html
@@ -0,0 +1,219 @@
1
+ module TmthemeToDeftheme
2
+
3
+ require 'plist4r'
4
+ require 'color'
5
+ require 'erubis'
6
+ require 'yaml'
7
+
8
+ class Main
9
+
10
+ SCOPE_MAP = YAML.load_file(File.join(File.dirname(__FILE__),'..','data','scopes-to-faces.yml'))
11
+ TM_SCOPES = SCOPE_MAP.map(&:first)
12
+ EMACS_FACES = SCOPE_MAP.map{|a| a[1..-1]}
13
+
14
+ def initialize theme_filename, options
15
+ @theme_filename = theme_filename
16
+ @options = options
17
+
18
+ @plist = Plist4r.open @theme_filename
19
+ @author = @plist["author"]
20
+ @name = @plist["name"]
21
+ @theme_name = "#{@plist["name"]}".downcase.tr ' _', '-'
22
+ @long_theme_name = "#{@theme_name}-theme"
23
+
24
+ if @options[:f]
25
+ @deftheme_filename = "#{@long_theme_name}.el"
26
+ unless @options[:s]
27
+ $stderr.puts "Creating: #{@deftheme_filename}"
28
+ end
29
+ if File.exist? @deftheme_filename
30
+ unless @options[:o]
31
+ $stderr.puts "#{@deftheme_filename} already exists, use -o to force overwrite"
32
+ exit 1
33
+ end
34
+ end
35
+ File.open(@deftheme_filename, "w") {|f| f.puts parse}
36
+ else
37
+ puts parse
38
+ end
39
+ end
40
+
41
+ def debug_out message
42
+ $stderr.puts message if @options[:debug]
43
+ end
44
+
45
+ def map_scope scope
46
+ if scope.index ","
47
+ names = scope.split(",").map(&:strip)
48
+ debug_out names
49
+ first_match = names.map{|n| TM_SCOPES.find_index n}.compact.first
50
+ else
51
+ debug_out scope
52
+ first_match = TM_SCOPES.find_index scope
53
+ end
54
+
55
+ if first_match.nil?
56
+ nil
57
+ else
58
+ debug_out "#{first_match} :: #{scope} : #{EMACS_FACES[first_match]}"
59
+ EMACS_FACES[first_match]
60
+ end
61
+ end
62
+
63
+ def make_attr s, k
64
+ debug_out "Make attrs: #{s[:face]} : #{k} : #{s} : #{s[k]}"
65
+ ":#{k} \"#{s[k]}\"" if s[k]
66
+ end
67
+
68
+ def italic_underline_bold s
69
+ if s["fontStyle"]
70
+ s["fontStyle"].split(" ").map{|i| ":#{i} t" }.join " "
71
+ end
72
+ end
73
+
74
+ def face_attrs s
75
+ "#{make_attr s, "foreground"} #{make_attr s, "background"} #{italic_underline_bold s}"
76
+ end
77
+
78
+ def map_scope_to_emacs_face hash
79
+ emacs_face = map_scope hash["scope"]
80
+ return nil if emacs_face.nil?
81
+ settings = hash["settings"]
82
+ emacs_face = [emacs_face] unless emacs_face.class == Array
83
+ mapped_scope = emacs_face.map{|face| {face: face, settings: settings, scope: hash["scope"]}}
84
+ debug_out mapped_scope
85
+ mapped_scope
86
+ end
87
+
88
+ def map_palette_key faces, key
89
+ faces.map{|f| Color::RGB.from_html f[:settings][key] if f[:settings][key]}.compact
90
+ end
91
+
92
+ def isolate_palette faces
93
+ [map_palette_key( faces, "foreground"), map_palette_key( faces, "background")]
94
+ end
95
+
96
+ def fix_rgba hexcolor
97
+ if hexcolor.length == 9
98
+ c = Color::RGB.from_html hexcolor[0,7]
99
+ a = hexcolor[7,2].to_i(16).to_f
100
+ p = (a / 255.0) * 100.0
101
+ unless @base_bg.nil?
102
+ c.mix_with(@base_bg, p).html
103
+ else
104
+ c.html
105
+ end
106
+ elsif hexcolor.length == 7 || hexcolor.length == 4
107
+ hexcolor
108
+ end
109
+ end
110
+
111
+ # Note: Foreground palette
112
+ def palette_average_values sample_palette
113
+ samples = sample_palette.map{|c|
114
+ c = c.to_hsl
115
+ {hue: c.hue, sat: c.saturation, lvl: c.brightness}
116
+ }
117
+
118
+ avg = {}
119
+ avg[:hue] = samples.map{|s| s[:hue]}.reduce{|sum,c| sum + c} / samples.size
120
+ avg[:sat] = samples.map{|s| s[:sat]}.reduce{|sum,c| sum + c} / samples.size
121
+ avg[:lvl] = samples.map{|s| s[:lvl]}.reduce{|sum,c| sum + c} / samples.size
122
+
123
+ {
124
+ color: Color::HSL.new(avg[:hue], avg[:sat], avg[:lvl]).to_rgb,
125
+ avg: avg,
126
+ brightest: sample_palette.max_by(&:brightness),
127
+ darkest: sample_palette.min_by(&:brightness),
128
+ samples: samples
129
+ }
130
+ end
131
+
132
+ def darktheme?
133
+ !lighttheme?
134
+ end
135
+
136
+ def lighttheme?
137
+ @base_bg.brightness > 0.45
138
+ end
139
+
140
+ def make_rainbow_parens
141
+ samples = if lighttheme?
142
+ @foreground_palette.sort_by{|c| c.brightness }.select{|c| c.brightness < 0.65 }
143
+ else
144
+ @foreground_palette.sort_by{|c| - c.brightness }.select{|c| c.brightness > 0.45 }
145
+ end
146
+
147
+ debug_out "- Palette sample -------------------------------"
148
+ debug_out samples.map(&:html)
149
+ debug_out "- <<<<<<<<<<<<<< -------------------------------"
150
+
151
+ values = palette_average_values samples
152
+ @average_foregroung_color = values[:color]
153
+ @darkest_foregroung_color = values[:darkest]
154
+ @brightest_foregroung_color = values[:brightest]
155
+ rainbow_top = @average_foregroung_color.mix_with @darkest_foregroung_color, 30
156
+
157
+ 9.times.collect do |i|
158
+ rainbow_top.adjust_brightness(i * 10).html
159
+ end
160
+ end
161
+
162
+ def parse
163
+ debug_out "= Converting : #{@theme_filename} =============================="
164
+ debug_out "- tmTheme scope settings --------------------"
165
+ debug_out @plist["settings"].to_yaml
166
+
167
+ @base_settings = @plist["settings"].first["settings"]
168
+ @base_bg_hex = fix_rgba @base_settings["background"]
169
+ @base_bg = Color::RGB.from_html @base_bg_hex
170
+ @base_fg_hex = fix_rgba @base_settings["foreground"]
171
+ @base_fg = Color::RGB.from_html @base_fg_hex
172
+
173
+ @emacs_faces = @plist["settings"].collect{|s| map_scope_to_emacs_face(s) if s["scope"] }.flatten.compact
174
+
175
+ if lighttheme?
176
+ debug_out "- Converting : Light Theme ----------------"
177
+ else
178
+ debug_out "- Converting : Dark Theme ----------------"
179
+ end
180
+
181
+ # Debug faces
182
+ debug_out "- Mapped faces ------------------------------"
183
+
184
+ # Fix any RGBA colors in the tmTheme
185
+ @emacs_faces.each do |f|
186
+ debug_out f.to_yaml
187
+ f[:settings]["foreground"] = fix_rgba f[:settings]["foreground"] if f[:settings]["foreground"]
188
+ f[:settings]["background"] = fix_rgba f[:settings]["background"] if f[:settings]["background"]
189
+ debug_out f.to_yaml
190
+ end
191
+
192
+ @foreground_palette, @background_palette = isolate_palette @emacs_faces
193
+ @rainbow_parens = make_rainbow_parens + ["#FF0000"]
194
+
195
+ if @emacs_faces.select{|f| f[:face] == "font-lock-comment-face"}
196
+ comment_face = @emacs_faces.select{|f| f[:face] == "font-lock-comment-face"}.first
197
+ if comment_face
198
+ @emacs_faces << {face: "font-lock-comment-delimiter-face", settings: comment_face[:settings]}
199
+ end
200
+ end
201
+
202
+ render
203
+ end
204
+
205
+ def render
206
+ Erubis::Eruby.new(
207
+ File.read(
208
+ File.join(
209
+ File.dirname(__FILE__),
210
+ '..',
211
+ 'templates',
212
+ 'deftheme.erb.el'
213
+ )))
214
+ .result binding
215
+ end
216
+
217
+ end
218
+
219
+ end
@@ -0,0 +1,156 @@
1
+ ;;; <%= @long_theme_name %>.el --- an Emacs 24 theme based on <%= @name %> (tmTheme)
2
+ ;;
3
+ ;;; Author: Auto Converted to Emacs 24 by tmtheme-to-deftheme (tm2deftheme)
4
+ ;;; Version: <%= Time.now.to_i %>
5
+ ;;; Original author: <%= @author %>
6
+ ;;; Url: https://github.com/emacsfodder/tmtheme-to-deftheme
7
+ ;;; Package-Requires: ((emacs "24.0"))
8
+ ;;
9
+ ;;; Commentary:
10
+ ;; This theme was automatically generated by tmtheme-to-deftheme (tm2deftheme),
11
+ ;; from <%= @name %> (tmTheme) by <%= @author %>
12
+ ;;
13
+ ;;; Code:
14
+
15
+ (deftheme <%= @theme_name %>
16
+ "<%= @long_theme_name %> - Created by tmtheme-to-deftheme - <%= Time.now.to_s %>")
17
+
18
+ (custom-theme-set-variables
19
+ '<%= @theme_name %>
20
+ )
21
+
22
+ (custom-theme-set-faces
23
+ '<%= @theme_name %>
24
+ ;; basic theming.
25
+
26
+ '(default ((t (<%= face_attrs(@base_settings) %>))))
27
+ '(region ((t (:background "<%= @base_settings["selection"] %>"))))
28
+ '(cursor ((t (:background "<%= @base_settings["caret"] %>"))))
29
+
30
+ ;; Temporary defaults
31
+ <%
32
+ # Linum and Fringe are generated from base_bg
33
+ %>
34
+ <% if darktheme? %>
35
+ <% debug_out "Dark theme linum/fringe" %>
36
+ '(linum ((t (:foreground "<%= @base_bg.mix_with(@base_fg, 80).html %>" :background "<%= @base_bg.mix_with(@base_fg, 90).html %>" ))))
37
+ '(fringe ((t ( :background "<%= @base_bg.mix_with(@base_fg, 90).html %>" ))))
38
+ <% else %>
39
+ <% debug_out "Light theme linum/fringe" %>
40
+ '(linum ((t (:foreground "<%= @base_fg.adjust_brightness(90).html %>" :background "<%= @base_bg.adjust_brightness(-30).html %>" ))))
41
+ '(fringe ((t ( :background "<%= @base_bg.adjust_brightness(-30).html %>" ))))
42
+ <% end %>
43
+
44
+ '(minibuffer-prompt ((t (:foreground "#1278A8" :background nil :weight bold ))))
45
+ '(escape-glyph ((t (:foreground "orange" :background nil ))))
46
+ '(highlight ((t (:foreground "orange" :background nil ))))
47
+ '(shadow ((t (:foreground "#777777" :background nil ))))
48
+
49
+ '(trailing-whitespace ((t (:foreground "#FFFFFF" :background "#C74000" ))))
50
+ '(link ((t (:foreground "#00b7f0" :background nil :underline t ))))
51
+ '(link-visited ((t (:foreground "#4488cc" :underline t :inherit (link) ))))
52
+ '(button ((t (:foreground "#FFFFFF" :background "#444444" :underline t :inherit (link) ))))
53
+ '(next-error ((t ( :inherit (region) ))))
54
+ '(query-replace ((t ( :inherit (isearch) ))))
55
+ '(header-line ((t (:foreground "#EEEEEE" :background "#444444" :box nil :inherit (mode-line) ))))
56
+
57
+ <%
58
+ # Modeline is generated from base_fg / base_bg
59
+ %>
60
+ '(mode-line-highlight ((t ( :box nil ))))
61
+ '(mode-line-emphasis ((t ( :weight bold ))))
62
+ '(mode-line-buffer-id ((t ( :box nil :weight bold ))))
63
+
64
+ <% if darktheme? %>
65
+ <% debug_out "Dark theme modeline" %>
66
+ '(mode-line-inactive ((t (:foreground "<%= @base_fg.adjust_brightness(-20).html %>" :background "<%= @base_bg.mix_with(@base_fg, 90).html %>" :box nil :weight light :inherit (mode-line) ))))
67
+ '(mode-line ((t (:foreground "<%= @base_fg.html %>" :background "<%= @base_bg.mix_with(@base_fg, 90).html %>" :box nil ))))
68
+ <% else %>
69
+ <% debug_out "Light theme modeline" %>
70
+ '(mode-line-inactive ((t (:foreground "<%= @base_fg.html %>" :background "<%= @base_bg.adjust_brightness(-10).html %>" :box nil :weight light :inherit (mode-line) ))))
71
+ '(mode-line ((t (:foreground "<%= @base_fg.html %>" :background "<%= @base_bg.adjust_brightness(-20).html %>" :box nil ))))
72
+ <% end %>
73
+
74
+ <%
75
+ # Search highlighting
76
+ %>
77
+ '(isearch ((t (:foreground "#99ccee" :background "#444444" ))))
78
+ '(isearch-fail ((t ( :background "#ffaaaa" ))))
79
+ '(lazy-highlight ((t ( :background "#77bbdd" ))))
80
+ '(match ((t ( :background "#3388cc" ))))
81
+
82
+ '(tooltip ((t (:foreground "black" :background "LightYellow" :inherit (variable-pitch) ))))
83
+
84
+ '(js3-function-param-face ((t (:foreground "#BFC3A9" ))))
85
+ '(js3-external-variable-face ((t (:foreground "#F0B090" :bold t ))))
86
+
87
+ <%
88
+ # TODO: Generate cua-rectangle and secondary-selection from region/selection import
89
+ %>
90
+ '(secondary-selection ((t ( :background "#342858" ))))
91
+ '(cua-rectangle ((t (:foreground "#E0E4CC" :background "#342858" ))))
92
+
93
+ ;; Magit hightlight
94
+ '(magit-item-highlight ((t (:foreground "white" :background "#1278A8" :inherit nil ))))
95
+
96
+ <%
97
+ # TODO: Generate flyspell / flymake / flycheck / git:gutter from diff defaults/imported colors.
98
+ %>
99
+ ;; flyspell-mode
100
+ '(flyspell-incorrect ((t (:underline "#AA0000" :background nil :inherit nil ))))
101
+ '(flyspell-duplicate ((t (:underline "#009945" :background nil :inherit nil ))))
102
+
103
+ ;; flymake-mode
104
+ '(flymake-errline ((t (:underline "#AA0000" :background nil :inherit nil ))))
105
+ '(flymake-warnline ((t (:underline "#009945" :background nil :inherit nil ))))
106
+
107
+ ;;git-gutter
108
+ '(git-gutter:added ((t (:foreground "#609f60" :bold t))))
109
+ '(git-gutter:modified ((t (:foreground "#3388cc" :bold t))))
110
+ '(git-gutter:deleted ((t (:foreground "#cc3333" :bold t))))
111
+
112
+ '(diff-added ((t (:background "#305030"))))
113
+ '(diff-removed ((t (:background "#903010"))))
114
+ '(diff-file-header ((t (:background "#362145"))))
115
+ '(diff-context ((t (:foreground "#E0E4CC"))))
116
+ '(diff-changed ((t (:foreground "#3388cc"))))
117
+ '(diff-hunk-header ((t (:background "#242130"))))
118
+
119
+ <%
120
+
121
+ # TODO: defaults should be overriden when the @emacs_faces below are
122
+ # evaluated, it'd be much cleaner if we managed it a better way.
123
+ # Perhaps keep the default faces as a hash and then merge the tmtheme
124
+ # imported faces hash, before writing out the face definitions.
125
+
126
+ # TODO: Infer color values of defaults based on the provided palette,
127
+ # using suitable tonal emphasis/de-emphasis as necessary. (this is
128
+ # being done "casually" for the modeline, fringe and linum)
129
+
130
+ %>
131
+
132
+ <% @emacs_faces.each do |e| debug_out e[:face] %>
133
+ '(<%= e[:face] %> ((t (<%= face_attrs(e[:settings]) %>))))
134
+ <% end %>
135
+
136
+ <% if @rainbow_parens && @rainbow_parens.count == 10 %>
137
+ ;; Rainbow delimiters
138
+ <% @rainbow_parens[0,9].each_with_index do |e,i| %>
139
+ '(rainbow-delimiters-depth-<%= i + 1 %>-face ((t (:foreground "<%= e %>"))))
140
+ <% end %>
141
+ '(rainbow-delimiters-unmatched-face ((t (:foreground "<%= @rainbow_parens[9] %>"))))
142
+ <% end %>
143
+ ) ;; End face definitions
144
+
145
+ ;;;###autoload
146
+ (when load-file-name
147
+ (add-to-list 'custom-theme-load-path
148
+ (file-name-as-directory (file-name-directory load-file-name))))
149
+
150
+ (provide-theme '<%= @theme_name %>)
151
+
152
+ ;; Local Variables:
153
+ ;; eval: (when (fboundp 'rainbow-mode) (rainbow-mode +1))
154
+ ;; End:
155
+
156
+ ;;; <%= @long_theme_name %>.el ends here