Changeset 83725 in webkit


Ignore:
Timestamp:
Apr 13, 2011 6:39:00 AM (13 years ago)
Author:
commit-queue@webkit.org
Message:

2011-04-13 Andrey Adaikin <aandrey@google.com>

Reviewed by Pavel Feldman.

Web Inspector: Implement undo/redo in text editor
https://bugs.webkit.org/show_bug.cgi?id=58426

Native undo/redo does not work because we modify DOM structure (highlights, chunks and etc.)
Implement it via keyboard shortcuts for now (Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z).
FIXME: Do something with the popup's Undo and Redo menu options - they invoke native's undo/redo.

  • inspector/front-end/SourceFrame.js: (WebInspector.SourceFrame.prototype.endEditing):
  • inspector/front-end/TextEditorModel.js: (WebInspector.TextEditorModel.prototype.setText): (WebInspector.TextEditorModel.prototype._innerSetText): (WebInspector.TextEditorModel.prototype._pushUndoableCommand): (WebInspector.TextEditorModel.prototype.undo): (WebInspector.TextEditorModel.prototype.redo): (WebInspector.TextEditorModel.prototype._doUndo):
  • inspector/front-end/TextViewer.js: (WebInspector.TextViewer.prototype._textChanged): (WebInspector.TextViewer.prototype._enterInternalTextChangeMode): (WebInspector.TextViewer.prototype._exitInternalTextChangeMode): (WebInspector.TextViewer.prototype._registerShortcuts): (WebInspector.TextViewer.prototype._cancelEditing): (WebInspector.TextViewer.prototype._handleUndoRedo): (WebInspector.TextEditorChunkedPanel.prototype.makeLineAChunk): (WebInspector.TextEditorChunkedPanel.prototype._repaintAll): (WebInspector.TextEditorGutterPanel.prototype.textChanged): (WebInspector.TextEditorMainPanel.prototype.handleUndoRedo.callback): (WebInspector.TextEditorMainPanel.prototype.handleUndoRedo): (WebInspector.TextEditorMainPanel.prototype._restoreSelection): (WebInspector.TextEditorMainPanel.prototype._applyDomUpdates): (WebInspector.TextEditorMainPanel.prototype.textChanged): (WebInspector.TextEditorMainPanel.prototype._updateChunksForRanges):
Location:
trunk/Source/WebCore
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r83724 r83725  
     12011-04-13  Andrey Adaikin  <aandrey@google.com>
     2
     3        Reviewed by Pavel Feldman.
     4
     5        Web Inspector: Implement undo/redo in text editor
     6        https://bugs.webkit.org/show_bug.cgi?id=58426
     7
     8        Native undo/redo does not work because we modify DOM structure (highlights, chunks and etc.)
     9        Implement it via keyboard shortcuts for now (Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z).
     10        FIXME: Do something with the popup's Undo and Redo menu options - they invoke native's undo/redo.
     11
     12        * inspector/front-end/SourceFrame.js:
     13        (WebInspector.SourceFrame.prototype.endEditing):
     14        * inspector/front-end/TextEditorModel.js:
     15        (WebInspector.TextEditorModel.prototype.setText):
     16        (WebInspector.TextEditorModel.prototype._innerSetText):
     17        (WebInspector.TextEditorModel.prototype._pushUndoableCommand):
     18        (WebInspector.TextEditorModel.prototype.undo):
     19        (WebInspector.TextEditorModel.prototype.redo):
     20        (WebInspector.TextEditorModel.prototype._doUndo):
     21        * inspector/front-end/TextViewer.js:
     22        (WebInspector.TextViewer.prototype._textChanged):
     23        (WebInspector.TextViewer.prototype._enterInternalTextChangeMode):
     24        (WebInspector.TextViewer.prototype._exitInternalTextChangeMode):
     25        (WebInspector.TextViewer.prototype._registerShortcuts):
     26        (WebInspector.TextViewer.prototype._cancelEditing):
     27        (WebInspector.TextViewer.prototype._handleUndoRedo):
     28        (WebInspector.TextEditorChunkedPanel.prototype.makeLineAChunk):
     29        (WebInspector.TextEditorChunkedPanel.prototype._repaintAll):
     30        (WebInspector.TextEditorGutterPanel.prototype.textChanged):
     31        (WebInspector.TextEditorMainPanel.prototype.handleUndoRedo.callback):
     32        (WebInspector.TextEditorMainPanel.prototype.handleUndoRedo):
     33        (WebInspector.TextEditorMainPanel.prototype._restoreSelection):
     34        (WebInspector.TextEditorMainPanel.prototype._applyDomUpdates):
     35        (WebInspector.TextEditorMainPanel.prototype.textChanged):
     36        (WebInspector.TextEditorMainPanel.prototype._updateChunksForRanges):
     37
    1382011-04-13  Pavel Feldman  <pfeldman@chromium.org>
    239
  • trunk/Source/WebCore/inspector/front-end/SourceFrame.js

    r83713 r83725  
    251251    endEditing: function(oldRange, newRange)
    252252    {
     253        if (!oldRange || !newRange)
     254            return;
     255
    253256        // Adjust execution line number.
    254257        if (typeof this._executionLineNumber === "number") {
  • trunk/Source/WebCore/inspector/front-end/TextEditorModel.js

    r80728 r83725  
    9494        if (!range)
    9595            range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length);
    96         var command = this._pushUndoableCommand(range, text);
     96        var command = this._pushUndoableCommand(range);
    9797        var newRange = this._innerSetText(range, text);
    9898        command.range = newRange.clone();
     
    118118
    119119        var prefix = this._lines[range.startLine].substring(0, range.startColumn);
    120         var prefixArguments = this._arguments
    121120        var suffix = this._lines[range.startLine].substring(range.startColumn);
    122121
     
    244243    },
    245244
    246     _pushUndoableCommand: function(range, text)
     245    _pushUndoableCommand: function(range)
    247246    {
    248247        var command = {
     
    263262    },
    264263
    265     undo: function()
     264    undo: function(callback)
    266265    {
    267266        this._markRedoableState();
    268267
    269268        this._inUndo = true;
    270         var range = this._doUndo(this._undoStack);
     269        var range = this._doUndo(this._undoStack, callback);
    271270        delete this._inUndo;
    272271
     
    274273    },
    275274
    276     redo: function()
     275    redo: function(callback)
    277276    {
    278277        this.markUndoableState();
    279278
    280279        this._inRedo = true;
    281         var range = this._doUndo(this._redoStack);
     280        var range = this._doUndo(this._redoStack, callback);
    282281        delete this._inRedo;
    283282
     
    285284    },
    286285
    287     _doUndo: function(stack)
     286    _doUndo: function(stack, callback)
    288287    {
    289288        var range = null;
     
    293292
    294293            range = this.setText(command.range, command.text);
     294            if (callback)
     295                callback(command.range, range);
    295296            if (i > 0 && stack[i - 1].explicit)
    296297                return range;
  • trunk/Source/WebCore/inspector/front-end/TextViewer.js

    r83585 r83725  
    3636    this._textModel = textModel;
    3737    this._textModel.changeListener = this._textChanged.bind(this);
     38    this._textModel.resetUndoStack();
    3839    this._delegate = delegate;
    3940
     
    152153    _textChanged: function(oldRange, newRange, oldText, newText)
    153154    {
    154         if (!this._internalTextChangeMode) {
    155             this._mainPanel.textChanged(oldRange, newRange);
    156             this._gutterPanel.textChanged(oldRange, newRange);
    157             this._updatePanelOffsets();
    158         }
    159     },
    160 
    161     _enterInternalTextChangeMode: function()
    162     {
    163         this._internalTextChangeMode = true;
    164 
    165         this._delegate.startEditing();
    166     },
    167 
    168     _exitInternalTextChangeMode: function(oldRange, newRange)
    169     {
    170         this._internalTextChangeMode = false;
    171 
    172         // Update the gutter panel.
     155        if (!this._internalTextChangeMode)
     156            this._textModel.resetUndoStack();
     157        this._mainPanel.textChanged(oldRange, newRange);
    173158        this._gutterPanel.textChanged(oldRange, newRange);
    174159        this._updatePanelOffsets();
    175 
     160    },
     161
     162    _enterInternalTextChangeMode: function()
     163    {
     164        this._internalTextChangeMode = true;
     165        this._delegate.startEditing();
     166    },
     167
     168    _exitInternalTextChangeMode: function(oldRange, newRange)
     169    {
     170        this._internalTextChangeMode = false;
    176171        this._delegate.endEditing(oldRange, newRange);
    177172    },
     
    236231    _registerShortcuts: function()
    237232    {
     233        var keys = WebInspector.KeyboardShortcut.Keys;
     234        var modifiers = WebInspector.KeyboardShortcut.Modifiers;
     235
    238236        this._shortcuts = {};
    239237        var commitEditing = this._commitEditing.bind(this);
    240238        var cancelEditing = this._cancelEditing.bind(this);
    241         this._shortcuts[WebInspector.KeyboardShortcut.makeKey("s", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)] = commitEditing;
    242         this._shortcuts[WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.Keys.Enter.code, WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)] = commitEditing;
    243         this._shortcuts[WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.Keys.Esc.code)] = cancelEditing;
     239        this._shortcuts[WebInspector.KeyboardShortcut.makeKey("s", modifiers.CtrlOrMeta)] = commitEditing;
     240        this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, modifiers.CtrlOrMeta)] = commitEditing;
     241        this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Esc.code)] = cancelEditing;
     242
     243        var handleUndo = this._handleUndoRedo.bind(this, 0);
     244        var handleRedo = this._handleUndoRedo.bind(this, 1);
     245        this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.CtrlOrMeta)] = handleUndo;
     246        this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | modifiers.CtrlOrMeta)] = handleRedo;
    244247    },
    245248
     
    279282        this._delegate.cancelEditing();
    280283        return true;
     284    },
     285
     286    _handleUndoRedo: function(redo)
     287    {
     288        return this._mainPanel.handleUndoRedo(redo);
    281289    }
    282290}
     
    357365        var chunk = this.chunkForLine(lineNumber);
    358366        chunk.removeDecoration(decoration);
    359     },
    360 
    361     textChanged: function(oldRange, newRange)
    362     {
    363         this._buildChunks();
    364367    },
    365368
     
    384387    makeLineAChunk: function(lineNumber)
    385388    {
    386         if (!this._textChunks)
    387             this._buildChunks();
    388 
    389389        var chunkNumber = this._chunkNumberForLine(lineNumber);
    390390        var oldChunk = this._textChunks[chunkNumber];
     
    542542        if (this._paintCoalescingLevel || this._dirtyLines)
    543543            return;
    544 
    545         if (!this._textChunks)
    546             this._buildChunks();
    547544
    548545        var visibleFrom = this.element.scrollTop;
     
    622619    textChanged: function(oldRange, newRange)
    623620    {
    624         if (!this._textChunks) {
    625             this._buildChunks();
    626             return;
    627         }
     621        this.beginDomUpdates();
    628622
    629623        var linesDiff = newRange.linesCount - oldRange.linesCount;
     
    661655            }
    662656        }
     657
     658        this.endDomUpdates();
    663659    },
    664660
     
    904900        this._cachedTextNodes = [];
    905901        this._cachedRows = [];
     902    },
     903
     904    handleUndoRedo: function(redo)
     905    {
     906        if (this._readOnly || this._dirtyLines)
     907            return false;
     908
     909        this.beginUpdates();
     910        this._enterTextChangeMode();
     911
     912        var callback = function(oldRange, newRange) {
     913            this._exitTextChangeMode(oldRange, newRange);
     914            this._enterTextChangeMode();
     915        }.bind(this);
     916
     917        var range = redo ? this._textModel.redo(callback) : this._textModel.undo(callback);
     918        if (range)
     919            this._restoreSelection(new WebInspector.TextRange(range.endLine, range.endColumn, range.endLine, range.endColumn), true);
     920
     921        this._exitTextChangeMode(null, null);
     922        this.endUpdates();
     923
     924        return true;
    906925    },
    907926
     
    11701189    },
    11711190
    1172     _restoreSelection: function(range)
     1191    _restoreSelection: function(range, scrollIntoView)
    11731192    {
    11741193        if (!range)
     
    11771196        var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn);
    11781197        window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset);
     1198
     1199        if (scrollIntoView) {
     1200            for (var node = end.container; node; node = node.parentElement) {
     1201                if (node.scrollIntoViewIfNeeded) {
     1202                    node.scrollIntoViewIfNeeded();
     1203                    break;
     1204                }
     1205            }
     1206        }
    11791207    },
    11801208
     
    14061434        }
    14071435
    1408         // Try to decrease the range being replaced if possible.
     1436        // Try to decrease the range being replaced, if possible.
    14091437        var startOffset = 0;
    14101438        while (startLine < dirtyLines.start && startOffset < lines.length) {
     
    14251453        lines = lines.slice(startOffset, endOffset);
    14261454
     1455        // Try to decrease the range being replaced by column offsets, if possible.
     1456        var startColumn = 0;
     1457        var endColumn = this._textModel.lineLength(endLine - 1);
     1458        if (lines.length > 0) {
     1459            var line1 = this._textModel.line(startLine);
     1460            var line2 = lines[0];
     1461            while (line1[startColumn] && line1[startColumn] === line2[startColumn])
     1462                ++startColumn;
     1463            lines[0] = line2.substring(startColumn);
     1464
     1465            var line1 = this._textModel.line(endLine - 1);
     1466            var line2 = lines[lines.length - 1];
     1467            for (var i = 0; i < endColumn && i < line2.length; ++i) {
     1468                if (startLine === endLine - 1 && endColumn - i <= startColumn)
     1469                    break;
     1470                if (line1[endColumn - i - 1] !== line2[line2.length - i - 1])
     1471                    break;
     1472            }
     1473            if (i) {
     1474                endColumn -= i;
     1475                lines[lines.length - 1] = line2.substring(0, line2.length - i);
     1476            }
     1477        }
     1478
    14271479        var selection = this._getSelection();
    14281480
    1429         if (lines.length === 0 && endLine < this._textModel.linesCount) {
     1481        if (lines.length === 0 && endLine < this._textModel.linesCount)
    14301482            var oldRange = new WebInspector.TextRange(startLine, 0, endLine, 0);
    1431             var newRange = this._textModel.setText(oldRange, "");
    1432         } else if (lines.length === 0 && startLine > 0) {
     1483        else if (lines.length === 0 && startLine > 0)
    14331484            var oldRange = new WebInspector.TextRange(startLine - 1, this._textModel.lineLength(startLine - 1), endLine - 1, this._textModel.lineLength(endLine - 1));
    1434             var newRange = this._textModel.setText(oldRange, "");
    1435         } else {
    1436             var oldRange = new WebInspector.TextRange(startLine, 0, endLine - 1, this._textModel.lineLength(endLine - 1));
    1437             var newRange = this._textModel.setText(oldRange, lines.join("\n"));
    1438         }
    1439 
     1485        else
     1486            var oldRange = new WebInspector.TextRange(startLine, startColumn, endLine - 1, endColumn);
     1487
     1488        if (this._lastEditedRange && (lines.length !== 1 || this._lastEditedRange.endLine !== oldRange.startLine || this._lastEditedRange.endColumn !== oldRange.startColumn))
     1489            this._textModel.markUndoableState();
     1490
     1491        var newRange = this._textModel.setText(oldRange, lines.join("\n"));
     1492        this._lastEditedRange = newRange;
     1493
     1494        this._paintScheduledLines(true);
     1495        this._restoreSelection(selection);
     1496
     1497        this._exitTextChangeMode(oldRange, newRange);
     1498    },
     1499
     1500    textChanged: function(oldRange, newRange)
     1501    {
    14401502        this.beginDomUpdates();
    14411503        this._removeDecorationsInRange(oldRange);
    14421504        this._updateChunksForRanges(oldRange, newRange);
    14431505        this._updateHighlightsForRange(newRange);
    1444         this._paintScheduledLines(true);
    14451506        this.endDomUpdates();
    1446 
    1447         this._restoreSelection(selection);
    1448 
    1449         this._exitTextChangeMode(oldRange, newRange);
    14501507    },
    14511508
     
    14931550        for (var chunkNumber = firstChunkNumber; chunkNumber <= lastChunkNumber; ++chunkNumber) {
    14941551            var chunk = this._textChunks[chunkNumber];
     1552            if (chunk.startLine + chunk.linesCount > this._textModel.linesCount)
     1553                break;
    14951554            var lineNumber = chunk.startLine;
    14961555            for (var lineRow = firstLineRow; lineRow && lineNumber < chunk.startLine + chunk.linesCount; lineRow = lineRow.nextSibling) {
Note: See TracChangeset for help on using the changeset viewer.