| 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 this.registerRequiredCSS('changes/changesView.css'); |
| 9 var splitWidget = new UI.SplitWidget(true, false); |
| 10 var mainWidget = new UI.Widget(); |
| 11 splitWidget.setMainWidget(mainWidget); |
| 12 splitWidget.show(this.contentElement); |
| 13 var diffArea = mainWidget.element.createChild('div', 'diff-area'); |
| 14 |
| 15 this._emptyWidget = new UI.EmptyWidget(Common.UIString('No changes')); |
| 16 this._emptyWidget.show(diffArea); |
| 17 |
| 18 this._workspaceDiff = WorkspaceDiff.workspaceDiff(); |
| 19 var fileList = new Changes.FileList(this._workspaceDiff); |
| 20 fileList.on(Changes.FileList.SelectionChanged, this._fileListSelectionChange
d, this); |
| 21 splitWidget.setSidebarWidget(fileList); |
| 22 |
| 23 /** @type {?Workspace.UISourceCode} */ |
| 24 this._uiSourceCode = null; |
| 25 |
| 26 /** @type {!Array<!Changes.ChangesView.Row>} */ |
| 27 this._rows = []; |
| 28 |
| 29 this._maxLineDigits = 1; |
| 30 |
| 31 this._editor = |
| 32 new TextEditor.CodeMirrorTextEditor({lineNumbers: true, lineWrapping: fa
lse, maxHighlightLength: Infinity}); |
| 33 this._editor.setReadOnly(true); |
| 34 this._editor.show(diffArea); |
| 35 this._editor.hideWidget(); |
| 36 this._editor.setLineNumberFormatter(() => ''); |
| 37 |
| 38 this._editor.element.addEventListener('click', this._click.bind(this), false
); |
| 39 |
| 40 this._toolbar = new UI.Toolbar('changes-toolbar', mainWidget.element); |
| 41 var revertButton = new UI.ToolbarButton(Common.UIString('Revert all changes'
), 'largeicon-undo'); |
| 42 revertButton.addEventListener(UI.ToolbarButton.Events.Click, this._revert.bi
nd(this)); |
| 43 this._toolbar.appendToolbarItem(revertButton); |
| 44 this._diffStats = new UI.ToolbarText(''); |
| 45 this._toolbar.appendToolbarItem(this._diffStats); |
| 46 this._toolbar.setEnabled(false); |
| 47 } |
| 48 |
| 49 /** |
| 50 * @param {!Changes.FileList.SelectionChanged} event |
| 51 */ |
| 52 _fileListSelectionChanged(event) { |
| 53 this._revealUISourceCode(event.uiSourceCode); |
| 54 } |
| 55 |
| 56 _revert() { |
| 57 var uiSourceCode = this._uiSourceCode; |
| 58 if (!uiSourceCode) |
| 59 return; |
| 60 uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevis
ion(original || '')); |
| 61 } |
| 62 |
| 63 _click(event) { |
| 64 var selection = this._editor.selection(); |
| 65 if (!selection.isEmpty()) |
| 66 return; |
| 67 var row = this._rows[selection.startLine]; |
| 68 if (!row) |
| 69 return; |
| 70 Common.Revealer.reveal(this._uiSourceCode.uiLocation(row.current - 1, select
ion.startColumn), false); |
| 71 event.consume(true); |
| 72 } |
| 73 |
| 74 /** |
| 75 * @param {?Workspace.UISourceCode} uiSourceCode |
| 76 */ |
| 77 _revealUISourceCode(uiSourceCode) { |
| 78 if (this._uiSourceCode === uiSourceCode) |
| 79 return; |
| 80 |
| 81 if (this._uiSourceCode) |
| 82 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re
freshDiff, this); |
| 83 if (uiSourceCode && this.isShowing()) |
| 84 this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff,
this); |
| 85 |
| 86 this._uiSourceCode = uiSourceCode; |
| 87 if (!uiSourceCode || this.isShowing()) |
| 88 this._refreshDiff(); |
| 89 } |
| 90 |
| 91 /** |
| 92 * @override |
| 93 */ |
| 94 wasShown() { |
| 95 if (!this._uiSourceCode) |
| 96 return; |
| 97 this._workspaceDiff.subscribeToDiffChange(this._uiSourceCode, this._refreshD
iff, this); |
| 98 this._refreshDiff(); |
| 99 } |
| 100 |
| 101 /** |
| 102 * @override |
| 103 */ |
| 104 willHide() { |
| 105 if (this._uiSourceCode) |
| 106 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re
freshDiff, this); |
| 107 } |
| 108 |
| 109 _refreshDiff() { |
| 110 if (!this._uiSourceCode) { |
| 111 this._renderRows(null); |
| 112 return; |
| 113 } |
| 114 var uiSourceCode = this._uiSourceCode; |
| 115 this._workspaceDiff.requestDiff(uiSourceCode).then(diff => { |
| 116 if (this._uiSourceCode !== uiSourceCode) |
| 117 return; |
| 118 this._renderRows(diff); |
| 119 }); |
| 120 } |
| 121 |
| 122 /** |
| 123 * @param {?Diff.Diff.DiffArray} diff |
| 124 */ |
| 125 _renderRows(diff) { |
| 126 this._rows = []; |
| 127 |
| 128 if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal)
) { |
| 129 this._diffStats.setText(''); |
| 130 this._toolbar.setEnabled(false); |
| 131 this._editor.hideWidget(); |
| 132 this._emptyWidget.showWidget(); |
| 133 return; |
| 134 } |
| 135 |
| 136 var insertions = 0; |
| 137 var deletions = 0; |
| 138 var currentLineNumber = 0; |
| 139 var baselineLineNumber = 0; |
| 140 var paddingLines = 3; |
| 141 var originalLines = []; |
| 142 var currentLines = []; |
| 143 |
| 144 for (var i = 0; i < diff.length; ++i) { |
| 145 var token = diff[i]; |
| 146 switch (token[0]) { |
| 147 case Diff.Diff.Operation.Equal: |
| 148 this._rows.pushAll(createEqualRows(token[1], i === 0, i === diff.lengt
h - 1)); |
| 149 originalLines.pushAll(token[1]); |
| 150 currentLines.pushAll(token[1]); |
| 151 break; |
| 152 case Diff.Diff.Operation.Insert: |
| 153 for (var line of token[1]) |
| 154 this._rows.push(createRow(line, 'addition')); |
| 155 currentLines.pushAll(token[1]); |
| 156 break; |
| 157 case Diff.Diff.Operation.Delete: |
| 158 originalLines.pushAll(token[1]); |
| 159 if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) { |
| 160 i++; |
| 161 this._rows.pushAll(createModifyRows(token[1].join('\n'), diff[i][1].
join('\n'))); |
| 162 currentLines.pushAll(diff[i][1]); |
| 163 } else { |
| 164 for (var line of token[1]) |
| 165 this._rows.push(createRow(line, 'deletion')); |
| 166 } |
| 167 break; |
| 168 } |
| 169 } |
| 170 |
| 171 this._maxLineDigits = Math.max(Math.ceil(Math.log10(Math.max(currentLineNumb
er, baselineLineNumber))), 1); |
| 172 |
| 173 this._editor.operation(() => { |
| 174 this._editor.setHighlightMode({ |
| 175 name: 'devtools-diff', |
| 176 rows: this._rows, |
| 177 mimeType: |
| 178 Bindings.NetworkProject.uiSourceCodeMimeType(/** @type {!Workspace.U
ISourceCode} */ (this._uiSourceCode)), |
| 179 baselineLines: originalLines, |
| 180 currentLines: currentLines |
| 181 }); |
| 182 this._editor.setText(this._rows.map(row => row.content.map(t => t.text).jo
in('')).join('\n')); |
| 183 this._editor.setLineNumberFormatter(this._lineFormatter.bind(this)); |
| 184 }); |
| 185 |
| 186 this._diffStats.setText(Common.UIString( |
| 187 '%d insertion%s (+), %d deletion%s (-)', insertions, insertions > 1 ? 's
' : '', deletions, |
| 188 deletions > 1 ? 's' : '')); |
| 189 this._toolbar.setEnabled(true); |
| 190 this._emptyWidget.hideWidget(); |
| 191 this._editor.showWidget(); |
| 192 |
| 193 /** |
| 194 * @param {!Array<string>} lines |
| 195 * @param {boolean} atStart |
| 196 * @param {boolean} atEnd |
| 197 * @return {!Array<!Changes.ChangesView.Row>}} |
| 198 */ |
| 199 function createEqualRows(lines, atStart, atEnd) { |
| 200 var equalRows = []; |
| 201 if (!atStart) { |
| 202 for (var i = 0; i < paddingLines && i < lines.length; i++) |
| 203 equalRows.push(createRow(lines[i], 'equal')); |
| 204 if (lines.length > paddingLines * 2 + 1 && !atEnd) { |
| 205 equalRows.push(createRow( |
| 206 Common.UIString('( \u2026 Skipping ') + (lines.length - paddingLin
es * 2) + |
| 207 Common.UIString(' matching lines \u2026 )'), |
| 208 'spacer')); |
| 209 } |
| 210 } |
| 211 if (!atEnd) { |
| 212 var start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : padd
ingLines); |
| 213 var skip = lines.length - paddingLines - 1; |
| 214 if (!atStart) |
| 215 skip -= paddingLines; |
| 216 if (skip > 0) { |
| 217 baselineLineNumber += skip; |
| 218 currentLineNumber += skip; |
| 219 } |
| 220 |
| 221 for (var i = start; i < lines.length; i++) |
| 222 equalRows.push(createRow(lines[i], 'equal')); |
| 223 } |
| 224 return equalRows; |
| 225 } |
| 226 |
| 227 /** |
| 228 * @param {string} before |
| 229 * @param {string} after |
| 230 * @return {!Array<!Changes.ChangesView.Row>}} |
| 231 */ |
| 232 function createModifyRows(before, after) { |
| 233 var internalDiff = Diff.Diff.charDiff(before, after, true); |
| 234 var deletionRows = [createRow('', 'deletion')]; |
| 235 var insertionRows = [createRow('', 'addition')]; |
| 236 |
| 237 for (var token of internalDiff) { |
| 238 var text = token[1]; |
| 239 var type = token[0]; |
| 240 var className = type === Diff.Diff.Operation.Equal ? '' : 'double'; |
| 241 var first = true; |
| 242 for (var line of text.split('\n')) { |
| 243 if (first) { |
| 244 first = false; |
| 245 } else { |
| 246 if (type !== Diff.Diff.Operation.Insert) |
| 247 deletionRows.push(createRow('', 'deletion')); |
| 248 if (type !== Diff.Diff.Operation.Delete) |
| 249 insertionRows.push(createRow('', 'addition')); |
| 250 } |
| 251 if (line) { |
| 252 if (type !== Diff.Diff.Operation.Insert) |
| 253 deletionRows[deletionRows.length - 1].content.push({text: line, cl
assName: className}); |
| 254 |
| 255 if (type !== Diff.Diff.Operation.Delete) |
| 256 insertionRows[insertionRows.length - 1].content.push({text: line,
className: className}); |
| 257 } |
| 258 } |
| 259 } |
| 260 return deletionRows.concat(insertionRows); |
| 261 } |
| 262 |
| 263 /** |
| 264 * @param {string} text |
| 265 * @param {string} className |
| 266 * @return {!Changes.ChangesView.Row} |
| 267 */ |
| 268 function createRow(text, className) { |
| 269 if (className === 'addition') { |
| 270 currentLineNumber++; |
| 271 insertions++; |
| 272 } |
| 273 if (className === 'deletion') { |
| 274 baselineLineNumber++; |
| 275 deletions++; |
| 276 } |
| 277 if (className === 'equal') { |
| 278 baselineLineNumber++; |
| 279 currentLineNumber++; |
| 280 } |
| 281 return { |
| 282 base: baselineLineNumber, |
| 283 current: currentLineNumber, |
| 284 content: text ? [{text: text, className: 'double'}] : [], |
| 285 className: className, |
| 286 loc: currentLineNumber |
| 287 }; |
| 288 } |
| 289 } |
| 290 |
| 291 /** |
| 292 * @param {number} lineNumber |
| 293 * @return {string} |
| 294 */ |
| 295 _lineFormatter(lineNumber) { |
| 296 var row = this._rows[lineNumber - 1]; |
| 297 if (!row) |
| 298 return spacesPadding(this._maxLineDigits * 2 + 1); |
| 299 var showBaseNumber = row.className === 'deletion'; |
| 300 var showCurrentNumber = row.className === 'addition'; |
| 301 if (row.className === 'equal') { |
| 302 showBaseNumber = true; |
| 303 showCurrentNumber = true; |
| 304 } |
| 305 var base = showBaseNumber ? numberToStringWithSpacesPadding(row.base, this._
maxLineDigits) : |
| 306 spacesPadding(this._maxLineDigits); |
| 307 var current = showCurrentNumber ? numberToStringWithSpacesPadding(row.curren
t, this._maxLineDigits) : |
| 308 spacesPadding(this._maxLineDigits); |
| 309 return base + spacesPadding(1) + current; |
| 310 } |
| 311 |
| 312 /** |
| 313 * @param {number} baselineLineNumber |
| 314 * @param {number} currentLineNumber |
| 315 * @param {string} text |
| 316 * @param {string=} className |
| 317 * @return {!Element} |
| 318 */ |
| 319 _createRow(baselineLineNumber, currentLineNumber, text, className) { |
| 320 var element = createElementWithClass('div', className); |
| 321 element.createChild('span', 'number').textContent = baselineLineNumber ? |
| 322 numberToStringWithSpacesPadding(baselineLineNumber, this._maxLineDigits)
: |
| 323 spacesPadding(this._maxLineDigits); |
| 324 element.createChild('span', 'number').textContent = currentLineNumber ? |
| 325 numberToStringWithSpacesPadding(currentLineNumber, this._maxLineDigits)
: |
| 326 spacesPadding(this._maxLineDigits); |
| 327 element.createChild('span', 'double').textContent = text; |
| 328 return element; |
| 329 } |
| 330 }; |
| 331 |
| 332 /** @typedef {!{base: number, current: number, content: !Array<!{text: string, c
lassName: string}>, className: string, loc: number}} */ |
| 333 Changes.ChangesView.Row; |
| OLD | NEW |