├── .gitignore
├── README.md
├── main.js
├── package.json
└── scopes
├── AceEditorSupportedScopes.js
└── CodeMirrorSupportedScopes.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | CodeMirror & AceEditor Theme Generator
2 | ====
3 |
4 | Generate Code Mirror and Ace Editor themes with the help of a sleek GUI and/or from the thousands of available themes for Sublime Text and Text Mate.
5 |
6 | This project is in its early stages and will continue to grow with new features.
7 |
8 | Some of the parsing from SublimeText and TextMate's XML to CodeMirror and AceEditor's CSS are not one-to-one, as they each have different levels and specificity for their selectors. That said, this tool should provide you a nice basis to help make CodeMirror and AceEditor themes.
9 |
10 | If something is not working or you would like a new feature, please use the issues page.
11 |
12 |
13 | ## Installation
14 |
15 | You can simply fork and clone (or download) the repo into your local directory:
16 |
17 | ```
18 | $ git clone https://github.com/FarhadG/codeMirror-aceEditor-theme-generator
19 | $ cd codeMirror-aceEditor-theme-generator
20 | $ npm install
21 | ```
22 |
23 |
24 | ## Usage
25 |
26 | After installing the package, you can run the `main.js` script to convert SublimeText's & TextMate's XML to CodeMirror's CSS. The script takes 2 arguments (3rd is optional).
27 |
28 | 1. Theme name: Should be the same one provided to CodeMirror's theme API option, so that the correct namespacing applies over the CSS classes.
29 | 2. The source file path: The relative path to the source file.
30 | 3. The destination file path: The relative path for the output directory/file.
31 | 4. You can pass a boolean (true/false) for having the CSS JSON printed out to the console. This can be useful is you find variables from Sublime or TextMate's XML that match up with CodeMirror and AceEditor's classes. If so, you can update the scopes files and/or open a GitHub issue.
32 |
33 | ```
34 | $ node main.js themeName | sourcePath | debugMode (bool, optional)
35 | ```
36 |
37 |
38 | ## Demo
39 |
40 | Here's a theme I extracted from one of my favorite Sublime Themes. I had to add an additional 4-5 CSS selectors to get it nearly there, as Sublime has a few more specific selectors than CodeMirror.
41 |
42 |
43 |
44 |
45 | ## GUI Interface
46 |
47 | Here's the LINK for making SublimeText and TextMate with a visual interface. After saving the file, you can, then, take the file and run the generator script for converting the XML to CSS.
48 |
49 |
50 | ## Options
51 |
52 | I'll be adding more features; that said, if you'd like a feature, let me know so that I'll try and implement it into future updates.
53 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Dependencies
3 | */
4 | var fs = require('fs');
5 | var path = require('path');
6 | var plist = require('plist');
7 | var cssjson = require('cssjson');
8 |
9 |
10 | /*******************************************************************************
11 | * Current supported scopes between Sublime/TextMate and CodeMirror.
12 | * Various references will not look the same, as CodeMirror
13 | * and Sublime have different and limited ways of refering to the code syntax.
14 | */
15 | var supportedScopes = require('./scopes/CodeMirrorSupportedScopes');
16 |
17 |
18 | // The root JSON that gets parsed to CSS after populated.
19 | var root = {};
20 |
21 |
22 | /*******************************************************************************
23 | * Basic parsing of CSS styles provided by the XML format form Sublime.
24 | */
25 | function parseStyles(styles) {
26 | var css = [];
27 |
28 | var fontStyle = styles.fontStyle || '';
29 |
30 | if (fontStyle.indexOf('underline') !== -1) {
31 | css.push(['text-decoration', 'underline']);
32 | }
33 |
34 | if (fontStyle.indexOf('italic') !== -1) {
35 | css.push(['font-style', 'italic']);
36 | }
37 |
38 | if (styles.foreground) {
39 | css.push(['color', styles.foreground]);
40 | }
41 |
42 | if (styles.background) {
43 | css.push(['background', styles.background]);
44 | }
45 |
46 | return css;
47 | }
48 |
49 |
50 | /*******************************************************************************
51 | * Convert a given JSON to CSS and then display message after file has
52 | * been written.
53 | */
54 | function writeFile(json, themeName, callback) {
55 | var data = cssjson.toCSS(json);
56 | var themeName = themeName.toLowerCase().split(' ').join('-');
57 | var destination = __dirname+ '/' +themeName+ '.css'
58 |
59 | fs.writeFile(destination, data, function(err) {
60 | if (err) console.log(err);
61 | callback(themeName);
62 | });
63 | }
64 |
65 |
66 | /*******************************************************************************
67 | * Given a string, turn it into a CSS class.
68 | */
69 | function convertClass(string) {
70 | return '.' + string;
71 | }
72 |
73 |
74 | /*******************************************************************************
75 | * A simple way of namespacing the entire CSS on top of CodeMirror's base styling.
76 | * NOTE: The theme class is applied to the code editor via
77 | * CodeMirror's API.
78 | */
79 | function nameSpace(themeName) {
80 | return convertClass('cm-s-' + themeName);
81 | }
82 |
83 |
84 | /*******************************************************************************
85 | * Build a CSS selector with namespacing.
86 | */
87 | function buildClass(themeName, selector) {
88 | return [nameSpace(themeName), convertClass(selector)].join(' ');
89 | }
90 |
91 |
92 | /*******************************************************************************
93 | * A helper function for printing our the entire root JSON.
94 | */
95 | function print(json) {
96 | console.log(JSON.stringify(json, null, 4));
97 | }
98 |
99 |
100 | /*******************************************************************************
101 | * Constructs the root JSON with the necessary keys and information.
102 | */
103 | function generateThemeInfo(themeInfo, theme) {
104 | for(var themeInfo in theme) {
105 | if (themeInfo.toLowerCase() !== 'settings') {
106 | var info = theme[themeInfo];
107 | root[themeInfo] = info;
108 | }
109 | }
110 | root.children = {};
111 | root.unsupported = {};
112 | }
113 |
114 |
115 | /*******************************************************************************
116 | * If a given scope is not yet supported, add it to the root's unsupported key.
117 | */
118 | function addToUnsupported(scope, info) {
119 | root.unsupported[scope] = info;
120 | }
121 |
122 |
123 | /*******************************************************************************
124 | * Given the necessary information for the CSS values,
125 | * write the information to the root JSON.
126 | */
127 | function writeToRoot(selector, property, value) {
128 | root.children[selector] = root.children[selector] || {};
129 | root.children[selector].attributes = root.children[selector].attributes || {};
130 | root.children[selector].attributes[property] = value;
131 | }
132 |
133 |
134 | /*******************************************************************************
135 | * The global styles are a special treatment as they are formatted differently
136 | * within Sublime's markup.
137 | */
138 | function generateGlobalStyles(styles, themeName, theme) {
139 | for(var scope in styles) {
140 | var codeMirror = supportedScopes[scope];
141 | if (codeMirror) {
142 | var selector, property, value;
143 | // Has extra information for a more complex selector
144 | if (Array.isArray(codeMirror)) {
145 | selector = buildClass(themeName, codeMirror[0]);
146 | property = codeMirror[1];
147 | value = styles[scope];
148 | }
149 | // The selector sits at the top of the theme
150 | else {
151 | selector = nameSpace(themeName),
152 | property = codeMirror;
153 | value = styles[scope];
154 | }
155 | writeToRoot(selector, property, value);
156 | }
157 | else {
158 | addToUnsupported(scope, 'Global styling');
159 | }
160 | }
161 | }
162 |
163 |
164 | /*******************************************************************************
165 | * If the style's scope is supportted, write it to the root JSON.
166 | * If not, add it to the unsupported key under the root JSON.
167 | */
168 | function generateStyles(styles, themeName, theme) {
169 | var codeMirror = supportedScopes[styles.scope];
170 | if (codeMirror) {
171 | var selector = buildClass(themeName, codeMirror);
172 | var cssStyles = parseStyles(styles.settings);
173 | for(var i = 0; i < cssStyles.length; i++) {
174 | var property = cssStyles[i][0];
175 | var value = cssStyles[i][1];
176 | writeToRoot(selector, property, value);
177 | }
178 | }
179 | else {
180 | addToUnsupported(styles.scope, styles);
181 | }
182 | }
183 |
184 |
185 | /*******************************************************************************
186 | * Iterate over the theme settings and write them off into the root JSON
187 | * as either global styling or normal styling.
188 | */
189 | function extractStyles(themeName, theme) {
190 | generateThemeInfo(themeName, theme);
191 | var settings = theme.settings;
192 |
193 | for(var i = 0; i < settings.length; i++) {
194 | if (!(settings[i].name || settings[i].scope)) {
195 | generateGlobalStyles(settings[i].settings, themeName, theme);
196 | }
197 | else {
198 | generateStyles(settings[i], themeName, theme);
199 | }
200 | }
201 | }
202 |
203 | /*******************************************************************************
204 | * Parse the XML structure
205 | */
206 | function parseTheme(themeXml, callback) {
207 | var theme = plist.parse(themeXml);
208 | callback(theme);
209 | }
210 |
211 |
212 | /*******************************************************************************
213 | * Let the user know that the theme has been generated
214 | */
215 | function printCompletedMessage(themeName) {
216 | console.log([
217 | '',
218 | ' __________________________________ ',
219 | ' ________| ',
220 | ' \\ | Converted theme: '+ themeName +'',
221 | ' \\ | ',
222 | ' / |__________________________________ ',
223 | ' /___________) ',
224 | '',
225 | ''
226 | ].join('\n'));
227 | }
228 |
229 |
230 | /*******************************************************************************
231 | * Read the given theme file and send it off to be parsed.
232 | * Once completed, send off the root JSON to be written to CSS.
233 | */
234 | function convertTheme(themeName, themePath, debug) {
235 | var srcTheme = fs.readFileSync(themePath, 'utf8');
236 | parseTheme(srcTheme, function(theme) {
237 | extractStyles(themeName, theme);
238 | if (debug) print(root);
239 | writeFile(root, themeName, printCompletedMessage);
240 | });
241 | }
242 |
243 |
244 | /*******************************************************************************
245 | * Processing the arguments from terminal
246 | */
247 | if (process.argv.length > 1) {
248 | var args = process.argv.splice(2);
249 | if (args.length < 2) {
250 | console.error('Usage: themeName | sourcePath | debugMode (bool, optional)');
251 | process.exit(1);
252 | }
253 | var themeName = args[0];
254 | var themePath = args[1];
255 | var debug = args[2] ? true : false;
256 | convertTheme(themeName, themePath, debug);
257 | }
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codeMirror-aceEditor-theme-generator",
3 | "version": "0.1.2",
4 | "description": "CodeMirror & Ace Editor themes converted from a GUI and/or Sublime Text & TextMate themes",
5 | "main": "main.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "sublime",
11 | "codemirror",
12 | "code mirror",
13 | "ace editor",
14 | "textmate",
15 | "sublime text"
16 | ],
17 | "author": "Farhad Ghayour",
18 | "license": "ISC",
19 | "dependencies": {
20 | "cssjson": "^2.1.3",
21 | "plist": "^1.1.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/scopes/AceEditorSupportedScopes.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Sublime/TextMate and Ace Editor (WIP)
3 | */
4 | module.exports = {};
5 |
--------------------------------------------------------------------------------
/scopes/CodeMirrorSupportedScopes.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Current supported scopes between Sublime/TextMate and CodeMirror
3 | */
4 | module.exports = {
5 | /*
6 | * Special cases for the values as the globals
7 | * don't have an associated css property
8 | */
9 | 'background': 'background',
10 | 'foreground': 'color',
11 | 'selection': ['CodeMirror-selected', 'background'],
12 | 'lineHighlight': ['CodeMirror-activeline-background', 'background'],
13 | /*
14 | * Specific styling with their associated CodeMirror
15 | * selectors as the values
16 | */
17 | 'comment': 'cm-comment',
18 | 'string': 'cm-string',
19 | 'constant.numeric': 'cm-number',
20 | 'constant.language': 'cm-atom',
21 | 'keyword': 'cm-keyword',
22 | 'entity.name.function': 'cm-variable',
23 | 'variable.parameter': 'cm-def',
24 | 'support.function': 'cm-property'
25 | };
26 |
--------------------------------------------------------------------------------