Dave Borowitz | 8cdc76b | 2018-03-26 10:04:27 -0400 | [diff] [blame] | 1 | /** |
| 2 | * @license |
| 3 | * Copyright (C) 2016 The Android Open Source Project |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 17 | import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; |
| 18 | import {PolymerElement} from '@polymer/polymer/polymer-element'; |
| 19 | import { |
| 20 | GrDiffLine, |
| 21 | GrDiffLineType, |
| 22 | FILE, |
| 23 | Highlights, |
Dhruv Srivastava | ac2bbd3 | 2021-02-04 22:08:55 +0100 | [diff] [blame] | 24 | LineNumber, |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 25 | } from '../gr-diff/gr-diff-line'; |
| 26 | import { |
| 27 | GrDiffGroup, |
| 28 | GrDiffGroupType, |
| 29 | hideInContextControl, |
| 30 | } from '../gr-diff/gr-diff-group'; |
| 31 | import {CancelablePromise, util} from '../../../scripts/util'; |
| 32 | import {customElement, property} from '@polymer/decorators'; |
Ole | 899f7d2 | 2020-11-17 17:18:22 +0100 | [diff] [blame] | 33 | import {DiffContent} from '../../../types/diff'; |
Ben Rohlfs | 1d48706 | 2020-09-26 11:26:03 +0200 | [diff] [blame] | 34 | import {Side} from '../../../constants/constants'; |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 35 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 36 | const WHOLE_FILE = -1; |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 37 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 38 | interface State { |
| 39 | lineNums: { |
| 40 | left: number; |
| 41 | right: number; |
| 42 | }; |
| 43 | chunkIndex: number; |
| 44 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 45 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 46 | interface ChunkEnd { |
| 47 | offset: number; |
| 48 | keyLocation: boolean; |
| 49 | } |
| 50 | |
Ben Rohlfs | 4fa7c53 | 2020-08-24 18:16:13 +0200 | [diff] [blame] | 51 | export interface KeyLocations { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 52 | left: {[key: string]: boolean}; |
| 53 | right: {[key: string]: boolean}; |
| 54 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 55 | |
| 56 | /** |
| 57 | * The maximum size for an addition or removal chunk before it is broken down |
| 58 | * into a series of chunks that are this size at most. |
| 59 | * |
| 60 | * Note: The value of 120 is chosen so that it is larger than the default |
| 61 | * _asyncThreshold of 64, but feel free to tune this constant to your |
| 62 | * performance needs. |
| 63 | */ |
| 64 | const MAX_GROUP_SIZE = 120; |
| 65 | |
Ben Rohlfs | 04fc1ca | 2021-02-13 13:06:53 +0100 | [diff] [blame] | 66 | const DEBOUNCER_RESET_IS_SCROLLING = 'resetIsScrolling'; |
| 67 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 68 | /** |
| 69 | * Converts the API's `DiffContent`s to `GrDiffGroup`s for rendering. |
| 70 | * |
| 71 | * Glossary: |
| 72 | * - "chunk": A single `DiffContent` as returned by the API. |
| 73 | * - "group": A single `GrDiffGroup` as used for rendering. |
| 74 | * - "common" chunk/group: A chunk/group that should be considered unchanged |
| 75 | * for diffing purposes. This can mean its either actually unchanged, or it |
| 76 | * has only whitespace changes. |
| 77 | * - "key location": A line number and side of the diff that should not be |
| 78 | * collapsed e.g. because a comment is attached to it, or because it was |
| 79 | * provided in the URL and thus should be visible |
| 80 | * - "uncollapsible" chunk/group: A chunk/group that is either not "common", |
| 81 | * or cannot be collapsed because it contains a key location |
| 82 | * |
| 83 | * Here a a number of tasks this processor performs: |
| 84 | * - splitting large chunks to allow more granular async rendering |
| 85 | * - adding a group for the "File" pseudo line that file-level comments can |
| 86 | * be attached to |
| 87 | * - replacing common parts of the diff that are outside the user's |
| 88 | * context setting and do not have comments with a group representing the |
| 89 | * "expand context" widget. This may require splitting a chunk/group so |
| 90 | * that the part that is within the context or has comments is shown, while |
| 91 | * the rest is not. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 92 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 93 | @customElement('gr-diff-processor') |
Ben Rohlfs | d94011e | 2021-03-08 23:37:58 +0100 | [diff] [blame] | 94 | export class GrDiffProcessor extends LegacyElementMixin(PolymerElement) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 95 | @property({type: Number}) |
| 96 | context = 3; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 97 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 98 | @property({type: Array, notify: true}) |
| 99 | groups: GrDiffGroup[] = []; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 100 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 101 | @property({type: Object}) |
| 102 | keyLocations: KeyLocations = {left: {}, right: {}}; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 103 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 104 | @property({type: Number}) |
| 105 | _asyncThreshold = 64; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 106 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 107 | @property({type: Number}) |
| 108 | _nextStepHandle: number | null = null; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 109 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 110 | @property({type: Object}) |
| 111 | _processPromise: CancelablePromise<void> | null = null; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 112 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 113 | @property({type: Boolean}) |
| 114 | _isScrolling?: boolean; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 115 | |
| 116 | /** @override */ |
Ben Rohlfs | 5f520da | 2021-03-10 14:58:43 +0100 | [diff] [blame^] | 117 | connectedCallback() { |
| 118 | super.connectedCallback(); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 119 | this.listen(window, 'scroll', '_handleWindowScroll'); |
| 120 | } |
| 121 | |
| 122 | /** @override */ |
Ben Rohlfs | 5f520da | 2021-03-10 14:58:43 +0100 | [diff] [blame^] | 123 | disconnectedCallback() { |
Ben Rohlfs | 04fc1ca | 2021-02-13 13:06:53 +0100 | [diff] [blame] | 124 | this.cancelDebouncer(DEBOUNCER_RESET_IS_SCROLLING); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 125 | this.cancel(); |
| 126 | this.unlisten(window, 'scroll', '_handleWindowScroll'); |
Ben Rohlfs | 5f520da | 2021-03-10 14:58:43 +0100 | [diff] [blame^] | 127 | super.disconnectedCallback(); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 128 | } |
| 129 | |
| 130 | _handleWindowScroll() { |
| 131 | this._isScrolling = true; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 132 | this.debounce( |
Ben Rohlfs | 04fc1ca | 2021-02-13 13:06:53 +0100 | [diff] [blame] | 133 | DEBOUNCER_RESET_IS_SCROLLING, |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 134 | () => { |
| 135 | this._isScrolling = false; |
| 136 | }, |
| 137 | 50 |
| 138 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 139 | } |
Renan Oliveira | a132fae | 2019-09-19 14:38:22 +0000 | [diff] [blame] | 140 | |
Wyatt Allen | 32b03fc | 2016-08-05 15:56:33 -0700 | [diff] [blame] | 141 | /** |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 142 | * Asynchronously process the diff chunks into groups. As it processes, it |
| 143 | * will splice groups into the `groups` property of the component. |
Wyatt Allen | 32b03fc | 2016-08-05 15:56:33 -0700 | [diff] [blame] | 144 | * |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 145 | * @return A promise that resolves with an |
| 146 | * array of GrDiffGroups when the diff is completely processed. |
Wyatt Allen | 32b03fc | 2016-08-05 15:56:33 -0700 | [diff] [blame] | 147 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 148 | process(chunks: DiffContent[], isBinary: boolean) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 149 | // Cancel any still running process() calls, because they append to the |
| 150 | // same groups field. |
| 151 | this.cancel(); |
Wyatt Allen | 32b03fc | 2016-08-05 15:56:33 -0700 | [diff] [blame] | 152 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 153 | this.groups = []; |
Dhruv Srivastava | ac2bbd3 | 2021-02-04 22:08:55 +0100 | [diff] [blame] | 154 | this.push('groups', this._makeGroup('LOST')); |
| 155 | this.push('groups', this._makeGroup(FILE)); |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 156 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 157 | // If it's a binary diff, we won't be rendering hunks of text differences |
| 158 | // so finish processing. |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 159 | if (isBinary) { |
| 160 | return Promise.resolve(); |
| 161 | } |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 162 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 163 | this._processPromise = util.makeCancelable( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 164 | new Promise(resolve => { |
| 165 | const state = { |
| 166 | lineNums: {left: 0, right: 0}, |
| 167 | chunkIndex: 0, |
| 168 | }; |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 169 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 170 | chunks = this._splitLargeChunks(chunks); |
| 171 | chunks = this._splitCommonChunksWithKeyLocations(chunks); |
Wyatt Allen | 7f2bd97 | 2016-06-27 12:19:21 -0700 | [diff] [blame] | 172 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 173 | let currentBatch = 0; |
| 174 | const nextStep = () => { |
| 175 | if (this._isScrolling) { |
| 176 | this._nextStepHandle = this.async(nextStep, 100); |
| 177 | return; |
| 178 | } |
| 179 | // If we are done, resolve the promise. |
| 180 | if (state.chunkIndex >= chunks.length) { |
| 181 | resolve(); |
| 182 | this._nextStepHandle = null; |
| 183 | return; |
| 184 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 185 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 186 | // Process the next chunk and incorporate the result. |
| 187 | const stateUpdate = this._processNext(state, chunks); |
| 188 | for (const group of stateUpdate.groups) { |
| 189 | this.push('groups', group); |
| 190 | currentBatch += group.lines.length; |
| 191 | } |
| 192 | state.lineNums.left += stateUpdate.lineDelta.left; |
| 193 | state.lineNums.right += stateUpdate.lineDelta.right; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 194 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 195 | // Increment the index and recurse. |
| 196 | state.chunkIndex = stateUpdate.newChunkIndex; |
| 197 | if (currentBatch >= this._asyncThreshold) { |
| 198 | currentBatch = 0; |
| 199 | this._nextStepHandle = this.async(nextStep, 1); |
| 200 | } else { |
| 201 | nextStep.call(this); |
| 202 | } |
| 203 | }; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 204 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 205 | nextStep.call(this); |
| 206 | }) |
| 207 | ); |
| 208 | return this._processPromise.finally(() => { |
| 209 | this._processPromise = null; |
| 210 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Cancel any jobs that are running. |
| 215 | */ |
| 216 | cancel() { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 217 | if (this._nextStepHandle !== null) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 218 | this.cancelAsync(this._nextStepHandle); |
| 219 | this._nextStepHandle = null; |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 220 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 221 | if (this._processPromise) { |
| 222 | this._processPromise.cancel(); |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 223 | } |
| 224 | } |
| 225 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 226 | /** |
| 227 | * Process the next uncollapsible chunk, or the next collapsible chunks. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 228 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 229 | _processNext(state: State, chunks: DiffContent[]) { |
| 230 | const firstUncollapsibleChunkIndex = this._firstUncollapsibleChunkIndex( |
| 231 | chunks, |
| 232 | state.chunkIndex |
| 233 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 234 | if (firstUncollapsibleChunkIndex === state.chunkIndex) { |
| 235 | const chunk = chunks[state.chunkIndex]; |
| 236 | return { |
| 237 | lineDelta: { |
| 238 | left: this._linesLeft(chunk).length, |
| 239 | right: this._linesRight(chunk).length, |
| 240 | }, |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 241 | groups: [ |
| 242 | this._chunkToGroup( |
| 243 | chunk, |
| 244 | state.lineNums.left + 1, |
| 245 | state.lineNums.right + 1 |
| 246 | ), |
| 247 | ], |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 248 | newChunkIndex: state.chunkIndex + 1, |
| 249 | }; |
| 250 | } |
| 251 | |
| 252 | return this._processCollapsibleChunks( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 253 | state, |
| 254 | chunks, |
| 255 | firstUncollapsibleChunkIndex |
| 256 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 257 | } |
| 258 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 259 | _linesLeft(chunk: DiffContent) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 260 | return chunk.ab || chunk.a || []; |
| 261 | } |
| 262 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 263 | _linesRight(chunk: DiffContent) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 264 | return chunk.ab || chunk.b || []; |
| 265 | } |
| 266 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 267 | _firstUncollapsibleChunkIndex(chunks: DiffContent[], offset: number) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 268 | let chunkIndex = offset; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 269 | while ( |
| 270 | chunkIndex < chunks.length && |
| 271 | this._isCollapsibleChunk(chunks[chunkIndex]) |
| 272 | ) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 273 | chunkIndex++; |
| 274 | } |
| 275 | return chunkIndex; |
| 276 | } |
| 277 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 278 | _isCollapsibleChunk(chunk: DiffContent) { |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 279 | return (chunk.ab || chunk.common || chunk.skip) && !chunk.keyLocation; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 280 | } |
| 281 | |
| 282 | /** |
| 283 | * Process a stretch of collapsible chunks. |
| 284 | * |
| 285 | * Outputs up to three groups: |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 286 | * 1) Visible context before the hidden common code, unless it's the |
| 287 | * very beginning of the file. |
| 288 | * 2) Context hidden behind a context bar, unless empty. |
| 289 | * 3) Visible context after the hidden common code, unless it's the very |
| 290 | * end of the file. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 291 | */ |
| 292 | _processCollapsibleChunks( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 293 | state: State, |
| 294 | chunks: DiffContent[], |
| 295 | firstUncollapsibleChunkIndex: number |
| 296 | ) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 297 | const collapsibleChunks = chunks.slice( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 298 | state.chunkIndex, |
| 299 | firstUncollapsibleChunkIndex |
| 300 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 301 | const lineCount = collapsibleChunks.reduce( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 302 | (sum, chunk) => sum + this._commonChunkLength(chunk), |
| 303 | 0 |
| 304 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 305 | |
| 306 | let groups = this._chunksToGroups( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 307 | collapsibleChunks, |
| 308 | state.lineNums.left + 1, |
| 309 | state.lineNums.right + 1 |
| 310 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 311 | |
Renan Oliveira | 2310dd4 | 2020-11-09 14:15:20 +0100 | [diff] [blame] | 312 | const hasSkippedGroup = !!groups.find(g => g.skip); |
| 313 | if (this.context !== WHOLE_FILE || hasSkippedGroup) { |
| 314 | const contextNumLines = this.context > 0 ? this.context : 0; |
| 315 | const hiddenStart = state.chunkIndex === 0 ? 0 : contextNumLines; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 316 | const hiddenEnd = |
| 317 | lineCount - |
| 318 | (firstUncollapsibleChunkIndex === chunks.length ? 0 : this.context); |
| 319 | groups = hideInContextControl(groups, hiddenStart, hiddenEnd); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 320 | } |
| 321 | |
| 322 | return { |
| 323 | lineDelta: { |
| 324 | left: lineCount, |
| 325 | right: lineCount, |
| 326 | }, |
| 327 | groups, |
| 328 | newChunkIndex: firstUncollapsibleChunkIndex, |
| 329 | }; |
| 330 | } |
| 331 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 332 | _commonChunkLength(chunk: DiffContent) { |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 333 | if (chunk.skip) { |
| 334 | return chunk.skip; |
| 335 | } |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 336 | console.assert(!!chunk.ab || !!chunk.common); |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 337 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 338 | console.assert( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 339 | !chunk.a || (!!chunk.b && chunk.a.length === chunk.b.length), |
| 340 | 'common chunk needs same number of a and b lines: ', |
| 341 | chunk |
| 342 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 343 | return this._linesLeft(chunk).length; |
| 344 | } |
| 345 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 346 | _chunksToGroups( |
| 347 | chunks: DiffContent[], |
| 348 | offsetLeft: number, |
| 349 | offsetRight: number |
| 350 | ): GrDiffGroup[] { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 351 | return chunks.map(chunk => { |
| 352 | const group = this._chunkToGroup(chunk, offsetLeft, offsetRight); |
| 353 | const chunkLength = this._commonChunkLength(chunk); |
| 354 | offsetLeft += chunkLength; |
| 355 | offsetRight += chunkLength; |
| 356 | return group; |
| 357 | }); |
| 358 | } |
| 359 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 360 | _chunkToGroup( |
| 361 | chunk: DiffContent, |
| 362 | offsetLeft: number, |
| 363 | offsetRight: number |
| 364 | ): GrDiffGroup { |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 365 | const type = |
| 366 | chunk.ab || chunk.skip ? GrDiffGroupType.BOTH : GrDiffGroupType.DELTA; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 367 | const lines = this._linesFromChunk(chunk, offsetLeft, offsetRight); |
| 368 | const group = new GrDiffGroup(type, lines); |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 369 | group.keyLocation = !!chunk.keyLocation; |
| 370 | group.dueToRebase = !!chunk.due_to_rebase; |
Renan Oliveira | fb4ac83 | 2020-11-26 21:17:59 +0100 | [diff] [blame] | 371 | group.moveDetails = chunk.move_details; |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 372 | group.skip = chunk.skip; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 373 | group.ignoredWhitespaceOnly = !!chunk.common; |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 374 | if (chunk.skip) { |
| 375 | group.lineRange = { |
Ole Rehmsen | 8e744f4 | 2021-02-05 11:10:20 +0100 | [diff] [blame] | 376 | left: {start_line: offsetLeft, end_line: offsetLeft + chunk.skip - 1}, |
| 377 | right: { |
| 378 | start_line: offsetRight, |
| 379 | end_line: offsetRight + chunk.skip - 1, |
| 380 | }, |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 381 | }; |
| 382 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 383 | return group; |
| 384 | } |
| 385 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 386 | _linesFromChunk(chunk: DiffContent, offsetLeft: number, offsetRight: number) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 387 | if (chunk.ab) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 388 | return chunk.ab.map((row, i) => |
| 389 | this._lineFromRow(GrDiffLineType.BOTH, offsetLeft, offsetRight, row, i) |
| 390 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 391 | } |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 392 | let lines: GrDiffLine[] = []; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 393 | if (chunk.a) { |
| 394 | // Avoiding a.push(...b) because that causes callstack overflows for |
| 395 | // large b, which can occur when large files are added removed. |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 396 | lines = lines.concat( |
| 397 | this._linesFromRows( |
| 398 | GrDiffLineType.REMOVE, |
| 399 | chunk.a, |
| 400 | offsetLeft, |
| 401 | chunk.edit_a |
| 402 | ) |
| 403 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 404 | } |
| 405 | if (chunk.b) { |
| 406 | // Avoiding a.push(...b) because that causes callstack overflows for |
| 407 | // large b, which can occur when large files are added removed. |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 408 | lines = lines.concat( |
| 409 | this._linesFromRows( |
| 410 | GrDiffLineType.ADD, |
| 411 | chunk.b, |
| 412 | offsetRight, |
| 413 | chunk.edit_b |
| 414 | ) |
| 415 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 416 | } |
| 417 | return lines; |
| 418 | } |
| 419 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 420 | _linesFromRows( |
| 421 | lineType: GrDiffLineType, |
| 422 | rows: string[], |
| 423 | offset: number, |
| 424 | intralineInfos?: number[][] |
| 425 | ): GrDiffLine[] { |
| 426 | const grDiffHighlights = intralineInfos |
| 427 | ? this._convertIntralineInfos(rows, intralineInfos) |
| 428 | : undefined; |
| 429 | return rows.map((row, i) => |
| 430 | this._lineFromRow(lineType, offset, offset, row, i, grDiffHighlights) |
| 431 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 432 | } |
| 433 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 434 | _lineFromRow( |
| 435 | type: GrDiffLineType, |
| 436 | offsetLeft: number, |
| 437 | offsetRight: number, |
| 438 | row: string, |
| 439 | i: number, |
Ben Rohlfs | a7a4f51 | 2020-08-25 13:40:59 +0200 | [diff] [blame] | 440 | highlights?: Highlights[] |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 441 | ): GrDiffLine { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 442 | const line = new GrDiffLine(type); |
| 443 | line.text = row; |
Ben Rohlfs | 5e2d1e7 | 2020-08-03 19:33:31 +0200 | [diff] [blame] | 444 | if (type !== GrDiffLineType.ADD) line.beforeNumber = offsetLeft + i; |
| 445 | if (type !== GrDiffLineType.REMOVE) line.afterNumber = offsetRight + i; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 446 | if (highlights) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 447 | line.hasIntralineInfo = true; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 448 | line.highlights = highlights.filter(hl => hl.contentIndex === i); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 449 | } else { |
| 450 | line.hasIntralineInfo = false; |
| 451 | } |
| 452 | return line; |
| 453 | } |
| 454 | |
Dhruv Srivastava | ac2bbd3 | 2021-02-04 22:08:55 +0100 | [diff] [blame] | 455 | _makeGroup(number: LineNumber) { |
Ben Rohlfs | 5e2d1e7 | 2020-08-03 19:33:31 +0200 | [diff] [blame] | 456 | const line = new GrDiffLine(GrDiffLineType.BOTH); |
Dhruv Srivastava | ac2bbd3 | 2021-02-04 22:08:55 +0100 | [diff] [blame] | 457 | line.beforeNumber = number; |
| 458 | line.afterNumber = number; |
Ben Rohlfs | 5e2d1e7 | 2020-08-03 19:33:31 +0200 | [diff] [blame] | 459 | return new GrDiffGroup(GrDiffGroupType.BOTH, [line]); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Split chunks into smaller chunks of the same kind. |
| 464 | * |
| 465 | * This is done to prevent doing too much work on the main thread in one |
| 466 | * uninterrupted rendering step, which would make the browser unresponsive. |
| 467 | * |
| 468 | * Note that in the case of unmodified chunks, we only split chunks if the |
| 469 | * context is set to file (because otherwise they are split up further down |
| 470 | * the processing into the visible and hidden context), and only split it |
| 471 | * into 2 chunks, one max sized one and the rest (for reasons that are |
| 472 | * unclear to me). |
| 473 | * |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 474 | * @param chunks Chunks as returned from the server |
| 475 | * @return Finer grained chunks. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 476 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 477 | _splitLargeChunks(chunks: DiffContent[]): DiffContent[] { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 478 | const newChunks = []; |
| 479 | |
| 480 | for (const chunk of chunks) { |
| 481 | if (!chunk.ab) { |
| 482 | for (const subChunk of this._breakdownChunk(chunk)) { |
| 483 | newChunks.push(subChunk); |
| 484 | } |
| 485 | continue; |
| 486 | } |
| 487 | |
| 488 | // If the context is set to "whole file", then break down the shared |
| 489 | // chunks so they can be rendered incrementally. Note: this is not |
| 490 | // enabled for any other context preference because manipulating the |
| 491 | // chunks in this way violates assumptions by the context grouper logic. |
| 492 | if (this.context === -1 && chunk.ab.length > MAX_GROUP_SIZE * 2) { |
| 493 | // Split large shared chunks in two, where the first is the maximum |
| 494 | // group size. |
| 495 | newChunks.push({ab: chunk.ab.slice(0, MAX_GROUP_SIZE)}); |
| 496 | newChunks.push({ab: chunk.ab.slice(MAX_GROUP_SIZE)}); |
| 497 | } else { |
| 498 | newChunks.push(chunk); |
| 499 | } |
| 500 | } |
| 501 | return newChunks; |
| 502 | } |
| 503 | |
| 504 | /** |
| 505 | * In order to show key locations, such as comments, out of the bounds of |
| 506 | * the selected context, treat them as separate chunks within the model so |
| 507 | * that the content (and context surrounding it) renders correctly. |
| 508 | * |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 509 | * @param chunks DiffContents as returned from server. |
| 510 | * @return Finer grained DiffContents. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 511 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 512 | _splitCommonChunksWithKeyLocations(chunks: DiffContent[]): DiffContent[] { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 513 | const result = []; |
| 514 | let leftLineNum = 1; |
| 515 | let rightLineNum = 1; |
| 516 | |
| 517 | for (const chunk of chunks) { |
| 518 | // If it isn't a common chunk, append it as-is and update line numbers. |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 519 | if (!chunk.ab && !chunk.skip && !chunk.common) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 520 | if (chunk.a) { |
| 521 | leftLineNum += chunk.a.length; |
| 522 | } |
| 523 | if (chunk.b) { |
| 524 | rightLineNum += chunk.b.length; |
| 525 | } |
| 526 | result.push(chunk); |
| 527 | continue; |
| 528 | } |
| 529 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 530 | if (chunk.common && chunk.a!.length !== chunk.b!.length) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 531 | throw new Error( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 532 | 'DiffContent with common=true must always have equal length' |
| 533 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 534 | } |
| 535 | const numLines = this._commonChunkLength(chunk); |
| 536 | const chunkEnds = this._findChunkEndsAtKeyLocations( |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 537 | numLines, |
| 538 | leftLineNum, |
| 539 | rightLineNum |
| 540 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 541 | leftLineNum += numLines; |
| 542 | rightLineNum += numLines; |
| 543 | |
Renan Oliveira | 81d677e | 2020-11-04 15:31:02 +0100 | [diff] [blame] | 544 | if (chunk.skip) { |
| 545 | result.push({ |
| 546 | ...chunk, |
| 547 | skip: chunk.skip, |
| 548 | keyLocation: false, |
| 549 | }); |
| 550 | } else if (chunk.ab) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 551 | result.push( |
| 552 | ...this._splitAtChunkEnds(chunk.ab, chunkEnds).map( |
| 553 | ({lines, keyLocation}) => { |
Tao Zhou | 4cd35cb | 2020-07-22 11:28:22 +0200 | [diff] [blame] | 554 | return { |
| 555 | ...chunk, |
| 556 | ab: lines, |
| 557 | keyLocation, |
| 558 | }; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 559 | } |
| 560 | ) |
| 561 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 562 | } else if (chunk.common) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 563 | const aChunks = this._splitAtChunkEnds(chunk.a!, chunkEnds); |
| 564 | const bChunks = this._splitAtChunkEnds(chunk.b!, chunkEnds); |
| 565 | result.push( |
| 566 | ...aChunks.map(({lines, keyLocation}, i) => { |
| 567 | return { |
| 568 | ...chunk, |
| 569 | a: lines, |
| 570 | b: bChunks[i].lines, |
| 571 | keyLocation, |
| 572 | }; |
| 573 | }) |
| 574 | ); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 575 | } |
| 576 | } |
| 577 | |
| 578 | return result; |
| 579 | } |
| 580 | |
| 581 | /** |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 582 | * @return Offsets of the new chunk ends, including whether it's a key |
| 583 | * location. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 584 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 585 | _findChunkEndsAtKeyLocations( |
| 586 | numLines: number, |
| 587 | leftOffset: number, |
| 588 | rightOffset: number |
| 589 | ): ChunkEnd[] { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 590 | const result = []; |
| 591 | let lastChunkEnd = 0; |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 592 | for (let i = 0; i < numLines; i++) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 593 | // If this line should not be collapsed. |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 594 | if ( |
Ben Rohlfs | 1d48706 | 2020-09-26 11:26:03 +0200 | [diff] [blame] | 595 | this.keyLocations[Side.LEFT][leftOffset + i] || |
| 596 | this.keyLocations[Side.RIGHT][rightOffset + i] |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 597 | ) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 598 | // If any lines have been accumulated into the chunk leading up to |
| 599 | // this non-collapse line, then add them as a chunk and start a new |
| 600 | // one. |
| 601 | if (i > lastChunkEnd) { |
| 602 | result.push({offset: i, keyLocation: false}); |
| 603 | lastChunkEnd = i; |
| 604 | } |
| 605 | |
| 606 | // Add the non-collapse line as its own chunk. |
| 607 | result.push({offset: i + 1, keyLocation: true}); |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | if (numLines > lastChunkEnd) { |
| 612 | result.push({offset: numLines, keyLocation: false}); |
| 613 | } |
| 614 | |
| 615 | return result; |
| 616 | } |
| 617 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 618 | _splitAtChunkEnds(lines: string[], chunkEnds: ChunkEnd[]) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 619 | const result = []; |
| 620 | let lastChunkEndOffset = 0; |
| 621 | for (const {offset, keyLocation} of chunkEnds) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 622 | result.push({ |
| 623 | lines: lines.slice(lastChunkEndOffset, offset), |
| 624 | keyLocation, |
| 625 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 626 | lastChunkEndOffset = offset; |
| 627 | } |
| 628 | return result; |
| 629 | } |
| 630 | |
| 631 | /** |
| 632 | * Converts `IntralineInfo`s return by the API to `GrLineHighlights` used |
| 633 | * for rendering. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 634 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 635 | _convertIntralineInfos( |
| 636 | rows: string[], |
| 637 | intralineInfos: number[][] |
| 638 | ): Highlights[] { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 639 | let rowIndex = 0; |
| 640 | let idx = 0; |
| 641 | const normalized = []; |
| 642 | for (const [skipLength, markLength] of intralineInfos) { |
| 643 | let line = rows[rowIndex] + '\n'; |
| 644 | let j = 0; |
| 645 | while (j < skipLength) { |
| 646 | if (idx === line.length) { |
| 647 | idx = 0; |
| 648 | line = rows[++rowIndex] + '\n'; |
| 649 | continue; |
| 650 | } |
| 651 | idx++; |
| 652 | j++; |
| 653 | } |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 654 | let lineHighlight: Highlights = { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 655 | contentIndex: rowIndex, |
| 656 | startIndex: idx, |
| 657 | }; |
| 658 | |
| 659 | j = 0; |
| 660 | while (line && j < markLength) { |
| 661 | if (idx === line.length) { |
| 662 | idx = 0; |
| 663 | line = rows[++rowIndex] + '\n'; |
| 664 | normalized.push(lineHighlight); |
| 665 | lineHighlight = { |
| 666 | contentIndex: rowIndex, |
| 667 | startIndex: idx, |
| 668 | }; |
| 669 | continue; |
| 670 | } |
| 671 | idx++; |
| 672 | j++; |
| 673 | } |
| 674 | lineHighlight.endIndex = idx; |
| 675 | normalized.push(lineHighlight); |
| 676 | } |
| 677 | return normalized; |
| 678 | } |
| 679 | |
| 680 | /** |
| 681 | * If a group is an addition or a removal, break it down into smaller groups |
| 682 | * of that type using the MAX_GROUP_SIZE. If the group is a shared chunk |
| 683 | * or a delta it is returned as the single element of the result array. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 684 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 685 | _breakdownChunk(chunk: DiffContent): DiffContent[] { |
| 686 | let key: 'a' | 'b' | 'ab' | null = null; |
Renan Oliveira | 176d3a3 | 2020-12-17 17:49:09 +0100 | [diff] [blame] | 687 | const {a, b, ab, move_details} = chunk; |
| 688 | if (a?.length && !b?.length) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 689 | key = 'a'; |
Renan Oliveira | 176d3a3 | 2020-12-17 17:49:09 +0100 | [diff] [blame] | 690 | } else if (b?.length && !a?.length) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 691 | key = 'b'; |
Renan Oliveira | 176d3a3 | 2020-12-17 17:49:09 +0100 | [diff] [blame] | 692 | } else if (ab?.length) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 693 | key = 'ab'; |
| 694 | } |
| 695 | |
Renan Oliveira | 176d3a3 | 2020-12-17 17:49:09 +0100 | [diff] [blame] | 696 | // Move chunks should not be divided because of move label |
| 697 | // positioned in the top of the chunk |
| 698 | if (!key || move_details) { |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 699 | return [chunk]; |
| 700 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 701 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 702 | return this._breakdown(chunk[key]!, MAX_GROUP_SIZE).map(subChunkLines => { |
| 703 | const subChunk: DiffContent = {}; |
| 704 | subChunk[key!] = subChunkLines; |
| 705 | if (chunk.due_to_rebase) { |
| 706 | subChunk.due_to_rebase = true; |
| 707 | } |
Renan Oliveira | f411dd0 | 2020-11-18 18:46:12 +0100 | [diff] [blame] | 708 | if (chunk.move_details) { |
| 709 | subChunk.move_details = chunk.move_details; |
| 710 | } |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 711 | return subChunk; |
| 712 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 713 | } |
| 714 | |
| 715 | /** |
| 716 | * Given an array and a size, return an array of arrays where no inner array |
| 717 | * is larger than that size, preserving the original order. |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 718 | */ |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 719 | _breakdown<T>(array: T[], size: number): T[][] { |
| 720 | if (!array.length) { |
| 721 | return []; |
| 722 | } |
| 723 | if (array.length < size) { |
| 724 | return [array]; |
| 725 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 726 | |
| 727 | const head = array.slice(0, array.length - size); |
| 728 | const tail = array.slice(array.length - size); |
| 729 | |
| 730 | return this._breakdown(head, size).concat([tail]); |
| 731 | } |
| 732 | } |
| 733 | |
Ben Rohlfs | 32b8382 | 2020-08-14 22:08:37 +0200 | [diff] [blame] | 734 | declare global { |
| 735 | interface HTMLElementTagNameMap { |
| 736 | 'gr-diff-processor': GrDiffProcessor; |
| 737 | } |
| 738 | } |