OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2009 Google Inc. All rights reserved. | |
3 * Copyright (C) 2009 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 | |
32 /** | |
33 * @constructor | |
34 */ | |
35 WebInspector.TextEditorHighlighter = function(textModel, damageCallback) | |
36 { | |
37 this._textModel = textModel; | |
38 this._mimeType = "text/html"; | |
39 this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTok
enizer(this._mimeType); | |
40 this._damageCallback = damageCallback; | |
41 this._highlightChunkLimit = 1000; | |
42 this._highlightLineLimit = 500; | |
43 } | |
44 | |
45 WebInspector.TextEditorHighlighter._MaxLineCount = 10000; | |
46 | |
47 WebInspector.TextEditorHighlighter.prototype = { | |
48 | |
49 get mimeType() | |
50 { | |
51 return this._mimeType; | |
52 }, | |
53 | |
54 /** | |
55 * @param {string} mimeType | |
56 */ | |
57 set mimeType(mimeType) | |
58 { | |
59 var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getT
okenizer(mimeType); | |
60 if (tokenizer) { | |
61 this._tokenizer = tokenizer; | |
62 this._mimeType = mimeType; | |
63 } | |
64 }, | |
65 | |
66 set highlightChunkLimit(highlightChunkLimit) | |
67 { | |
68 this._highlightChunkLimit = highlightChunkLimit; | |
69 }, | |
70 | |
71 /** | |
72 * @param {number} highlightLineLimit | |
73 */ | |
74 setHighlightLineLimit: function(highlightLineLimit) | |
75 { | |
76 this._highlightLineLimit = highlightLineLimit; | |
77 }, | |
78 | |
79 /** | |
80 * @param {boolean=} forceRun | |
81 */ | |
82 highlight: function(endLine, forceRun) | |
83 { | |
84 if (this._textModel.linesCount > WebInspector.TextEditorHighlighter._Max
LineCount) | |
85 return; | |
86 | |
87 // First check if we have work to do. | |
88 var state = this._textModel.getAttribute(endLine - 1, "highlight"); | |
89 if (state && state.postConditionStringified) { | |
90 // Last line is highlighted, just exit. | |
91 return; | |
92 } | |
93 | |
94 this._requestedEndLine = endLine; | |
95 | |
96 if (this._highlightTimer && !forceRun) { | |
97 // There is a timer scheduled, it will catch the new job based on th
e new endLine set. | |
98 return; | |
99 } | |
100 | |
101 // We will be highlighting. First rewind to the last highlighted line to
gain proper highlighter context. | |
102 var startLine = endLine; | |
103 while (startLine > 0) { | |
104 state = this._textModel.getAttribute(startLine - 1, "highlight"); | |
105 if (state && state.postConditionStringified) | |
106 break; | |
107 startLine--; | |
108 } | |
109 | |
110 // Do small highlight synchronously. This will provide instant highlight
on PageUp / PageDown, gentle scrolling. | |
111 this._highlightInChunks(startLine, endLine); | |
112 }, | |
113 | |
114 updateHighlight: function(startLine, endLine) | |
115 { | |
116 if (this._textModel.linesCount > WebInspector.TextEditorHighlighter._Max
LineCount) | |
117 return; | |
118 | |
119 // Start line was edited, we should highlight everything until endLine. | |
120 this._clearHighlightState(startLine); | |
121 | |
122 if (startLine) { | |
123 var state = this._textModel.getAttribute(startLine - 1, "highlight")
; | |
124 if (!state || !state.postConditionStringified) { | |
125 // Highlighter did not reach this point yet, nothing to update.
It will reach it on subsequent timer tick and do the job. | |
126 return false; | |
127 } | |
128 } | |
129 | |
130 var restored = this._highlightLines(startLine, endLine); | |
131 if (!restored) { | |
132 for (var i = this._lastHighlightedLine; i < this._textModel.linesCou
nt; ++i) { | |
133 var state = this._textModel.getAttribute(i, "highlight"); | |
134 if (!state && i > endLine) | |
135 break; | |
136 this._textModel.setAttribute(i, "highlight-outdated", state); | |
137 this._textModel.removeAttribute(i, "highlight"); | |
138 } | |
139 | |
140 if (this._highlightTimer) { | |
141 clearTimeout(this._highlightTimer); | |
142 this._requestedEndLine = endLine; | |
143 this._highlightTimer = setTimeout(this._highlightInChunks.bind(t
his, this._lastHighlightedLine, this._requestedEndLine), 10); | |
144 } | |
145 } | |
146 return restored; | |
147 }, | |
148 | |
149 _highlightInChunks: function(startLine, endLine) | |
150 { | |
151 delete this._highlightTimer; | |
152 | |
153 // First we always check if we have work to do. Could be that user scrol
led back and we can quit. | |
154 var state = this._textModel.getAttribute(this._requestedEndLine - 1, "hi
ghlight"); | |
155 if (state && state.postConditionStringified) | |
156 return; | |
157 | |
158 if (this._requestedEndLine !== endLine) { | |
159 // User keeps updating the job in between of our timer ticks. Just r
eschedule self, don't eat CPU (they must be scrolling). | |
160 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this,
startLine, this._requestedEndLine), 100); | |
161 return; | |
162 } | |
163 | |
164 // The textModel may have been already updated. | |
165 if (this._requestedEndLine > this._textModel.linesCount) | |
166 this._requestedEndLine = this._textModel.linesCount; | |
167 | |
168 this._highlightLines(startLine, this._requestedEndLine); | |
169 | |
170 // Schedule tail highlight if necessary. | |
171 if (this._lastHighlightedLine < this._requestedEndLine) | |
172 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this,
this._lastHighlightedLine, this._requestedEndLine), 10); | |
173 }, | |
174 | |
175 _highlightLines: function(startLine, endLine) | |
176 { | |
177 // Restore highlighter context taken from previous line. | |
178 var state = this._textModel.getAttribute(startLine - 1, "highlight"); | |
179 var postConditionStringified = state ? state.postConditionStringified :
JSON.stringify(this._tokenizer.createInitialCondition()); | |
180 | |
181 var tokensCount = 0; | |
182 for (var lineNumber = startLine; lineNumber < endLine; ++lineNumber) { | |
183 state = this._selectHighlightState(lineNumber, postConditionStringif
ied); | |
184 if (state.postConditionStringified) { | |
185 // This line is already highlighted. | |
186 postConditionStringified = state.postConditionStringified; | |
187 } else { | |
188 var lastHighlightedColumn = 0; | |
189 if (state.midConditionStringified) { | |
190 lastHighlightedColumn = state.lastHighlightedColumn; | |
191 postConditionStringified = state.midConditionStringified; | |
192 } | |
193 | |
194 var line = this._textModel.line(lineNumber); | |
195 this._tokenizer.line = line; | |
196 this._tokenizer.condition = JSON.parse(postConditionStringified)
; | |
197 | |
198 // Highlight line. | |
199 state.ranges = state.ranges || []; | |
200 state.braces = state.braces || []; | |
201 do { | |
202 var newColumn = this._tokenizer.nextToken(lastHighlightedCol
umn); | |
203 var tokenType = this._tokenizer.tokenType; | |
204 if (tokenType && lastHighlightedColumn < this._highlightLine
Limit) { | |
205 if (tokenType === "brace-start" || tokenType === "brace-
end" || tokenType === "block-start" || tokenType === "block-end") { | |
206 state.braces.push({ | |
207 startColumn: lastHighlightedColumn, | |
208 endColumn: newColumn - 1, | |
209 token: tokenType | |
210 }); | |
211 } else { | |
212 state.ranges.push({ | |
213 startColumn: lastHighlightedColumn, | |
214 endColumn: newColumn - 1, | |
215 token: tokenType | |
216 }); | |
217 } | |
218 } | |
219 lastHighlightedColumn = newColumn; | |
220 if (++tokensCount > this._highlightChunkLimit) | |
221 break; | |
222 } while (lastHighlightedColumn < line.length); | |
223 | |
224 postConditionStringified = JSON.stringify(this._tokenizer.condit
ion); | |
225 | |
226 if (lastHighlightedColumn < line.length) { | |
227 // Too much work for single chunk - exit. | |
228 state.lastHighlightedColumn = lastHighlightedColumn; | |
229 state.midConditionStringified = postConditionStringified; | |
230 break; | |
231 } else { | |
232 delete state.lastHighlightedColumn; | |
233 delete state.midConditionStringified; | |
234 state.postConditionStringified = postConditionStringified; | |
235 } | |
236 } | |
237 | |
238 var nextLineState = this._textModel.getAttribute(lineNumber + 1, "hi
ghlight"); | |
239 if (nextLineState && nextLineState.preConditionStringified === state
.postConditionStringified) { | |
240 // Following lines are up to date, no need re-highlight. | |
241 ++lineNumber; | |
242 this._damageCallback(startLine, lineNumber); | |
243 | |
244 // Advance the "pointer" to the last highlighted line within the
given chunk. | |
245 for (; lineNumber < endLine; ++lineNumber) { | |
246 state = this._textModel.getAttribute(lineNumber, "highlight"
); | |
247 if (!state || !state.postConditionStringified) | |
248 break; | |
249 } | |
250 this._lastHighlightedLine = lineNumber; | |
251 return true; | |
252 } | |
253 } | |
254 | |
255 this._damageCallback(startLine, lineNumber); | |
256 this._lastHighlightedLine = lineNumber; | |
257 return false; | |
258 }, | |
259 | |
260 _selectHighlightState: function(lineNumber, preConditionStringified) | |
261 { | |
262 var state = this._textModel.getAttribute(lineNumber, "highlight"); | |
263 if (state && state.preConditionStringified === preConditionStringified) | |
264 return state; | |
265 | |
266 var outdatedState = this._textModel.getAttribute(lineNumber, "highlight-
outdated"); | |
267 if (outdatedState && outdatedState.preConditionStringified === preCondit
ionStringified) { | |
268 // Swap states. | |
269 this._textModel.setAttribute(lineNumber, "highlight", outdatedState)
; | |
270 this._textModel.setAttribute(lineNumber, "highlight-outdated", state
); | |
271 return outdatedState; | |
272 } | |
273 | |
274 if (state) | |
275 this._textModel.setAttribute(lineNumber, "highlight-outdated", state
); | |
276 | |
277 state = {}; | |
278 state.preConditionStringified = preConditionStringified; | |
279 this._textModel.setAttribute(lineNumber, "highlight", state); | |
280 return state; | |
281 }, | |
282 | |
283 _clearHighlightState: function(lineNumber) | |
284 { | |
285 this._textModel.removeAttribute(lineNumber, "highlight"); | |
286 this._textModel.removeAttribute(lineNumber, "highlight-outdated"); | |
287 } | |
288 } | |
OLD | NEW |