
The change converts the following files to typescript: * elements/diff/gr-syntax-layer/gr-syntax-layer.ts Change-Id: Iebd9e0de1686653cc3b94f455b5db4449fa3ec9d
308 lines
9.3 KiB
TypeScript
308 lines
9.3 KiB
TypeScript
/**
|
|
* @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 {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
|
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
|
import {htmlTemplate} from './gr-ranged-comment-layer_html';
|
|
import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
|
|
import {strToClassName} from '../../../utils/dom-util';
|
|
import {customElement, property, observe} from '@polymer/decorators';
|
|
import {Side} from '../../../constants/constants';
|
|
import {
|
|
PolymerDeepPropertyChange,
|
|
PolymerSpliceChange,
|
|
} from '@polymer/polymer/interfaces';
|
|
import {CommentRange} from '../../../types/common';
|
|
import {DiffLayer, DiffLayerListener} from '../../../types/types';
|
|
|
|
/**
|
|
* Enhanced CommentRange by UI state. Interface for incoming ranges set from the
|
|
* outside.
|
|
*
|
|
* TODO(TS): Unify with what is used in gr-diff when these objects are created.
|
|
*/
|
|
export interface CommentRangeLayer {
|
|
side: Side;
|
|
range: CommentRange;
|
|
hovering: boolean;
|
|
rootId: string;
|
|
}
|
|
|
|
/**
|
|
* This class breaks down all comment ranges into individual line segment
|
|
* highlights.
|
|
*/
|
|
interface CommentRangeLineLayer {
|
|
hovering: boolean;
|
|
rootId: string;
|
|
start: number;
|
|
end: number;
|
|
}
|
|
|
|
type LinesMap = {
|
|
[line in number]: CommentRangeLineLayer[];
|
|
};
|
|
|
|
type RangesMap = {
|
|
[side in Side]: LinesMap;
|
|
};
|
|
|
|
// Polymer 1 adds # before array's key, while Polymer 2 doesn't
|
|
const HOVER_PATH_PATTERN = /^(commentRanges\.#?\d+)\.hovering$/;
|
|
|
|
const RANGE_HIGHLIGHT = 'style-scope gr-diff range';
|
|
const HOVER_HIGHLIGHT = 'style-scope gr-diff rangeHighlight';
|
|
|
|
@customElement('gr-ranged-comment-layer')
|
|
export class GrRangedCommentLayer
|
|
extends GestureEventListeners(LegacyElementMixin(PolymerElement))
|
|
implements DiffLayer {
|
|
static get template() {
|
|
return htmlTemplate;
|
|
}
|
|
|
|
/**
|
|
* Fired when the range in a range comment was malformed and had to be
|
|
* normalized.
|
|
*
|
|
* It's `detail` has a `lineNum` and `side` parameter.
|
|
*
|
|
* @event normalize-range
|
|
*/
|
|
|
|
@property({type: Array})
|
|
commentRanges: CommentRangeLayer[] = [];
|
|
|
|
@property({type: Array})
|
|
_listeners: DiffLayerListener[] = [];
|
|
|
|
@property({type: Object})
|
|
_rangesMap: RangesMap = {left: {}, right: {}};
|
|
|
|
get styleModuleName() {
|
|
return 'gr-ranged-comment-styles';
|
|
}
|
|
|
|
/**
|
|
* Layer method to add annotations to a line.
|
|
*
|
|
* @param el The DIV.contentText element to apply the annotation to.
|
|
*/
|
|
annotate(el: HTMLElement, _: HTMLElement, line: GrDiffLine) {
|
|
let ranges: CommentRangeLineLayer[] = [];
|
|
if (
|
|
line.type === GrDiffLineType.REMOVE ||
|
|
(line.type === GrDiffLineType.BOTH &&
|
|
el.getAttribute('data-side') !== 'right')
|
|
) {
|
|
ranges = ranges.concat(this._getRangesForLine(line, Side.LEFT));
|
|
}
|
|
if (
|
|
line.type === GrDiffLineType.ADD ||
|
|
(line.type === GrDiffLineType.BOTH &&
|
|
el.getAttribute('data-side') !== 'left')
|
|
) {
|
|
ranges = ranges.concat(this._getRangesForLine(line, Side.RIGHT));
|
|
}
|
|
|
|
for (const range of ranges) {
|
|
GrAnnotation.annotateElement(
|
|
el,
|
|
range.start,
|
|
range.end - range.start,
|
|
(range.hovering ? HOVER_HIGHLIGHT : RANGE_HIGHLIGHT) +
|
|
` ${strToClassName(range.rootId)}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register a listener for layer updates.
|
|
*/
|
|
addListener(listener: DiffLayerListener) {
|
|
this._listeners.push(listener);
|
|
}
|
|
|
|
removeListener(listener: DiffLayerListener) {
|
|
this._listeners = this._listeners.filter(f => f !== listener);
|
|
}
|
|
|
|
/**
|
|
* Notify Layer listeners of changes to annotations.
|
|
*/
|
|
_notifyUpdateRange(start: number, end: number, side: Side) {
|
|
for (const listener of this._listeners) {
|
|
listener(start, end, side);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle change in the ranges by updating the ranges maps and by
|
|
* emitting appropriate update notifications.
|
|
*/
|
|
@observe('commentRanges.*')
|
|
_handleCommentRangesChange(
|
|
record: PolymerDeepPropertyChange<
|
|
CommentRangeLayer[],
|
|
PolymerSpliceChange<CommentRangeLayer[]>
|
|
>
|
|
) {
|
|
if (!record) return;
|
|
|
|
// If the entire set of comments was changed.
|
|
if (record.path === 'commentRanges') {
|
|
const value = record.value as CommentRangeLayer[];
|
|
this._rangesMap = {left: {}, right: {}};
|
|
for (const {side, range, rootId, hovering} of value) {
|
|
this._updateRangesMap({
|
|
side,
|
|
range,
|
|
hovering,
|
|
operation: (forLine, start, end, hovering) => {
|
|
forLine.push({start, end, hovering, rootId});
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// If the change only changed the `hovering` property of a comment.
|
|
const match = record.path.match(HOVER_PATH_PATTERN);
|
|
if (match) {
|
|
// The #number indicates the key of that item in the array
|
|
// not the index, especially in polymer 1.
|
|
const {side, range, hovering, rootId} = this.get(match[1]);
|
|
|
|
this._updateRangesMap({
|
|
side,
|
|
range,
|
|
hovering,
|
|
skipLayerUpdate: true,
|
|
operation: (forLine, start, end, hovering) => {
|
|
const index = forLine.findIndex(
|
|
lineRange => lineRange.start === start && lineRange.end === end
|
|
);
|
|
forLine[index].hovering = hovering;
|
|
forLine[index].rootId = rootId;
|
|
},
|
|
});
|
|
}
|
|
|
|
// If comments were spliced in or out.
|
|
if (record.path === 'commentRanges.splices') {
|
|
const value = record.value as PolymerSpliceChange<CommentRangeLayer[]>;
|
|
for (const indexSplice of value.indexSplices) {
|
|
const removed = indexSplice.removed;
|
|
for (const {side, range, hovering, rootId} of removed) {
|
|
this._updateRangesMap({
|
|
side,
|
|
range,
|
|
hovering,
|
|
operation: (forLine, start, end) => {
|
|
const index = forLine.findIndex(
|
|
lineRange =>
|
|
lineRange.start === start &&
|
|
lineRange.end === end &&
|
|
rootId === lineRange.rootId
|
|
);
|
|
forLine.splice(index, 1);
|
|
},
|
|
});
|
|
}
|
|
const added = indexSplice.object.slice(
|
|
indexSplice.index,
|
|
indexSplice.index + indexSplice.addedCount
|
|
);
|
|
for (const {side, range, hovering, rootId} of added) {
|
|
this._updateRangesMap({
|
|
side,
|
|
range,
|
|
hovering,
|
|
operation: (forLine, start, end, hovering) => {
|
|
forLine.push({start, end, hovering, rootId});
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateRangesMap(options: {
|
|
side: Side;
|
|
range: CommentRange;
|
|
hovering: boolean;
|
|
operation: (
|
|
forLine: CommentRangeLineLayer[],
|
|
start: number,
|
|
end: number,
|
|
hovering: boolean
|
|
) => void;
|
|
skipLayerUpdate?: boolean;
|
|
}) {
|
|
const {side, range, hovering, operation, skipLayerUpdate} = options;
|
|
const forSide = this._rangesMap[side] || (this._rangesMap[side] = {});
|
|
for (let line = range.start_line; line <= range.end_line; line++) {
|
|
const forLine = forSide[line] || (forSide[line] = []);
|
|
const start = line === range.start_line ? range.start_character : 0;
|
|
const end = line === range.end_line ? range.end_character : -1;
|
|
operation(forLine, start, end, hovering);
|
|
}
|
|
if (!skipLayerUpdate) {
|
|
this._notifyUpdateRange(range.start_line, range.end_line, side);
|
|
}
|
|
}
|
|
|
|
_getRangesForLine(line: GrDiffLine, side: Side) {
|
|
const lineNum = side === Side.LEFT ? line.beforeNumber : line.afterNumber;
|
|
const ranges: CommentRangeLineLayer[] =
|
|
this.get(['_rangesMap', side, lineNum]) || [];
|
|
return (
|
|
ranges
|
|
.map(range => {
|
|
// Make a copy, so that the normalization below does not mess with
|
|
// our map.
|
|
range = {...range};
|
|
range.end = range.end === -1 ? line.text.length : range.end;
|
|
|
|
// Normalize invalid ranges where the start is after the end but the
|
|
// start still makes sense. Set the end to the end of the line.
|
|
// @see Issue 5744
|
|
if (range.start! >= range.end! && range.start! < line.text.length) {
|
|
range.end = line.text.length;
|
|
this.dispatchEvent(
|
|
new CustomEvent('normalize-range', {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {lineNum, side},
|
|
})
|
|
);
|
|
}
|
|
|
|
return range;
|
|
})
|
|
// Sort the ranges so that hovering highlights are on top.
|
|
.sort((a, b) => (a.hovering && !b.hovering ? 1 : 0))
|
|
);
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'gr-ranged-comment-layer': GrRangedCommentLayer;
|
|
}
|
|
}
|