tm2deftheme 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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