Commit 6cc4917f authored by Joel Einbinder's avatar Joel Einbinder Committed by Commit Bot

DevTools: Auto pretty print in sources panel experiment

Change-Id: Ib16be938e7df8c90f02da1211f350d172c34ebff
Reviewed-on: https://chromium-review.googlesource.com/1047949
Commit-Queue: Joel Einbinder <einbinder@chromium.org>
Reviewed-by: default avatarAndrey Lushnikov <lushnikov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558766}
parent ac8b8e11
......@@ -9,55 +9,56 @@
var textEditor = SourcesTestRunner.createTestEditor();
textEditor.setText('1\n2\n3\n4');
let changeGeneration = 0;
TestRunner.runTestSuite([
function testMarkiningInitialStateAsClean(next) {
TestRunner.addResult('Initial state: clean=' + textEditor.isClean());
textEditor.markClean();
TestRunner.addResult('After marking clean: clean=' + textEditor.isClean());
TestRunner.addResult('Initial state: clean=' + textEditor.isClean(changeGeneration));
changeGeneration = textEditor.markClean();
TestRunner.addResult('After marking clean: clean=' + textEditor.isClean(changeGeneration));
textEditor.editRange(TextUtils.TextRange.createFromLocation(0, 0), 'newText');
TestRunner.addResult('EDIT; clean=' + textEditor.isClean());
TestRunner.addResult('EDIT; clean=' + textEditor.isClean(changeGeneration));
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
textEditor.redo();
TestRunner.addResult('REDO; clean=' + textEditor.isClean());
TestRunner.addResult('REDO; clean=' + textEditor.isClean(changeGeneration));
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
textEditor.editRange(TextUtils.TextRange.createFromLocation(1, 0), 'newText2');
TestRunner.addResult('EDIT; clean=' + textEditor.isClean());
TestRunner.addResult('EDIT; clean=' + textEditor.isClean(changeGeneration));
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
next();
},
function testMiddleStateAsClean(next) {
TestRunner.addResult('Initial state: clean=' + textEditor.isClean());
TestRunner.addResult('Initial state: clean=' + textEditor.isClean(changeGeneration));
for (var i = 0; i < 3; ++i) {
textEditor.editRange(TextUtils.TextRange.createFromLocation(i, 0), 'newText' + i);
TestRunner.addResult('EDIT; clean=' + textEditor.isClean());
TestRunner.addResult('EDIT; clean=' + textEditor.isClean(changeGeneration));
}
textEditor.markClean();
TestRunner.addResult('After marking clean: clean=' + textEditor.isClean());
changeGeneration = textEditor.markClean();
TestRunner.addResult('After marking clean: clean=' + textEditor.isClean(changeGeneration));
textEditor.editRange(TextUtils.TextRange.createFromLocation(3, 0), 'newText' + 3);
TestRunner.addResult('EDIT; clean=' + textEditor.isClean());
TestRunner.addResult('EDIT; clean=' + textEditor.isClean(changeGeneration));
for (var i = 0; i < 4; ++i) {
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
}
for (var i = 0; i < 4; ++i) {
textEditor.redo();
TestRunner.addResult('REDO; clean=' + textEditor.isClean());
TestRunner.addResult('REDO; clean=' + textEditor.isClean(changeGeneration));
}
for (var i = 0; i < 2; ++i) {
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
}
textEditor.editRange(TextUtils.TextRange.createFromLocation(1, 0), 'foo');
TestRunner.addResult('EDIT; clean=' + textEditor.isClean());
TestRunner.addResult('EDIT; clean=' + textEditor.isClean(changeGeneration));
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
textEditor.undo();
TestRunner.addResult('UNDO; clean=' + textEditor.isClean());
TestRunner.addResult('UNDO; clean=' + textEditor.isClean(changeGeneration));
next();
},
]);
......
function uglyFunction() {console.log('ugly function');}
\ No newline at end of file
Verifies that editing a pretty printed resource works properly.
* Initial state *
pretty print button: off
text: function uglyFunction() {console.log('ugly function');}
isDirty: false
workingCopy: function uglyFunction() {console.log('ugly function');}
* Toggle pretty print on *
pretty print button: on
text: function uglyFunction() {
console.log('ugly function');
}
isDirty: false
workingCopy: function uglyFunction() {console.log('ugly function');}
* Type in "X" *
pretty print button: off disabled
text: Xfunction uglyFunction() {
console.log('ugly function');
}
isDirty: true
workingCopy: Xfunction uglyFunction() {
console.log('ugly function');
}
* Undo *
pretty print button: on
text: function uglyFunction() {
console.log('ugly function');
}
isDirty: false
workingCopy: function uglyFunction() {console.log('ugly function');}
* Toggle pretty print off *
pretty print button: off
text: function uglyFunction() {console.log('ugly function');}
isDirty: false
workingCopy: function uglyFunction() {console.log('ugly function');}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function() {
TestRunner.addResult(`Verifies that editing a pretty printed resource works properly.\n`);
Runtime.experiments.enableForTest('sourcesPrettyPrint');
await TestRunner.loadModule('sources_test_runner');
await TestRunner.showPanel('sources');
await TestRunner.addScriptTag('resources/ugly-function.js');
const sourceFrame = await SourcesTestRunner.showScriptSourcePromise('ugly-function.js');
dumpState('Initial state');
await sourceFrame._setPretty(true);
dumpState('Toggle pretty print on');
await new Promise(x => SourcesTestRunner.typeIn(sourceFrame.textEditor, 'X', x));
dumpState('Type in "X"');
sourceFrame.textEditor.codeMirror().execCommand('undo');
dumpState('Undo');
await sourceFrame._setPretty(false);
dumpState('Toggle pretty print off');
TestRunner.completeTest();
function dumpState(info) {
const uiSourceCode = sourceFrame.uiSourceCode();
const button = sourceFrame._prettyToggle;
let buttonState = 'invisible';
button.element.disabled
if (button.visible()) {
buttonState = button.toggled() ? 'on' : 'off';
if (button.element.disabled)
buttonState += ' disabled';
}
TestRunner.addResult(`* ${info} *`);
TestRunner.addResult(`pretty print button: ${buttonState}`);
TestRunner.addResult(`text: ${sourceFrame.textEditor.text().trim()}`);
TestRunner.addResult(`isDirty: ${uiSourceCode.isDirty()}`);
TestRunner.addResult(`workingCopy: ${uiSourceCode.workingCopy().trim()}`);
TestRunner.addResult('');
}
})();
......@@ -410,6 +410,8 @@ CodeMirror.prototype = {
*/
addOverlay: function(spec, options) {},
addWidget: function(pos, node, scroll, vert, horiz) {},
/** @param {boolean=} isClosed bv */
changeGeneration: function(isClosed) {},
charCoords: function(pos, mode) {},
clearGutter: function(gutterID) {},
clearHistory: function() {},
......@@ -478,7 +480,8 @@ CodeMirror.prototype = {
indentLine: function(n, dir, aggressive) {},
indentSelection: function(how) {},
indexFromPos: function(coords) {},
isClean: function() {},
/** @param {number=} generation */
isClean: function(generation) {},
iterLinkedDocs: function(f) {},
lastLine: function() {},
lineCount: function() {},
......
......@@ -116,6 +116,7 @@ Main.Main = class {
Runtime.experiments.register('oopifInlineDOM', 'OOPIF: inline DOM ', true);
Runtime.experiments.register('protocolMonitor', 'Protocol Monitor');
Runtime.experiments.register('sourceDiff', 'Source diff');
Runtime.experiments.register('sourcesPrettyPrint', 'Automatically pretty print in the Sources Panel');
Runtime.experiments.register(
'stepIntoAsync', 'Introduce separate step action, stepInto becomes powerful enough to go inside async call');
Runtime.experiments.register('splitInDrawer', 'Split in drawer', true);
......
......@@ -43,8 +43,9 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
this._lazyContent = lazyContent;
this._pretty = false;
this._rawContent = '';
/** @type {?Promise<string>} */
/** @type {?string} */
this._rawContent = null;
/** @type {?Promise<{content: string, map: !Formatter.FormatterSourceMapping}>} */
this._formattedContentPromise = null;
/** @type {?Formatter.FormatterSourceMapping} */
this._formattedMap = null;
......@@ -58,6 +59,10 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
this._textEditor = new SourceFrame.SourcesTextEditor(this);
this._textEditor.show(this.element);
/** @type {?number} */
this._prettyCleanGeneration = null;
this._cleanGeneration = 0;
this._searchConfig = null;
this._delayedFindSearchMatches = null;
this._currentSearchResultIndex = -1;
......@@ -91,6 +96,30 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
this._loaded = false;
this._contentRequested = false;
this._highlighterType = '';
/** @type {!SourceFrame.Transformer} */
this._transformer = {
/**
* @param {number} editorLineNumber
* @param {number=} editorColumnNumber
* @return {!Array<number>}
*/
editorToRawLocation: (editorLineNumber, editorColumnNumber = 0) => {
if (!this._pretty)
return [editorLineNumber, editorColumnNumber];
return this._prettyToRawLocation(editorLineNumber, editorColumnNumber);
},
/**
* @param {number} lineNumber
* @param {number=} columnNumber
* @return {!Array<number>}
*/
rawToEditorLocation: (lineNumber, columnNumber = 0) => {
if (!this._pretty)
return [lineNumber, columnNumber];
return this._rawToPrettyLocation(lineNumber, columnNumber);
}
};
}
/**
......@@ -104,22 +133,26 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
/**
* @param {boolean} value
* @return {!Promise}
*/
async _setPretty(value) {
this._pretty = value;
this._prettyToggle.setToggled(value);
this._prettyToggle.setEnabled(false);
const wasLoaded = this.loaded;
const selection = this.selection();
let newSelection;
if (this._pretty && this._rawContent) {
this.setContent(await this._requestFormattedContent());
if (this._pretty) {
const formatInfo = await this._requestFormattedContent();
this._formattedMap = formatInfo.map;
this.setContent(formatInfo.content);
this._prettyCleanGeneration = this._textEditor.markClean();
const start = this._rawToPrettyLocation(selection.startLine, selection.startColumn);
const end = this._rawToPrettyLocation(selection.endLine, selection.endColumn);
newSelection = new TextUtils.TextRange(start[0], start[1], end[0], end[1]);
} else {
this.setContent(this._rawContent);
this._cleanGeneration = this._textEditor.markClean();
const start = this._prettyToRawLocation(selection.startLine, selection.startColumn);
const end = this._prettyToRawLocation(selection.endLine, selection.endColumn);
newSelection = new TextUtils.TextRange(start[0], start[1], end[0], end[1]);
......@@ -151,6 +184,14 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
}
}
/**
* @return {!SourceFrame.Transformer}
*/
transformer() {
return this._transformer;
}
/**
* @param {number} line
* @param {number} column
......@@ -216,6 +257,13 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
return this._textEditor;
}
/**
* @protected
*/
get pretty() {
return this._pretty;
}
async _ensureContentLoaded() {
if (!this._contentRequested) {
this._contentRequested = true;
......@@ -223,7 +271,8 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
this._rawContent = content || '';
this._formattedContentPromise = null;
this._formattedMap = null;
if (this._shouldAutoPrettyPrint && TextUtils.isMinified(this._rawContent))
this._prettyToggle.setEnabled(true);
if (this._shouldAutoPrettyPrint && TextUtils.isMinified(content))
await this._setPretty(true);
else
this.setContent(this._rawContent);
......@@ -231,16 +280,15 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
}
/**
* @return {!Promise<string>}
* @return {!Promise<{content: string, map: !Formatter.FormatterSourceMapping}>}
*/
_requestFormattedContent() {
if (this._formattedContentPromise)
return this._formattedContentPromise;
let fulfill;
this._formattedContentPromise = new Promise(x => fulfill = x);
new Formatter.ScriptFormatter(this._highlighterType, this._rawContent, (data, map) => {
this._formattedMap = map;
fulfill(data);
new Formatter.ScriptFormatter(this._highlighterType, this._rawContent || '', (content, map) => {
fulfill({content, map});
});
return this._formattedContentPromise;
}
......@@ -325,10 +373,37 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
* @param {!TextUtils.TextRange} newRange
*/
onTextChanged(oldRange, newRange) {
const wasPretty = this.pretty;
this._pretty = this._prettyCleanGeneration !== null && this.textEditor.isClean(this._prettyCleanGeneration);
if (this._pretty !== wasPretty)
this._updatePrettyPrintState();
this._prettyToggle.setEnabled(this.isClean());
if (this._searchConfig && this._searchableView)
this.performSearch(this._searchConfig, false, false);
}
/**
* @return {boolean}
*/
isClean() {
return this.textEditor.isClean(this._cleanGeneration) ||
(this._prettyCleanGeneration !== null && this.textEditor.isClean(this._prettyCleanGeneration));
}
contentCommitted() {
this._cleanGeneration = this._textEditor.markClean();
this._prettyCleanGeneration = null;
this._rawContent = this.textEditor.text();
this._formattedMap = null;
this._formattedContentPromise = null;
if (this._pretty) {
this._pretty = false;
this._updatePrettyPrintState();
}
this._prettyToggle.setEnabled(true);
}
/**
* @param {string} content
* @param {string} mimeType
......@@ -376,7 +451,7 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
if (!this._loaded) {
this._loaded = true;
this._textEditor.setText(content || '');
this._textEditor.markClean();
this._cleanGeneration = this._textEditor.markClean();
this._textEditor.setReadOnly(!this._editable);
} else {
const scrollTop = this._textEditor.scrollTop();
......@@ -625,7 +700,7 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
* @override
* @return {!Promise}
*/
populateLineGutterContextMenu(contextMenu, lineNumber) {
populateLineGutterContextMenu(contextMenu, editorLineNumber) {
return Promise.resolve();
}
......@@ -633,7 +708,7 @@ SourceFrame.SourceFrame = class extends UI.SimpleView {
* @override
* @return {!Promise}
*/
populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) {
populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) {
return Promise.resolve();
}
......@@ -682,3 +757,11 @@ SourceFrame.LineDecorator.prototype = {
*/
decorate(uiSourceCode, textEditor) {}
};
/**
* @typedef {{
* editorToRawLocation: function(number, number=):!Array<number>,
* rawToEditorLocation: function(number, number=):!Array<number>
* }}
*/
SourceFrame.Transformer;
......@@ -36,9 +36,10 @@ Sources.SourcesView = class extends UI.VBox {
this._historyManager = new Sources.EditingLocationHistoryManager(this, this.currentSourceFrame.bind(this));
this._toolbarContainerElement = this.element.createChild('div', 'sources-toolbar');
if (!Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
this._toolbarEditorActions = new UI.Toolbar('', this._toolbarContainerElement);
self.runtime.allInstances(Sources.SourcesView.EditorAction).then(appendButtonsForExtensions.bind(this));
}
/**
* @param {!Array.<!Sources.SourcesView.EditorAction>} actions
* @this {Sources.SourcesView}
......
......@@ -636,14 +636,18 @@ TextEditor.CodeMirrorTextEditor = class extends UI.VBox {
}
/**
* @param {number} generation
* @return {boolean}
*/
isClean() {
return this._codeMirror.isClean();
isClean(generation) {
return this._codeMirror.isClean(generation);
}
/**
* @return {number}
*/
markClean() {
this._codeMirror.markClean();
return this._codeMirror.changeGeneration(true);
}
/**
......@@ -1216,6 +1220,9 @@ TextEditor.CodeMirrorTextEditor = class extends UI.VBox {
this._enableLongLinesMode();
else
this._disableLongLinesMode();
if (!this.isShowing())
this.refresh();
}
/**
......
......@@ -114,6 +114,10 @@
white-space: nowrap;
}
.pretty-printed .CodeMirror-linenumber {
color: var( --accent-color-b);
}
.cm-highlight {
-webkit-animation: fadeout 2s 0s;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment