1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
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
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32importScript("ace/ace.js");
33
34
35/**
36 * @constructor
37 * @extends {WebInspector.View}
38 * @implements {WebInspector.TextEditor}
39 * @param {?string} url
40 * @param {WebInspector.TextEditorDelegate} delegate
41 */
42
43WebInspector.AceTextEditor = function(url, delegate)
44{
45    WebInspector.View.call(this);
46    this._delegate = delegate;
47    this._url = url;
48    this.element.className = "ace-editor-container source-code";
49
50    var prefix = window.flattenImports ? "" : "ace/";
51    ace.config.setModuleUrl("ace/mode/javascript", prefix + "mode_javascript.js");
52    ace.config.setModuleUrl("ace/mode/javascript", prefix + "mode_css.js");
53    ace.config.setModuleUrl("ace/mode/javascript", prefix + "mode_html.js");
54    ace.config.setModuleUrl("ace/theme/textmate", prefix + "theme_textmate.js");
55    this._aceEditor = window.ace.edit(this.element);
56
57    this._aceEditor.setShowFoldWidgets(false);
58    this._aceEditor.session.setFixedGutterWidth(true);
59    this._aceEditor.on("gutterclick", this._gutterClick.bind(this));
60    this._aceEditor.on("change", this._onTextChange.bind(this));
61    this._aceEditor.setHighlightActiveLine(false);
62    this._aceEditor.session.setUseWorker(false);
63    this.registerRequiredCSS("ace/acedevtools.css");
64    this._attributes = [];
65}
66
67WebInspector.AceTextEditor.prototype = {
68
69    _updateBreakpoints: function()
70    {
71        this._aceEditor.session.clearBreakpoints();
72        for(var i in this._attributes) {
73            var breakpoint = this.getAttribute(i, "ace_breakpoint");
74            if (!breakpoint)
75                continue;
76            var className = breakpoint.conditional ? "webkit-breakpoint-conditional" : "webkit-breakpoint";
77            if (breakpoint.disabled) className += " webkit-breakpoint-disabled";
78            this._aceEditor.session.setBreakpoint(i, className);
79        }
80    },
81
82    _updateLineAttributes: function(delta) {
83        var range = delta.range;
84        var length, insertionIndex;
85
86        if (range.end.row === range.start.row)
87            return;
88
89        if (delta.action === "insertText") {
90            length = range.end.row - range.start.row;
91            insertionIndex = range.start.column === 0 ? range.start.row: range.start.row + 1;
92        } else if (delta.action === "insertLines") {
93            length = range.end.row - range.start.row;
94            insertionIndex = range.start.row;
95        } else if (delta.action === "removeText") {
96            length = range.start.row - range.end.row;
97            insertionIndex = range.start.row;
98        } else if (delta.action === "removeLines") {
99            length = range.start.row - range.end.row;
100            insertionIndex = range.start.row;
101        }
102
103        if (length > 0) {
104            var spliceArguments = new Array(length);
105            spliceArguments.unshift(insertionIndex, 0);
106            Array.prototype.splice.apply(this._attributes, spliceArguments);
107        } else if (length < 0) {
108            this._attributes.splice(insertionIndex, -length);
109        }
110        this._updateBreakpoints();
111    },
112
113    _onTextChange: function(event)
114    {
115        this._updateLineAttributes(event.data);
116        this._delegate.onTextChanged(null, null);
117    },
118
119    _gutterClick: function(event)
120    {
121        var lineNumber = parseInt(event.domEvent.target.textContent) - 1;
122        this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event.domEvent });
123    },
124
125    /**
126     * @param {string} mimeType
127     */
128    set mimeType(mimeType)
129    {
130        switch(mimeType) {
131        case "text/html":
132            this._aceEditor.getSession().setMode("ace/mode/html");
133            break;
134        case "text/css":
135            this._aceEditor.getSession().setMode("ace/mode/css");
136            break;
137        case "text/javascript":
138            this._aceEditor.getSession().setMode("ace/mode/javascript");
139            break;
140        }
141    },
142
143    /**
144     * @param {boolean} readOnly
145     */
146    setReadOnly: function(readOnly)
147    {
148        this._aceEditor.setReadOnly(readOnly);
149    },
150
151    /**
152     * @return {boolean}
153     */
154    readOnly: function()
155    {
156        return this._aceEditor.getReadOnly();
157    },
158
159    focus: function()
160    {
161        this._aceEditor.focus();
162    },
163
164    /**
165     * @return {Element}
166     */
167    defaultFocusedElement: function()
168    {
169        return this.element.firstChild;
170    },
171
172    /**
173     * @param {string} regex
174     * @param {string} cssClass
175     * @return {WebInspector.TextEditorMainPanel.HighlightDescriptor}
176     */
177    highlightRegex: function(regex, cssClass)
178    {
179        console.log("aceEditor.highlightRegex not implemented");
180    },
181
182    /**
183     * @param {WebInspector.TextRange} range
184     * @param {string} cssClass
185     */
186    highlightRange: function(range, cssClass)
187    {
188        console.log("aceEditor.highlightRange not implemented");
189    },
190
191    /**
192     * @param {WebInspector.TextEditorMainPanel.HighlightDescriptor} highlightDescriptor
193     */
194    removeHighlight: function(highlightDescriptor)
195    {
196        console.log("aceEditor.removeHighlight not implemented");
197    },
198
199    /**
200     * @param {number} lineNumber
201     */
202    revealLine: function(lineNumber) {
203        this._aceEditor.scrollToLine(lineNumber, false, true);
204    },
205
206    /**
207     * @param {number} lineNumber
208     * @param {boolean} disabled
209     * @param {boolean} conditional
210     */
211    addBreakpoint: function(lineNumber, disabled, conditional)
212    {
213        this.setAttribute(lineNumber, "ace_breakpoint", {
214            disabled: disabled,
215            conditional: conditional
216        });
217        this._updateBreakpoints();
218    },
219
220    /**
221     * @param {number} lineNumber
222     */
223    removeBreakpoint: function(lineNumber)
224    {
225        this.removeAttribute(lineNumber, "ace_breakpoint");
226        this._updateBreakpoints();
227    },
228
229    /**
230     * @param {number} lineNumber
231     */
232    setExecutionLine: function(lineNumber)
233    {
234        this._executionLine = lineNumber;
235        const Range = ace.require('ace/range').Range;
236        this._executionLineMarker = this._aceEditor.session.addMarker(new Range(lineNumber, 0, lineNumber, Infinity), "webkit-execution-line", "fullLine");
237        this._aceEditor.session.addGutterDecoration(lineNumber, "webkit-gutter-execution-line");
238    },
239
240    /**
241     * @param {WebInspector.TextRange} range
242     * @return {string}
243     */
244    copyRange: function(range)
245    {
246        console.log("aceEditor.copyRange not implemented");
247        return "";
248    },
249
250    clearExecutionLine: function()
251    {
252        this._aceEditor.session.removeMarker(this._executionLineMarker);
253        this._aceEditor.session.removeGutterDecoration(this._executionLine, "webkit-gutter-execution-line");
254    },
255
256    /**
257     * @param {number} lineNumber
258     * @param {Element} element
259     */
260    addDecoration: function(lineNumber, element)
261    {
262        console.log("aceEditor.addDecoration not implemented");
263    },
264
265    /**
266     * @param {number} lineNumber
267     * @param {Element} element
268     */
269    removeDecoration: function(lineNumber, element)
270    {
271        console.log("aceEditor.removeDecoration not implemented");
272    },
273
274    /**
275     * @param {WebInspector.TextRange} range
276     */
277    markAndRevealRange: function(range)
278    {
279        console.log("aceEditor.markAndRevealRange not implemented");
280    },
281
282    /**
283     * @param {number} lineNumber
284     */
285    highlightLine: function(lineNumber)
286    {
287        console.log("aceEditor.highlightLine not implemented");
288    },
289
290    clearLineHighlight: function() {
291        console.log("aceEditor.clearLineHighlight not implemented");
292    },
293
294    /**
295     * @return {Array.<Element>}
296     */
297    elementsToRestoreScrollPositionsFor: function()
298    {
299        return [];
300    },
301
302    /**
303     * @param {WebInspector.TextEditor} textEditor
304     */
305    inheritScrollPositions: function(textEditor)
306    {
307        console.log("aceEditor.inheritScrollPositions not implemented");
308    },
309
310    beginUpdates: function() { },
311
312    endUpdates: function() { },
313
314    onResize: function() { },
315
316    /**
317     * @param {WebInspector.TextRange} range
318     * @param {string} text
319     * @return {WebInspector.TextRange}
320     */
321    editRange: function(range, text)
322    {
323        console.log("aceEditor.editRange not implemented");
324    },
325
326    /**
327     * @param {number} lineNumber
328     */
329    scrollToLine: function(lineNumber)
330    {
331        this._aceEditor.scrollToLine(lineNumber, false, true);
332    },
333
334    /**
335     * @return {WebInspector.TextRange}
336     */
337    selection: function()
338    {
339        console.log("aceEditor.selection not implemented");
340    },
341
342    /**
343     * @return {WebInspector.TextRange?}
344     */
345    lastSelection: function()
346    {
347        console.log("aceEditor.lastSelection not implemented");
348    },
349
350    /**
351     * @param {WebInspector.TextRange} textRange
352     */
353    setSelection: function(textRange)
354    {
355        this._aceEditor.scrollToLine(textRange.startLine, true);
356    },
357
358    /**
359     * @param {string} text
360     */
361    setText: function(text)
362    {
363        this._aceEditor.getSession().setValue(text);
364    },
365
366    /**
367     * @return {string}
368     */
369    text: function()
370    {
371        return this._aceEditor.getSession().getValue();
372    },
373
374    /**
375     * @return {WebInspector.TextRange}
376     */
377    range: function()
378    {
379        console.log("aceEditor.range not implemented");
380    },
381
382    /**
383     * @param {number} lineNumber
384     * @return {string}
385     */
386    line: function(lineNumber)
387    {
388        return this._aceEditor.getSession().getLine(lineNumber);
389    },
390
391    /**
392     * @return {number}
393     */
394    get linesCount() {
395        return this._aceEditor.getSession().getLength();
396    },
397
398    /**
399     * @param {number} line
400     * @param {string} name
401     * @return {Object|null} value
402     */
403    getAttribute: function(line, name)
404    {
405        var attrs = this._attributes[line];
406        return attrs ? attrs[name] : null;
407    },
408
409    /**
410     * @param {number} line
411     * @param {string} name
412     * @param {Object?} value
413     */
414    setAttribute: function(line, name, value)
415    {
416        var attrs = this._attributes[line];
417        if (!attrs) {
418            attrs = {};
419            this._attributes[line] = attrs;
420        }
421        attrs[name] = value;
422    },
423
424    /**
425     * @param {number} line
426     * @param {string} name
427     */
428    removeAttribute: function(line, name)
429    {
430        var attrs = this._attributes[line];
431        if (attrs)
432            delete attrs[name];
433    },
434
435    wasShown: function() { },
436
437    __proto__: WebInspector.View.prototype
438}
439