/lenasys/trunk

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/lenasys/trunk
15.1.1 by galaxyAbstractor
Started implementation of a new codeviewer using Ace
1
/* ***** BEGIN LICENSE BLOCK *****
2
 * Distributed under the BSD license:
3
 *
4
 * Copyright (c) 2010, Ajax.org B.V.
5
 * All rights reserved.
6
 * 
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions are met:
9
 *     * Redistributions of source code must retain the above copyright
10
 *       notice, this list of conditions and the following disclaimer.
11
 *     * Redistributions in binary form must reproduce the above copyright
12
 *       notice, this list of conditions and the following disclaimer in the
13
 *       documentation and/or other materials provided with the distribution.
14
 *     * Neither the name of Ajax.org B.V. nor the
15
 *       names of its contributors may be used to endorse or promote products
16
 *       derived from this software without specific prior written permission.
17
 * 
18
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * ***** END LICENSE BLOCK ***** */
30
31
define('ace/ext/textarea', ['require', 'exports', 'module' , 'ace/lib/event', 'ace/lib/useragent', 'ace/lib/net', 'ace/ace', 'ace/theme/textmate', 'ace/mode/text'], function(require, exports, module) {
32
33
34
var event = require("../lib/event");
35
var UA = require("../lib/useragent");
36
var net = require("../lib/net");
37
var ace = require("../ace");
38
39
require("../theme/textmate");
40
41
module.exports = exports = ace;
42
var getCSSProperty = function(element, container, property) {
43
    var ret = element.style[property];
44
45
    if (!ret) {
46
        if (window.getComputedStyle) {
47
            ret = window.getComputedStyle(element, '').getPropertyValue(property);
48
        } else {
49
            ret = element.currentStyle[property];
50
        }
51
    }
52
53
    if (!ret || ret == 'auto' || ret == 'intrinsic') {
54
        ret = container.style[property];
55
    }
56
    return ret;
57
};
58
59
function applyStyles(elm, styles) {
60
    for (var style in styles) {
61
        elm.style[style] = styles[style];
62
    }
63
}
64
65
function setupContainer(element, getValue) {
66
    if (element.type != 'textarea') {
67
        throw "Textarea required!";
68
    }
69
70
    var parentNode = element.parentNode;
71
    var container = document.createElement('div');
72
    var resizeEvent = function() {
73
        var style = 'position:relative;';
74
        [
75
            'margin-top', 'margin-left', 'margin-right', 'margin-bottom'
76
        ].forEach(function(item) {
77
            style += item + ':' +
78
                        getCSSProperty(element, container, item) + ';';
79
        });
80
        var width = getCSSProperty(element, container, 'width') || (element.clientWidth + "px");
81
        var height = getCSSProperty(element, container, 'height')  || (element.clientHeight + "px");
82
        style += 'height:' + height + ';width:' + width + ';';
83
        style += 'display:inline-block;';
84
        container.setAttribute('style', style);
85
    };
86
    event.addListener(window, 'resize', resizeEvent);
87
    resizeEvent();
88
    parentNode.insertBefore(container, element.nextSibling);
89
    while (parentNode !== document) {
90
        if (parentNode.tagName.toUpperCase() === 'FORM') {
91
            var oldSumit = parentNode.onsubmit;
92
            parentNode.onsubmit = function(evt) {
93
                element.value = getValue();
94
                if (oldSumit) {
95
                    oldSumit.call(this, evt);
96
                }
97
            };
98
            break;
99
        }
100
        parentNode = parentNode.parentNode;
101
    }
102
    return container;
103
}
104
105
exports.transformTextarea = function(element, loader) {
106
    var session;
107
    var container = setupContainer(element, function() {
108
        return session.getValue();
109
    });
110
    element.style.display = 'none';
111
    container.style.background = 'white';
112
    var editorDiv = document.createElement("div");
113
    applyStyles(editorDiv, {
114
        top: "0px",
115
        left: "0px",
116
        right: "0px",
117
        bottom: "0px",
118
        border: "1px solid gray",
119
        position: "absolute"
120
    });
121
    container.appendChild(editorDiv);
122
123
    var settingOpener = document.createElement("div");
124
    applyStyles(settingOpener, {
125
        position: "absolute",
126
        right: "0px",
127
        bottom: "0px",
128
        background: "red",
129
        cursor: "nw-resize",
130
        borderStyle: "solid",
131
        borderWidth: "9px 8px 10px 9px",
132
        width: "2px",
133
        borderColor: "lightblue gray gray lightblue",
134
        zIndex: 101
135
    });
136
137
    var settingDiv = document.createElement("div");
138
    var settingDivStyles = {
139
        top: "0px",
140
        left: "20%",
141
        right: "0px",
142
        bottom: "0px",
143
        position: "absolute",
144
        padding: "5px",
145
        zIndex: 100,
146
        color: "white",
147
        display: "none",
148
        overflow: "auto",
149
        fontSize: "14px",
150
        boxShadow: "-5px 2px 3px gray"
151
    };
152
    if (!UA.isOldIE) {
153
        settingDivStyles.backgroundColor = "rgba(0, 0, 0, 0.6)";
154
    } else {
155
        settingDivStyles.backgroundColor = "#333";
156
    }
157
158
    applyStyles(settingDiv, settingDivStyles);
159
    container.appendChild(settingDiv);
160
    var options = {};
161
162
    var editor = ace.edit(editorDiv);
163
    session = editor.getSession();
164
165
    session.setValue(element.value || element.innerHTML);
166
    editor.focus();
167
    container.appendChild(settingOpener);
168
    setupApi(editor, editorDiv, settingDiv, ace, options, loader);
169
    setupSettingPanel(settingDiv, settingOpener, editor, options);
170
    
171
    var state = "";
172
    event.addListener(settingOpener, "mousemove", function(e) {
173
        var rect = this.getBoundingClientRect();
174
        var x = e.clientX - rect.left, y = e.clientY - rect.top;
175
        if (x + y < (rect.width + rect.height)/2) {
176
            this.style.cursor = "pointer";
177
            state = "toggle";
178
        } else {
179
            state = "resize";
180
            this.style.cursor = "nw-resize";
181
        }
182
    });
183
    
184
    event.addListener(settingOpener, "mousedown", function(e) {
185
        if (state == "toggle") {
186
            editor.setDisplaySettings();
187
            return;
188
        }
189
        container.style.zIndex = 100000;
190
        var rect = container.getBoundingClientRect();
191
        var startX = rect.width  + rect.left - e.clientX;
192
        var startY = rect.height  + rect.top - e.clientY;
193
        event.capture(settingOpener, function(e) {
194
            container.style.width = e.clientX - rect.left + startX + "px";
195
            container.style.height = e.clientY - rect.top + startY + "px";
196
            editor.resize();
197
        }, function() {});
198
    });
199
200
    return editor;
201
};
202
203
function load(url, module, callback) {
204
    net.loadScript(url, function() {
205
        require([module], callback);
206
    });
207
}
208
209
function setupApi(editor, editorDiv, settingDiv, ace, options, loader) {
210
    var session = editor.getSession();
211
    var renderer = editor.renderer;
212
    loader = loader || load;
213
214
    function toBool(value) {
215
        return value === "true" || value == true;
216
    }
217
218
    editor.setDisplaySettings = function(display) {
219
        if (display == null)
220
            display = settingDiv.style.display == "none";
221
        if (display) {
222
            settingDiv.style.display = "block";
223
            settingDiv.hideButton.focus();
224
            editor.on("focus", function onFocus() {
225
                editor.removeListener("focus", onFocus);
226
                settingDiv.style.display = "none"
227
            });
228
        } else {
229
            editor.focus();
230
        };
231
    };
232
    
233
    editor.setOption = function(key, value) {
234
        if (options[key] == value) return;
235
236
        switch (key) {
237
            case "gutter":
238
                renderer.setShowGutter(toBool(value));
239
            break;
240
241
            case "mode":
242
                if (value != "text") {
243
                    loader("mode-" + value + ".js", "ace/mode/" + value, function() {
244
                        var aceMode = require("../mode/" + value).Mode;
245
                        session.setMode(new aceMode());
246
                    });
247
                } else {
248
                    session.setMode(new (require("../mode/text").Mode));
249
                }
250
            break;
251
252
            case "theme":
253
                if (value != "textmate") {
254
                    loader("theme-" + value + ".js", "ace/theme/" + value, function() {
255
                        editor.setTheme("ace/theme/" + value);
256
                    });
257
                } else {
258
                    editor.setTheme("ace/theme/textmate");
259
                }
260
            break;
261
262
            case "fontSize":
263
                editorDiv.style.fontSize = value;
264
            break;
265
266
            case "keybindings":
267
                switch (value) {
268
                    case "vim":
269
                        editor.setKeyboardHandler("ace/keyboard/vim");
270
                        break;
271
                    case "emacs": 
272
                        editor.setKeyboardHandler("ace/keyboard/emacs");
273
                        break;
274
                    default:
275
                        editor.setKeyboardHandler(null);
276
                }
277
            break;
278
279
            case "softWrap":
280
                switch (value) {
281
                    case "off":
282
                        session.setUseWrapMode(false);
283
                        renderer.setPrintMarginColumn(80);
284
                    break;
285
                    case "40":
286
                        session.setUseWrapMode(true);
287
                        session.setWrapLimitRange(40, 40);
288
                        renderer.setPrintMarginColumn(40);
289
                    break;
290
                    case "80":
291
                        session.setUseWrapMode(true);
292
                        session.setWrapLimitRange(80, 80);
293
                        renderer.setPrintMarginColumn(80);
294
                    break;
295
                    case "free":
296
                        session.setUseWrapMode(true);
297
                        session.setWrapLimitRange(null, null);
298
                        renderer.setPrintMarginColumn(80);
299
                    break;
300
                }
301
            break;
302
303
            case "useSoftTabs":
304
                session.setUseSoftTabs(toBool(value));
305
            break;
306
307
            case "showPrintMargin":
308
                renderer.setShowPrintMargin(toBool(value));
309
            break;
310
311
            case "showInvisibles":
312
                editor.setShowInvisibles(toBool(value));
313
            break;
314
        }
315
316
        options[key] = value;
317
    };
318
319
    editor.getOption = function(key) {
320
        return options[key];
321
    };
322
323
    editor.getOptions = function() {
324
        return options;
325
    };
326
327
    for (var option in exports.options) {
328
        editor.setOption(option, exports.options[option]);
329
    }
330
331
    return editor;
332
}
333
334
function setupSettingPanel(settingDiv, settingOpener, editor, options) {
335
    var BOOL = null;
336
337
    var desc = {
338
        mode:            "Mode:",
339
        gutter:          "Display Gutter:",
340
        theme:           "Theme:",
341
        fontSize:        "Font Size:",
342
        softWrap:        "Soft Wrap:",
343
        keybindings:     "Keyboard",
344
        showPrintMargin: "Show Print Margin:",
345
        useSoftTabs:     "Use Soft Tabs:",
346
        showInvisibles:  "Show Invisibles"
347
    };
348
349
    var optionValues = {
350
        mode: {
351
            text:       "Plain",
352
            javascript: "JavaScript",
353
            xml:        "XML",
354
            html:       "HTML",
355
            css:        "CSS",
356
            scss:       "SCSS",
357
            python:     "Python",
358
            php:        "PHP",
359
            java:       "Java",
360
            ruby:       "Ruby",
361
            c_cpp:      "C/C++",
362
            coffee:     "CoffeeScript",
363
            json:       "json",
364
            perl:       "Perl",
365
            clojure:    "Clojure",
366
            ocaml:      "OCaml",
367
            csharp:     "C#",
368
            haxe:       "haXe",
369
            svg:        "SVG",
370
            textile:    "Textile",
371
            groovy:     "Groovy",
372
            liquid:     "Liquid",
373
            Scala:      "Scala"
374
        },
375
        theme: {
376
            clouds:           "Clouds",
377
            clouds_midnight:  "Clouds Midnight",
378
            cobalt:           "Cobalt",
379
            crimson_editor:   "Crimson Editor",
380
            dawn:             "Dawn",
381
            eclipse:          "Eclipse",
382
            idle_fingers:     "Idle Fingers",
383
            kr_theme:         "Kr Theme",
384
            merbivore:        "Merbivore",
385
            merbivore_soft:   "Merbivore Soft",
386
            mono_industrial:  "Mono Industrial",
387
            monokai:          "Monokai",
388
            pastel_on_dark:   "Pastel On Dark",
389
            solarized_dark:   "Solarized Dark",
390
            solarized_light:  "Solarized Light",
391
            textmate:         "Textmate",
392
            twilight:         "Twilight",
393
            vibrant_ink:      "Vibrant Ink"
394
        },
395
        gutter: BOOL,
396
        fontSize: {
397
            "10px": "10px",
398
            "11px": "11px",
399
            "12px": "12px",
400
            "14px": "14px",
401
            "16px": "16px"
402
        },
403
        softWrap: {
404
            off:    "Off",
405
            40:     "40",
406
            80:     "80",
407
            free:   "Free"
408
        },
409
        keybindings: {
410
            ace: "ace",
411
            vim: "vim",
412
            emacs: "emacs"
413
        },
414
        showPrintMargin:    BOOL,
415
        useSoftTabs:        BOOL,
416
        showInvisibles:     BOOL
417
    };
418
419
    var table = [];
420
    table.push("<table><tr><th>Setting</th><th>Value</th></tr>");
421
422
    function renderOption(builder, option, obj, cValue) {
423
        if (!obj) {
424
            builder.push(
425
                "<input type='checkbox' title='", option, "' ",
426
                    cValue == "true" ? "checked='true'" : "",
427
               "'></input>"
428
            );
429
            return
430
        }
431
        builder.push("<select title='" + option + "'>");
432
        for (var value in obj) {
433
            builder.push("<option value='" + value + "' ");
434
435
            if (cValue == value) {
436
                builder.push(" selected ");
437
            }
438
439
            builder.push(">",
440
                obj[value],
441
                "</option>");
442
        }
443
        builder.push("</select>");
444
    }
445
446
    for (var option in options) {
447
        table.push("<tr><td>", desc[option], "</td>");
448
        table.push("<td>");
449
        renderOption(table, option, optionValues[option], options[option]);
450
        table.push("</td></tr>");
451
    }
452
    table.push("</table>");
453
    settingDiv.innerHTML = table.join("");
454
455
    var onChange = function(e) {
456
        var select = e.currentTarget;
457
        editor.setOption(select.title, select.value);
458
    };
459
    var onClick = function(e) {
460
        var cb = e.currentTarget;
461
        editor.setOption(cb.title, cb.checked);
462
    };
463
    var selects = settingDiv.getElementsByTagName("select");
464
    for (var i = 0; i < selects.length; i++)
465
        selects[i].onchange = onChange;
466
    var cbs = settingDiv.getElementsByTagName("input");
467
    for (var i = 0; i < cbs.length; i++)
468
        cbs[i].onclick = onClick;
469
470
471
    var button = document.createElement("input");
472
    button.type = "button";
473
    button.value = "Hide";
474
    event.addListener(button, "click", function() {
475
        editor.setDisplaySettings(false);
476
    });
477
    settingDiv.appendChild(button);
478
    settingDiv.hideButton = button;
479
}
480
exports.options = {
481
    mode:               "text",
482
    theme:              "textmate",
483
    gutter:             "false",
484
    fontSize:           "12px",
485
    softWrap:           "off",
486
    keybindings:        "ace",
487
    showPrintMargin:    "false",
488
    useSoftTabs:        "true",
489
    showInvisibles:     "false"
490
};
491
492
});