1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 * This class stores the filter queries as history and highlight the log text
6 * to increase the readability of the log
8 * - Enable / Disable highlights
9 * - Highlights text with multiple colors
10 * - Resolve hghlight conficts (A text highlighted by multiple colors) so that
11 * the latest added highlight always has highest priority to display.
14 var CrosLogMarker = (function() {
17 // Special classes (defined in log_visualizer_view.css)
18 var LOG_MARKER_HIGHLIGHT_CLASS = 'cros-log-visualizer-marker-highlight';
19 var LOG_MARKER_CONTAINER_ID = 'cros-log-visualizer-marker-container';
20 var LOG_MARKER_HISTORY_ENTRY_CLASS =
21 'cros-log-visualizer-marker-history-entry';
22 var LOG_MARKER_HISTORY_COLOR_TAG_CLASS =
23 'cros-log-visualizer-marker-history-color-tag';
26 * Colors used for highlighting. (Current we support 6 colors)
27 * TODO(shinfan): Add more supoorted colors.
29 var COLOR_USAGE_SET = {
32 'DarkSeaGreen': false,
37 var COLOR_NUMBER = Object.keys(COLOR_USAGE_SET).length;
41 * CrosHighlightTag represents a single highlight tag in text.
43 var CrosHighlightTag = (function() {
47 function CrosHighlightTag(color, field, range, priority) {
51 this.priority = priority;
55 return CrosHighlightTag;
60 * @param {CrosLogVisualizerView} logVisualizerView A reference to
61 * CrosLogVisualizerView.
63 function CrosLogMarker(logVisualizerView) {
64 this.container = $(LOG_MARKER_CONTAINER_ID);
65 // Stores highlight objects for each entry.
66 this.entryHighlights = [];
67 // Stores all the filter queries.
68 this.markHistory = {};
69 // Object references from CrosLogVisualizerView.
70 this.logEntries = logVisualizerView.logEntries;
71 this.logVisualizerView = logVisualizerView;
72 // Counts how many highlights are created.
74 for (var i = 0; i < this.logEntries.length; i++) {
75 this.entryHighlights.push([]);
79 CrosLogMarker.prototype = {
81 * Saves the query to the mark history and highlights the text
84 addMarkHistory: function(query) {
85 // Increases the counter
88 // Find an avaiable color.
89 var color = this.pickColor();
91 // If all colors are occupied.
92 alert('You can only add at most ' + COLOR_NUMBER + 'markers.');
96 // Updates HTML elements.
97 var historyEntry = addNode(this.container, 'div');
98 historyEntry.className = LOG_MARKER_HISTORY_ENTRY_CLASS;
100 // A color tag that indicats the color used.
101 var colorTag = addNode(historyEntry, 'div');
102 colorTag.className = LOG_MARKER_HISTORY_COLOR_TAG_CLASS;
103 colorTag.style.background = color;
105 // Displays the query text.
106 var queryText = addNodeWithText(historyEntry, 'p', query);
107 queryText.style.color = color;
109 // Adds a button to remove the marker.
110 var removeBtn = addNodeWithText(historyEntry, 'a', 'Remove');
111 removeBtn.addEventListener(
112 'click', this.onRemoveBtnClicked_.bind(this, historyEntry, color));
114 // A checkbox that lets user enable and disable the marker.
115 var enableCheckbox = addNode(historyEntry, 'input');
116 enableCheckbox.type = 'checkbox';
117 enableCheckbox.checked = true;
118 enableCheckbox.color = color;
119 enableCheckbox.addEventListener('change',
120 this.onEnableCheckboxChange_.bind(this, enableCheckbox), false);
122 // Searches log text for matched patterns and highlights them.
123 this.patternMatch(query, color);
127 * Search the text for matched strings
129 patternMatch: function(query, color) {
130 var pattern = new RegExp(query, 'i');
131 for (var i = 0; i < this.logEntries.length; i++) {
132 var entry = this.logEntries[i];
133 // Search description of each log entry
134 // TODO(shinfan): Add more search fields
135 var positions = this.findPositions(
136 pattern, entry.description);
137 for (var j = 0; j < positions.length; j++) {
138 var pos = positions[j];
139 this.mark(entry, pos, 'description', color);
141 this.sortHighlightsByStartPosition_(this.entryHighlights[i]);
146 * Highlights the text.
147 * @param {CrosLogEntry} entry The log entry to be highlighted
148 * @param {int|Array} position [start, end]
149 * @param {string} field The field of entry to be highlighted
150 * @param {string} color color used for highlighting
152 mark: function(entry, position, field, color) {
153 // Creates the highlight object
154 var tag = new CrosHighlightTag(color, field, position, this.markCount);
155 // Add the highlight into entryHighlights
156 this.entryHighlights[entry.rowNum].push(tag);
160 * Find the highlight objects that covers the given position
161 * @param {CrosHighlightTag|Array} highlights highlights of a log entry
162 * @param {int} position The target index
163 * @param {string} field The target field
164 * @return {CrosHighlightTag|Array} Highlights that cover the position
166 getHighlight: function(highlights, index, field) {
168 for (var j = 0; j < highlights.length; j++) {
169 var highlight = highlights[j];
170 if (highlight.range[0] <= index &&
171 highlight.range[1] > index &&
172 highlight.field == field &&
178 * Sorts the result by priority so that the highlight with
179 * highest priority comes first.
181 this.sortHighlightsByPriority_(res);
186 * This function highlights the entry by going through the text from left
187 * to right and searching for "key" positions.
188 * A "key" position is a position that one (or more) highlight
189 * starts or ends. We only care about "key" positions because this is where
190 * the text highlight status changes.
191 * At each key position, the function decides if the text between this
192 * position and previous position need to be highlighted and resolves
193 * highlight conflicts.
195 * @param {CrosLogEntry} entry The entry going to be highlighted.
196 * @param {string} field The specified field of the entry.
197 * @param {DOMElement} parent Parent node.
199 getHighlightedEntry: function(entry, field, parent) {
200 var rowNum = entry.rowNum;
201 // Get the original text content of the entry (without any highlights).
202 var content = this.logEntries[rowNum][field];
204 while (index < content.length) {
205 var nextIndex = this.getNextIndex(
206 this.entryHighlights[rowNum], index, field, content);
207 // Searches for highlights that have the position in range.
208 var highlights = this.getHighlight(
209 this.entryHighlights[rowNum], index, field);
210 var text = content.substr(index, nextIndex - index);
211 if (highlights.length > 0) {
212 // Always picks the highlight with highest priority.
213 this.addSpan(text, highlights[0].color, parent);
215 addNodeWithText(parent, 'span', text);
222 * A helper function that is used by this.getHightlightedEntry
223 * It returns the first index where a highlight begins or ends from
225 * @param {CrosHighlightTag|Array} highlights An array of highlights
227 * @param {int} index The start position.
228 * @param {string} field The specified field of entry.
229 * Other fields are ignored.
230 * @param {string} content The text content of the log entry.
231 * @return {int} The first index where a highlight begins or ends.
233 getNextIndex: function(highlights, index, field, content) {
234 var minGap = Infinity;
236 for (var i = 0; i < highlights.length; i++) {
237 if (highlights[i].field != field || !highlights[i].enabled)
239 // Distance between current index and the start index of highlight.
240 var gap1 = highlights[i].range[0] - index;
241 // Distance between current index and the end index of highlight.
242 var gap2 = highlights[i].range[1] - index;
243 if (gap1 > 0 && gap1 < minGap) {
245 res = highlights[i].range[0];
247 if (gap2 > 0 && gap2 < minGap) {
249 res = highlights[i].range[1];
252 // Returns |res| if found. Otherwise returns the end position of the text.
253 return res > 0 ? res : content.length;
257 * A helper function that is used by this.getHightlightedEntry.
258 * It adds the HTML label to the text.
260 addSpan: function(text, color, parent) {
261 var span = addNodeWithText(parent, 'span', text);
262 span.style.color = color;
263 span.className = LOG_MARKER_HIGHLIGHT_CLASS;
267 * A helper function that is used by this.getHightlightedEntry.
268 * It adds the HTML label to the text.
270 pickColor: function() {
271 for (var color in COLOR_USAGE_SET) {
272 if (!COLOR_USAGE_SET[color]) {
273 COLOR_USAGE_SET[color] = true;
281 * A event handler that enables and disables the corresponding marker.
284 onEnableCheckboxChange_: function(checkbox) {
285 for (var i = 0; i < this.entryHighlights.length; i++) {
286 for (var j = 0; j < this.entryHighlights[i].length; j++) {
287 if (this.entryHighlights[i][j].color == checkbox.color) {
288 this.entryHighlights[i][j].enabled = checkbox.checked;
292 this.refreshLogTable();
296 * A event handlier that removes the marker from history.
299 onRemoveBtnClicked_: function(entry, color) {
300 entry.parentNode.removeChild(entry);
301 COLOR_USAGE_SET[color] = false;
302 for (var i = 0; i < this.entryHighlights.length; i++) {
303 var highlights = this.entryHighlights[i];
305 var index = this.findHighlightByColor_(highlights, color);
308 highlights.splice(index, 1);
311 this.refreshLogTable();
315 * A helper function that returns the index of first highlight that
316 * has the target color. Otherwise returns -1.
319 findHighlightByColor_: function(highlights, color) {
320 for (var i = 0; i < highlights.length; i++) {
321 if (highlights[i].color == color)
328 * Refresh the log table in the CrosLogVisualizerView.
330 refreshLogTable: function() {
331 this.logVisualizerView.populateTable();
332 this.logVisualizerView.filterLog();
336 * A pattern can appear multiple times in a string.
337 * Returns positions of all the appearance.
339 findPositions: function(pattern, str) {
341 str = str.toLowerCase();
342 var match = str.match(pattern);
345 for (var i = 0; i < match.length; i++) {
348 var start = str.indexOf(match[i].toLowerCase(), index);
351 var end = start + match[i].length;
352 res.push([start, end]);
360 * A helper function used in sorting highlights by start position.
361 * @param {HighlightTag} h1, h2 Two highlight tags in the array.
364 compareStartPosition_: function(h1, h2) {
365 return h1.range[0] - h2.range[0];
369 * A helper function used in sorting highlights by priority.
370 * @param {HighlightTag} h1, h2 Two highlight tags in the array.
373 comparePriority_: function(h1, h2) {
374 return h2.priority - h1.priority;
378 * A helper function that sorts the highlights array by start position.
381 sortHighlightsByStartPosition_: function(highlights) {
382 highlights.sort(this.compareStartPosition_);
386 * A helper function that sorts the highlights array by priority.
389 sortHighlightsByPriority_: function(highlights) {
390 highlights.sort(this.comparePriority_);
394 return CrosLogMarker;