• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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