| 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 |