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 32WebInspector.TextEditorHighlighter = function(textModel, damageCallback) 33{ 34 this._textModel = textModel; 35 36 this._styles = []; 37 38 this._styles["css-comment"] = "rgb(0, 116, 0)"; 39 this._styles["css-params"] = "rgb(7, 144, 154)"; 40 this._styles["css-string"] = "rgb(7, 144, 154)"; 41 this._styles["css-keyword"] = "rgb(7, 144, 154)"; 42 this._styles["css-number"] = "rgb(50, 0, 255)"; 43 this._styles["css-property"] = "rgb(200, 0, 0)"; 44 this._styles["css-at-rule"] = "rgb(200, 0, 0)"; 45 this._styles["css-selector"] = "rgb(0, 0, 0)"; 46 this._styles["css-important"] = "rgb(200, 0, 180)"; 47 48 /* Keep this in sync with inspector.css and view-source.css */ 49 this._styles["html-tag"] = "rgb(136, 18, 128)"; 50 this._styles["html-attribute-name"] = "rgb(153, 69, 0)"; 51 this._styles["html-attribute-value"] = "rgb(26, 26, 166)"; 52 this._styles["html-comment"] = "rgb(35, 110, 37)"; 53 this._styles["html-doctype"] = "rgb(192, 192, 192)"; 54 this._styles["html-external-link"] = "#00e"; 55 this._styles["html-resource-link"] = "#00e"; 56 57 this._styles["javascript-comment"] = "rgb(0, 116, 0)"; 58 this._styles["javascript-string"] = "rgb(196, 26, 22)"; 59 this._styles["javascript-regexp"] = "rgb(196, 26, 22)"; 60 this._styles["javascript-keyword"] = "rgb(170, 13, 145)"; 61 this._styles["javascript-number"] = "rgb(28, 0, 207)"; 62 63 this.mimeType = "text/html"; 64 this._damageCallback = damageCallback; 65} 66 67WebInspector.TextEditorHighlighter.prototype = { 68 set mimeType(mimeType) 69 { 70 var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType); 71 if (tokenizer) 72 this._tokenizer = tokenizer; 73 }, 74 75 highlight: function(endLine) 76 { 77 // First check if we have work to do. 78 var state = this._textModel.getAttribute(endLine - 1, "highlighter-state") 79 if (state && !state.outOfDate) { 80 // Last line is highlighted, just exit. 81 return; 82 } 83 84 this._requestedEndLine = endLine; 85 86 if (this._highlightTimer) { 87 // There is a timer scheduled, it will catch the new job based on the new endLine set. 88 return; 89 } 90 91 // We will be highlighting. First rewind to the last highlighted line to gain proper highlighter context. 92 var startLine = endLine; 93 while (startLine > 0) { 94 var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); 95 if (state && !state.outOfDate) 96 break; 97 startLine--; 98 } 99 100 // Do small highlight synchronously. This will provide instant highlight on PageUp / PageDown, gentle scrolling. 101 var toLine = Math.min(startLine + 200, endLine); 102 this._highlightInChunks(startLine, toLine); 103 104 // Schedule tail highlight if necessary. 105 if (endLine > toLine) 106 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, toLine, endLine), 100); 107 }, 108 109 _highlightInChunks: function(startLine, endLine) 110 { 111 delete this._highlightTimer; 112 113 // First we always check if we have work to do. Could be that user scrolled back and we can quit. 114 var state = this._textModel.getAttribute(this._requestedEndLine - 1, "highlighter-state"); 115 if (state && !state.outOfDate) 116 return; 117 118 if (this._requestedEndLine !== endLine) { 119 // User keeps updating the job in between of our timer ticks. Just reschedule self, don't eat CPU (they must be scrolling). 120 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, startLine, this._requestedEndLine), 100); 121 return; 122 } 123 124 // Highlight 500 lines chunk. 125 var toLine = Math.min(startLine + 500, this._requestedEndLine); 126 this._highlightLines(startLine, toLine); 127 128 // Schedule tail highlight if necessary. 129 if (toLine < this._requestedEndLine) 130 this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, toLine, this._requestedEndLine), 10); 131 }, 132 133 updateHighlight: function(startLine, endLine) 134 { 135 // Start line was edited, we should highlight everything until endLine synchronously. 136 if (startLine) { 137 var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); 138 if (!state || state.outOfDate) { 139 // Highlighter did not reach this point yet, nothing to update. It will reach it on subsequent timer tick and do the job. 140 return; 141 } 142 } 143 144 var restored = this._highlightLines(startLine, endLine); 145 146 // Set invalidated flag to the subsequent lines. 147 for (var i = endLine; i < this._textModel.linesCount; ++i) { 148 var highlighterState = this._textModel.getAttribute(i, "highlighter-state"); 149 if (highlighterState) 150 highlighterState.outOfDate = !restored; 151 else 152 return; 153 } 154 }, 155 156 _highlightLines: function(startLine, endLine) 157 { 158 // Restore highlighter context taken from previous line. 159 var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); 160 if (state) 161 this._tokenizer.condition = state.postCondition; 162 else 163 this._tokenizer.condition = this._tokenizer.initialCondition; 164 165 for (var i = startLine; i < endLine; ++i) { 166 state = {}; 167 state.preCondition = this._tokenizer.condition; 168 state.attributes = {}; 169 170 this._lex(this._textModel.line(i), i, state.attributes); 171 172 state.postCondition = this._tokenizer.condition; 173 this._textModel.setAttribute(i, "highlighter-state", state); 174 175 var nextLineState = this._textModel.getAttribute(i + 1, "highlighter-state"); 176 if (nextLineState && this._tokenizer.hasCondition(nextLineState.preCondition)) { 177 // Following lines are up to date, no need re-highlight. 178 this._damageCallback(startLine, i + 1); 179 return true; 180 } 181 } 182 this._damageCallback(startLine, endLine); 183 return false; 184 }, 185 186 _lex: function(line, lineNumber, attributes) { 187 this._tokenizer.line = line; 188 var column = 0; 189 do { 190 var newColumn = this._tokenizer.nextToken(column); 191 var tokenType = this._tokenizer.tokenType; 192 if (tokenType) 193 attributes[column] = { length: newColumn - column, tokenType: tokenType, style: this._styles[tokenType] }; 194 column = newColumn; 195 } while (column < line.length) 196 } 197} 198