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 Changes.ChangesView = class extends UI.VBox { | |
| 6 constructor() { | |
| 7 super(true); | |
| 8 Changes.ChangesView.SharedInstance = this; | |
|
luoe
2017/03/23 02:03:16
Not needed?
einbinder
2017/03/23 04:32:48
Done.
| |
| 9 this.registerRequiredCSS('changes/changesView.css'); | |
| 10 var splitWidget = new UI.SplitWidget(true, false); | |
| 11 var mainWidget = new UI.Widget(); | |
| 12 splitWidget.setMainWidget(mainWidget); | |
| 13 splitWidget.show(this.contentElement); | |
| 14 var diffArea = mainWidget.element.createChild('div', 'diff-area'); | |
| 15 | |
| 16 this._emptyWidget = new UI.EmptyWidget(Common.UIString('No changes')); | |
| 17 this._emptyWidget.show(diffArea); | |
| 18 | |
| 19 this._workspaceDiff = WorkspaceDiff.workspaceDiff(); | |
| 20 this._fileList = new Changes.FileList(this._workspaceDiff); | |
|
luoe
2017/03/23 02:03:16
var filelist = ...?
einbinder
2017/03/23 04:32:47
Done.
| |
| 21 this._fileList.on(Changes.FileList.SelectionChanged, this._fileListSelection Changed, this); | |
| 22 splitWidget.setSidebarWidget(this._fileList); | |
| 23 | |
| 24 /** @type {?Workspace.UISourceCode} */ | |
| 25 this._uiSourceCode = null; | |
| 26 | |
| 27 /** @type {!Array<!Changes.ChangesView.Row>} */ | |
| 28 this._rows = []; | |
| 29 | |
| 30 this._maxLineDigits = 1; | |
| 31 | |
| 32 this._editor = | |
| 33 new TextEditor.CodeMirrorTextEditor({lineNumbers: true, lineWrapping: fa lse, maxHighlightLength: Infinity}); | |
| 34 this._editor.setReadOnly(true); | |
| 35 this._editor.show(diffArea); | |
| 36 this._editor.hideWidget(); | |
| 37 this._editor.setLineNumberFormatter(() => ''); | |
| 38 | |
| 39 this._editor.element.addEventListener('mouseup', this._click.bind(this), fal se); | |
|
luoe
2017/03/23 02:03:16
Why not click?
einbinder
2017/03/23 04:32:48
Done.
| |
| 40 | |
| 41 this._toolbar = new UI.Toolbar('changes-toolbar', mainWidget.element); | |
| 42 var revertButton = new UI.ToolbarButton(Common.UIString('Revert all changes' ), 'largeicon-undo'); | |
| 43 revertButton.addEventListener(UI.ToolbarButton.Events.Click, this._revert.bi nd(this)); | |
| 44 this._toolbar.appendToolbarItem(revertButton); | |
| 45 this._diffStats = new UI.ToolbarText(''); | |
| 46 this._toolbar.appendToolbarItem(this._diffStats); | |
| 47 this._toolbar.setEnabled(false); | |
| 48 } | |
| 49 | |
| 50 /** | |
| 51 * @param {!Changes.FileList.SelectionChanged} event | |
| 52 */ | |
| 53 _fileListSelectionChanged(event) { | |
| 54 this._revealUISourceCode(event.uiSourceCode); | |
| 55 } | |
| 56 | |
| 57 _revert() { | |
| 58 var uiSourceCode = this._uiSourceCode; | |
| 59 if (!uiSourceCode) | |
| 60 return; | |
| 61 uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevis ion(original || '')); | |
| 62 } | |
| 63 | |
| 64 _click(event) { | |
| 65 var selection = this._editor.selection(); | |
| 66 if (!selection.isEmpty()) | |
| 67 return; | |
| 68 var row = this._rows[selection.startLine]; | |
| 69 if (!row) | |
| 70 return; | |
| 71 Common.Revealer.reveal(this._uiSourceCode.uiLocation(row.current - 1, select ion.startColumn), false); | |
| 72 event.consume(true); | |
| 73 } | |
| 74 | |
| 75 /** | |
| 76 * @param {?Workspace.UISourceCode} uiSourceCode | |
| 77 */ | |
| 78 _revealUISourceCode(uiSourceCode) { | |
| 79 if (this._uiSourceCode === uiSourceCode) | |
| 80 return; | |
| 81 | |
| 82 if (this._uiSourceCode) | |
| 83 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re freshDiff, this); | |
| 84 if (uiSourceCode) | |
| 85 this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff, this); | |
| 86 | |
| 87 this._uiSourceCode = uiSourceCode; | |
| 88 this._refreshDiff(); | |
| 89 } | |
| 90 | |
| 91 /** | |
| 92 * @override | |
| 93 */ | |
| 94 wasShown() { | |
| 95 if (this._uiSourceCode) | |
| 96 this._workspaceDiff.subscribeToDiffChange(this._uiSourceCode, this._refres hDiff, this); | |
| 97 this._refreshDiff(); | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * @override | |
| 102 */ | |
| 103 willHide() { | |
| 104 if (this._uiSourceCode) | |
| 105 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re freshDiff, this); | |
| 106 } | |
| 107 | |
| 108 _refreshDiff() { | |
| 109 if (!this._uiSourceCode) { | |
| 110 this._renderRows(null); | |
| 111 return; | |
| 112 } | |
| 113 var uiSourceCode = this._uiSourceCode; | |
| 114 this._workspaceDiff.requestDiff(uiSourceCode).then(diff => { | |
| 115 if (this._uiSourceCode !== uiSourceCode) | |
| 116 return; | |
| 117 this._renderRows(diff); | |
| 118 }); | |
| 119 } | |
| 120 | |
| 121 /** | |
| 122 * @param {?Diff.Diff.DiffArray} diff | |
| 123 */ | |
| 124 _renderRows(diff) { | |
| 125 this._rows = []; | |
| 126 | |
| 127 if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal) ) { | |
| 128 this._diffStats.setText(''); | |
| 129 this._toolbar.setEnabled(false); | |
| 130 this._editor.hideWidget(); | |
| 131 this._emptyWidget.showWidget(); | |
| 132 return; | |
| 133 } | |
| 134 | |
| 135 var insertions = 0; | |
| 136 var deletions = 0; | |
| 137 var currentLineNumber = 0; | |
| 138 var baselineLineNumber = 0; | |
| 139 var paddingLines = 3; | |
| 140 var originalLines = []; | |
| 141 var currentLines = []; | |
| 142 | |
| 143 for (var i = 0; i < diff.length; ++i) { | |
| 144 var token = diff[i]; | |
| 145 switch (token[0]) { | |
| 146 case Diff.Diff.Operation.Equal: | |
| 147 this._rows.pushAll(createEqualRows(token[1], i === 0, i === diff.lengt h - 1)); | |
| 148 originalLines.pushAll(token[1]); | |
| 149 currentLines.pushAll(token[1]); | |
| 150 break; | |
| 151 case Diff.Diff.Operation.Insert: | |
| 152 for (var line of token[1]) | |
| 153 this._rows.push(createRow(line, 'addition')); | |
| 154 currentLines.pushAll(token[1]); | |
| 155 break; | |
| 156 case Diff.Diff.Operation.Delete: | |
| 157 originalLines.pushAll(token[1]); | |
| 158 if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) { | |
| 159 i++; | |
| 160 this._rows.pushAll(createModifyRows(token[1].join('\n'), diff[i][1]. join('\n'))); | |
| 161 currentLines.pushAll(diff[i][1]); | |
| 162 } else { | |
| 163 for (var line of token[1]) | |
| 164 this._rows.push(createRow(line, 'deletion')); | |
| 165 } | |
| 166 break; | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 this._maxLineDigits = Math.max(Math.ceil(Math.log10(Math.max(currentLineNumb er, baselineLineNumber))), 1); | |
| 171 | |
| 172 this._editor.operation(() => { | |
| 173 this._editor.setHighlightMode({ | |
| 174 name: 'devtools-diff', | |
| 175 rows: this._rows, | |
| 176 mimeType: | |
| 177 Bindings.NetworkProject.uiSourceCodeMimeType(/** @type {!Workspace.U ISourceCode} */ (this._uiSourceCode)), | |
| 178 baselineLines: originalLines, | |
| 179 currentLines: currentLines | |
| 180 }); | |
| 181 this._editor.setText(this._rows.map(row => row.content.map(t => t.text).jo in('')).join('\n')); | |
| 182 this._editor.setLineNumberFormatter(this._lineFormatter.bind(this)); | |
| 183 }); | |
| 184 | |
| 185 this._diffStats.setText(Common.UIString( | |
| 186 '%d insertion%s (+), %d deletion%s (-)', insertions, insertions > 1 ? 's ' : '', deletions, | |
| 187 deletions > 1 ? 's' : '')); | |
| 188 this._toolbar.setEnabled(true); | |
| 189 this._emptyWidget.hideWidget(); | |
| 190 this._editor.showWidget(); | |
| 191 | |
| 192 /** | |
| 193 * @param {!Array<string>} lines | |
| 194 * @param {boolean} atStart | |
| 195 * @param {boolean} atEnd | |
| 196 * @return {!Array<!Changes.ChangesView.Row>}} | |
| 197 */ | |
| 198 function createEqualRows(lines, atStart, atEnd) { | |
| 199 var equalRows = []; | |
| 200 if (!atStart) { | |
| 201 for (var i = 0; i < paddingLines && i < lines.length; i++) | |
| 202 equalRows.push(createRow(lines[i], 'equal')); | |
| 203 if (lines.length > paddingLines * 2 + 1 && !atEnd) { | |
| 204 equalRows.push(createRow( | |
| 205 Common.UIString('( \u2026 Skipping ') + (lines.length - paddingLin es * 2) + | |
| 206 Common.UIString(' matching lines \u2026 )'), | |
| 207 'spacer')); | |
| 208 } | |
| 209 } | |
| 210 if (!atEnd) { | |
| 211 var start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : padd ingLines); | |
| 212 var skip = lines.length - paddingLines - 1; | |
| 213 if (!atStart) | |
| 214 skip -= paddingLines; | |
| 215 if (skip > 0) { | |
| 216 baselineLineNumber += skip; | |
| 217 currentLineNumber += skip; | |
| 218 } | |
| 219 | |
| 220 for (var i = start; i < lines.length; i++) | |
| 221 equalRows.push(createRow(lines[i], 'equal')); | |
| 222 } | |
| 223 return equalRows; | |
| 224 } | |
| 225 | |
| 226 /** | |
| 227 * @param {string} before | |
| 228 * @param {string} after | |
| 229 * @return {!Array<!Changes.ChangesView.Row>}} | |
| 230 */ | |
| 231 function createModifyRows(before, after) { | |
| 232 var internalDiff = Diff.Diff.charDiff(before, after, true); | |
| 233 var deletionRows = [createRow('', 'deletion')]; | |
| 234 var insertionRows = [createRow('', 'addition')]; | |
| 235 | |
| 236 for (var token of internalDiff) { | |
| 237 var text = token[1]; | |
| 238 var type = token[0]; | |
| 239 var className = type === Diff.Diff.Operation.Equal ? '' : 'double'; | |
| 240 var first = true; | |
| 241 for (var line of text.split('\n')) { | |
| 242 if (first) { | |
| 243 first = false; | |
| 244 } else { | |
| 245 if (type !== Diff.Diff.Operation.Insert) | |
| 246 deletionRows.push(createRow('', 'deletion')); | |
| 247 if (type !== Diff.Diff.Operation.Delete) | |
| 248 insertionRows.push(createRow('', 'addition')); | |
| 249 } | |
| 250 if (line) { | |
| 251 if (type !== Diff.Diff.Operation.Insert) | |
| 252 deletionRows[deletionRows.length - 1].content.push({text: line, cl assName: className}); | |
| 253 | |
| 254 if (type !== Diff.Diff.Operation.Delete) | |
| 255 insertionRows[insertionRows.length - 1].content.push({text: line, className: className}); | |
| 256 } | |
| 257 } | |
| 258 } | |
| 259 return deletionRows.concat(insertionRows); | |
| 260 } | |
| 261 | |
| 262 /** | |
| 263 * @param {string} text | |
| 264 * @param {string} className | |
| 265 * @return {!Changes.ChangesView.Row} | |
| 266 */ | |
| 267 function createRow(text, className) { | |
| 268 if (className === 'addition') { | |
| 269 currentLineNumber++; | |
| 270 insertions++; | |
| 271 } | |
| 272 if (className === 'deletion') { | |
| 273 baselineLineNumber++; | |
| 274 deletions++; | |
| 275 } | |
| 276 if (className === 'equal') { | |
| 277 baselineLineNumber++; | |
| 278 currentLineNumber++; | |
| 279 } | |
| 280 return { | |
| 281 base: baselineLineNumber, | |
| 282 current: currentLineNumber, | |
| 283 content: text ? [{text: text, className: 'double'}] : [], | |
| 284 className: className, | |
| 285 loc: currentLineNumber | |
| 286 }; | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 /** | |
| 291 * @param {number} lineNumber | |
| 292 * @return {string} | |
| 293 */ | |
| 294 _lineFormatter(lineNumber) { | |
| 295 var row = this._rows[lineNumber - 1]; | |
| 296 if (!row) | |
| 297 return spacesPadding(this._maxLineDigits * 2 + 1); | |
| 298 var showBaseNumber = row.className === 'deletion'; | |
| 299 var showCurrentNumber = row.className === 'addition'; | |
| 300 if (row.className === 'equal') { | |
| 301 showBaseNumber = true; | |
| 302 showCurrentNumber = true; | |
| 303 } | |
| 304 var base = showBaseNumber ? numberToStringWithSpacesPadding(row.base, this._ maxLineDigits) : | |
| 305 spacesPadding(this._maxLineDigits); | |
| 306 var current = showCurrentNumber ? numberToStringWithSpacesPadding(row.curren t, this._maxLineDigits) : | |
| 307 spacesPadding(this._maxLineDigits); | |
| 308 return base + spacesPadding(1) + current; | |
| 309 } | |
| 310 | |
| 311 /** | |
| 312 * @param {number} baselineLineNumber | |
| 313 * @param {number} currentLineNumber | |
| 314 * @param {string} text | |
| 315 * @param {string=} className | |
| 316 * @return {!Element} | |
| 317 */ | |
| 318 _createRow(baselineLineNumber, currentLineNumber, text, className) { | |
| 319 var element = createElementWithClass('div', className); | |
| 320 element.createChild('span', 'number').textContent = baselineLineNumber ? | |
| 321 numberToStringWithSpacesPadding(baselineLineNumber, this._maxLineDigits) : | |
| 322 spacesPadding(this._maxLineDigits); | |
| 323 element.createChild('span', 'number').textContent = currentLineNumber ? | |
| 324 numberToStringWithSpacesPadding(currentLineNumber, this._maxLineDigits) : | |
| 325 spacesPadding(this._maxLineDigits); | |
| 326 element.createChild('span', 'double').textContent = text; | |
| 327 return element; | |
| 328 } | |
| 329 }; | |
| 330 | |
| 331 /** @typedef {!{base: number, current: number, content: !Array<!{text: string, c lassName: string}>, className: string, loc: number}} */ | |
| 332 Changes.ChangesView.Row; | |
| 333 | |
| 334 /** @typedef {!{lineNumber: number, index: number}} */ | |
| 335 Changes.ChangesView.DiffState; | |
| 336 | |
| 337 CodeMirror.defineMode('devtools-diff', function(config, parserConfig) { | |
| 338 var rows = parserConfig.rows || []; | |
| 339 var baselineLines = parserConfig.baselineLines; | |
| 340 var currentLines = parserConfig.currentLines; | |
| 341 var innerMode = CodeMirror.getMode({indentUnit: 2}, parserConfig.mimeType); | |
| 342 | |
| 343 function fastForward(state, baselineLineNumber, currentLineNumber) { | |
| 344 if (baselineLineNumber > state.baselineLineNumber) { | |
| 345 fastForwardState(state.baselineState, state.baselineLineNumber, baselineLi neNumber, baselineLines); | |
| 346 state.baselineLineNumber = baselineLineNumber; | |
| 347 } | |
| 348 if (currentLineNumber > state.currentLineNumber) { | |
| 349 fastForwardState(state.currentState, state.currentLineNumber, currentLineN umber, currentLines); | |
| 350 state.currentLineNumber = currentLineNumber; | |
| 351 } | |
| 352 } | |
| 353 | |
| 354 function fastForwardState(state, from, to, lines) { | |
| 355 var lineNumber = from; | |
| 356 while (lineNumber < to && lineNumber < lines.length) { | |
| 357 var stream = new CodeMirror.StringStream(lines[lineNumber]); | |
| 358 while (!stream.eol()) { | |
| 359 innerMode.token(stream, state); | |
| 360 stream.start = stream.pos; | |
| 361 } | |
| 362 lineNumber++; | |
| 363 } | |
| 364 } | |
| 365 | |
| 366 return { | |
| 367 /** | |
| 368 * @return {!Changes.ChangesView.DiffState} | |
| 369 */ | |
| 370 startState: function() { | |
| 371 return { | |
| 372 lineNumber: 0, | |
| 373 index: 0, | |
| 374 currentLineNumber: 0, | |
| 375 baselineLineNumber: 0, | |
| 376 currentState: CodeMirror.startState(innerMode), | |
| 377 baselineState: CodeMirror.startState(innerMode), | |
| 378 mismatch: 0 | |
| 379 }; | |
| 380 }, | |
| 381 | |
| 382 /** | |
| 383 * @param {!{next: function()}} stream | |
| 384 * @param {!Changes.ChangesView.DiffState} state | |
| 385 * @return {string} | |
| 386 */ | |
| 387 token: function(stream, state) { | |
| 388 var row = rows[state.lineNumber]; | |
| 389 if (!row) { | |
| 390 stream.next(); | |
| 391 return ''; | |
| 392 } | |
| 393 fastForward(state, row.base - 1, row.current - 1); | |
| 394 var classes = ''; | |
| 395 if (state.index === 0) | |
| 396 classes += ' line-background-' + row.className + ' line-' + row.classNam e; | |
| 397 var chars = row.content[state.index].text.length; | |
| 398 if (state.mismatch > 0) | |
| 399 chars = state.mismatch; | |
| 400 var pos = stream.pos; | |
| 401 var innerStyle = state.lastInnerStyle; | |
| 402 var innerPos = pos - state.mismatch; | |
| 403 if (state.mismatch >= 0) { | |
| 404 if (row.className === 'deletion' || row.className === 'addition' || row. className === 'equal') { | |
| 405 innerStyle = innerMode.token(stream, row.className === 'deletion' ? st ate.baselineState : state.currentState); | |
| 406 innerPos = stream.pos; | |
| 407 } else { | |
| 408 innerStyle = ''; | |
| 409 innerPos = pos + chars; | |
| 410 } | |
| 411 } | |
| 412 stream.pos = Math.min(innerPos, pos + chars); | |
| 413 if (innerStyle) | |
| 414 classes += ' ' + innerStyle; | |
| 415 classes += ' ' + row.content[state.index].className; | |
| 416 state.mismatch = pos + chars - innerPos; | |
| 417 if (state.mismatch <= 0) { | |
| 418 state.index++; | |
| 419 state.lastInnerStyle = innerStyle; | |
| 420 } | |
| 421 if (state.index >= row.content.length) { | |
| 422 state.lineNumber++; | |
| 423 if (row.className === 'deletion') | |
| 424 state.baselineLineNumber++; | |
| 425 else | |
| 426 state.currentLineNumber++; | |
| 427 state.index = 0; | |
| 428 } | |
| 429 return classes; | |
| 430 }, | |
| 431 | |
| 432 /** | |
| 433 * @param {!Changes.ChangesView.DiffState} state | |
| 434 * @return {string} | |
| 435 */ | |
| 436 blankLine: function(state) { | |
| 437 var row = rows[state.lineNumber]; | |
| 438 state.lineNumber++; | |
| 439 state.mismatch = 0; | |
| 440 state.index = 0; | |
| 441 if (!row) | |
| 442 return ''; | |
| 443 if (row.className === 'deletion') | |
| 444 state.baselineLineNumber++; | |
| 445 else | |
| 446 state.currentLineNumber++; | |
| 447 var style = ''; | |
| 448 if (innerMode.blankLine) | |
| 449 style = innerMode.blankLine(row.className === 'deletion' ? state.baselin eState : state.currentState); | |
| 450 return style + ' line-background-' + row.className + ' line-' + row.classN ame; | |
| 451 }, | |
| 452 | |
| 453 /** | |
| 454 * @param {!Changes.ChangesView.DiffState} state | |
| 455 * @return {!Changes.ChangesView.DiffState} | |
| 456 */ | |
| 457 copyState: function(state) { | |
| 458 var newState = /** @type {!Changes.ChangesView.DiffState} */ ({}); | |
| 459 for (var i in state) | |
| 460 newState[i] = state[i]; | |
| 461 newState.currentState = CodeMirror.copyState(innerMode, state.currentState ); | |
| 462 newState.baselineState = CodeMirror.copyState(innerMode, state.baselineSta te); | |
| 463 return newState; | |
| 464 } | |
| 465 }; | |
| 466 }); | |
| OLD | NEW |