2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 * Copyright (C) 2009 Apple Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
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
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.
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.
32 WebInspector.TextEditorHighlighter = function(textModel, damageCallback)
34 this._textModel = textModel;
35 this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/html");
36 this._damageCallback = damageCallback;
37 this._highlightChunkLimit = 1000;
40 WebInspector.TextEditorHighlighter.prototype = {
41 set mimeType(mimeType)
43 var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType);
45 this._tokenizer = tokenizer;
48 set highlightChunkLimit(highlightChunkLimit)
50 this._highlightChunkLimit = highlightChunkLimit;
53 highlight: function(endLine, opt_forceRun)
55 // First check if we have work to do.
56 var state = this._textModel.getAttribute(endLine - 1, "highlight");
57 if (state && state.postConditionStringified) {
58 // Last line is highlighted, just exit.
62 this._requestedEndLine = endLine;
64 if (this._highlightTimer && !opt_forceRun) {
65 // There is a timer scheduled, it will catch the new job based on the new endLine set.
69 // We will be highlighting. First rewind to the last highlighted line to gain proper highlighter context.
70 var startLine = endLine;
71 while (startLine > 0) {
72 var state = this._textModel.getAttribute(startLine - 1, "highlight");
73 if (state && state.postConditionStringified)
78 // Do small highlight synchronously. This will provide instant highlight on PageUp / PageDown, gentle scrolling.
79 this._highlightInChunks(startLine, endLine);
82 updateHighlight: function(startLine, endLine)
84 // Start line was edited, we should highlight everything until endLine.
85 this._clearHighlightState(startLine);
88 var state = this._textModel.getAttribute(startLine - 1, "highlight");
89 if (!state || !state.postConditionStringified) {
90 // Highlighter did not reach this point yet, nothing to update. It will reach it on subsequent timer tick and do the job.
95 var restored = this._highlightLines(startLine, endLine);
97 for (var i = this._lastHighlightedLine; i < this._textModel.linesCount; ++i) {
98 var state = this._textModel.getAttribute(i, "highlight");
99 if (!state && i > endLine)
101 this._textModel.setAttribute(i, "highlight-outdated", state);
102 this._textModel.removeAttribute(i, "highlight");
105 if (this._highlightTimer) {
106 clearTimeout(this._highlightTimer);
107 this._requestedEndLine = endLine;
108 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._lastHighlightedLine, this._requestedEndLine), 10);
114 _highlightInChunks: function(startLine, endLine)
116 delete this._highlightTimer;
118 // First we always check if we have work to do. Could be that user scrolled back and we can quit.
119 var state = this._textModel.getAttribute(this._requestedEndLine - 1, "highlight");
120 if (state && state.postConditionStringified)
123 if (this._requestedEndLine !== endLine) {
124 // User keeps updating the job in between of our timer ticks. Just reschedule self, don't eat CPU (they must be scrolling).
125 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, startLine, this._requestedEndLine), 100);
129 // The textModel may have been already updated.
130 if (this._requestedEndLine > this._textModel.linesCount)
131 this._requestedEndLine = this._textModel.linesCount;
133 this._highlightLines(startLine, this._requestedEndLine);
135 // Schedule tail highlight if necessary.
136 if (this._lastHighlightedLine < this._requestedEndLine)
137 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._lastHighlightedLine, this._requestedEndLine), 10);
140 _highlightLines: function(startLine, endLine)
142 // Restore highlighter context taken from previous line.
143 var state = this._textModel.getAttribute(startLine - 1, "highlight");
144 var postConditionStringified = state ? state.postConditionStringified : JSON.stringify(this._tokenizer.createInitialCondition());
147 for (var lineNumber = startLine; lineNumber < endLine; ++lineNumber) {
148 var state = this._selectHighlightState(lineNumber, postConditionStringified);
149 if (state.postConditionStringified) {
150 // This line is already highlighted.
151 postConditionStringified = state.postConditionStringified;
153 var lastHighlightedColumn = 0;
154 if (state.midConditionStringified) {
155 lastHighlightedColumn = state.lastHighlightedColumn;
156 postConditionStringified = state.midConditionStringified;
159 var line = this._textModel.line(lineNumber);
160 this._tokenizer.line = line;
161 this._tokenizer.condition = JSON.parse(postConditionStringified);
165 var newColumn = this._tokenizer.nextToken(lastHighlightedColumn);
166 var tokenType = this._tokenizer.tokenType;
168 state[lastHighlightedColumn] = { length: newColumn - lastHighlightedColumn, tokenType: tokenType };
169 lastHighlightedColumn = newColumn;
170 if (++tokensCount > this._highlightChunkLimit)
172 } while (lastHighlightedColumn < line.length);
174 postConditionStringified = JSON.stringify(this._tokenizer.condition);
176 if (lastHighlightedColumn < line.length) {
177 // Too much work for single chunk - exit.
178 state.lastHighlightedColumn = lastHighlightedColumn;
179 state.midConditionStringified = postConditionStringified;
182 delete state.lastHighlightedColumn;
183 delete state.midConditionStringified;
184 state.postConditionStringified = postConditionStringified;
188 var nextLineState = this._textModel.getAttribute(lineNumber + 1, "highlight");
189 if (nextLineState && nextLineState.preConditionStringified === state.postConditionStringified) {
190 // Following lines are up to date, no need re-highlight.
192 this._damageCallback(startLine, lineNumber);
194 // Advance the "pointer" to the last highlighted line within the given chunk.
195 for (; lineNumber < endLine; ++lineNumber) {
196 var state = this._textModel.getAttribute(lineNumber, "highlight");
197 if (!state || !state.postConditionStringified)
200 this._lastHighlightedLine = lineNumber;
205 this._damageCallback(startLine, lineNumber);
206 this._lastHighlightedLine = lineNumber;
210 _selectHighlightState: function(lineNumber, preConditionStringified)
212 var state = this._textModel.getAttribute(lineNumber, "highlight");
213 if (state && state.preConditionStringified === preConditionStringified)
216 var outdatedState = this._textModel.getAttribute(lineNumber, "highlight-outdated");
217 if (outdatedState && outdatedState.preConditionStringified === preConditionStringified) {
219 this._textModel.setAttribute(lineNumber, "highlight", outdatedState);
220 this._textModel.setAttribute(lineNumber, "highlight-outdated", state);
221 return outdatedState;
225 this._textModel.setAttribute(lineNumber, "highlight-outdated", state);
228 state.preConditionStringified = preConditionStringified;
229 this._textModel.setAttribute(lineNumber, "highlight", state);
233 _clearHighlightState: function(lineNumber)
235 this._textModel.removeAttribute(lineNumber, "highlight");
236 this._textModel.removeAttribute(lineNumber, "highlight-outdated");