diff options
Diffstat (limited to 'polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts')
-rw-r--r-- | polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts new file mode 100644 index 0000000000..469c24afda --- /dev/null +++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts @@ -0,0 +1,114 @@ +/** + * @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. + */ + +// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode +const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; + +export interface NormalizedRange { + endContainer: Node; + endOffset: number; + startContainer: Node; + startOffset: number; +} + +/** + * Remap DOM range to whole lines of a diff if necessary. If the start or + * end containers are DOM elements that are singular pieces of syntax + * highlighting, the containers are remapped to the .contentText divs that + * contain the entire line of code. + * + * @param range - the standard DOM selector range. + * @return A modified version of the range that correctly accounts + * for syntax highlighting. + */ +export function normalize(range: Range): NormalizedRange { + const startContainer = _getContentTextParent(range.startContainer); + const startOffset = + range.startOffset + _getTextOffset(startContainer, range.startContainer); + const endContainer = _getContentTextParent(range.endContainer); + const endOffset = + range.endOffset + _getTextOffset(endContainer, range.endContainer); + return { + startContainer, + startOffset, + endContainer, + endOffset, + }; +} + +function _getContentTextParent(target: Node): Node { + if (!target.parentElement) return target; + + let element: Element | null; + if (target instanceof Element) { + element = target; + } else { + element = target.parentElement; + } + + while (element && !element.classList.contains('contentText')) { + if (element.parentElement === null) { + return target; + } + element = element.parentElement; + } + return element ? element : target; +} + +/** + * Gets the character offset of the child within the parent. + * Performs a synchronous in-order traversal from top to bottom of the node + * element, counting the length of the syntax until child is found. + * + * @param node The root DOM element to be searched through. + * @param child The child element being searched for. + */ +// TODO(TS): Only export for test. +export function _getTextOffset(node: Node | null, child: Node): number { + let count = 0; + let stack = [node]; + while (stack.length) { + const n = stack.pop(); + if (n === child) { + break; + } + if (n?.childNodes && n.childNodes.length !== 0) { + const arr = []; + for (const childNode of n.childNodes) { + arr.push(childNode); + } + arr.reverse(); + stack = stack.concat(arr); + } else { + count += _getLength(n); + } + } + return count; +} + +/** + * The DOM API textContent.length calculation is broken when the text + * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode . + * + * @param node A text node. + * @return The length of the text. + */ +function _getLength(node?: Node | null) { + return node && node.textContent + ? node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length + : 0; +} |