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
;