Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @unrestricted | |
|
lushnikov
2017/02/15 02:29:54
why?
einbinder
2017/03/14 01:19:35
Gone.
| |
| 7 * @implements {UI.ListDelegate} | |
|
lushnikov
2017/02/15 02:29:54
template pls
einbinder
2017/03/14 01:19:36
Gone.
| |
| 8 */ | |
| 9 Changes.ChangesView = class extends UI.VBox { | |
| 10 constructor() { | |
| 11 super(); | |
|
lushnikov
2017/02/15 02:29:54
super(true)
einbinder
2017/03/14 01:19:36
Done.
| |
| 12 Changes.ChangesView.SharedInstance = this; | |
| 13 this.registerRequiredCSS('changes/changesView.css'); | |
| 14 var splitWidget = new UI.SplitWidget(true, false); | |
| 15 var mainWidget = new UI.Widget(); | |
| 16 splitWidget.setMainWidget(mainWidget); | |
| 17 | |
| 18 /** @type {?Workspace.UISourceCode} */ | |
| 19 this._uiSourceCode = null; | |
| 20 | |
| 21 /** @type {!Array<!Changes.ChangesView.Row>} */ | |
| 22 this._rows = []; | |
| 23 | |
| 24 this._maxLineDigits = 1; | |
| 25 | |
| 26 | |
|
lushnikov
2017/02/15 02:29:54
two lines
einbinder
2017/03/14 01:19:36
Done.
| |
| 27 var code = mainWidget.element.createChild('div', 'code'); | |
| 28 this._editor = new TextEditor.CodeMirrorTextEditor( | |
| 29 {lineNumbers: true, lineWrapping: false, mimeType: 'devtools-diff', maxH ighlightLength: Infinity}); | |
| 30 this._editor.setReadOnly(true); | |
| 31 this._editor.show(code); | |
| 32 this._editor.setLineNumberFormatter(() => ''); | |
| 33 | |
| 34 this._toolbar = new UI.Toolbar('changes-toolbar', mainWidget.element); | |
| 35 var revertButton = new UI.ToolbarButton(Common.UIString('Revert all changes' ), 'largeicon-undo'); | |
| 36 revertButton.addEventListener(UI.ToolbarButton.Events.Click, this._revert.bi nd(this)); | |
| 37 this._toolbar.appendToolbarItem(revertButton); | |
| 38 this._diffStats = new UI.ToolbarText(''); | |
| 39 this._toolbar.appendToolbarItem(this._diffStats); | |
| 40 this._toolbar.setEnabled(false); | |
| 41 | |
| 42 this._navigator = new Changes.ChangesNavigator(); | |
| 43 splitWidget.setSidebarWidget(this._navigator); | |
| 44 splitWidget.show(this.element); | |
| 45 | |
| 46 Workspace.workspace.addEventListener( | |
| 47 Workspace.Workspace.Events.WorkingCopyCommittedByUser, this._refreshUISo urceCode, this); | |
| 48 Workspace.workspace.addEventListener( | |
| 49 Workspace.Workspace.Events.WorkingCopyChanged, this._refreshUISourceCode , this); | |
| 50 } | |
| 51 | |
| 52 /** | |
| 53 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 54 */ | |
| 55 static showChanges(uiSourceCode) { | |
| 56 UI.viewManager.showView('changes.changes'); | |
| 57 var changesView = | |
| 58 /** @type {!Changes.ChangesView} */ (self.runtime.sharedInstance(Changes .ChangesView)); | |
| 59 this._navigator.revealUISourceCode(uiSourceCode, true); | |
| 60 changesView._revealUISourceCode(uiSourceCode); | |
| 61 } | |
| 62 | |
| 63 _revert() { | |
| 64 var uiSourceCode = this._uiSourceCode; | |
| 65 if (!uiSourceCode) | |
| 66 return; | |
| 67 uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevis ion(original || '')); | |
| 68 } | |
| 69 | |
| 70 /** | |
| 71 * @param {!Common.Event} event | |
| 72 */ | |
| 73 _refreshUISourceCode(event) { | |
| 74 var uiSourceCode = /** @type !Workspace.UISourceCode */ (event.data.uiSource Code); | |
| 75 this._navigator.refreshUISourceCode(uiSourceCode); | |
| 76 if (uiSourceCode === this._uiSourceCode) | |
| 77 this._revealUISourceCode(uiSourceCode); | |
| 78 } | |
| 79 | |
| 80 /** | |
| 81 * @param {?Workspace.UISourceCode} uiSourceCode | |
| 82 */ | |
| 83 _revealUISourceCode(uiSourceCode) { | |
| 84 var scrollTop = 0; | |
| 85 if (this._uiSourceCode === uiSourceCode) { | |
| 86 scrollTop = this._editor.element.scrollTop; | |
| 87 } else { | |
| 88 this._diffStats.setText('Loading..'); | |
|
lushnikov
2017/02/15 02:29:54
UIString
einbinder
2017/03/14 01:19:36
Done.
| |
| 89 this._editor.setText(''); | |
| 90 } | |
| 91 | |
| 92 this._uiSourceCode = uiSourceCode; | |
| 93 this._toolbar.setEnabled(!!uiSourceCode); | |
| 94 | |
| 95 if (!uiSourceCode) { | |
| 96 this._rows = []; | |
| 97 this._diffStats.setText(''); | |
| 98 this._editor.setText(''); | |
| 99 return; | |
| 100 } | |
| 101 uiSourceCode.requestOriginalContent().then(originalContent => { | |
| 102 if (this._uiSourceCode !== uiSourceCode) | |
| 103 return; | |
| 104 this._renderRows(originalContent || '', uiSourceCode.workingCopy()); | |
| 105 this._editor.element.scrollTop = scrollTop; | |
| 106 }); | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * @param {string} originalContent | |
| 111 * @param {string} currentContent | |
| 112 */ | |
| 113 _renderRows(originalContent, currentContent) { | |
| 114 this._rows = []; | |
| 115 var lineSeparator = originalContent.indexOf('\r\n') >= 0 ? '\r\n' : '\n'; | |
| 116 var originalLines = originalContent.split(lineSeparator); | |
| 117 var currentLines = currentContent.split(lineSeparator); | |
| 118 | |
| 119 var insertions = 0; | |
| 120 var deletions = 0; | |
| 121 this._maxLineDigits = Math.ceil(Math.log10(Math.max(originalLines.length, cu rrentLines.length))); | |
| 122 | |
| 123 var diff = Diff.Diff.lineDiff(originalLines, currentLines); | |
|
lushnikov
2017/02/15 02:29:54
let's extract this and use the entity as a target
| |
| 124 var currentLineNumber = 0; | |
| 125 var baselineLineNumber = 0; | |
| 126 var paddingLines = 3; | |
| 127 | |
| 128 for (var i = 0; i < diff.length; ++i) { | |
| 129 var token = diff[i]; | |
| 130 switch (token[0]) { | |
| 131 case Diff.Diff.Operation.Equal: | |
| 132 this._rows.pushAll(createEqualRows(token[1], i === 0, i === diff.lengt h - 1)); | |
| 133 break; | |
| 134 case Diff.Diff.Operation.Insert: | |
| 135 for (var line in token[1]) | |
| 136 this._rows.push(createRow(line, 'addition')); | |
| 137 break; | |
| 138 case Diff.Diff.Operation.Delete: | |
| 139 if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) { | |
| 140 this._rows.pushAll(createModifyRows(token[1].join(lineSeparator), di ff[i + 1][1].join(lineSeparator))); | |
| 141 i++; | |
| 142 } else { | |
| 143 for (var line in token[1]) | |
| 144 this._rows.push(createRow(line, 'addition')); | |
| 145 } | |
| 146 break; | |
| 147 } | |
| 148 } | |
| 149 Changes.ChangesView._currentContent = this._rows; | |
| 150 this._editor.setText(this._rows.map(row => row.content.map(t => t.text).join ('')).join(lineSeparator)); | |
| 151 this._editor.setLineNumberFormatter(this._lineFormatter.bind(this)); | |
| 152 | |
| 153 this._diffStats.setText(Common.UIString( | |
| 154 '%d insertion%s (+), %d deletion%s (-)', insertions, insertions > 1 ? 's ' : '', deletions, | |
| 155 deletions > 1 ? 's' : '')); | |
| 156 | |
| 157 /** | |
| 158 * @param {!Array<string>} lines | |
| 159 * @param {boolean} atStart | |
| 160 * @param {boolean} atEnd | |
| 161 * @return {!Array<!Changes.ChangesView.Row>}} | |
| 162 */ | |
| 163 function createEqualRows(lines, atStart, atEnd) { | |
| 164 var equalRows = []; | |
| 165 if (!atStart) { | |
| 166 for (var i = 0; i < paddingLines && i < lines.length; i++) | |
| 167 equalRows.push(createRow(lines[i], 'equal')); | |
| 168 if (lines.length > paddingLines * 2 + 1 && !atEnd) { | |
| 169 equalRows.push(createRow( | |
| 170 Common.UIString('( \u2026 Skipping ') + (lines.length - paddingLin es * 2) + | |
| 171 Common.UIString(' matching lines \u2026 )'), | |
| 172 'spacer')); | |
| 173 } | |
| 174 } | |
| 175 if (!atEnd) { | |
| 176 var start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : padd ingLines); | |
| 177 baselineLineNumber += start - Math.min(atStart ? 0 : paddingLines, lines .length); | |
| 178 currentLineNumber += start - Math.min(atStart ? 0 : paddingLines, lines. length); | |
| 179 for (var i = start; i < lines.length; i++) | |
| 180 equalRows.push(createRow(lines[i], 'equal')); | |
| 181 } | |
| 182 return equalRows; | |
| 183 } | |
| 184 | |
| 185 /** | |
| 186 * @param {string} before | |
| 187 * @param {string} after | |
| 188 * @return {!Array<!Changes.ChangesView.Row>}} | |
| 189 */ | |
| 190 function createModifyRows(before, after) { | |
| 191 var internalDiff = Diff.Diff.charDiff(before, after); | |
| 192 var deletionRows = [createRow('', 'deletion')]; | |
| 193 var insertionRows = [createRow('', 'addition')]; | |
| 194 | |
| 195 for (var token of internalDiff) { | |
| 196 var text = token[1]; | |
| 197 var type = token[0]; | |
| 198 var className = type === Diff.Diff.Operation.Equal ? '' : 'double'; | |
| 199 var first = true; | |
| 200 for (var line of text.split(lineSeparator)) { | |
| 201 if (first) { | |
| 202 first = false; | |
| 203 } else { | |
| 204 if (type !== Diff.Diff.Operation.Insert) | |
| 205 deletionRows.push(createRow('', 'deletion')); | |
| 206 if (type !== Diff.Diff.Operation.Delete) | |
| 207 insertionRows.push(createRow('', 'addition')); | |
| 208 } | |
| 209 if (line) { | |
| 210 if (type !== Diff.Diff.Operation.Insert) | |
| 211 deletionRows[deletionRows.length - 1].content.push({text: line, cl assName: className}); | |
| 212 | |
| 213 if (type !== Diff.Diff.Operation.Delete) | |
| 214 insertionRows[insertionRows.length - 1].content.push({text: line, className: className}); | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 return deletionRows.concat(insertionRows); | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * @param {string} text | |
| 223 * @param {string} className | |
| 224 * @return {!Changes.ChangesView.Row} | |
| 225 */ | |
| 226 function createRow(text, className) { | |
| 227 if (className === 'addition') { | |
| 228 currentLineNumber++; | |
| 229 insertions++; | |
| 230 } | |
| 231 if (className === 'deletion') { | |
| 232 baselineLineNumber++; | |
| 233 deletions++; | |
| 234 } | |
| 235 if (className === 'equal') { | |
| 236 baselineLineNumber++; | |
| 237 currentLineNumber++; | |
| 238 } | |
| 239 return { | |
| 240 base: baselineLineNumber, | |
| 241 current: currentLineNumber, | |
| 242 content: text ? [{text: text, className: 'double'}] : [], | |
| 243 className: className, | |
| 244 loc: currentLineNumber | |
| 245 }; | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * @param {number} lineNumber | |
| 251 * @return {string} | |
| 252 */ | |
| 253 _lineFormatter(lineNumber) { | |
| 254 var row = this._rows[lineNumber - 1]; | |
| 255 if (!row) | |
| 256 return spacesPadding(this._maxLineDigits * 2 + 1); | |
| 257 var showBaseNumber = row.className === 'deletion'; | |
| 258 var showCurrentNumber = row.className === 'addition'; | |
| 259 if (row.className === 'equal') { | |
| 260 showBaseNumber = true; | |
| 261 showCurrentNumber = true; | |
| 262 } | |
| 263 var base = showBaseNumber ? numberToStringWithSpacesPadding(row.base, this._ maxLineDigits) : | |
| 264 spacesPadding(this._maxLineDigits); | |
| 265 var current = showCurrentNumber ? numberToStringWithSpacesPadding(row.curren t, this._maxLineDigits) : | |
| 266 spacesPadding(this._maxLineDigits); | |
| 267 return base + spacesPadding(1) + current; | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * @param {number} baselineLineNumber | |
| 272 * @param {number} currentLineNumber | |
| 273 * @param {string} text | |
| 274 * @param {string=} className | |
| 275 * @return {!Element} | |
| 276 */ | |
| 277 _createRow(baselineLineNumber, currentLineNumber, text, className) { | |
| 278 var element = createElementWithClass('div', className); | |
| 279 element.createChild('span', 'number').textContent = baselineLineNumber ? | |
| 280 numberToStringWithSpacesPadding(baselineLineNumber, this._maxLineDigits) : | |
| 281 spacesPadding(this._maxLineDigits); | |
| 282 element.createChild('span', 'number').textContent = currentLineNumber ? | |
| 283 numberToStringWithSpacesPadding(currentLineNumber, this._maxLineDigits) : | |
| 284 spacesPadding(this._maxLineDigits); | |
| 285 element.createChild('span', 'double').textContent = text; | |
| 286 return element; | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * @override | |
| 291 * @param {!Element} item | |
| 292 * @return {!Element} | |
| 293 */ | |
| 294 createElementForItem(item) { | |
| 295 return item; | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * @override | |
| 300 * @return {number} | |
| 301 */ | |
| 302 heightForItem() { | |
| 303 return 0; | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * @override | |
| 308 * @return {boolean} | |
| 309 */ | |
| 310 isItemSelectable(item) { | |
| 311 return false; | |
| 312 } | |
| 313 | |
| 314 /** | |
| 315 * @override | |
| 316 */ | |
| 317 selectedItemChanged() { | |
| 318 } | |
| 319 }; | |
| 320 | |
| 321 /** @typedef {!{base: number, current: number, content: !Array<!{text: string, c lassName: string}>, className: string, loc: number}} */ | |
| 322 Changes.ChangesView.Row; | |
| 323 | |
| 324 Changes.ChangesNavigator = class extends Sources.NavigatorView { | |
| 325 constructor() { | |
| 326 super(); | |
| 327 this.dontGroup(); | |
| 328 this.element.classList.add('changes-navigator'); | |
| 329 } | |
| 330 | |
| 331 /** | |
| 332 * @override | |
| 333 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 334 * @return {boolean} | |
| 335 */ | |
| 336 accept(uiSourceCode) { | |
| 337 if (uiSourceCode.project().type() !== Workspace.projectTypes.Network) | |
| 338 return false; | |
| 339 | |
| 340 if (!uiSourceCode.mightHaveChanges) | |
| 341 return false; | |
| 342 uiSourceCode.hasChanges().then(hasChanges => { | |
| 343 if (!hasChanges) | |
| 344 this.refreshUISourceCode(uiSourceCode); | |
| 345 }); | |
| 346 return true; | |
| 347 } | |
| 348 | |
| 349 /** | |
| 350 * @override | |
| 351 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 352 * @param {boolean} focusSource | |
| 353 */ | |
| 354 revealSource(uiSourceCode, focusSource) { | |
| 355 Changes.ChangesView.SharedInstance._revealUISourceCode(uiSourceCode); | |
| 356 if (focusSource) | |
| 357 Changes.ChangesView.SharedInstance._editor.focus(); | |
| 358 } | |
| 359 | |
| 360 /** | |
| 361 * @override | |
| 362 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 363 */ | |
| 364 uiSourceCodeAdded(uiSourceCode) { | |
| 365 if (!Changes.ChangesView.SharedInstance._uiSourceCode) | |
| 366 this.revealSource(uiSourceCode, false); | |
| 367 } | |
| 368 | |
| 369 /** | |
| 370 * @override | |
| 371 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 372 */ | |
| 373 uiSourceCodeRemoved(uiSourceCode) { | |
| 374 if (Changes.ChangesView.SharedInstance._uiSourceCode !== uiSourceCode) | |
| 375 return; | |
| 376 var anyUISourceCode = this.anyUISourceCode(); | |
| 377 if (anyUISourceCode) | |
| 378 this.revealSource(anyUISourceCode, false); | |
| 379 } | |
| 380 }; | |
| 381 | |
| 382 Changes.UISourceCodeChange = class { | |
| 383 /** | |
| 384 * @param {!Workspace.UISourceCode} uiSourceCode | |
| 385 */ | |
| 386 constructor(uiSourceCode) { | |
| 387 this.uiSourceCode = uiSourceCode; | |
| 388 } | |
| 389 }; | |
| 390 | |
| 391 /** | |
| 392 * @implements {Common.Revealer} | |
| 393 */ | |
| 394 Changes.UISourceCodeChangeRevealer = class { | |
| 395 /** | |
| 396 * @override | |
| 397 * @param {!Object} uiSourceCodeChange | |
| 398 * @param {boolean=} omitFocus | |
| 399 * @return {!Promise} | |
| 400 */ | |
| 401 reveal(uiSourceCodeChange, omitFocus) { | |
| 402 if (!(uiSourceCodeChange instanceof Changes.UISourceCodeChange)) | |
| 403 return Promise.reject(new Error('Changes.UISourceCodeChange')); | |
| 404 Changes.ChangesView.showChanges(uiSourceCodeChange.uiSourceCode); | |
| 405 return Promise.resolve(); | |
| 406 } | |
| 407 }; | |
| 408 | |
| 409 Changes.ChangesView._currentContent = []; | |
| 410 /** @typedef {!{lineNumber: number, index: number}} */ | |
| 411 Changes.ChangesView.DiffState; | |
| 412 | |
| 413 CodeMirror.defineMode('devtools-diff', function() { | |
|
lushnikov
2017/02/15 02:39:19
this potentially will cache the outdated diff. We
einbinder
2017/03/14 01:19:36
Done.
| |
| 414 return { | |
| 415 /** | |
| 416 * @return {!Changes.ChangesView.DiffState} | |
| 417 */ | |
| 418 startState: function() { | |
| 419 return {lineNumber: 0, index: 0}; | |
| 420 }, | |
| 421 | |
| 422 /** | |
| 423 * @param {!{next: function()}} stream | |
| 424 * @param {!Changes.ChangesView.DiffState} state | |
| 425 * @return {string} | |
| 426 */ | |
| 427 token: function(stream, state) { | |
| 428 var row = Changes.ChangesView._currentContent[state.lineNumber]; | |
|
lushnikov
2017/02/15 02:39:19
you probably can pass this through options of the
einbinder
2017/03/14 01:19:36
Done.
| |
| 429 if (!row) { | |
| 430 stream.next(); | |
| 431 return ''; | |
| 432 } | |
| 433 var classes = ''; | |
| 434 if (state.index === 0) | |
| 435 classes += ' line-background-' + row.className + ' line-' + row.classNam e; | |
| 436 var chars = row.content[state.index].text.length; | |
| 437 for (var i = 0; i < chars; i++) | |
| 438 stream.next(); | |
| 439 classes += ' ' + row.content[state.index].className; | |
| 440 state.index++; | |
| 441 if (state.index >= row.content.length) { | |
| 442 state.lineNumber++; | |
| 443 state.index = 0; | |
| 444 } | |
| 445 return classes; | |
| 446 }, | |
| 447 | |
| 448 /** | |
| 449 * @param {!Changes.ChangesView.DiffState} state | |
| 450 * @return {string} | |
| 451 */ | |
| 452 blankLine: function(state) { | |
| 453 var row = Changes.ChangesView._currentContent[state.lineNumber]; | |
| 454 state.lineNumber++; | |
| 455 return row ? 'line-background-' + row.className + ' line-' + row.className : ''; | |
| 456 }, | |
| 457 }; | |
| 458 }); | |
| 459 | |
| 460 CodeMirror.defineMIME('devtools-diff', 'devtools-diff'); | |
| OLD | NEW |