summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts')
-rw-r--r--polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts505
1 files changed, 505 insertions, 0 deletions
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
new file mode 100644
index 0000000000..ceadc94e45
--- /dev/null
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
@@ -0,0 +1,505 @@
+/**
+ * @license
+ * Copyright (C) 2016 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 {
+ MovedLinkClickedEventDetail,
+ RenderPreferences,
+} from '../../../api/diff';
+import {fire} from '../../../utils/event-util';
+import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
+import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
+import '../gr-context-controls/gr-context-controls';
+import {
+ GrContextControls,
+ GrContextControlsShowConfig,
+} from '../gr-context-controls/gr-context-controls';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
+import {DiffViewMode, Side} from '../../../constants/constants';
+import {DiffLayer} from '../../../types/types';
+import {
+ createBlameElement,
+ createElementDiff,
+ createElementDiffWithText,
+ formatText,
+ getResponsiveMode,
+} from '../gr-diff/gr-diff-utils';
+import {GrDiffBuilder} from './gr-diff-builder';
+import {BlameInfo} from '../../../types/common';
+
+function lineTdSelector(lineNumber: LineNumber, side?: Side): string {
+ const sideSelector = side ? `.${side}` : '';
+ return `td.lineNum[data-value="${lineNumber}"]${sideSelector}`;
+}
+/**
+ * Base class for builders that are creating the DOM elements programmatically
+ * by calling `document.createElement()` and such. We are calling such builders
+ * "legacy", because we want to create (Lit) component based diff elements.
+ *
+ * TODO: Do not subclass `GrDiffBuilder`. Use composition and interfaces.
+ */
+export abstract class GrDiffBuilderLegacy extends GrDiffBuilder {
+ constructor(
+ diff: DiffInfo,
+ prefs: DiffPreferencesInfo,
+ outputEl: HTMLElement,
+ layers: DiffLayer[] = [],
+ renderPrefs?: RenderPreferences
+ ) {
+ super(diff, prefs, outputEl, layers, renderPrefs);
+ }
+
+ override getContentTdByLine(
+ lineNumber: LineNumber,
+ side?: Side,
+ root: Element = this.outputEl
+ ): HTMLTableCellElement | null {
+ return root.querySelector<HTMLTableCellElement>(
+ `${lineTdSelector(lineNumber, side)} ~ td.content`
+ );
+ }
+
+ override getLineElByNumber(
+ lineNumber: LineNumber,
+ side?: Side
+ ): HTMLTableCellElement | null {
+ return this.outputEl.querySelector<HTMLTableCellElement>(
+ lineTdSelector(lineNumber, side)
+ );
+ }
+
+ override getLineNumberRows() {
+ return Array.from(
+ this.outputEl.querySelectorAll<HTMLTableRowElement>(
+ ':not(.contextControl) > .diff-row'
+ ) ?? []
+ ).filter(tr => tr.querySelector('button'));
+ }
+
+ override getLineNumEls(side: Side): HTMLTableCellElement[] {
+ return Array.from(
+ this.outputEl.querySelectorAll<HTMLTableCellElement>(
+ `td.lineNum.${side}`
+ ) ?? []
+ );
+ }
+
+ override getBlameTdByLine(lineNum: number): Element | undefined {
+ return (
+ this.outputEl.querySelector(`td.blame[data-line-number="${lineNum}"]`) ??
+ undefined
+ );
+ }
+
+ override getContentByLine(
+ lineNumber: LineNumber,
+ side?: Side,
+ root?: HTMLElement
+ ): HTMLElement | null {
+ const td = this.getContentTdByLine(lineNumber, side, root);
+ return td ? td.querySelector('.contentText') : null;
+ }
+
+ override renderContentByRange(
+ start: LineNumber,
+ end: LineNumber,
+ side: Side
+ ) {
+ const lines: GrDiffLine[] = [];
+ const elements: HTMLElement[] = [];
+ let line;
+ let el;
+ this.findLinesByRange(start, end, side, lines, elements);
+ for (let i = 0; i < lines.length; i++) {
+ line = lines[i];
+ el = elements[i];
+ if (!el || !el.parentElement) {
+ // Cannot re-render an element if it does not exist. This can happen
+ // if lines are collapsed and not visible on the page yet.
+ continue;
+ }
+ const lineNumberEl = this.getLineNumberEl(el, side);
+ el.parentElement.replaceChild(
+ this.createTextEl(lineNumberEl, line, side).firstChild!,
+ el
+ );
+ }
+ }
+
+ override renderBlameByRange(blame: BlameInfo, start: number, end: number) {
+ for (let i = start; i <= end; i++) {
+ // TODO(wyatta): this query is expensive, but, when traversing a
+ // range, the lines are consecutive, and given the previous blame
+ // cell, the next one can be reached cheaply.
+ const blameCell = this.getBlameTdByLine(i);
+ if (!blameCell) continue;
+
+ // Remove the element's children (if any).
+ while (blameCell.hasChildNodes()) {
+ blameCell.removeChild(blameCell.lastChild!);
+ }
+ const blameEl = createBlameElement(i, blame);
+ if (blameEl) blameCell.appendChild(blameEl);
+ }
+ }
+
+ /**
+ * Finds the line number element given the content element by walking up the
+ * DOM tree to the diff row and then querying for a .lineNum element on the
+ * requested side.
+ *
+ * TODO(brohlfs): Consolidate this with getLineEl... methods in html file.
+ */
+ private getLineNumberEl(
+ content: HTMLElement,
+ side: Side
+ ): HTMLElement | null {
+ let row: HTMLElement | null = content;
+ while (row && !row.classList.contains('diff-row')) row = row.parentElement;
+ return row ? (row.querySelector('.lineNum.' + side) as HTMLElement) : null;
+ }
+
+ /**
+ * Adds <tr> table rows to a <tbody> section for allowing the user to expand
+ * collapsed of lines. Called by subclasses.
+ */
+ protected createContextControls(
+ section: HTMLElement,
+ group: GrDiffGroup,
+ viewMode: DiffViewMode
+ ) {
+ const leftStart = group.lineRange.left.start_line;
+ const leftEnd = group.lineRange.left.end_line;
+ const firstGroupIsSkipped = !!group.contextGroups[0].skip;
+ const lastGroupIsSkipped =
+ !!group.contextGroups[group.contextGroups.length - 1].skip;
+
+ const containsWholeFile = this.numLinesLeft === leftEnd - leftStart + 1;
+ const showAbove =
+ (leftStart > 1 && !firstGroupIsSkipped) || containsWholeFile;
+ const showBelow = leftEnd < this.numLinesLeft && !lastGroupIsSkipped;
+
+ if (showAbove) {
+ const paddingRow = this.createContextControlPaddingRow(viewMode);
+ paddingRow.classList.add('above');
+ section.appendChild(paddingRow);
+ }
+ section.appendChild(
+ this.createContextControlRow(group, showAbove, showBelow, viewMode)
+ );
+ if (showBelow) {
+ const paddingRow = this.createContextControlPaddingRow(viewMode);
+ paddingRow.classList.add('below');
+ section.appendChild(paddingRow);
+ }
+ }
+
+ /**
+ * Creates a context control <tr> table row for with buttons the allow the
+ * user to expand collapsed lines. Buttons extend from the gap created by this
+ * method up or down into the area of code that they affect.
+ */
+ private createContextControlRow(
+ group: GrDiffGroup,
+ showAbove: boolean,
+ showBelow: boolean,
+ viewMode: DiffViewMode
+ ): HTMLElement {
+ const row = createElementDiff('tr', 'dividerRow');
+ let showConfig: GrContextControlsShowConfig;
+ if (showAbove && !showBelow) {
+ showConfig = 'above';
+ } else if (!showAbove && showBelow) {
+ showConfig = 'below';
+ } else {
+ // Note that !showAbove && !showBelow also intentionally creates
+ // "show-both". This means the file is completely collapsed, which is
+ // unusual, but at least happens in one test.
+ showConfig = 'both';
+ }
+ row.classList.add(`show-${showConfig}`);
+
+ row.appendChild(this.createBlameCell(0));
+ if (viewMode === DiffViewMode.SIDE_BY_SIDE) {
+ row.appendChild(createElementDiff('td'));
+ }
+
+ const cell = createElementDiff('td', 'dividerCell');
+ const colspan = this.renderPrefs?.show_sign_col ? '5' : '3';
+ cell.setAttribute('colspan', colspan);
+ row.appendChild(cell);
+
+ const contextControls = createElementDiff(
+ 'gr-context-controls'
+ ) as GrContextControls;
+ contextControls.diff = this._diff;
+ contextControls.renderPreferences = this.renderPrefs;
+ contextControls.group = group;
+ contextControls.showConfig = showConfig;
+ cell.appendChild(contextControls);
+ return row;
+ }
+
+ /**
+ * Creates a table row to serve as padding between code and context controls.
+ * Blame column, line gutters, and content area will continue visually, but
+ * context controls can render over this background to map more clearly to
+ * the area of code they expand.
+ */
+ private createContextControlPaddingRow(viewMode: DiffViewMode) {
+ const row = createElementDiff('tr', 'contextBackground');
+
+ if (viewMode === DiffViewMode.SIDE_BY_SIDE) {
+ row.classList.add('side-by-side');
+ row.setAttribute('left-type', GrDiffGroupType.CONTEXT_CONTROL);
+ row.setAttribute('right-type', GrDiffGroupType.CONTEXT_CONTROL);
+ } else {
+ row.classList.add('unified');
+ }
+
+ row.appendChild(this.createBlameCell(0));
+ row.appendChild(createElementDiff('td', 'contextLineNum'));
+ if (viewMode === DiffViewMode.SIDE_BY_SIDE) {
+ row.appendChild(createElementDiff('td', 'sign'));
+ row.appendChild(createElementDiff('td'));
+ }
+ row.appendChild(createElementDiff('td', 'contextLineNum'));
+ if (viewMode === DiffViewMode.SIDE_BY_SIDE) {
+ row.appendChild(createElementDiff('td', 'sign'));
+ }
+ row.appendChild(createElementDiff('td'));
+
+ return row;
+ }
+
+ protected createLineEl(
+ line: GrDiffLine,
+ number: LineNumber,
+ type: GrDiffLineType,
+ side: Side
+ ) {
+ const td = createElementDiff('td');
+ td.classList.add(side);
+ if (line.type === GrDiffLineType.BLANK) {
+ td.classList.add('blankLineNum');
+ return td;
+ }
+ if (line.type === GrDiffLineType.BOTH || line.type === type) {
+ td.classList.add('lineNum');
+ td.dataset['value'] = number.toString();
+
+ if (
+ ((this._prefs.show_file_comment_button === false ||
+ this.renderPrefs?.show_file_comment_button === false) &&
+ number === 'FILE') ||
+ number === 'LOST'
+ ) {
+ return td;
+ }
+
+ const button = createElementDiff('button');
+ td.appendChild(button);
+ button.tabIndex = -1;
+ button.classList.add('lineNumButton');
+ button.classList.add(side);
+ button.dataset['value'] = number.toString();
+ button.textContent = number === 'FILE' ? 'File' : number.toString();
+ if (number === 'FILE') {
+ button.setAttribute('aria-label', 'Add file comment');
+ }
+
+ // Add aria-labels for valid line numbers.
+ // For unified diff, this method will be called with number set to 0 for
+ // the empty line number column for added/removed lines. This should not
+ // be announced to the screenreader.
+ if (number > 0) {
+ if (line.type === GrDiffLineType.REMOVE) {
+ button.setAttribute('aria-label', `${number} removed`);
+ } else if (line.type === GrDiffLineType.ADD) {
+ button.setAttribute('aria-label', `${number} added`);
+ }
+ }
+ this.addLineNumberMouseEvents(td, number, side);
+ }
+ return td;
+ }
+
+ private addLineNumberMouseEvents(
+ el: HTMLElement,
+ number: LineNumber,
+ side: Side
+ ) {
+ el.addEventListener('mouseenter', () => {
+ fire(el, 'line-mouse-enter', {lineNum: number, side});
+ });
+ el.addEventListener('mouseleave', () => {
+ fire(el, 'line-mouse-leave', {lineNum: number, side});
+ });
+ }
+
+ protected createTextEl(
+ lineNumberEl: HTMLElement | null,
+ line: GrDiffLine,
+ side?: Side
+ ) {
+ const td = createElementDiff('td');
+ if (line.type !== GrDiffLineType.BLANK) {
+ td.classList.add('content');
+ }
+ if (side) {
+ td.classList.add(side);
+ }
+
+ // If intraline info is not available, the entire line will be
+ // considered as changed and marked as dark red / green color
+ if (!line.hasIntralineInfo) {
+ td.classList.add('no-intraline-info');
+ }
+ td.classList.add(line.type);
+
+ const {beforeNumber, afterNumber} = line;
+ if (beforeNumber !== 'FILE' && beforeNumber !== 'LOST') {
+ const responsiveMode = getResponsiveMode(this._prefs, this.renderPrefs);
+ const contentText = formatText(
+ line.text,
+ responsiveMode,
+ this._prefs.tab_size,
+ this._prefs.line_length
+ );
+
+ if (side) {
+ contentText.setAttribute('data-side', side);
+ const number = side === Side.LEFT ? beforeNumber : afterNumber;
+ this.addLineNumberMouseEvents(td, number, side);
+ }
+
+ if (lineNumberEl && side) {
+ for (const layer of this.layers) {
+ if (typeof layer.annotate === 'function') {
+ layer.annotate(contentText, lineNumberEl, line, side);
+ }
+ }
+ } else {
+ console.error('lineNumberEl or side not set, skipping layer.annotate');
+ }
+
+ td.appendChild(contentText);
+ } else if (line.beforeNumber === 'FILE') td.classList.add('file');
+ else if (line.beforeNumber === 'LOST') td.classList.add('lost');
+
+ return td;
+ }
+
+ private createMovedLineAnchor(line: number, side: Side) {
+ const anchor = createElementDiffWithText('a', `${line}`);
+
+ // href is not actually used but important for Screen Readers
+ anchor.setAttribute('href', `#${line}`);
+ anchor.addEventListener('click', e => {
+ e.preventDefault();
+ anchor.dispatchEvent(
+ new CustomEvent<MovedLinkClickedEventDetail>('moved-link-clicked', {
+ detail: {
+ lineNum: line,
+ side,
+ },
+ composed: true,
+ bubbles: true,
+ })
+ );
+ });
+ return anchor;
+ }
+
+ private createMoveDescriptionDiv(movedIn: boolean, group: GrDiffGroup) {
+ const div = createElementDiff('div');
+ if (group.moveDetails?.range) {
+ const {changed, range} = group.moveDetails;
+ const otherSide = movedIn ? Side.LEFT : Side.RIGHT;
+ const andChangedLabel = changed ? 'and changed ' : '';
+ const direction = movedIn ? 'from' : 'to';
+ const textLabel = `Moved ${andChangedLabel}${direction} lines `;
+ div.appendChild(createElementDiffWithText('span', textLabel));
+ div.appendChild(this.createMovedLineAnchor(range.start, otherSide));
+ div.appendChild(createElementDiffWithText('span', ' - '));
+ div.appendChild(this.createMovedLineAnchor(range.end, otherSide));
+ } else {
+ div.appendChild(
+ createElementDiffWithText('span', movedIn ? 'Moved in' : 'Moved out')
+ );
+ }
+ return div;
+ }
+
+ protected buildMoveControls(group: GrDiffGroup) {
+ const movedIn = group.adds.length > 0;
+ const {
+ numberOfCells,
+ movedOutIndex,
+ movedInIndex,
+ lineNumberCols,
+ signCols,
+ } = this.getMoveControlsConfig();
+
+ let controlsClass;
+ let descriptionIndex;
+ const descriptionTextDiv = this.createMoveDescriptionDiv(movedIn, group);
+ if (movedIn) {
+ controlsClass = 'movedIn';
+ descriptionIndex = movedInIndex;
+ } else {
+ controlsClass = 'movedOut';
+ descriptionIndex = movedOutIndex;
+ }
+
+ const controls = createElementDiff('tr', `moveControls ${controlsClass}`);
+ const cells = [...Array(numberOfCells).keys()].map(() =>
+ createElementDiff('td')
+ );
+ lineNumberCols.forEach(index => {
+ cells[index].classList.add('moveControlsLineNumCol');
+ });
+
+ if (signCols) {
+ cells[signCols.left].classList.add('sign', 'left');
+ cells[signCols.right].classList.add('sign', 'right');
+ }
+ const moveRangeHeader = createElementDiff('gr-range-header');
+ moveRangeHeader.setAttribute('icon', 'gr-icons:move-item');
+ moveRangeHeader.appendChild(descriptionTextDiv);
+ cells[descriptionIndex].classList.add('moveHeader');
+ cells[descriptionIndex].appendChild(moveRangeHeader);
+ cells.forEach(c => {
+ controls.appendChild(c);
+ });
+ return controls;
+ }
+
+ /**
+ * Create a blame cell for the given base line. Blame information will be
+ * included in the cell if available.
+ */
+ protected createBlameCell(lineNumber: LineNumber): HTMLTableCellElement {
+ const blameTd = createElementDiff('td', 'blame') as HTMLTableCellElement;
+ blameTd.setAttribute('data-line-number', lineNumber.toString());
+ if (!lineNumber) return blameTd;
+
+ const blameInfo = this.getBlameCommitForBaseLine(lineNumber);
+ if (!blameInfo) return blameTd;
+
+ blameTd.appendChild(createBlameElement(lineNumber, blameInfo));
+ return blameTd;
+ }
+}