DevTools: Auto pretty print in sources panel experiment

Change-Id: Ib16be938e7df8c90f02da1211f350d172c34ebff
Reviewed-on: https://chromium-review.googlesource.com/1047949
Commit-Queue: Joel Einbinder <[email protected]>
Reviewed-by: Andrey Lushnikov <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#558766}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 6cc4917ff46b654205b4aa1700c049c14d2ebabd
diff --git a/front_end/externs.js b/front_end/externs.js
index b363c2c..2993f2c 100644
--- a/front_end/externs.js
+++ b/front_end/externs.js
@@ -410,6 +410,8 @@
    */
   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 @@
   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() {},
diff --git a/front_end/main/Main.js b/front_end/main/Main.js
index 72fcced..6428493 100644
--- a/front_end/main/Main.js
+++ b/front_end/main/Main.js
@@ -116,6 +116,7 @@
     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);
diff --git a/front_end/source_frame/SourceFrame.js b/front_end/source_frame/SourceFrame.js
index 740e181..04a9d36 100644
--- a/front_end/source_frame/SourceFrame.js
+++ b/front_end/source_frame/SourceFrame.js
@@ -43,8 +43,9 @@
     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 @@
     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 @@
     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 @@
 
   /**
    * @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]);
@@ -152,6 +185,14 @@
   }
 
   /**
+   * @return {!SourceFrame.Transformer}
+   */
+  transformer() {
+    return this._transformer;
+  }
+
+
+  /**
    * @param {number} line
    * @param {number} column
    * @return {!Array<number>}
@@ -216,6 +257,13 @@
     return this._textEditor;
   }
 
+  /**
+   * @protected
+   */
+  get pretty() {
+    return this._pretty;
+  }
+
   async _ensureContentLoaded() {
     if (!this._contentRequested) {
       this._contentRequested = true;
@@ -223,7 +271,8 @@
       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 @@
   }
 
   /**
-   * @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,11 +373,38 @@
    * @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
    * @return {string}
@@ -376,7 +451,7 @@
     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 @@
    * @override
    * @return {!Promise}
    */
-  populateLineGutterContextMenu(contextMenu, lineNumber) {
+  populateLineGutterContextMenu(contextMenu, editorLineNumber) {
     return Promise.resolve();
   }
 
@@ -633,7 +708,7 @@
    * @override
    * @return {!Promise}
    */
-  populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) {
+  populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) {
     return Promise.resolve();
   }
 
@@ -682,3 +757,11 @@
    */
   decorate(uiSourceCode, textEditor) {}
 };
+
+/**
+ * @typedef {{
+ *  editorToRawLocation: function(number, number=):!Array<number>,
+ *  rawToEditorLocation: function(number, number=):!Array<number>
+ * }}
+ */
+SourceFrame.Transformer;
diff --git a/front_end/sources/DebuggerPlugin.js b/front_end/sources/DebuggerPlugin.js
index 6601037..17a3370 100644
--- a/front_end/sources/DebuggerPlugin.js
+++ b/front_end/sources/DebuggerPlugin.js
@@ -32,11 +32,13 @@
   /**
    * @param {!SourceFrame.SourcesTextEditor} textEditor
    * @param {!Workspace.UISourceCode} uiSourceCode
+   * @param {!SourceFrame.Transformer} transformer
    */
-  constructor(textEditor, uiSourceCode) {
+  constructor(textEditor, uiSourceCode, transformer) {
     super();
     this._textEditor = textEditor;
     this._uiSourceCode = uiSourceCode;
+    this._transformer = transformer;
 
     /** @type {?Element} */
     this._conditionEditorElement = null;
@@ -137,9 +139,11 @@
 
     this._hasLineWithoutMapping = false;
     this._updateLinesWithoutMappingHighlight();
-    /** @type {?UI.Infobar} */
-    this._prettyPrintInfobar = null;
-    this._detectMinified();
+    if (!Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
+      /** @type {?UI.Infobar} */
+      this._prettyPrintInfobar = null;
+      this._detectMinified();
+    }
   }
 
   /**
@@ -234,27 +238,27 @@
   /**
    * @override
    * @param {!UI.ContextMenu} contextMenu
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @return {!Promise}
    */
-  populateLineGutterContextMenu(contextMenu, lineNumber) {
+  populateLineGutterContextMenu(contextMenu, editorLineNumber) {
     /**
      * @this {Sources.DebuggerPlugin}
      */
     function populate(resolve, reject) {
-      const uiLocation = new Workspace.UILocation(this._uiSourceCode, lineNumber, 0);
+      const uiLocation = new Workspace.UILocation(this._uiSourceCode, editorLineNumber, 0);
       this._scriptsPanel.appendUILocationItems(contextMenu, uiLocation);
-      const breakpoints = this._lineBreakpointDecorations(lineNumber)
+      const breakpoints = this._lineBreakpointDecorations(editorLineNumber)
                               .map(decoration => decoration.breakpoint)
                               .filter(breakpoint => !!breakpoint);
       if (!breakpoints.length) {
         contextMenu.debugSection().appendItem(
-            Common.UIString('Add breakpoint'), this._createNewBreakpoint.bind(this, lineNumber, '', true));
+            Common.UIString('Add breakpoint'), this._createNewBreakpoint.bind(this, editorLineNumber, '', true));
         contextMenu.debugSection().appendItem(
             Common.UIString('Add conditional breakpoint\u2026'),
-            this._editBreakpointCondition.bind(this, lineNumber, null, null));
+            this._editBreakpointCondition.bind(this, editorLineNumber, null, null));
         contextMenu.debugSection().appendItem(
-            Common.UIString('Never pause here'), this._createNewBreakpoint.bind(this, lineNumber, 'false', true));
+            Common.UIString('Never pause here'), this._createNewBreakpoint.bind(this, editorLineNumber, 'false', true));
       } else {
         const hasOneBreakpoint = breakpoints.length === 1;
         const removeTitle =
@@ -263,7 +267,7 @@
         if (hasOneBreakpoint) {
           contextMenu.debugSection().appendItem(
               Common.UIString('Edit breakpoint\u2026'),
-              this._editBreakpointCondition.bind(this, lineNumber, breakpoints[0], null));
+              this._editBreakpointCondition.bind(this, editorLineNumber, breakpoints[0], null));
         }
         const hasEnabled = breakpoints.some(breakpoint => breakpoint.enabled());
         if (hasEnabled) {
@@ -288,11 +292,11 @@
   /**
    * @override
    * @param {!UI.ContextMenu} contextMenu
-   * @param {number} lineNumber
-   * @param {number} columnNumber
+   * @param {number} editorLineNumber
+   * @param {number} editorColumnNumber
    * @return {!Promise}
    */
-  populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) {
+  populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) {
     /**
      * @param {!Bindings.ResourceScriptFile} scriptFile
      */
@@ -325,7 +329,7 @@
       }
     }
 
-    return super.populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber)
+    return super.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber)
         .then(populateSourceMapMembers.bind(this));
   }
 
@@ -434,7 +438,7 @@
     const mouseColumn = textPosition.startColumn;
     const textSelection = this._textEditor.selection().normalize();
     let anchorBox;
-    let lineNumber;
+    let editorLineNumber;
     let startHighlight;
     let endHighlight;
 
@@ -447,29 +451,29 @@
           this._textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn);
       const rightCorner = this._textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn);
       anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
-      lineNumber = textSelection.startLine;
+      editorLineNumber = textSelection.startLine;
       startHighlight = textSelection.startColumn;
       endHighlight = textSelection.endColumn - 1;
     } else {
       const token = this._textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
       if (!token || !token.type)
         return null;
-      lineNumber = textPosition.startLine;
-      const line = this._textEditor.line(lineNumber);
+      editorLineNumber = textPosition.startLine;
+      const line = this._textEditor.line(editorLineNumber);
       const tokenContent = line.substring(token.startColumn, token.endColumn);
 
       const isIdentifier = this._isIdentifier(token.type);
       if (!isIdentifier && (token.type !== 'js-keyword' || tokenContent !== 'this'))
         return null;
 
-      const leftCorner = this._textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn);
-      const rightCorner = this._textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn - 1);
+      const leftCorner = this._textEditor.cursorPositionToCoordinates(editorLineNumber, token.startColumn);
+      const rightCorner = this._textEditor.cursorPositionToCoordinates(editorLineNumber, token.endColumn - 1);
       anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
 
       startHighlight = token.startColumn;
       endHighlight = token.endColumn - 1;
       while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') {
-        const tokenBefore = this._textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2);
+        const tokenBefore = this._textEditor.tokenAtTextPosition(editorLineNumber, startHighlight - 2);
         if (!tokenBefore || !tokenBefore.type)
           return null;
         startHighlight = tokenBefore.startColumn;
@@ -485,10 +489,10 @@
         const selectedCallFrame = UI.context.flavor(SDK.DebuggerModel.CallFrame);
         if (!selectedCallFrame)
           return false;
-        const evaluationText = this._textEditor.line(lineNumber).substring(startHighlight, endHighlight + 1);
+        const evaluationText = this._textEditor.line(editorLineNumber).substring(startHighlight, endHighlight + 1);
         const resolvedText = await Sources.SourceMapNamesResolver.resolveExpression(
             /** @type {!SDK.DebuggerModel.CallFrame} */ (selectedCallFrame), evaluationText, this._uiSourceCode,
-            lineNumber, startHighlight, endHighlight);
+            editorLineNumber, startHighlight, endHighlight);
         const result = await selectedCallFrame.evaluate({
           expression: resolvedText || evaluationText,
           objectGroup: 'popover',
@@ -507,7 +511,8 @@
             objectPopoverHelper.dispose();
           return false;
         }
-        const highlightRange = new TextUtils.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
+        const highlightRange =
+            new TextUtils.TextRange(editorLineNumber, startHighlight, editorLineNumber, endHighlight);
         highlightDescriptor = this._textEditor.highlightRange(highlightRange, 'source-frame-eval-expression');
         return true;
       },
@@ -577,15 +582,15 @@
   }
 
   /**
-   * @param {?number} line
+   * @param {?number} editorLineNumber
    * @param {boolean} hovered
    */
-  _setAsyncStepInHoveredLine(line, hovered) {
-    if (this._asyncStepInHoveredLine === line && this._asyncStepInHovered === hovered)
+  _setAsyncStepInHoveredLine(editorLineNumber, hovered) {
+    if (this._asyncStepInHoveredLine === editorLineNumber && this._asyncStepInHovered === hovered)
       return;
     if (this._asyncStepInHovered && this._asyncStepInHoveredLine)
       this._textEditor.toggleLineClass(this._asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', false);
-    this._asyncStepInHoveredLine = line;
+    this._asyncStepInHoveredLine = editorLineNumber;
     this._asyncStepInHovered = hovered;
     if (this._asyncStepInHovered && this._asyncStepInHoveredLine)
       this._textEditor.toggleLineClass(this._asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', true);
@@ -637,19 +642,19 @@
   }
 
   /**
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @param {?Bindings.BreakpointManager.Breakpoint} breakpoint
    * @param {?{lineNumber: number, columnNumber: number}} location
    */
-  _editBreakpointCondition(lineNumber, breakpoint, location) {
-    this._conditionElement = this._createConditionElement(lineNumber);
-    this._textEditor.addDecoration(this._conditionElement, lineNumber);
+  _editBreakpointCondition(editorLineNumber, breakpoint, location) {
+    this._conditionElement = this._createConditionElement(editorLineNumber);
+    this._textEditor.addDecoration(this._conditionElement, editorLineNumber);
 
     /**
      * @this {Sources.DebuggerPlugin}
      */
     function finishEditing(committed, element, newText) {
-      this._textEditor.removeDecoration(/** @type {!Element} */ (this._conditionElement), lineNumber);
+      this._textEditor.removeDecoration(/** @type {!Element} */ (this._conditionElement), editorLineNumber);
       this._conditionEditorElement = null;
       this._conditionElement = null;
       if (!committed)
@@ -660,7 +665,7 @@
       else if (location)
         this._setBreakpoint(location.lineNumber, location.columnNumber, newText, true);
       else
-        this._createNewBreakpoint(lineNumber, newText, true);
+        this._createNewBreakpoint(editorLineNumber, newText, true);
     }
 
     const config = new UI.InplaceEditor.Config(finishEditing.bind(this, true), finishEditing.bind(this, false));
@@ -670,16 +675,16 @@
   }
 
   /**
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @return {!Element}
    */
-  _createConditionElement(lineNumber) {
+  _createConditionElement(editorLineNumber) {
     const conditionElement = createElementWithClass('div', 'source-frame-breakpoint-condition');
 
     const labelElement = conditionElement.createChild('label', 'source-frame-breakpoint-message');
     labelElement.htmlFor = 'source-frame-breakpoint-condition';
     labelElement.createTextChild(
-        Common.UIString('The breakpoint on line %d will stop only if this expression is true:', lineNumber + 1));
+        Common.UIString('The breakpoint on line %d will stop only if this expression is true:', editorLineNumber + 1));
 
     const editorElement = UI.createInput('monospace', 'text');
     conditionElement.appendChild(editorElement);
@@ -701,7 +706,8 @@
     }
 
     this._executionLocation = uiLocation;
-    this._textEditor.setExecutionLocation(uiLocation.lineNumber, uiLocation.columnNumber);
+    const editorLocation = this._transformer.rawToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
+    this._textEditor.setExecutionLocation(editorLocation[0], editorLocation[1]);
     if (this._textEditor.isShowing()) {
       // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/506566
       setImmediate(() => {
@@ -761,14 +767,14 @@
       locations = locations.reverse();
       let previousCallLine = -1;
       for (const location of locations) {
-        const lineNumber = location.lineNumber;
-        let token = this._textEditor.tokenAtTextPosition(lineNumber, location.columnNumber);
+        const editorLocation = this._transformer.rawToEditorLocation(location.lineNumber, location.columnNumber);
+        let token = this._textEditor.tokenAtTextPosition(editorLocation[0], editorLocation[1]);
         if (!token)
           continue;
-        const line = this._textEditor.line(lineNumber);
+        const line = this._textEditor.line(editorLocation[0]);
         let tokenContent = line.substring(token.startColumn, token.endColumn);
         if (!token.type && tokenContent === '.') {
-          token = this._textEditor.tokenAtTextPosition(lineNumber, token.endColumn + 1);
+          token = this._textEditor.tokenAtTextPosition(editorLocation[0], token.endColumn + 1);
           tokenContent = line.substring(token.startColumn, token.endColumn);
         }
         if (!token.type)
@@ -778,29 +784,32 @@
              tokenContent === 'continue' || tokenContent === 'break');
         if (!validKeyword && !this._isIdentifier(token.type))
           continue;
-        if (previousCallLine === lineNumber && location.type !== Protocol.Debugger.BreakLocationType.Call)
+        if (previousCallLine === editorLocation[0] && location.type !== Protocol.Debugger.BreakLocationType.Call)
           continue;
 
-        let highlightRange = new TextUtils.TextRange(lineNumber, token.startColumn, lineNumber, token.endColumn - 1);
+        let highlightRange =
+            new TextUtils.TextRange(editorLocation[0], token.startColumn, editorLocation[0], token.endColumn - 1);
         let decoration = this._textEditor.highlightRange(highlightRange, 'source-frame-continue-to-location');
         this._continueToLocationDecorations.set(decoration, location.continueToLocation.bind(location));
         if (location.type === Protocol.Debugger.BreakLocationType.Call)
-          previousCallLine = lineNumber;
+          previousCallLine = editorLocation[0];
 
         let isAsyncCall = (line[token.startColumn - 1] === '.' && tokenContent === 'then') ||
             tokenContent === 'setTimeout' || tokenContent === 'setInterval' || tokenContent === 'postMessage';
         if (tokenContent === 'new') {
-          token = this._textEditor.tokenAtTextPosition(lineNumber, token.endColumn + 1);
+          token = this._textEditor.tokenAtTextPosition(editorLocation[0], token.endColumn + 1);
           tokenContent = line.substring(token.startColumn, token.endColumn);
           isAsyncCall = tokenContent === 'Worker';
         }
-        const isCurrentPosition = this._executionLocation && lineNumber === this._executionLocation.lineNumber &&
+        const isCurrentPosition = this._executionLocation &&
+            location.lineNumber === this._executionLocation.lineNumber &&
             location.columnNumber === this._executionLocation.columnNumber;
         if (location.type === Protocol.Debugger.BreakLocationType.Call && isAsyncCall) {
-          const asyncStepInRange = this._findAsyncStepInRange(this._textEditor, lineNumber, line, token.endColumn);
+          const asyncStepInRange =
+              this._findAsyncStepInRange(this._textEditor, editorLocation[0], line, token.endColumn);
           if (asyncStepInRange) {
-            highlightRange =
-                new TextUtils.TextRange(lineNumber, asyncStepInRange.from, lineNumber, asyncStepInRange.to - 1);
+            highlightRange = new TextUtils.TextRange(
+                editorLocation[0], asyncStepInRange.from, editorLocation[0], asyncStepInRange.to - 1);
             decoration = this._textEditor.highlightRange(highlightRange, 'source-frame-async-step-in');
             this._continueToLocationDecorations.set(
                 decoration, this._asyncStepIn.bind(this, location, !!isCurrentPosition));
@@ -817,12 +826,12 @@
 
   /**
    * @param {!SourceFrame.SourcesTextEditor} textEditor
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @param {string} line
    * @param {number} column
    * @return {?{from: number, to: number}}
    */
-  _findAsyncStepInRange(textEditor, lineNumber, line, column) {
+  _findAsyncStepInRange(textEditor, editorLineNumber, line, column) {
     let token;
     let tokenText;
     let from = column;
@@ -869,7 +878,7 @@
     return {from: from, to: closeParen + 1};
 
     function nextToken() {
-      token = textEditor.tokenAtTextPosition(lineNumber, position);
+      token = textEditor.tokenAtTextPosition(editorLineNumber, position);
       if (token) {
         position = token.endColumn;
         to = token.endColumn;
@@ -883,7 +892,7 @@
           position++;
           continue;
         }
-        const token = textEditor.tokenAtTextPosition(lineNumber, position);
+        const token = textEditor.tokenAtTextPosition(editorLineNumber, position);
         if (token.type === 'js-comment') {
           position = token.endColumn;
           continue;
@@ -928,9 +937,13 @@
       return;
     }
 
-    const fromLine = functionUILocation.lineNumber;
-    const fromColumn = functionUILocation.columnNumber;
-    let toLine = executionUILocation.lineNumber;
+    const functionEditorLocation =
+        this._transformer.rawToEditorLocation(functionUILocation.lineNumber, functionUILocation.columnNumber);
+    const executionEditorLocation =
+        this._transformer.rawToEditorLocation(executionUILocation.lineNumber, executionUILocation.columnNumber);
+    const fromLine = functionEditorLocation[0];
+    const fromColumn = functionEditorLocation[1];
+    let toLine = executionEditorLocation[0];
 
     // Make sure we have a chance to update all existing widgets.
     if (this._valueWidgets) {
@@ -955,19 +968,19 @@
       tokenizer(this._textEditor.line(i), processToken.bind(this, i));
 
     /**
-     * @param {number} lineNumber
+     * @param {number} editorLineNumber
      * @param {string} tokenValue
      * @param {?string} tokenType
      * @param {number} column
      * @param {number} newColumn
      * @this {Sources.DebuggerPlugin}
      */
-    function processToken(lineNumber, tokenValue, tokenType, column, newColumn) {
+    function processToken(editorLineNumber, tokenValue, tokenType, column, newColumn) {
       if (!skipObjectProperty && tokenType && this._isIdentifier(tokenType) && valuesMap.get(tokenValue)) {
-        let names = namesPerLine.get(lineNumber);
+        let names = namesPerLine.get(editorLineNumber);
         if (!names) {
           names = new Set();
-          namesPerLine.set(lineNumber, names);
+          namesPerLine.set(editorLineNumber, names);
         }
         names.add(tokenValue);
       }
@@ -1102,16 +1115,16 @@
   }
 
   /**
-   * @param {number} lineNumber
-   * @param {number} columnNumber
+   * @param {number} editorLineNumber
+   * @param {number} editorColumnNumber
    * @return {?Sources.DebuggerPlugin.BreakpointDecoration}
    */
-  _breakpointDecoration(lineNumber, columnNumber) {
+  _breakpointDecoration(editorLineNumber, editorColumnNumber) {
     for (const decoration of this._breakpointDecorations) {
       const location = decoration.handle.resolve();
       if (!location)
         continue;
-      if (location.lineNumber === lineNumber && location.columnNumber === columnNumber)
+      if (location.lineNumber === editorLineNumber && location.columnNumber === editorColumnNumber)
         return decoration;
     }
     return null;
@@ -1134,16 +1147,16 @@
     function update() {
       if (!this._scheduledBreakpointDecorationUpdates)
         return;
-      const lineNumbers = new Set();
+      const editorLineNumbers = new Set();
       for (const decoration of this._scheduledBreakpointDecorationUpdates) {
         const location = decoration.handle.resolve();
         if (!location)
           continue;
-        lineNumbers.add(location.lineNumber);
+        editorLineNumbers.add(location.lineNumber);
       }
       this._scheduledBreakpointDecorationUpdates = null;
       let waitingForInlineDecorations = false;
-      for (const lineNumber of lineNumbers) {
+      for (const lineNumber of editorLineNumbers) {
         const decorations = this._lineBreakpointDecorations(lineNumber);
         updateGutter.call(this, lineNumber, decorations);
         if (this._possibleBreakpointsRequested.has(lineNumber)) {
@@ -1157,34 +1170,35 @@
     }
 
     /**
-     * @param {number} lineNumber
+     * @param {number} editorLineNumber
      * @param {!Array<!Sources.DebuggerPlugin.BreakpointDecoration>} decorations
      * @this {Sources.DebuggerPlugin}
      */
-    function updateGutter(lineNumber, decorations) {
-      this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint', false);
-      this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint-disabled', false);
-      this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint-conditional', false);
+    function updateGutter(editorLineNumber, decorations) {
+      this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', false);
+      this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-disabled', false);
+      this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', false);
 
       if (decorations.length) {
         decorations.sort(Sources.DebuggerPlugin.BreakpointDecoration.mostSpecificFirst);
-        this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint', true);
-        this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint-disabled', !decorations[0].enabled || this._muted);
-        this._textEditor.toggleLineClass(lineNumber, 'cm-breakpoint-conditional', !!decorations[0].condition);
+        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', true);
+        this._textEditor.toggleLineClass(
+            editorLineNumber, 'cm-breakpoint-disabled', !decorations[0].enabled || this._muted);
+        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', !!decorations[0].condition);
       }
     }
 
     /**
-     * @param {number} lineNumber
+     * @param {number} editorLineNumber
      * @param {!Array<!Sources.DebuggerPlugin.BreakpointDecoration>} decorations
      * @this {Sources.DebuggerPlugin}
      */
-    function updateInlineDecorations(lineNumber, decorations) {
+    function updateInlineDecorations(editorLineNumber, decorations) {
       const actualBookmarks =
           new Set(decorations.map(decoration => decoration.bookmark).filter(bookmark => !!bookmark));
-      const lineEnd = this._textEditor.line(lineNumber).length;
+      const lineEnd = this._textEditor.line(editorLineNumber).length;
       const bookmarks = this._textEditor.bookmarks(
-          new TextUtils.TextRange(lineNumber, 0, lineNumber, lineEnd),
+          new TextUtils.TextRange(editorLineNumber, 0, editorLineNumber, lineEnd),
           Sources.DebuggerPlugin.BreakpointDecoration.bookmarkSymbol);
       for (const bookmark of bookmarks) {
         if (!actualBookmarks.has(bookmark))
@@ -1222,10 +1236,11 @@
       else
         decoration.breakpoint.remove();
     } else {
-      const location = decoration.handle.resolve();
-      if (!location)
+      const editorLocation = decoration.handle.resolve();
+      if (!editorLocation)
         return;
-      this._setBreakpoint(location.lineNumber, location.columnNumber, decoration.condition, true);
+      const location = this._transformer.editorToRawLocation(editorLocation.lineNumber, editorLocation.columnNumber);
+      this._setBreakpoint(location[0], location[1], decoration.condition, true);
     }
   }
 
@@ -1235,21 +1250,21 @@
    */
   _inlineBreakpointContextMenu(decoration, event) {
     event.consume(true);
-    const location = decoration.handle.resolve();
-    if (!location)
+    const editorLocation = decoration.handle.resolve();
+    if (!editorLocation)
       return;
+    const location = this._transformer.editorToRawLocation(editorLocation[0], editorLocation[1]);
     const contextMenu = new UI.ContextMenu(event);
     if (decoration.breakpoint) {
       contextMenu.debugSection().appendItem(
           Common.UIString('Edit breakpoint\u2026'),
-          this._editBreakpointCondition.bind(this, location.lineNumber, decoration.breakpoint, null));
+          this._editBreakpointCondition.bind(this, editorLocation.lineNumber, decoration.breakpoint, null));
     } else {
       contextMenu.debugSection().appendItem(
           Common.UIString('Add conditional breakpoint\u2026'),
-          this._editBreakpointCondition.bind(this, location.lineNumber, null, location));
+          this._editBreakpointCondition.bind(this, editorLocation.lineNumber, null, editorLocation));
       contextMenu.debugSection().appendItem(
-          Common.UIString('Never pause here'),
-          this._setBreakpoint.bind(this, location.lineNumber, location.columnNumber, 'false', true));
+          Common.UIString('Never pause here'), this._setBreakpoint.bind(this, location[0], location[1], 'false', true));
     }
     contextMenu.show();
   }
@@ -1290,14 +1305,15 @@
    * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
    */
   _addBreakpoint(uiLocation, breakpoint) {
+    const editorLocation = this._transformer.rawToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
     const lineDecorations = this._lineBreakpointDecorations(uiLocation.lineNumber);
-    let decoration = this._breakpointDecoration(uiLocation.lineNumber, uiLocation.columnNumber);
+    let decoration = this._breakpointDecoration(editorLocation[0], editorLocation[1]);
     if (decoration) {
       decoration.breakpoint = breakpoint;
       decoration.condition = breakpoint.condition();
       decoration.enabled = breakpoint.enabled();
     } else {
-      const handle = this._textEditor.textEditorPositionHandle(uiLocation.lineNumber, uiLocation.columnNumber);
+      const handle = this._textEditor.textEditorPositionHandle(editorLocation[0], editorLocation[1]);
       decoration = new Sources.DebuggerPlugin.BreakpointDecoration(
           this._textEditor, handle, breakpoint.condition(), breakpoint.enabled(), breakpoint);
       decoration.element.addEventListener('click', this._inlineBreakpointClick.bind(this, decoration), true);
@@ -1308,21 +1324,22 @@
     this._decorationByBreakpoint.set(breakpoint, decoration);
     this._updateBreakpointDecoration(decoration);
     if (breakpoint.enabled() && !lineDecorations.length) {
-      this._possibleBreakpointsRequested.add(uiLocation.lineNumber);
+      this._possibleBreakpointsRequested.add(editorLocation[0]);
+      const start = this._transformer.editorToRawLocation(editorLocation[0], 0);
+      const end = this._transformer.editorToRawLocation(editorLocation[0] + 1, 0);
       this._breakpointManager
-          .possibleBreakpoints(
-              this._uiSourceCode, new TextUtils.TextRange(uiLocation.lineNumber, 0, uiLocation.lineNumber + 1, 0))
-          .then(addInlineDecorations.bind(this, uiLocation.lineNumber));
+          .possibleBreakpoints(this._uiSourceCode, new TextUtils.TextRange(start[0], start[1], end[0], end[1]))
+          .then(addInlineDecorations.bind(this, editorLocation[0]));
     }
 
     /**
      * @this {Sources.DebuggerPlugin}
-     * @param {number} lineNumber
+     * @param {number} editorLineNumber
      * @param {!Array<!Workspace.UILocation>} possibleLocations
      */
-    function addInlineDecorations(lineNumber, possibleLocations) {
-      this._possibleBreakpointsRequested.delete(lineNumber);
-      const decorations = this._lineBreakpointDecorations(lineNumber);
+    function addInlineDecorations(editorLineNumber, possibleLocations) {
+      this._possibleBreakpointsRequested.delete(editorLineNumber);
+      const decorations = this._lineBreakpointDecorations(editorLineNumber);
       for (const decoration of decorations)
         this._updateBreakpointDecoration(decoration);
       if (!decorations.some(decoration => !!decoration.breakpoint))
@@ -1330,15 +1347,16 @@
       /** @type {!Set<number>} */
       const columns = new Set();
       for (const decoration of decorations) {
-        const location = decoration.handle.resolve();
-        if (!location)
+        const editorLocation = decoration.handle.resolve();
+        if (!editorLocation)
           continue;
-        columns.add(location.columnNumber);
+        columns.add(editorLocation.columnNumber);
       }
       for (const location of possibleLocations) {
-        if (columns.has(location.columnNumber))
+        const editorLocation = this._transformer.rawToEditorLocation(location.lineNumber, location.columnNumber);
+        if (columns.has(editorLocation[1]))
           continue;
-        const handle = this._textEditor.textEditorPositionHandle(location.lineNumber, location.columnNumber);
+        const handle = this._textEditor.textEditorPositionHandle(editorLocation[0], editorLocation[1]);
         const decoration = new Sources.DebuggerPlugin.BreakpointDecoration(this._textEditor, handle, '', false, null);
         decoration.element.addEventListener('click', this._inlineBreakpointClick.bind(this, decoration), true);
         decoration.element.addEventListener(
@@ -1362,10 +1380,11 @@
       return;
     this._decorationByBreakpoint.delete(breakpoint);
 
+    const editorLocation = this._transformer.rawToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
     decoration.breakpoint = null;
     decoration.enabled = false;
 
-    const lineDecorations = this._lineBreakpointDecorations(uiLocation.lineNumber);
+    const lineDecorations = this._lineBreakpointDecorations(editorLocation[0]);
     if (!lineDecorations.some(decoration => !!decoration.breakpoint)) {
       for (const lineDecoration of lineDecorations) {
         this._breakpointDecorations.delete(lineDecoration);
@@ -1479,27 +1498,27 @@
       return;
 
     const eventData = /** @type {!SourceFrame.SourcesTextEditor.GutterClickEventData} */ (event.data);
-    const lineNumber = eventData.lineNumber;
+    const editorLineNumber = eventData.lineNumber;
     const eventObject = eventData.event;
 
     if (eventObject.button !== 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey)
       return;
 
-    this._toggleBreakpoint(lineNumber, eventObject.shiftKey);
+    this._toggleBreakpoint(editorLineNumber, eventObject.shiftKey);
     eventObject.consume(true);
   }
 
   /**
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @param {boolean} onlyDisable
    */
-  _toggleBreakpoint(lineNumber, onlyDisable) {
-    const decorations = this._lineBreakpointDecorations(lineNumber);
+  _toggleBreakpoint(editorLineNumber, onlyDisable) {
+    const decorations = this._lineBreakpointDecorations(editorLineNumber);
     if (!decorations.length) {
-      this._createNewBreakpoint(lineNumber, '', true);
+      this._createNewBreakpoint(editorLineNumber, '', true);
       return;
     }
-    const hasDisabled = this._textEditor.hasLineClass(lineNumber, 'cm-breakpoint-disabled');
+    const hasDisabled = this._textEditor.hasLineClass(editorLineNumber, 'cm-breakpoint-disabled');
     const breakpoints = decorations.map(decoration => decoration.breakpoint).filter(breakpoint => !!breakpoint);
     for (const breakpoint of breakpoints) {
       if (onlyDisable)
@@ -1510,31 +1529,34 @@
   }
 
   /**
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @param {string} condition
    * @param {boolean} enabled
    */
-  async _createNewBreakpoint(lineNumber, condition, enabled) {
+  async _createNewBreakpoint(editorLineNumber, condition, enabled) {
     Host.userMetrics.actionTaken(Host.UserMetrics.Action.ScriptsBreakpointSet);
 
-    const originLineNumber = lineNumber;
+    const origin = this._transformer.editorToRawLocation(editorLineNumber, 0);
     const maxLengthToCheck = 1024;
     let linesToCheck = 5;
-    for (; lineNumber < this._textEditor.linesCount && linesToCheck > 0; ++lineNumber) {
-      const lineLength = this._textEditor.line(lineNumber).length;
+    for (; editorLineNumber < this._textEditor.linesCount && linesToCheck > 0; ++editorLineNumber) {
+      const lineLength = this._textEditor.line(editorLineNumber).length;
       if (lineLength > maxLengthToCheck)
         break;
       if (lineLength === 0)
         continue;
       --linesToCheck;
+
+      const start = this._transformer.editorToRawLocation(editorLineNumber, 0);
+      const end = this._transformer.editorToRawLocation(editorLineNumber, lineLength);
       const locations = await this._breakpointManager.possibleBreakpoints(
-          this._uiSourceCode, new TextUtils.TextRange(lineNumber, 0, lineNumber, lineLength));
+          this._uiSourceCode, new TextUtils.TextRange(start[0], start[1], end[0], end[1]));
       if (locations && locations.length) {
         this._setBreakpoint(locations[0].lineNumber, locations[0].columnNumber, condition, enabled);
         return;
       }
     }
-    this._setBreakpoint(originLineNumber, 0, condition, enabled);
+    this._setBreakpoint(origin[0], origin[1], condition, enabled);
   }
 
   /**
@@ -1691,11 +1713,11 @@
   show() {
     if (this.bookmark)
       return;
-    const location = this.handle.resolve();
-    if (!location)
+    const editorLocation = this.handle.resolve();
+    if (!editorLocation)
       return;
     this.bookmark = this._textEditor.addBookmark(
-        location.lineNumber, location.columnNumber, this.element,
+        editorLocation.lineNumber, editorLocation.columnNumber, this.element,
         Sources.DebuggerPlugin.BreakpointDecoration.bookmarkSymbol);
     this.bookmark[Sources.DebuggerPlugin.BreakpointDecoration._elementSymbolForTest] = this.element;
   }
diff --git a/front_end/sources/SourcesView.js b/front_end/sources/SourcesView.js
index 0e4352a..f02e8d4 100644
--- a/front_end/sources/SourcesView.js
+++ b/front_end/sources/SourcesView.js
@@ -36,9 +36,10 @@
     this._historyManager = new Sources.EditingLocationHistoryManager(this, this.currentSourceFrame.bind(this));
 
     this._toolbarContainerElement = this.element.createChild('div', 'sources-toolbar');
-    this._toolbarEditorActions = new UI.Toolbar('', this._toolbarContainerElement);
-
-    self.runtime.allInstances(Sources.SourcesView.EditorAction).then(appendButtonsForExtensions.bind(this));
+    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}
diff --git a/front_end/sources/UISourceCodeFrame.js b/front_end/sources/UISourceCodeFrame.js
index b76c7d7..47de620 100644
--- a/front_end/sources/UISourceCodeFrame.js
+++ b/front_end/sources/UISourceCodeFrame.js
@@ -165,6 +165,10 @@
     this._updateStyle();
     this._decorateAllTypes();
     this._refreshHighlighterType();
+    if (Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
+      const supportedPrettyTypes = new Set(['text/html', 'text/css', 'text/javascript']);
+      this.setCanPrettyPrint(supportedPrettyTypes.has(this.highlighterType()), true);
+    }
     this._ensurePluginsLoaded();
   }
 
@@ -214,6 +218,9 @@
     if (this._uiSourceCode.project().type() === Workspace.projectTypes.Network &&
         Persistence.networkPersistenceManager.active())
       return true;
+    // Because live edit fails on large whitespace changes, pretty printed scripts are not editable.
+    if (this.pretty && this._uiSourceCode.contentType().hasScripts())
+      return false;
     return this._uiSourceCode.contentType() !== Common.resourceTypes.Document;
   }
 
@@ -236,6 +243,7 @@
    */
   setContent(content) {
     this._disposePlugins();
+    this._rowMessageBuckets.clear();
     super.setContent(content);
     for (const message of this._allMessages())
       this._addMessageToSource(message);
@@ -261,17 +269,23 @@
    * @param {!TextUtils.TextRange} newRange
    */
   onTextChanged(oldRange, newRange) {
+    const wasPretty = this.pretty;
     super.onTextChanged(oldRange, newRange);
     this._errorPopoverHelper.hidePopover();
     if (this._isSettingContent)
       return;
     Sources.SourcesPanel.instance().updateLastModificationTime();
     this._muteSourceCodeEvents = true;
-    if (this.textEditor.isClean())
+    if (this.isClean())
       this._uiSourceCode.resetWorkingCopy();
     else
       this._uiSourceCode.setWorkingCopyGetter(this.textEditor.text.bind(this.textEditor));
     this._muteSourceCodeEvents = false;
+    if (wasPretty !== this.pretty) {
+      this._updateStyle();
+      this._disposePlugins();
+      this._ensurePluginsLoaded();
+    }
   }
 
   /**
@@ -289,7 +303,7 @@
   _onWorkingCopyCommitted(event) {
     if (!this._muteSourceCodeEvents)
       this._innerSetContent(this._uiSourceCode.workingCopy());
-    this.textEditor.markClean();
+    this.contentCommitted();
     this._updateStyle();
   }
 
@@ -302,14 +316,15 @@
 
     // The order of these plugins matters for toolbar items
     if (Sources.DebuggerPlugin.accepts(pluginUISourceCode))
-      this._plugins.push(new Sources.DebuggerPlugin(this.textEditor, pluginUISourceCode));
+      this._plugins.push(new Sources.DebuggerPlugin(this.textEditor, pluginUISourceCode, this.transformer()));
     if (Sources.CSSPlugin.accepts(pluginUISourceCode))
       this._plugins.push(new Sources.CSSPlugin(this.textEditor));
-    if (Sources.JavaScriptCompilerPlugin.accepts(pluginUISourceCode))
+    if (!this.pretty && Sources.JavaScriptCompilerPlugin.accepts(pluginUISourceCode))
       this._plugins.push(new Sources.JavaScriptCompilerPlugin(this.textEditor, pluginUISourceCode));
     if (Sources.SnippetsPlugin.accepts(pluginUISourceCode))
       this._plugins.push(new Sources.SnippetsPlugin(this.textEditor, pluginUISourceCode));
-    if (Runtime.experiments.isEnabled('sourceDiff') && Sources.GutterDiffPlugin.accepts(pluginUISourceCode))
+    if (!this.pretty && Runtime.experiments.isEnabled('sourceDiff') &&
+        Sources.GutterDiffPlugin.accepts(pluginUISourceCode))
       this._plugins.push(new Sources.GutterDiffPlugin(this.textEditor, pluginUISourceCode));
 
     this.dispatchEventToListeners(Sources.UISourceCodeFrame.Events.ToolbarItemsChanged);
@@ -318,8 +333,10 @@
   }
 
   _disposePlugins() {
-    for (const plugin of this._plugins)
-      plugin.dispose();
+    this.textEditor.operation(() => {
+      for (const plugin of this._plugins)
+        plugin.dispose();
+    });
     this._plugins = [];
   }
 
@@ -353,13 +370,14 @@
    * @override
    * @return {!Promise}
    */
-  async populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) {
-    await super.populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber);
+  async populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) {
+    await super.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber);
     contextMenu.appendApplicableItems(this._uiSourceCode);
-    contextMenu.appendApplicableItems(new Workspace.UILocation(this._uiSourceCode, lineNumber, columnNumber));
+    const location = this.transformer().editorToRawLocation(editorLineNumber, editorColumnNumber);
+    contextMenu.appendApplicableItems(new Workspace.UILocation(this._uiSourceCode, location[0], location[1]));
     contextMenu.appendApplicableItems(this);
     for (const plugin of this._plugins)
-      await plugin.populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber);
+      await plugin.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber);
   }
 
   dispose() {
@@ -385,16 +403,17 @@
   _addMessageToSource(message) {
     if (!this.loaded)
       return;
-    let lineNumber = message.lineNumber();
-    if (lineNumber >= this.textEditor.linesCount)
-      lineNumber = this.textEditor.linesCount - 1;
-    if (lineNumber < 0)
-      lineNumber = 0;
+    const editorLocation = this.transformer().rawToEditorLocation(message.lineNumber(), message.columnNumber());
+    let editorLineNumber = editorLocation[0];
+    if (editorLineNumber >= this.textEditor.linesCount)
+      editorLineNumber = this.textEditor.linesCount - 1;
+    if (editorLineNumber < 0)
+      editorLineNumber = 0;
 
-    let messageBucket = this._rowMessageBuckets.get(lineNumber);
+    let messageBucket = this._rowMessageBuckets.get(editorLineNumber);
     if (!messageBucket) {
-      messageBucket = new Sources.UISourceCodeFrame.RowMessageBucket(this, this.textEditor, lineNumber);
-      this._rowMessageBuckets.set(lineNumber, messageBucket);
+      messageBucket = new Sources.UISourceCodeFrame.RowMessageBucket(this, this.textEditor, editorLineNumber);
+      this._rowMessageBuckets.set(editorLineNumber, messageBucket);
     }
     messageBucket.addMessage(message);
   }
@@ -414,19 +433,20 @@
     if (!this.loaded)
       return;
 
-    let lineNumber = message.lineNumber();
-    if (lineNumber >= this.textEditor.linesCount)
-      lineNumber = this.textEditor.linesCount - 1;
-    if (lineNumber < 0)
-      lineNumber = 0;
+    const editorLocation = this.transformer().rawToEditorLocation(message.lineNumber(), message.columnNumber());
+    let editorLineNumber = editorLocation[0];
+    if (editorLineNumber >= this.textEditor.linesCount)
+      editorLineNumber = this.textEditor.linesCount - 1;
+    if (editorLineNumber < 0)
+      editorLineNumber = 0;
 
-    const messageBucket = this._rowMessageBuckets.get(lineNumber);
+    const messageBucket = this._rowMessageBuckets.get(editorLineNumber);
     if (!messageBucket)
       return;
     messageBucket.removeMessage(message);
     if (!messageBucket.uniqueMessagesCount()) {
       messageBucket.detachFromEditor();
-      this._rowMessageBuckets.delete(lineNumber);
+      this._rowMessageBuckets.delete(editorLineNumber);
     }
   }
 
@@ -605,12 +625,12 @@
   /**
    * @param {!Sources.UISourceCodeFrame} sourceFrame
    * @param {!TextEditor.CodeMirrorTextEditor} textEditor
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    */
-  constructor(sourceFrame, textEditor, lineNumber) {
+  constructor(sourceFrame, textEditor, editorLineNumber) {
     this._sourceFrame = sourceFrame;
     this.textEditor = textEditor;
-    this._lineHandle = textEditor.textEditorPositionHandle(lineNumber, 0);
+    this._lineHandle = textEditor.textEditorPositionHandle(editorLineNumber, 0);
     this._decoration = createElementWithClass('div', 'text-editor-line-decoration');
     this._decoration._messageBucket = this;
     this._wave = this._decoration.createChild('div', 'text-editor-line-decoration-wave');
@@ -626,20 +646,20 @@
   }
 
   /**
-   * @param {number} lineNumber
+   * @param {number} editorLineNumber
    * @param {number} columnNumber
    */
-  _updateWavePosition(lineNumber, columnNumber) {
-    lineNumber = Math.min(lineNumber, this.textEditor.linesCount - 1);
-    const lineText = this.textEditor.line(lineNumber);
+  _updateWavePosition(editorLineNumber, columnNumber) {
+    editorLineNumber = Math.min(editorLineNumber, this.textEditor.linesCount - 1);
+    const lineText = this.textEditor.line(editorLineNumber);
     columnNumber = Math.min(columnNumber, lineText.length);
     const lineIndent = TextUtils.TextUtils.lineIndent(lineText).length;
     const startColumn = Math.max(columnNumber - 1, lineIndent);
     if (this._decorationStartColumn === startColumn)
       return;
     if (this._decorationStartColumn !== null)
-      this.textEditor.removeDecoration(this._decoration, lineNumber);
-    this.textEditor.addDecoration(this._decoration, lineNumber, startColumn);
+      this.textEditor.removeDecoration(this._decoration, editorLineNumber);
+    this.textEditor.addDecoration(this._decoration, editorLineNumber, startColumn);
     this._decorationStartColumn = startColumn;
   }
 
@@ -659,11 +679,13 @@
     const position = this._lineHandle.resolve();
     if (!position)
       return;
-    const lineNumber = position.lineNumber;
-    if (this._level)
-      this.textEditor.toggleLineClass(lineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false);
+    const editorLineNumber = position.lineNumber;
+    if (this._level) {
+      this.textEditor.toggleLineClass(
+          editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false);
+    }
     if (this._decorationStartColumn !== null) {
-      this.textEditor.removeDecoration(this._decoration, lineNumber);
+      this.textEditor.removeDecoration(this._decoration, editorLineNumber);
       this._decorationStartColumn = null;
     }
   }
@@ -717,27 +739,30 @@
     if (!position)
       return;
 
-    const lineNumber = position.lineNumber;
+    const editorLineNumber = position.lineNumber;
     let columnNumber = Number.MAX_VALUE;
     let maxMessage = null;
     for (let i = 0; i < this._messages.length; ++i) {
       const message = this._messages[i].message();
-      columnNumber = Math.min(columnNumber, message.columnNumber());
+      const editorLocation =
+          this._sourceFrame.transformer().rawToEditorLocation(editorLineNumber, message.columnNumber());
+      columnNumber = Math.min(columnNumber, editorLocation[1]);
       if (!maxMessage || Workspace.UISourceCode.Message.messageLevelComparator(maxMessage, message) < 0)
         maxMessage = message;
     }
-    this._updateWavePosition(lineNumber, columnNumber);
+    this._updateWavePosition(editorLineNumber, columnNumber);
 
     if (this._level === maxMessage.level())
       return;
     if (this._level) {
-      this.textEditor.toggleLineClass(lineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false);
+      this.textEditor.toggleLineClass(
+          editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false);
       this._icon.type = '';
     }
     this._level = maxMessage.level();
     if (!this._level)
       return;
-    this.textEditor.toggleLineClass(lineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], true);
+    this.textEditor.toggleLineClass(editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], true);
     this._icon.type = Sources.UISourceCodeFrame._iconClassPerLevel[this._level];
   }
 };
diff --git a/front_end/text_editor/CodeMirrorTextEditor.js b/front_end/text_editor/CodeMirrorTextEditor.js
index adb3bd1..3d0a1cf 100644
--- a/front_end/text_editor/CodeMirrorTextEditor.js
+++ b/front_end/text_editor/CodeMirrorTextEditor.js
@@ -636,14 +636,18 @@
   }
 
   /**
+   * @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 @@
       this._enableLongLinesMode();
     else
       this._disableLongLinesMode();
+
+    if (!this.isShowing())
+      this.refresh();
   }
 
   /**
diff --git a/front_end/text_editor/cmdevtools.css b/front_end/text_editor/cmdevtools.css
index e451a8f..f30c334 100644
--- a/front_end/text_editor/cmdevtools.css
+++ b/front_end/text_editor/cmdevtools.css
@@ -114,6 +114,10 @@
     white-space: nowrap;
 }
 
+.pretty-printed .CodeMirror-linenumber {
+    color: var( --accent-color-b);
+}
+
 .cm-highlight {
     -webkit-animation: fadeout 2s 0s;
 }