Revert "Revert "Add token highlighting to gr-diff"" This reverts commit e39103dcfd5333251cf4d1e24dc38e8e47ee6afb. Reason for revert: With fixed highlighting in unified diff. Change-Id: I02739e9a19a1bfa984568188c7b9f7031e01ede2
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts index 022492d..f7d5a59 100644 --- a/polygerrit-ui/app/api/diff.ts +++ b/polygerrit-ui/app/api/diff.ts
@@ -298,7 +298,8 @@ annotate( textElement: HTMLElement, lineNumberElement: HTMLElement, - line: GrDiffLine + line: GrDiffLine, + side: Side ): void; }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts index 76ebc50..96206f2 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -46,8 +46,9 @@ import {GrDiffLine, LineNumber} from '../gr-diff/gr-diff-line'; import {GrDiffGroup} from '../gr-diff/gr-diff-group'; import {PolymerSpliceChange} from '@polymer/polymer/interfaces'; -import {getLineNumber} from '../gr-diff/gr-diff-utils'; +import {getLineNumber, getSideByLineEl} from '../gr-diff/gr-diff-utils'; import {fireAlert, fireEvent} from '../../../utils/event-util'; +import {TokenHighlightLayer} from './token-highlight-layer'; const TRAILING_WHITESPACE_PATTERN = /\s+$/; @@ -249,6 +250,7 @@ this.$.rangeLayer, this.$.coverageLayerLeft, this.$.coverageLayerRight, + new TokenHighlightLayer(), ]; if (this.layers) { @@ -257,26 +259,6 @@ this._layers = layers; } - getLineElByChild(node?: Node): HTMLElement | null { - while (node) { - if (node instanceof Element) { - if (node.classList.contains('lineNum')) { - return node as HTMLElement; - } - if (node.classList.contains('section')) { - return null; - } - } - node = node.previousSibling ?? node.parentElement ?? undefined; - } - return null; - } - - getLineNumberByChild(node: Node) { - const lineEl = this.getLineElByChild(node); - return getLineNumber(lineEl); - } - getContentTdByLine(lineNumber: LineNumber, side?: Side, root?: Element) { if (!this._builder) return null; return this._builder.getContentTdByLine(lineNumber, side, root); @@ -293,7 +275,7 @@ if (!lineEl) return null; const line = getLineNumber(lineEl); if (!line) return null; - const side = this.getSideByLineEl(lineEl); + const side = getSideByLineEl(lineEl); // Performance optimization because we already have an element in the // correct row const row = this._getDiffRowByChild(lineEl); @@ -307,10 +289,6 @@ ); } - getSideByLineEl(lineEl: Element) { - return lineEl.classList.contains(Side.RIGHT) ? Side.RIGHT : Side.LEFT; - } - emitGroup(group: GrDiffGroup, sectionEl: HTMLElement) { if (!this._builder) return; this._builder.emitGroup(group, sectionEl);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts index e927fdf..2067455 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -122,7 +122,14 @@ Side.RIGHT ); row.appendChild(lineNumberEl); - row.appendChild(this._createTextEl(lineNumberEl, line)); + let side = undefined; + if (line.type === GrDiffLineType.ADD || line.type === GrDiffLineType.BOTH) { + side = Side.RIGHT; + } + if (line.type === GrDiffLineType.REMOVE) { + side = Side.LEFT; + } + row.appendChild(this._createTextEl(lineNumberEl, line, side)); return row; }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts index 3c8150c..65c991c 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -605,14 +605,14 @@ contentText.setAttribute('data-side', side); } - if (lineNumberEl) { + if (lineNumberEl && side) { for (const layer of this.layers) { if (typeof layer.annotate === 'function') { - layer.annotate(contentText, lineNumberEl, line); + layer.annotate(contentText, lineNumberEl, line, side); } } } else { - console.error('The lineNumberEl is null, skipping layer annotations.'); + console.error('lineNumberEl or side not set, skipping layer.annotate'); } td.appendChild(contentText);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts new file mode 100644 index 0000000..d0b3d3c --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -0,0 +1,259 @@ +/** + * @license + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {DiffLayer, DiffLayerListener} from '../../../types/types'; +import {GrDiffLine, Side} from '../../../api/diff'; +import {GrAnnotation} from '../gr-diff-highlight/gr-annotation'; +import {debounce, DelayedTask} from '../../../utils/async-util'; +import { + getLineNumberByChild, + lineNumberToNumber, +} from '../gr-diff/gr-diff-utils'; +import {appContext} from '../../../services/app-context'; +import {KnownExperimentId} from '../../../services/flags/flags'; + +const tokenMatcher = new RegExp(/[a-zA-Z0-9_-]+/g); + +/** CSS class for all tokens. */ +const CSS_TOKEN = 'token'; + +/** CSS class for the currently hovered token. */ +const CSS_HIGHLIGHT = 'token-highlight'; + +const UPDATE_TOKEN_TASK_DELAY_MS = 50; + +const LINE_LENGTH_LIMIT = 500; + +const TOKEN_LENGTH_LIMIT = 100; + +const TOKEN_COUNT_LIMIT = 10000; + +const TOKEN_OCCURRENCES_LIMIT = 1000; + +/** + * Token highlighting is only useful for code on-screen, so don't bother + * highlighting tokens that are further away than this threshold from where the + * user is hovering. + */ +const LINE_DISTANCE_THRESHOLD = 100; + +/** + * When a user hovers over a token in the diff, then this layer makes sure that + * all occurrences of this token are annotated with the 'token-highlight' css + * class. And removes that class when the user moves the mouse away from the + * token. + * + * The layer does not react to mouse events directly by adding a css class to + * the appropriate elements, but instead it just sets the currently highlighted + * token and notifies the diff renderer that certain lines must be re-rendered. + * And when that re-rendering happens the appropriate css class is added. + */ +export class TokenHighlightLayer implements DiffLayer { + /** The only listener is typically the renderer of gr-diff. */ + private listeners: DiffLayerListener[] = []; + + /** The currently highlighted token. */ + private currentHighlight?: string; + + /** + * The line of the currently highlighted token. We store this in order to + * re-render only relevant lines of the diff. Only lines visible on the screen + * need a highlight. For example in a file with 10,000 lines it is sufficient + * to just re-render the ~100 lines that are visible to the user. + * + * It is a known issue that we are only storing the line number on the side of + * where the user is hovering and we use that also to determine which line + * numbers to re-render on the other side, but it is non-trivial to look up or + * store a reliable mapping of line numbers, so we just accept this + * shortcoming with the reasoning that the user is mostly interested in the + * tokens on the side where they are hovering anyway. + * + * Another known issue is that we are not able to see past collapsed lines + * with the current implementation. + */ + private currentHighlightLineNumber = 0; + + /** + * Keeps track of where tokens occur in a file during rendering, so that it is + * easy to look up when processing mouse events. + */ + private tokenToLinesLeft = new Map<string, Set<number>>(); + + private tokenToLinesRight = new Map<string, Set<number>>(); + + private updateTokenTask?: DelayedTask; + + private readonly enabled = appContext.flagsService.isEnabled( + KnownExperimentId.TOKEN_HIGHLIGHTING + ); + + annotate( + el: HTMLElement, + _: HTMLElement, + line: GrDiffLine, + side: Side + ): void { + if (!this.enabled) return; + const text = el.textContent; + if (!text) return; + // Binary files encoded as text for example can have super long lines + // with super long tokens. Let's guard against against this scenario. + if (text.length > LINE_LENGTH_LIMIT) return; + let match; + let atLeastOneTokenMatched = false; + while ((match = tokenMatcher.exec(text))) { + const token = match[0]; + const index = match.index; + const length = token.length; + // Binary files encoded as text for example can have super long lines + // with super long tokens. Let's guard against this scenario. + if (length > TOKEN_LENGTH_LIMIT) continue; + atLeastOneTokenMatched = true; + const css = token === this.currentHighlight ? CSS_HIGHLIGHT : CSS_TOKEN; + // We add the tk-* class so that we can look up the token later easily + // even if the token element was split up into multiple smaller nodes. + GrAnnotation.annotateElement(el, index, length, `tk-${token} ${css}`); + // We could try to detect whether we are re-rendering instead of initially + // rendering the line. Then we would not have to call storeLineForToken() + // again. But since the Set swallows the duplicates we don't care. + this.storeLineForToken(token, line, side); + } + if (atLeastOneTokenMatched) { + // These listeners do not have to be cleaned, because listeners are + // garbage collected along with the element itself once it is not attached + // to the DOM anymore and no references exist anymore. + el.addEventListener('mouseover', this.handleMouseOver); + el.addEventListener('mouseout', this.handleMouseOut); + } + } + + private storeLineForToken(token: string, line: GrDiffLine, side: Side) { + const tokenToLines = + side === Side.LEFT ? this.tokenToLinesLeft : this.tokenToLinesRight; + // Just to make sure that we don't break down on large files. + if (tokenToLines.size > TOKEN_COUNT_LIMIT) return; + let numbers = tokenToLines.get(token); + if (!numbers) { + numbers = new Set<number>(); + tokenToLines.set(token, numbers); + } + // Just to make sure that we don't break down on large files. + if (numbers.size > TOKEN_OCCURRENCES_LIMIT) return; + const lineNumber = + side === Side.LEFT ? line.beforeNumber : line.afterNumber; + numbers.add(Number(lineNumber)); + } + + private readonly handleMouseOut = (e: MouseEvent) => { + if (!this.currentHighlight) return; + if (this.interferesWithSelection(e)) return; + const el = this.findTokenAncestor(e?.target); + if (!el) return; + this.updateTokenHighlight(undefined, undefined); + }; + + private readonly handleMouseOver = (e: MouseEvent) => { + if (this.interferesWithSelection(e)) return; + const {line, token} = this.findTokenAncestor(e?.target); + if (!token) return; + const oldHighlight = this.currentHighlight; + const newHighlight = token; + if (!newHighlight || newHighlight === oldHighlight) return; + if (this.countOccurrences(newHighlight) <= 1) return; + this.updateTokenHighlight(line, newHighlight); + }; + + private interferesWithSelection(e: MouseEvent) { + if (e.buttons > 0) return true; + if (window.getSelection()?.type === 'Range') return true; + return false; + } + + private updateTokenHighlight( + newLineNumber: number | undefined, + newHighlight: string | undefined + ) { + this.updateTokenTask = debounce( + this.updateTokenTask, + () => { + const oldHighlight = this.currentHighlight; + const oldLineNumber = this.currentHighlightLineNumber; + this.currentHighlight = newHighlight; + this.currentHighlightLineNumber = newLineNumber ?? 0; + this.notifyForToken(oldHighlight, oldLineNumber); + this.notifyForToken(newHighlight, newLineNumber ?? 0); + }, + UPDATE_TOKEN_TASK_DELAY_MS + ); + } + + findTokenAncestor( + el?: EventTarget | Element | null + ): { + token?: string; + line: number; + } { + if (!(el instanceof Element)) return {line: 0, token: undefined}; + if ( + el.classList.contains(CSS_TOKEN) || + el.classList.contains(CSS_HIGHLIGHT) + ) { + const tkClass = [...el.classList].find(c => c.startsWith('tk-')); + const line = lineNumberToNumber(getLineNumberByChild(el)); + if (!line || !tkClass) return {line: 0, token: undefined}; + return {line, token: tkClass.substring(3)}; + } + if (el.tagName === 'TD') return {line: 0, token: undefined}; + return this.findTokenAncestor(el.parentElement); + } + + countOccurrences(token: string | undefined) { + if (!token) return 0; + const linesLeft = this.tokenToLinesLeft.get(token); + const linesRight = this.tokenToLinesRight.get(token); + return (linesLeft?.size ?? 0) + (linesRight?.size ?? 0); + } + + notifyForToken(token: string | undefined, lineNumber: number) { + if (!token) return; + const linesLeft = this.tokenToLinesLeft.get(token); + linesLeft?.forEach(line => { + if (Math.abs(line - lineNumber) < LINE_DISTANCE_THRESHOLD) { + this.notifyListeners(line, Side.LEFT); + } + }); + const linesRight = this.tokenToLinesRight.get(token); + linesRight?.forEach(line => { + if (Math.abs(line - lineNumber) < LINE_DISTANCE_THRESHOLD) { + this.notifyListeners(line, Side.RIGHT); + } + }); + } + + addListener(listener: DiffLayerListener) { + this.listeners.push(listener); + } + + removeListener(listener: DiffLayerListener) { + this.listeners = this.listeners.filter(f => f !== listener); + } + + notifyListeners(line: number, side: Side) { + for (const listener of this.listeners) { + listener(line, line, side); + } + } +}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts index 76e02f0..ca003f3 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -28,7 +28,13 @@ import {GrSelectionActionBox} from '../gr-selection-action-box/gr-selection-action-box'; import {GrDiffBuilderElement} from '../gr-diff-builder/gr-diff-builder-element'; import {FILE} from '../gr-diff/gr-diff-line'; -import {getRange, getSide} from '../gr-diff/gr-diff-utils'; +import { + getLineElByChild, + getLineNumberByChild, + getRange, + getSide, + getSideByLineEl, +} from '../gr-diff/gr-diff-utils'; import {debounce, DelayedTask} from '../../../utils/async-util'; interface SidedRange { @@ -356,11 +362,11 @@ ): NormalizedPosition | null { let column; if (!node || !this.contains(node)) return null; - const lineEl = this.diffBuilder.getLineElByChild(node); + const lineEl = getLineElByChild(node); if (!lineEl) return null; - const side = this.diffBuilder.getSideByLineEl(lineEl); + const side = getSideByLineEl(lineEl); if (!side) return null; - const line = this.diffBuilder.getLineNumberByChild(lineEl); + const line = getLineNumberByChild(lineEl); if (!line || line === FILE || line === 'LOST') return null; const contentTd = this.diffBuilder.getContentTdByLineEl(lineEl); if (!contentTd) return null;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js index 07e83a8..18fbe9a 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js
@@ -438,7 +438,7 @@ const contentText = stubContent(140, 'left'); const contentTd = contentText.parentElement; - emulateSelection(contentTd.previousElementSibling, 0, + emulateSelection(contentTd.parentElement, 0, contentText.firstChild, 2); assert.isFalse(!!element.selectedRange); }); @@ -584,21 +584,6 @@ }); assert.equal(side, 'right'); }); - - test('_fixTripleClickSelection empty line', () => { - const startContent = stubContent(146, 'right'); - const endContent = stubContent(165, 'left'); - emulateSelection(startContent.firstChild, 0, - endContent.parentElement.previousElementSibling, 0); - const {range, side} = element.selectedRange; - assert.deepEqual(range, { - start_line: 146, - start_character: 0, - end_line: 146, - end_character: 84, - }); - assert.equal(side, 'right'); - }); }); });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts index 3df2e18..b64f61d 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
@@ -28,7 +28,12 @@ import {DiffInfo} from '../../../types/diff'; import {Side} from '../../../constants/constants'; import {GrDiffBuilderElement} from '../gr-diff-builder/gr-diff-builder-element'; -import {getSide, isThreadEl} from '../gr-diff/gr-diff-utils'; +import { + getLineElByChild, + getSide, + getSideByLineEl, + isThreadEl, +} from '../gr-diff/gr-diff-utils'; /** * Possible CSS classes indicating the state of selection. Dynamically added/ @@ -110,7 +115,7 @@ // Handle the down event on comment thread in Polymer 2 const handled = this._handleDownOnRangeComment(target); if (handled) return; - const lineEl = this.diffBuilder.getLineElByChild(target); + const lineEl = getLineElByChild(target); const blameSelected = this._elementDescendedFromClass(target, 'blame'); if (!lineEl && !blameSelected) { return; @@ -125,7 +130,7 @@ target, 'gr-comment' ); - const side = this.diffBuilder.getSideByLineEl(lineEl); + const side = getSideByLineEl(lineEl); targetClasses.push( side === 'left' ? SelectionClass.LEFT : SelectionClass.RIGHT @@ -179,9 +184,9 @@ if (this.classList.contains(SelectionClass.COMMENT)) { commentSelected = true; } - const lineEl = this.diffBuilder.getLineElByChild(target); + const lineEl = getLineElByChild(target); if (!lineEl) return; - const side = this.diffBuilder.getSideByLineEl(lineEl); + const side = getSideByLineEl(lineEl); const text = this._getSelectedText(side, commentSelected); if (text && e.clipboardData) { e.clipboardData.setData('Text', text); @@ -224,9 +229,9 @@ return this._getCommentLines(sel, side); } const range = normalize(sel.getRangeAt(0)); - const startLineEl = this.diffBuilder.getLineElByChild(range.startContainer); + const startLineEl = getLineElByChild(range.startContainer); if (!startLineEl) return; - const endLineEl = this.diffBuilder.getLineElByChild(range.endContainer); + const endLineEl = getLineElByChild(range.endContainer); // Happens when triple click in side-by-side mode with other side empty. const endsAtOtherEmptySide = !endLineEl &&
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js index 5c9fe3f..8d7264c 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js
@@ -143,8 +143,8 @@ test('applies selected-left on left side click', () => { element.classList.add('selected-right'); - element._cachedDiffBuilder.getSideByLineEl.returns('left'); - MockInteractions.down(element); + const lineNumberEl = element.querySelector('.lineNum.left'); + MockInteractions.down(lineNumberEl); assert.isTrue( element.classList.contains('selected-left'), 'adds selected-left'); assert.isFalse( @@ -154,8 +154,8 @@ test('applies selected-right on right side click', () => { element.classList.add('selected-left'); - element._cachedDiffBuilder.getSideByLineEl.returns('right'); - MockInteractions.down(element); + const lineNumberEl = element.querySelector('.lineNum.right'); + MockInteractions.down(lineNumberEl); assert.isTrue( element.classList.contains('selected-right'), 'adds selected-right'); assert.isFalse(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts index 0ca929a..96fbd8d 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
@@ -43,6 +43,36 @@ return range.end_line - range.start_line > 10; } +export function getLineNumberByChild(node?: Node) { + return getLineNumber(getLineElByChild(node)); +} + +export function lineNumberToNumber(lineNumber?: LineNumber | null): number { + if (!lineNumber) return 0; + if (lineNumber === 'LOST') return 0; + if (lineNumber === 'FILE') return 0; + return lineNumber; +} + +export function getLineElByChild(node?: Node): HTMLElement | null { + while (node) { + if (node instanceof Element) { + if (node.classList.contains('lineNum')) { + return node as HTMLElement; + } + if (node.classList.contains('section')) { + return null; + } + } + node = node.previousSibling ?? node.parentElement ?? undefined; + } + return null; +} + +export function getSideByLineEl(lineEl: Element) { + return lineEl.classList.contains(Side.RIGHT) ? Side.RIGHT : Side.LEFT; +} + export function getLineNumber(lineEl?: Element | null): LineNumber | null { if (!lineEl) return null; const lineNumberStr = lineEl.getAttribute('data-value');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts index 41dd159..3b76698 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -29,6 +29,7 @@ import {LineNumber} from './gr-diff-line'; import { getLine, + getLineElByChild, getLineNumber, getRange, getSide, @@ -546,7 +547,7 @@ el.classList.contains('content') || el.classList.contains('contentText') ) { - const target = this.$.diffBuilder.getLineElByChild(el); + const target = getLineElByChild(el); if (target) { this._selectLine(target); }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts index a93d5b5..059b0ee 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -605,6 +605,10 @@ border: 1px solid var(--diff-context-control-border-color); text-align: center; } + + .token-highlight { + background-color: var(--token-highlighting-color, #fffd54); + } </style> <style include="gr-syntax-theme"> /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js index 86946a6..53a2915 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -472,10 +472,12 @@ test('_handleTap content', done => { const content = document.createElement('div'); const lineEl = document.createElement('div'); + lineEl.className = 'lineNum'; + const row = document.createElement('div'); + row.appendChild(lineEl); + row.appendChild(content); const selectStub = sinon.stub(element, '_selectLine'); - sinon.stub(element.$.diffBuilder, 'getLineElByChild') - .callsFake(() => lineEl); content.className = 'content'; content.addEventListener('click', e => {
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts index ff950b8..6f94b8d 100644 --- a/polygerrit-ui/app/services/flags/flags.ts +++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -31,4 +31,5 @@ NEW_CHANGE_SUMMARY_UI = 'UiFeature__new_change_summary_ui', NEW_IMAGE_DIFF_UI = 'UiFeature__new_image_diff_ui', COMMENT_CONTEXT = 'UiFeature__comment_context', + TOKEN_HIGHLIGHTING = 'UiFeature__token_highlighting', }
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts index a81dcdf..d768c96 100644 --- a/polygerrit-ui/app/styles/themes/app-theme.ts +++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -337,6 +337,7 @@ --coverage-covered: #e0f2f1; --coverage-not-covered: #ffd1a4; --ranged-comment-hint-text-color: var(--orange-900); + --token-highlighting-color: #fffd54; /* syntax colors */ --syntax-attr-color: #219;
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts index 1d20c86..dec438d 100644 --- a/polygerrit-ui/app/styles/themes/dark-theme.ts +++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -199,6 +199,7 @@ --coverage-covered: #112826; --coverage-not-covered: #6b3600; --ranged-comment-hint-text-color: var(--blue-50); + --token-highlighting-color: var(--yellow-tonal); /* syntax colors */ --syntax-attr-color: #80cbbf;