Merge "Move computation of key locations into diff model"
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
index 8fbda14..d2e997c 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
@@ -8,18 +8,26 @@
import {
DiffInfo,
DiffPreferencesInfo,
+ DisplayLine,
RenderPreferences,
} from '../../../api/diff';
import {define} from '../../../models/dependency';
import {Model} from '../../../models/model';
import {isDefined} from '../../../types/types';
import {select} from '../../../utils/observable-util';
+import {
+ GrDiffCommentThread,
+ KeyLocations,
+ computeKeyLocations,
+} from '../gr-diff/gr-diff-utils';
export interface DiffState {
diff: DiffInfo;
path?: string;
renderPrefs: RenderPreferences;
diffPrefs: DiffPreferencesInfo;
+ lineOfInterest?: DisplayLine;
+ comments: GrDiffCommentThread[];
}
export const diffModelToken = define<DiffModel>('diff-model');
@@ -44,4 +52,10 @@
this.state$.pipe(filter(isDefined)),
diffState => diffState.diffPrefs
);
+
+ readonly keyLocations$: Observable<KeyLocations> = select(
+ this.state$.pipe(filter(isDefined)),
+ diffState =>
+ computeKeyLocations(diffState.lineOfInterest, diffState.comments ?? [])
+ );
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
index 6346573..483a4da 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
@@ -15,6 +15,7 @@
import {assert} from '../../../utils/common-util';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
import {FILE, GrDiffLineType, LineNumber} from '../../../api/diff';
+import {KeyLocations} from '../gr-diff/gr-diff-utils';
const WHOLE_FILE = -1;
@@ -32,11 +33,6 @@
keyLocation: boolean;
}
-export interface KeyLocations {
- left: {[key: string]: boolean};
- right: {[key: string]: boolean};
-}
-
/**
* The maximum size for an addition or removal chunk before it is broken down
* into a series of chunks that are this size at most.
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index e1de348..d309556 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -8,6 +8,7 @@
import {
DiffPreferencesInfo,
DiffResponsiveMode,
+ DisplayLine,
FILE,
LOST,
LineNumber,
@@ -159,6 +160,87 @@
return range;
}
+/**
+ * This is all the data that gr-diff extracts from comment thread elements.
+ * Otherwise gr-diff treats such elements as a black box.
+ */
+export interface GrDiffCommentThread {
+ side: Side;
+ line: LineNumber;
+ range?: CommentRange;
+ rootId?: string;
+}
+
+export function toCommentThreadModel(
+ threadEl: HTMLElement
+): GrDiffCommentThread | undefined {
+ if (!isThreadEl(threadEl)) return undefined;
+ const side = getSide(threadEl);
+ const line = getLine(threadEl);
+ const range = getRange(threadEl);
+ if (!side) return undefined;
+ if (!line) return undefined;
+ return {side, line, range, rootId: threadEl.rootId};
+}
+
+export interface KeyLocations {
+ left: {[key: string]: boolean};
+ right: {[key: string]: boolean};
+}
+
+export function computeKeyLocations(
+ lineOfInterest: DisplayLine | undefined,
+ comments: GrDiffCommentThread[]
+) {
+ const keyLocations: KeyLocations = {left: {}, right: {}};
+
+ if (lineOfInterest) {
+ keyLocations[lineOfInterest.side][lineOfInterest.lineNum] = true;
+ }
+
+ for (const comment of comments) {
+ keyLocations[comment.side][comment.line] = true;
+ if (comment.range?.start_line) {
+ keyLocations[comment.side][comment.range.start_line] = true;
+ }
+ }
+
+ return keyLocations;
+}
+
+export function compareComments(
+ c1: GrDiffCommentThread,
+ c2: GrDiffCommentThread
+): number {
+ if (c1.side !== c2.side) {
+ return c1.side === Side.RIGHT ? 1 : -1;
+ }
+
+ if (c1.line !== c2.line) {
+ if (c1.line === FILE && c2.line !== FILE) return -1;
+ if (c1.line !== FILE && c2.line === FILE) return 1;
+ if (c1.line === LOST && c2.line !== LOST) return -1;
+ if (c1.line !== LOST && c2.line === LOST) return 1;
+ return (c1.line as number) - (c2.line as number);
+ }
+
+ if (c1.rootId !== c2.rootId) {
+ if (!c1.rootId) return -1;
+ if (!c2.rootId) return 1;
+ return c1.rootId > c2.rootId ? 1 : -1;
+ }
+
+ if (c1.range && c2.range) {
+ const r1 = JSON.stringify(c1.range);
+ const r2 = JSON.stringify(c2.range);
+ return r1 > r2 ? 1 : -1;
+ }
+ if (c1.range) return 1;
+ if (c2.range) return -1;
+
+ return 0;
+}
+
// TODO: This type should be exposed to gr-diff clients in a separate type file.
// For Gerrit these are instances of GrCommentThread, but other gr-diff users
// have different HTML elements in use for comment threads.
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
index 6549230..7e6e7fc 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
@@ -10,7 +10,13 @@
formatText,
createTabWrapper,
getRange,
+ computeKeyLocations,
+ GrDiffCommentThread,
+ toCommentThreadModel,
+ compareComments,
+ GrDiffThreadElement,
} from './gr-diff-utils';
+import {FILE, LOST, Side} from '../../../api/diff';
const LINE_BREAK_HTML = '<span class="gr-diff br"></span>';
@@ -177,4 +183,151 @@
threadEl.setAttribute('slot', 'right-1');
assert.isUndefined(getRange(threadEl));
});
+
+ suite('key locations', () => {
+ test('lineOfInterest is a key location', () => {
+ const lineOfInterest = {lineNum: 789, side: Side.LEFT};
+ assert.deepEqual(computeKeyLocations(lineOfInterest, []), {
+ left: {789: true},
+ right: {},
+ });
+ });
+
+ test('line comments are key locations', async () => {
+ const comments: GrDiffCommentThread[] = [{side: Side.RIGHT, line: 3}];
+ assert.deepEqual(computeKeyLocations(undefined, comments), {
+ left: {},
+ right: {3: true},
+ });
+ });
+
+ test('file comments are key locations', async () => {
+ const comments: GrDiffCommentThread[] = [{side: Side.LEFT, line: FILE}];
+ assert.deepEqual(computeKeyLocations(undefined, comments), {
+ left: {FILE: true},
+ right: {},
+ });
+ });
+
+ test('lots of key locations', () => {
+ const lineOfInterest = {lineNum: 789, side: Side.LEFT};
+ const comments: GrDiffCommentThread[] = [
+ {side: Side.LEFT, line: FILE},
+ {side: Side.LEFT, line: 2},
+ {side: Side.LEFT, line: 111},
+ {side: Side.RIGHT, line: LOST},
+ {side: Side.RIGHT, line: 13},
+ {side: Side.RIGHT, line: 19},
+ ];
+ assert.deepEqual(computeKeyLocations(lineOfInterest, comments), {
+ left: {FILE: true, 2: true, 111: true, 789: true},
+ right: {LOST: true, 13: true, 19: true},
+ });
+ });
+ });
+
+ suite('toCommentThreadModel', () => {
+ test('simple example', () => {
+ const el = document.createElement(
+ 'div'
+ ) as unknown as GrDiffThreadElement;
+ el.className = 'comment-thread';
+ el.setAttribute('diff-side', 'left');
+ el.setAttribute('line-num', '3');
+ el.rootId = 'ab12';
+
+ assert.deepEqual(toCommentThreadModel(el), {
+ line: 3,
+ side: Side.LEFT,
+ range: undefined,
+ rootId: 'ab12',
+ });
+ });
+
+ test('FILE default', () => {
+ const el = document.createElement(
+ 'div'
+ ) as unknown as GrDiffThreadElement;
+ el.className = 'comment-thread';
+ el.setAttribute('diff-side', 'left');
+ el.rootId = 'ab12';
+
+ assert.deepEqual(toCommentThreadModel(el), {
+ line: FILE,
+ side: Side.LEFT,
+ range: undefined,
+ rootId: 'ab12',
+ });
+ });
+
+ test('undefined', () => {
+ const el = document.createElement(
+ 'div'
+ ) as unknown as GrDiffThreadElement;
+ assert.isUndefined(toCommentThreadModel(el));
+ el.className = 'comment-thread';
+ assert.isUndefined(toCommentThreadModel(el));
+ el.setAttribute('line-num', '3');
+ assert.isUndefined(toCommentThreadModel(el));
+ });
+ });
+
+ suite('compare comments', () => {
+ test('sort array of comments', () => {
+ const comments: GrDiffCommentThread[] = [
+ {side: Side.RIGHT, line: 3},
+ {side: Side.RIGHT, line: 2},
+ {side: Side.RIGHT, line: 1},
+ {side: Side.RIGHT, line: LOST},
+ {side: Side.RIGHT, line: FILE},
+ {side: Side.LEFT, line: 3},
+ {side: Side.LEFT, line: 2},
+ {
+ side: Side.LEFT,
+ line: 1,
+ rootId: 'b',
+ range: {
+ start_line: 1,
+ start_character: 0,
+ end_line: 5,
+ end_character: 14,
+ },
+ },
+ {
+ side: Side.LEFT,
+ line: 1,
+ rootId: 'b',
+ range: {
+ start_line: 1,
+ start_character: 0,
+ end_line: 2,
+ end_character: 4,
+ },
+ },
+ {side: Side.LEFT, line: 1, rootId: 'b'},
+ {side: Side.LEFT, line: 1, rootId: 'a'},
+ {side: Side.LEFT, line: 1},
+ {side: Side.LEFT, line: LOST},
+ ];
+ const commentsOrdered: GrDiffCommentThread[] = [
+ comments[12],
+ comments[11],
+ comments[10],
+ comments[9],
+ comments[8],
+ comments[7],
+ comments[6],
+ comments[5],
+ comments[4],
+ comments[3],
+ comments[2],
+ comments[1],
+ comments[0],
+ ];
+ assert.sameOrderedMembers(
+ comments.sort(compareComments),
+ commentsOrdered
+ );
+ });
+ });
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 32ffc73..1f212b0 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -25,6 +25,9 @@
isResponsive,
isNewDiff,
getSideByLineEl,
+ compareComments,
+ toCommentThreadModel,
+ KeyLocations,
} from '../gr-diff/gr-diff-utils';
import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
@@ -32,7 +35,7 @@
CreateRangeCommentEventDetail,
GrDiffHighlight,
} from '../gr-diff-highlight/gr-diff-highlight';
-import {CoverageRange, DiffLayer} from '../../../types/types';
+import {CoverageRange, DiffLayer, isDefined} from '../../../types/types';
import {
CommentRangeLayer,
GrRangedCommentLayer,
@@ -44,7 +47,6 @@
} from '../../../constants/constants';
import {
GrDiffProcessor,
- KeyLocations,
ProcessingOptions,
} from '../gr-diff-processor/gr-diff-processor';
import {fire, fireAlert} from '../../../utils/event-util';
@@ -58,7 +60,7 @@
LineNumber,
LOST,
} from '../../../api/diff';
-import {isSafari, toggleClass} from '../../../utils/dom-util';
+import {isHtmlElement, isSafari, toggleClass} from '../../../utils/dom-util';
import {assertIsDefined} from '../../../utils/common-util';
import {
debounceP,
@@ -95,6 +97,7 @@
hideInContextControl,
} from './gr-diff-group';
import {GrDiffLine} from './gr-diff-line';
+import {subscribe} from '../../../elements/lit/subscription-controller';
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
@@ -319,6 +322,8 @@
*/
private groups: GrDiffGroup[] = [];
+ private keyLocations: KeyLocations = {left: {}, right: {}};
+
static override get styles() {
return [
iconStyles,
@@ -332,6 +337,11 @@
constructor() {
super();
provide(this, diffModelToken, () => this.diffModel);
+ subscribe(
+ this,
+ () => this.diffModel.keyLocations$,
+ keyLocations => (this.keyLocations = keyLocations)
+ );
this.addEventListener(
'create-range-comment',
(e: CustomEvent<CreateRangeCommentEventDetail>) =>
@@ -368,6 +378,21 @@
protected override willUpdate(changedProperties: PropertyValues<this>): void {
if (
+ changedProperties.has('diff') ||
+ changedProperties.has('path') ||
+ changedProperties.has('renderPrefs') ||
+ changedProperties.has('prefs') ||
+ changedProperties.has('lineOfInterest')
+ ) {
+ this.diffModel.updateState({
+ diff: this.diff,
+ path: this.path,
+ renderPrefs: this.renderPrefs,
+ diffPrefs: this.prefs,
+ lineOfInterest: this.lineOfInterest,
+ });
+ }
+ if (
changedProperties.has('path') ||
changedProperties.has('lineWrapping') ||
changedProperties.has('viewMode') ||
@@ -575,35 +600,6 @@
this.updateCommentRanges(this.commentRanges);
}
- /**
- * The key locations based on the comments and line of interests,
- * where lines should not be collapsed.
- *
- */
- // visible for testing
- computeKeyLocations() {
- const keyLocations: KeyLocations = {left: {}, right: {}};
- if (this.lineOfInterest) {
- const side = this.lineOfInterest.side;
- keyLocations[side][this.lineOfInterest.lineNum] = true;
- }
- const threadEls = [...this.childNodes].filter(isThreadEl);
-
- for (const threadEl of threadEls) {
- const side = getSide(threadEl);
- if (!side) continue;
- const lineNum = getLine(threadEl);
- const commentRange = getRange(threadEl);
- keyLocations[side][lineNum] = true;
- // Add start_line as well if exists,
- // the being and end of the range should not be collapsed.
- if (commentRange?.start_line) {
- keyLocations[side][commentRange.start_line] = true;
- }
- }
- return keyLocations;
- }
-
// Dispatch events that are handled by the gr-diff-highlight.
private redispatchHoverEvents(
hoverEl: HTMLElement,
@@ -774,7 +770,6 @@
private prefsChanged() {
if (!this.prefs) return;
- this.diffModel.updateState({diffPrefs: this.prefs});
this.blame = null;
this.updatePreferenceStyles();
@@ -845,7 +840,6 @@
}
private renderPrefsChanged() {
- this.diffModel.updateState({renderPrefs: this.renderPrefs});
if (this.renderPrefs.hide_left_side) {
this.classList.add('no-left');
}
@@ -929,13 +923,6 @@
this.showWarning = false;
- this.diffModel.setState({
- diff: this.diff,
- path: this.path,
- renderPrefs: this.renderPrefs,
- diffPrefs: this.prefs,
- });
-
this.updateCommentRanges(this.commentRanges);
this.updateCoverageRanges(this.coverageRanges);
await this.legacyRender();
@@ -973,6 +960,13 @@
addedThreadEls: GrDiffThreadElement[],
removedThreadEls: GrDiffThreadElement[]
) {
+ this.diffModel.updateState({
+ comments: [...this.childNodes]
+ .filter(isHtmlElement)
+ .map(toCommentThreadModel)
+ .filter(isDefined)
+ .sort(compareComments),
+ });
this.updateRanges(addedThreadEls, removedThreadEls);
addedThreadEls.forEach(threadEl =>
this.redispatchHoverEvents(threadEl, threadEl)
@@ -1159,7 +1153,7 @@
const options: ProcessingOptions = {
context: this.getBypassPrefs().context,
- keyLocations: this.computeKeyLocations(),
+ keyLocations: this.keyLocations,
isBinary: !!(this.isImageDiff || this.diff.binary),
};
if (this.renderPrefs?.num_lines_rendered_at_once) {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index d542633..99db4e9 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -3964,48 +3964,6 @@
});
});
- suite('key locations', () => {
- setup(async () => {
- element.prefs = {...MINIMAL_PREFS};
- element.diff = createDiff();
- await element.updateComplete;
- });
-
- test('lineOfInterest is a key location', () => {
- element.lineOfInterest = {lineNum: 789, side: Side.LEFT};
- assert.deepEqual(element.computeKeyLocations(), {
- left: {789: true},
- right: {},
- });
- });
-
- test('line comments are key locations', async () => {
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'right');
- threadEl.setAttribute('line-num', '3');
- element.appendChild(threadEl);
- await element.updateComplete;
-
- assert.deepEqual(element.computeKeyLocations(), {
- left: {},
- right: {3: true},
- });
- });
-
- test('file comments are key locations', async () => {
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'left');
- element.appendChild(threadEl);
- await element.updateComplete;
-
- assert.deepEqual(element.computeKeyLocations(), {
- left: {FILE: true},
- right: {},
- });
- });
- });
const setupSampleDiff = async function (params: {
content: DiffContent[];
ignore_whitespace?: IgnoreWhitespaceType;
diff --git a/polygerrit-ui/app/utils/dom-util.ts b/polygerrit-ui/app/utils/dom-util.ts
index 056238a..67c78cd 100644
--- a/polygerrit-ui/app/utils/dom-util.ts
+++ b/polygerrit-ui/app/utils/dom-util.ts
@@ -20,6 +20,10 @@
return node.nodeType === 1;
}
+export function isHtmlElement(node: Node): node is HTMLElement {
+ return isElement(node) && node instanceof HTMLElement;
+}
+
export function isElementTarget(
target: EventTarget | null | undefined
): target is Element {