| /** | |
| * @license | |
| * Copyright 2024 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import {DiffRangesToFocus, GrDiffLine, Side} from '../../../api/diff'; | |
| import {DiffLayer, DiffLayerListener} from '../../../types/types'; | |
| // A range of lines in a diff. | |
| export type Range = { | |
| start: number; | |
| end: number; | |
| }; | |
| export class GrFocusLayer implements DiffLayer { | |
| private diffRangesToFocus?: DiffRangesToFocus; | |
| /** | |
| * Diff Ranges which were unfocused(colors are saturated) in previous call. | |
| */ | |
| private previousUnfocusedRanges?: DiffRangesToFocus; | |
| /** | |
| * Has any line been annotated already in the lifetime of this layer? | |
| * If not, then `setRanges()` does not have to call `notify()` and thus | |
| * trigger re-rendering of the affected diff rows. | |
| */ | |
| // visible for testing | |
| annotated = false; | |
| private listeners: DiffLayerListener[] = []; | |
| addListener(listener: DiffLayerListener) { | |
| this.listeners.push(listener); | |
| } | |
| removeListener(listener: DiffLayerListener) { | |
| this.listeners = this.listeners.filter(f => f !== listener); | |
| } | |
| setRanges(diffRangesToFocus?: DiffRangesToFocus) { | |
| if (!this.previousUnfocusedRanges && !diffRangesToFocus) return; | |
| this.diffRangesToFocus = diffRangesToFocus; | |
| // If ranges are set before any diff row was rendered, then great, no need | |
| // to notify and re-render. | |
| if (this.annotated) { | |
| this.notify({ | |
| left: [ | |
| ...(this.previousUnfocusedRanges?.left ?? []), | |
| ...(diffRangesToFocus?.left ?? []), | |
| ], | |
| right: [ | |
| ...(this.previousUnfocusedRanges?.right ?? []), | |
| ...(diffRangesToFocus?.right ?? []), | |
| ], | |
| }); | |
| } | |
| this.previousUnfocusedRanges = undefined; | |
| } | |
| private notify(ranges: DiffRangesToFocus) { | |
| for (const r of ranges.left) { | |
| for (const l of this.listeners) l(r.start, r.end, Side.LEFT); | |
| } | |
| for (const r of ranges.right) { | |
| for (const l of this.listeners) l(r.start, r.end, Side.RIGHT); | |
| } | |
| } | |
| /** | |
| * Layer method to add is-out-of-focus-range to a textElement | |
| * if line is out of focus. | |
| * | |
| * @param textEl The gr-text element for this line. | |
| * @param lineNumberEl The <td> element with the line number. | |
| * @param _line Not used for this layer. (unused parameter) | |
| * @param side The side of the diff. | |
| */ | |
| annotate( | |
| textEl: HTMLElement, | |
| lineNumberEl: HTMLElement, | |
| _line: GrDiffLine, | |
| side: Side | |
| ) { | |
| this.annotated = true; | |
| if (!lineNumberEl || !textEl || !this.diffRangesToFocus) { | |
| return; | |
| } | |
| let elementLineNumber = -1; | |
| const dataValue = lineNumberEl.getAttribute('data-value'); | |
| if (dataValue) { | |
| elementLineNumber = Number(dataValue); | |
| } | |
| if (!elementLineNumber || elementLineNumber < 1) return; | |
| let focusedRanges: Range[] = []; | |
| if (side === Side.LEFT) { | |
| focusedRanges = this.diffRangesToFocus.left; | |
| } else if (side === Side.RIGHT) { | |
| focusedRanges = this.diffRangesToFocus.right; | |
| } | |
| // TODO(anuragpathak): Optimize this using the same approach as gr-coverage-layer.ts | |
| if ( | |
| !focusedRanges.some( | |
| range => | |
| elementLineNumber >= range.start && elementLineNumber <= range.end | |
| ) | |
| ) { | |
| textEl.classList.add('is-out-of-focus-range'); | |
| this.updateUnfocusedRanges(elementLineNumber, side); | |
| } | |
| } | |
| private updateUnfocusedRanges(lineNumber: number, side: Side) { | |
| this.previousUnfocusedRanges = { | |
| left: | |
| side === Side.LEFT | |
| ? this.addToRange(lineNumber, this.previousUnfocusedRanges?.left) | |
| : this.previousUnfocusedRanges?.left ?? [], | |
| right: | |
| side === Side.RIGHT | |
| ? this.addToRange(lineNumber, this.previousUnfocusedRanges?.right) | |
| : this.previousUnfocusedRanges?.right ?? [], | |
| }; | |
| } | |
| private addToRange(lineNumber: number, ranges?: Range[]) { | |
| const previousRange: Range[] = []; | |
| if (ranges) { | |
| previousRange.push(...ranges); | |
| } | |
| let lastEntryInRange = previousRange.pop(); | |
| if (lastEntryInRange) { | |
| if (lastEntryInRange.end + 1 === lineNumber) { | |
| lastEntryInRange = {start: lastEntryInRange.start, end: lineNumber}; | |
| previousRange.push(lastEntryInRange); | |
| } else { | |
| previousRange.push(lastEntryInRange, { | |
| start: lineNumber, | |
| end: lineNumber, | |
| }); | |
| } | |
| } else { | |
| previousRange.push({ | |
| start: lineNumber, | |
| end: lineNumber, | |
| }); | |
| } | |
| return previousRange; | |
| } | |
| } |