2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 importScript("cm/codemirror.js");
32 importScript("cm/css.js");
33 importScript("cm/javascript.js");
34 importScript("cm/xml.js");
35 importScript("cm/htmlmixed.js");
39 * @extends {WebInspector.View}
40 * @implements {WebInspector.TextEditor}
41 * @param {?string} url
42 * @param {WebInspector.TextEditorDelegate} delegate
44 WebInspector.CodeMirrorTextEditor = function(url, delegate)
46 WebInspector.View.call(this);
47 this._delegate = delegate;
50 this.registerRequiredCSS("codemirror.css");
51 this.registerRequiredCSS("cmdevtools.css");
53 this._codeMirror = window.CodeMirror(this.element, {
55 gutters: ["CodeMirror-linenumbers", "breakpoints"]
58 this._codeMirror.on("change", this._change.bind(this));
59 this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
60 this.element.addEventListener("contextmenu", this._contextMenu.bind(this));
62 this._lastRange = this.range();
64 this.element.firstChild.addStyleClass("source-code");
65 this.element.firstChild.addStyleClass("fill");
66 this._elementToWidget = new Map();
69 WebInspector.CodeMirrorTextEditor.prototype = {
71 * @param {string} mimeType
73 set mimeType(mimeType)
75 this._codeMirror.setOption("mode", mimeType);
77 case "text/html": this._codeMirror.setOption("theme", "web-inspector-html"); break;
78 case "text/css": this._codeMirror.setOption("theme", "web-inspector-css"); break;
79 case "text/javascript": this._codeMirror.setOption("theme", "web-inspector-js"); break;
84 * @param {boolean} readOnly
86 setReadOnly: function(readOnly)
88 this._codeMirror.setOption("readOnly", readOnly);
96 return !!this._codeMirror.getOption("readOnly");
102 defaultFocusedElement: function()
104 return this.element.firstChild;
109 this._codeMirror.focus();
112 beginUpdates: function() { },
114 endUpdates: function() { },
117 * @param {number} lineNumber
119 revealLine: function(lineNumber)
121 this._codeMirror.setCursor({ line: lineNumber, ch: 0 });
122 this._codeMirror.scrollIntoView();
125 _gutterClick: function(instance, lineNumber, gutter, event)
127 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
130 _contextMenu: function(event)
132 var contextMenu = new WebInspector.ContextMenu(event);
133 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
135 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
137 this._delegate.populateTextAreaContextMenu(contextMenu, null);
142 * @param {number} lineNumber
143 * @param {boolean} disabled
144 * @param {boolean} conditional
146 addBreakpoint: function(lineNumber, disabled, conditional)
148 var element = document.createElement("span");
149 element.textContent = lineNumber + 1;
150 element.className = "cm-breakpoint" + (disabled ? " cm-breakpoint-disabled" : "") + (conditional ? " cm-breakpoint-conditional" : "");
151 this._codeMirror.setGutterMarker(lineNumber, "breakpoints", element);
155 * @param {number} lineNumber
157 removeBreakpoint: function(lineNumber)
159 this._codeMirror.setGutterMarker(lineNumber, "breakpoints", null);
163 * @param {number} lineNumber
165 setExecutionLine: function(lineNumber)
167 this._executionLine = this._codeMirror.getLineHandle(lineNumber);
168 this._codeMirror.addLineClass(this._executionLine, null, "cm-execution-line");
171 clearExecutionLine: function()
173 if (this._executionLine)
174 this._codeMirror.removeLineClass(this._executionLine, null, "cm-execution-line");
175 delete this._executionLine;
179 * @param {number} lineNumber
180 * @param {Element} element
182 addDecoration: function(lineNumber, element)
184 var widget = this._codeMirror.addLineWidget(lineNumber, element);
185 this._elementToWidget.put(element, widget);
189 * @param {number} lineNumber
190 * @param {Element} element
192 removeDecoration: function(lineNumber, element)
194 var widget = this._elementToWidget.remove(element);
196 this._codeMirror.removeLineWidget(widget);
200 * @param {WebInspector.TextRange} range
202 markAndRevealRange: function(range)
205 this.setSelection(range);
209 * @param {number} lineNumber
211 highlightLine: function(lineNumber)
213 this.clearLineHighlight();
214 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
215 if (!this._highlightedLine)
217 this.revealLine(lineNumber);
218 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
219 this._clearHighlightTimeout = setTimeout(this.clearLineHighlight.bind(this), 2000);
222 clearLineHighlight: function()
224 if (this._clearHighlightTimeout)
225 clearTimeout(this._clearHighlightTimeout);
226 delete this._clearHighlightTimeout;
228 if (this._highlightedLine)
229 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
230 delete this._highlightedLine;
234 * @return {Array.<Element>}
236 elementsToRestoreScrollPositionsFor: function()
242 * @param {WebInspector.TextEditor} textEditor
244 inheritScrollPositions: function(textEditor)
250 this._codeMirror.refresh();
254 * @param {WebInspector.TextRange} range
255 * @param {string} text
256 * @return {WebInspector.TextRange}
258 editRange: function(range, text)
260 var pos = this._toPos(range);
261 this._codeMirror.replaceRange(text, pos.start, pos.end);
262 var newRange = this._toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
263 this._delegate.onTextChanged(range, newRange);
269 var widgets = this._elementToWidget.values();
270 for (var i = 0; i < widgets.length; ++i)
271 this._codeMirror.removeLineWidget(widgets[i]);
272 this._elementToWidget.clear();
274 var newRange = this.range();
275 this._delegate.onTextChanged(this._lastRange, newRange);
276 this._lastRange = newRange;
280 * @param {number} lineNumber
282 scrollToLine: function(lineNumber)
284 this._codeMirror.setCursor({line:lineNumber, ch:0});
288 * @return {WebInspector.TextRange}
290 selection: function(textRange)
292 var start = this._codeMirror.getCursor(true);
293 var end = this._codeMirror.getCursor(false);
295 if (start.line > end.line || (start.line == end.line && start.ch > end.ch))
296 return this._toRange(end, start);
298 return this._toRange(start, end);
302 * @return {WebInspector.TextRange?}
304 lastSelection: function()
306 return this._lastSelection;
310 * @param {WebInspector.TextRange} textRange
312 setSelection: function(textRange)
314 this._lastSelection = textRange;
315 var pos = this._toPos(textRange);
316 this._codeMirror.setSelection(pos.start, pos.end);
320 * @param {string} text
322 setText: function(text)
324 this._codeMirror.setValue(text);
332 return this._codeMirror.getValue();
336 * @return {WebInspector.TextRange}
340 var lineCount = this.linesCount;
341 var lastLine = this._codeMirror.getLine(lineCount - 1);
342 return this._toRange({ line: 0, ch: 0 }, { line: lineCount - 1, ch: lastLine.length });
346 * @param {number} lineNumber
349 line: function(lineNumber)
351 return this._codeMirror.getLine(lineNumber);
359 return this._codeMirror.lineCount();
363 * @param {number} line
364 * @param {string} name
365 * @param {Object?} value
367 setAttribute: function(line, name, value)
369 var handle = this._codeMirror.getLineHandle(line);
370 if (handle.attributes === undefined) handle.attributes = {};
371 handle.attributes[name] = value;
375 * @param {number} line
376 * @param {string} name
377 * @return {Object|null} value
379 getAttribute: function(line, name)
381 var handle = this._codeMirror.getLineHandle(line);
382 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
386 * @param {number} line
387 * @param {string} name
389 removeAttribute: function(line, name)
391 var handle = this._codeMirror.getLineHandle(line);
392 if (handle && handle.attributes)
393 delete handle.attributes[name];
396 _toPos: function(range)
399 start: {line: range.startLine, ch: range.startColumn},
400 end: {line: range.endLine, ch: range.endColumn}
404 _toRange: function(start, end)
406 return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
409 __proto__: WebInspector.View.prototype