From 7798de7bd3a266a819290e05671d5d8001b76115 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 16 Jun 2013 16:45:38 +0400 Subject: [PATCH 01/10] add selection.toJSON method --- lib/ace/multi_select_test.js | 24 ++++++ lib/ace/selection.js | 144 +++++++++++++++++++++++------------ lib/ace/selection_test.js | 29 +++++-- 3 files changed, 141 insertions(+), 56 deletions(-) diff --git a/lib/ace/multi_select_test.js b/lib/ace/multi_select_test.js index b1c9f397..45ab4965 100644 --- a/lib/ace/multi_select_test.js +++ b/lib/ace/multi_select_test.js @@ -171,6 +171,30 @@ module.exports = { selection.addRange(range2); editor.execCommand('insertfoo'); assert.equal('l1foo\nl2foo', editor.getValue()); + }, + + "test multiselect fromJSON/toJSON": function() { + var doc = new EditSession(["l1", "l2"]); + editor = new Editor(new MockRenderer(), doc); + MultiSelect(editor); + var selection = editor.selection; + + var before = selection.toJSON(); + + var range1 = new Range(0,2,0,2); + var range2 = new Range(1,2,1,2); + selection.fromOrientedRange(range1) + selection.addRange(range2); + + var after = selection.toJSON(); + + selection.fromJSON(before); + assert.ok(!selection.isEqual(after)); + assert.ok(selection.isEqual(before)); + + selection.fromJSON(after); + assert.ok(!selection.isEqual(before)); + assert.ok(selection.isEqual(after)); } }; diff --git a/lib/ace/selection.js b/lib/ace/selection.js index dd5435f1..68a22a5c 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -3,7 +3,7 @@ * * Copyright (c) 2010, Ajax.org B.V. * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright @@ -14,7 +14,7 @@ * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -37,7 +37,7 @@ var EventEmitter = require("./lib/event_emitter").EventEmitter; var Range = require("./range").Range; /** - * + * * Contains the cursor position and the text selection of an edit session. * * The row/columns used in the selection are in document coordinates representing ths coordinates as thez appear in the document before applying soft wrap and folding. @@ -49,22 +49,22 @@ var Range = require("./range").Range; * Emitted when the cursor position changes. * @event changeCursor * - * + * * **/ /** * Emitted when the cursor selection changes. * @event changeSelection * - * + * * **/ /** * Creates a new `Selection` object. * @param {EditSession} session The session to use * - * - * + * + * * @constructor **/ var Selection = function(session) { @@ -95,7 +95,7 @@ var Selection = function(session) { oop.implement(this, EventEmitter); /** - * + * * Returns `true` if the selection is empty. * @returns {Boolean} **/ @@ -106,7 +106,7 @@ var Selection = function(session) { )); }; - /** + /** * Returns `true` if the selection is a multi-line. * @returns {Boolean} **/ @@ -118,7 +118,7 @@ var Selection = function(session) { return this.getRange().isMultiLine(); }; - /** + /** * Gets the current position of the cursor. * @returns {Number} **/ @@ -131,7 +131,7 @@ var Selection = function(session) { * @param {Number} row The new row * @param {Number} column The new column * - * + * **/ this.setSelectionAnchor = function(row, column) { this.anchor.setPosition(row, column); @@ -156,7 +156,7 @@ var Selection = function(session) { }; /** - * + * * Returns an object containing the `row` and `column` of the calling selection lead. * @returns {Object} **/ @@ -168,7 +168,7 @@ var Selection = function(session) { * Shifts the selection up (or down, if [[Selection.isBackwards `isBackwards()`]] is true) the given number of columns. * @param {Number} columns The number of columns to shift by * - * + * * **/ this.shiftSelection = function(columns) { @@ -245,7 +245,7 @@ var Selection = function(session) { * @param {Range} range The range of text to select * @param {Boolean} reverse Indicates if the range should go backwards (`true`) or not * - * + * * @method setSelectionRange * @alias setRange **/ @@ -274,7 +274,7 @@ var Selection = function(session) { * @param {Number} row The row to select to * @param {Number} column The column to select to * - * + * * **/ this.selectTo = function(row, column) { @@ -287,7 +287,7 @@ var Selection = function(session) { * Moves the selection cursor to the row and column indicated by `pos`. * @param {Object} pos An object containing the row and column * - * + * * **/ this.selectToPosition = function(pos) { @@ -297,7 +297,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection up one row. **/ this.selectUp = function() { @@ -305,7 +305,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection down one row. **/ this.selectDown = function() { @@ -313,7 +313,7 @@ var Selection = function(session) { }; /** - * + * * * Moves the selection right one column. **/ @@ -322,7 +322,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection left one column. **/ this.selectLeft = function() { @@ -330,7 +330,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the beginning of the current line. **/ this.selectLineStart = function() { @@ -338,7 +338,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the end of the current line. **/ this.selectLineEnd = function() { @@ -346,7 +346,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the end of the file. **/ this.selectFileEnd = function() { @@ -354,7 +354,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the start of the file. **/ this.selectFileStart = function() { @@ -362,7 +362,7 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the first word on the right. **/ this.selectWordRight = function() { @@ -370,14 +370,14 @@ var Selection = function(session) { }; /** - * + * * Moves the selection to the first word on the left. **/ this.selectWordLeft = function() { this.$moveSelection(this.moveCursorWordLeft); }; - /** + /** * Moves the selection to highlight the entire word. * @related EditSession.getWordRange **/ @@ -389,16 +389,16 @@ var Selection = function(session) { } return this.session.getWordRange(row, column); }; - + /** - * + * * Selects an entire word boundary. **/ this.selectWord = function() { this.setSelectionRange(this.getWordRange()); }; - /** + /** * Selects a word, including its right whitespace. * @related EditSession.getAWordRange **/ @@ -433,7 +433,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor up one row. **/ this.moveCursorUp = function() { @@ -441,7 +441,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor down one row. **/ this.moveCursorDown = function() { @@ -449,7 +449,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor left one column. **/ this.moveCursorLeft = function() { @@ -474,7 +474,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor right one column. **/ this.moveCursorRight = function() { @@ -499,7 +499,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the start of the line. **/ this.moveCursorLineStart = function() { @@ -524,7 +524,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the end of the line. **/ this.moveCursorLineEnd = function() { @@ -543,7 +543,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the end of the file. **/ this.moveCursorFileEnd = function() { @@ -553,7 +553,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the start of the file. **/ this.moveCursorFileStart = function() { @@ -561,7 +561,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the word on the right. **/ this.moveCursorLongWordRight = function() { @@ -607,7 +607,7 @@ var Selection = function(session) { }; /** - * + * * Moves the cursor to the word on the left. **/ this.moveCursorLongWordLeft = function() { @@ -704,11 +704,11 @@ var Selection = function(session) { if (column == line.length) { var l = this.doc.getLength(); - do { + do { row++; rightOfCursor = this.doc.getLine(row) } while (row < l && /^\s*$/.test(rightOfCursor)) - + if (!/^\s+/.test(rightOfCursor)) rightOfCursor = "" column = 0; @@ -729,11 +729,11 @@ var Selection = function(session) { var line = this.session.getLine(row).substring(0, column); if (column == 0) { - do { + do { row--; line = this.doc.getLine(row); } while (row > 0 && /^\s*$/.test(line)) - + column = line.length; if (!/\s+$/.test(line)) line = "" @@ -760,11 +760,11 @@ var Selection = function(session) { }; /** - * Moves the cursor to position indicated by the parameters. Negative numbers move the cursor backwards in the document. + * Moves the cursor to position indicated by the parameters. Negative numbers move the cursor backwards in the document. * @param {Number} rows The number of rows to move by * @param {Number} chars The number of characters to move by * - * + * * @related EditSession.documentToScreenPosition **/ this.moveCursorBy = function(rows, chars) { @@ -790,7 +790,7 @@ var Selection = function(session) { * Moves the selection to the position indicated by its `row` and `column`. * @param {Object} position The position to move to * - * + * **/ this.moveCursorToPosition = function(position) { this.moveCursorTo(position.row, position.column); @@ -802,7 +802,7 @@ var Selection = function(session) { * @param {Number} column The column to move to * @param {Boolean} keepDesiredColumn [If `true`, the cursor move does not respect the previous column]{: #preventUpdateBool} * - * + * **/ this.moveCursorTo = function(row, column, keepDesiredColumn) { // Ensure the row/column is not inside of a fold. @@ -826,7 +826,7 @@ var Selection = function(session) { * @param {Number} column The column to move to * @param {Boolean} keepDesiredColumn {:preventUpdateBool} * - * + * **/ this.moveCursorToScreen = function(row, column, keepDesiredColumn) { var pos = this.session.screenToDocumentPosition(row, column); @@ -861,6 +861,52 @@ var Selection = function(session) { return range; } + this.toJSON = function() { + if (this.rangeCount) { + var data = this.ranges.map(function(r) { + var r1 = r.clone(); + r1.isBackwards = r.cursor == r.start; + return r1; + }); + } else { + var data = this.getRange(); + data.isBackwards = this.isBackwards(); + } + return data; + }; + + this.fromJSON = function(data) { + if (data.start == undefined) { + if (this.toSingleRange) { + this.toSingleRange(data[0]); + for (var i = data.length; i--; ) { + var r = Range.fromPoints(data[i].start, data[i].end); + if (data.isBackwards) + r.cursor = r.start; + this.addRange(r, true); + } + return; + } else + data = data[0]; + } + if (this.toSingleRange) + this.toSingleRange(data); + this.setSelectionRange(data, data.isBackwards); + }; + + this.isEqual = function(data) { + if ((data.length || this.rangeCount) && data.length != this.rangeCount) + return false; + if (!data.length || !this.ranges) + return this.getRange().isEqual(data); + + for (var i = this.ranges.length; i--; ) { + if (!this.ranges[i].isEqual(data[i])) + return false + } + return true; + } + }).call(Selection.prototype); exports.Selection = Selection; diff --git a/lib/ace/selection_test.js b/lib/ace/selection_test.js index b69fd6dd..e1a67669 100644 --- a/lib/ace/selection_test.js +++ b/lib/ace/selection_test.js @@ -434,9 +434,7 @@ module.exports = { }, "test (keyboard navigation) when curLine is not EOL and targetLine is all whitespace new column should be current column": function() { - var session = new EditSession("function (a) {\n\ - \n\ -}"); + var session = new EditSession("function (a) {\n \n}"); var selection = session.getSelection(); selection.moveCursorTo(2, 0); @@ -445,16 +443,33 @@ module.exports = { assert.position(selection.getCursor(), 1, 0); }, - "test (keyboard navigation) when curLine is EOL and targetLine is shorter dan current column, new column should be targetLine's EOL": function() { - var session = new EditSession("function (a) {\n\ - \n\ -}"); + "test (keyboard navigation) when curLine is EOL and targetLine is shorter than current column, new column should be targetLine's EOL": function() { + var session = new EditSession("function (a) {\n \n}"); var selection = session.getSelection(); selection.moveCursorTo(0, 14); selection.moveCursorDown(); assert.position(selection.getCursor(), 1, 4); + }, + + "test fromJSON/toJSON": function() { + var session = new EditSession("function (a) {\n \n}"); + var selection = session.getSelection(); + + selection.moveCursorTo(0, 14); + selection.moveCursorDown(); + assert.position(selection.getCursor(), 1, 4); + var data = selection.toJSON(); + data = JSON.parse(JSON.stringify(data)) + selection.moveCursorDown(); + assert.position(selection.getCursor(), 2, 1); + + assert.ok(!selection.isEqual(data)); + + selection.fromJSON(data); + assert.position(selection.getCursor(), 1, 4); + assert.ok(selection.isEqual(data)); } }; From 277675525b1c09b4d4304fcca641682cfad7eb6d Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Fri, 14 Jun 2013 16:36:30 +1000 Subject: [PATCH 02/10] Almost working draft of basic undo coalescence --- lib/ace/edit_session.js | 5 ++-- lib/ace/editor.js | 8 +++++- lib/ace/keyboard/keybinding.js | 52 +++++++++++++++++++++++++++++++++- lib/ace/undomanager.js | 3 ++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 4f9ace1f..facc4e4e 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -406,10 +406,11 @@ var EditSession = function(text, mode) { if (self.$deltas.length > 0) { undoManager.execute({ action: "aceupdate", - args: [self.$deltas, self] + args: [self.$deltas, self], + merge: self.coalesceDeltas }); } - + self.coalesceDeltas = false; self.$deltas = []; } this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 62a4c37f..e1403414 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -701,8 +701,14 @@ var Editor = function(renderer, session) { if (this.getBehavioursEnabled()) { // Get a transform if the current mode wants one. var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text); - if (transform) + if (transform) { + if (text !== transform.text) { + this.session.coalesceDeltas = false; + this.coalesceNextCommand = false; + } text = transform.text; + + } } text = text.replace("\t", this.session.getTabString()); diff --git a/lib/ace/keyboard/keybinding.js b/lib/ace/keyboard/keybinding.js index 9c54a947..a02a6346 100644 --- a/lib/ace/keyboard/keybinding.js +++ b/lib/ace/keyboard/keybinding.js @@ -105,6 +105,28 @@ var KeyBinding = function(editor) { if (toExecute.command == "null") { success = true; } else { + var mergeableCommands = ["backspace", "del"]; + var previousCommand = this.$editor.previousCommand || {}; + if ( + // previous command was the same + toExecute.command.name == previousCommand.name + // the command is mergeable + && mergeableCommands.indexOf(toExecute.command.name) !== -1 + // cursor was not moved since last command + && !this.$editor.cursorMoved + // the sequence is not too long + && (new Date()) - this.$editor.sequenceStartTime < 2000 + ) + this.$editor.session.coalesceDeltas = true + else + if (mergeableCommands.indexOf(toExecute.command.name) !== -1) + this.$editor.sequenceStartTime = new Date().getTime(); + + this.$editor.previousCommand = { + name: toExecute.command.name, + text: toExecute.command.text + } + this.$editor.cursorMoved = false; success = commands.exec(toExecute.command, this.$editor, toExecute.args, e); } // do not stop input events to not break repeating @@ -123,8 +145,36 @@ var KeyBinding = function(editor) { this.onTextInput = function(text) { var success = this.$callKeyboardHandlers(-1, text); - if (!success) + if (!success) { + if (this.$editor.coalesceNextCommand === undefined) { + this.$editor.coalesceNextCommand = true; + } + var previousCommand = this.$editor.previousCommand || {}; + if ( + // previous command was the same + "insertstring" == previousCommand.name + // cursor was not moved since last command + && !this.$editor.cursorMoved + // previous command allows to coalesce with + && this.$editor.coalesceNextCommand + // previous insertion was not a new line or a whitespace + // or this is a sequence of a new line or a whitespace insertions + && (!/ |\r\n|\r|\n/.test(text) || / |\r\n|\r|\n/.test(previousCommand.text)) + // the sequence is not too long + && (new Date()) - this.$editor.sequenceStartTime < 2000 + ) + this.$editor.session.coalesceDeltas = true; + else + this.$editor.sequenceStartTime = new Date().getTime(); + + this.$editor.previousCommand = { + name: "insertstring", + text: text + } + this.$editor.cursorMoved = false; + this.$editor.coalesceNextCommand = true; this.$editor.commands.exec("insertstring", this.$editor, text); + } }; }).call(KeyBinding.prototype); diff --git a/lib/ace/undomanager.js b/lib/ace/undomanager.js index 0a226d5e..12aaa01d 100644 --- a/lib/ace/undomanager.js +++ b/lib/ace/undomanager.js @@ -63,6 +63,9 @@ var UndoManager = function() { this.execute = function(options) { var deltas = options.args[0]; this.$doc = options.args[1]; + if (options.merge && this.hasUndo()){ + deltas = this.$undoStack.pop().concat(deltas); + } this.$undoStack.push(deltas); this.$redoStack = []; From ef2e2cdd1164ffd28adfac0a7511f4fb7621e034 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 01:30:22 +0400 Subject: [PATCH 03/10] move undo delta merging to the editor --- lib/ace/edit_session.js | 4 +-- lib/ace/editor.js | 63 ++++++++++++++++++++++++++++++++-- lib/ace/keyboard/keybinding.js | 52 +--------------------------- 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index facc4e4e..cc34ce6f 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -407,10 +407,10 @@ var EditSession = function(text, mode) { undoManager.execute({ action: "aceupdate", args: [self.$deltas, self], - merge: self.coalesceDeltas + merge: self.mergeUndoDeltas }); } - self.coalesceDeltas = false; + self.mergeUndoDeltas = false; self.$deltas = []; } this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index e1403414..3bdf1a08 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -88,6 +88,9 @@ var Editor = function(renderer, session) { wrap: true }); + this.$historyTracker = this.$historyTracker.bind(this); + this.commands.on("exec", this.$historyTracker); + this.setSession(session || new EditSession("")); config.resetOptions(this); config._emit("editor", this); @@ -96,6 +99,62 @@ var Editor = function(renderer, session) { (function(){ oop.implement(this, EventEmitter); + + this.$historyTracker = function(e) { + if (e.command.name == "insertstring") { + var text = e.args; + if (this.mergeNextCommand === undefined) { + this.mergeNextCommand = true; + } + var previousCommand = this.previousCommand || {}; + if ( + // previous command was the same + "insertstring" == previousCommand.name + // cursor was not moved since last command + && !this.cursorMoved + // previous command allows to coalesce with + && this.mergeNextCommand + // previous insertion was not a new line or a whitespace + // or this is a sequence of a new line or a whitespace insertions + && (!/ |\r\n|\r|\n/.test(text) || / |\r\n|\r|\n/.test(previousCommand.text)) + // the sequence is not too long + && (new Date()) - this.sequenceStartTime < 2000 + ) + this.session.mergeUndoDeltas = true; + else + this.sequenceStartTime = new Date().getTime(); + + this.previousCommand = { + name: "insertstring", + text: text + } + this.cursorMoved = false; + this.mergeNextCommand = true; + } else { + var mergeableCommands = ["backspace", "del"]; + var previousCommand = this.previousCommand || {}; + if ( + // previous command was the same + e.command.name == previousCommand.name + // the command is mergeable + && mergeableCommands.indexOf(e.command.name) !== -1 + // cursor was not moved since last command + && !this.cursorMoved + // the sequence is not too long + && (new Date()) - this.sequenceStartTime < 2000 + ) + this.session.mergeUndoDeltas = true + else + if (mergeableCommands.indexOf(e.command.name) !== -1) + this.sequenceStartTime = new Date().getTime(); + + this.previousCommand = { + name: e.command.name, + text: e.command.text + } + this.cursorMoved = false; + } + }; /** * Sets a new key handler, such as "vim" or "windows". @@ -703,8 +762,8 @@ var Editor = function(renderer, session) { var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text); if (transform) { if (text !== transform.text) { - this.session.coalesceDeltas = false; - this.coalesceNextCommand = false; + this.session.mergeUndoDeltas = false; + this.$mergeNextCommand = false; } text = transform.text; diff --git a/lib/ace/keyboard/keybinding.js b/lib/ace/keyboard/keybinding.js index a02a6346..9c54a947 100644 --- a/lib/ace/keyboard/keybinding.js +++ b/lib/ace/keyboard/keybinding.js @@ -105,28 +105,6 @@ var KeyBinding = function(editor) { if (toExecute.command == "null") { success = true; } else { - var mergeableCommands = ["backspace", "del"]; - var previousCommand = this.$editor.previousCommand || {}; - if ( - // previous command was the same - toExecute.command.name == previousCommand.name - // the command is mergeable - && mergeableCommands.indexOf(toExecute.command.name) !== -1 - // cursor was not moved since last command - && !this.$editor.cursorMoved - // the sequence is not too long - && (new Date()) - this.$editor.sequenceStartTime < 2000 - ) - this.$editor.session.coalesceDeltas = true - else - if (mergeableCommands.indexOf(toExecute.command.name) !== -1) - this.$editor.sequenceStartTime = new Date().getTime(); - - this.$editor.previousCommand = { - name: toExecute.command.name, - text: toExecute.command.text - } - this.$editor.cursorMoved = false; success = commands.exec(toExecute.command, this.$editor, toExecute.args, e); } // do not stop input events to not break repeating @@ -145,36 +123,8 @@ var KeyBinding = function(editor) { this.onTextInput = function(text) { var success = this.$callKeyboardHandlers(-1, text); - if (!success) { - if (this.$editor.coalesceNextCommand === undefined) { - this.$editor.coalesceNextCommand = true; - } - var previousCommand = this.$editor.previousCommand || {}; - if ( - // previous command was the same - "insertstring" == previousCommand.name - // cursor was not moved since last command - && !this.$editor.cursorMoved - // previous command allows to coalesce with - && this.$editor.coalesceNextCommand - // previous insertion was not a new line or a whitespace - // or this is a sequence of a new line or a whitespace insertions - && (!/ |\r\n|\r|\n/.test(text) || / |\r\n|\r|\n/.test(previousCommand.text)) - // the sequence is not too long - && (new Date()) - this.$editor.sequenceStartTime < 2000 - ) - this.$editor.session.coalesceDeltas = true; - else - this.$editor.sequenceStartTime = new Date().getTime(); - - this.$editor.previousCommand = { - name: "insertstring", - text: text - } - this.$editor.cursorMoved = false; - this.$editor.coalesceNextCommand = true; + if (!success) this.$editor.commands.exec("insertstring", this.$editor, text); - } }; }).call(KeyBinding.prototype); From b18b86b6c90e0afd703253f707d468434dfba60e Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 02:05:27 +0400 Subject: [PATCH 04/10] make undo coalescence configurable off by default --- lib/ace/editor.js | 79 +++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 3bdf1a08..97e78d36 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -50,8 +50,6 @@ var defaultCommands = require("./commands/default_commands").commands; var config = require("./config"); /** - * - * * The main entry point into the Ace functionality. * * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen. @@ -101,59 +99,40 @@ var Editor = function(renderer, session) { oop.implement(this, EventEmitter); this.$historyTracker = function(e) { + if (!this.$mergeUndoDeltas) + return; + var previous = this.previousCommand || {command:{}}; + var mergeableCommands = ["backspace", "del", "insertstring"]; if (e.command.name == "insertstring") { var text = e.args; - if (this.mergeNextCommand === undefined) { + if (this.mergeNextCommand === undefined) this.mergeNextCommand = true; - } - var previousCommand = this.previousCommand || {}; - if ( - // previous command was the same - "insertstring" == previousCommand.name - // cursor was not moved since last command - && !this.cursorMoved - // previous command allows to coalesce with - && this.mergeNextCommand - // previous insertion was not a new line or a whitespace - // or this is a sequence of a new line or a whitespace insertions - && (!/ |\r\n|\r|\n/.test(text) || / |\r\n|\r|\n/.test(previousCommand.text)) - // the sequence is not too long - && (new Date()) - this.sequenceStartTime < 2000 - ) - this.session.mergeUndoDeltas = true; - else - this.sequenceStartTime = new Date().getTime(); - this.previousCommand = { - name: "insertstring", - text: text - } - this.cursorMoved = false; + var shouldMerge = e.command.name == previous.command.name // previous command was the same + && !this.cursorMoved // cursor was not moved since last command + && this.mergeNextCommand // previous command allows to coalesce with + && (!/\s/.test(text) || /\s/.test(previous.args)) // previous insertion was of same type + this.mergeNextCommand = true; } else { - var mergeableCommands = ["backspace", "del"]; - var previousCommand = this.previousCommand || {}; - if ( - // previous command was the same - e.command.name == previousCommand.name - // the command is mergeable - && mergeableCommands.indexOf(e.command.name) !== -1 - // cursor was not moved since last command - && !this.cursorMoved - // the sequence is not too long - && (new Date()) - this.sequenceStartTime < 2000 - ) - this.session.mergeUndoDeltas = true - else - if (mergeableCommands.indexOf(e.command.name) !== -1) - this.sequenceStartTime = new Date().getTime(); - - this.previousCommand = { - name: e.command.name, - text: e.command.text - } - this.cursorMoved = false; + var shouldMerge = e.command.name == previous.command.name // previous command was the same + && mergeableCommands.indexOf(e.command.name) !== -1// the command is mergeable + && !this.cursorMoved // cursor was not moved since last command } + + if ( + this.$mergeUndoDeltas != "always" + && Date.now() - this.sequenceStartTime < 2000 + ) { + shouldMerge = false; // the sequence is too long + } + + if (shouldMerge) + this.session.mergeUndoDeltas = true; + else if (mergeableCommands.indexOf(e.command.name) !== -1) + this.sequenceStartTime = Date.now(); + this.cursorMoved = false; + this.previousCommand = e; }; /** @@ -2308,6 +2287,10 @@ config.defineOptions(Editor.prototype, "editor", { values: ["ace", "slim", "smooth", "wide"], initialValue: "ace" }, + mergeUndoDeltas: { + values: [false, true, "always"], + initialValue: false + }, behavioursEnabled: {initialValue: true}, wrapBehavioursEnabled: {initialValue: true}, From ec8b42c5e060b191070f9b7b03eb1ca541849401 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 16:30:09 +0400 Subject: [PATCH 05/10] add start/endOperation --- lib/ace/editor.js | 62 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 97e78d36..6771c0dd 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -88,7 +88,9 @@ var Editor = function(renderer, session) { this.$historyTracker = this.$historyTracker.bind(this); this.commands.on("exec", this.$historyTracker); - + + this.$initOperationListeners(); + this.setSession(session || new EditSession("")); config.resetOptions(this); config._emit("editor", this); @@ -97,7 +99,63 @@ var Editor = function(renderer, session) { (function(){ oop.implement(this, EventEmitter); - + + this.$initOperationListeners = function() { + function last(a) {return a[a.length - 1]}; + + this.selections = []; + this.commands.on("exec", function(e) { + this.startOperation(e); + + }.bind(this), true); + + this.commands.on("afterExec", function(e) { + this.endOperation(e); + + }.bind(this), true); + + this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this)); + + this.on("change", function() { + this.curOp || this.startOperation(); + this.curOp.docChanged = true; + }.bind(this), true); + + this.on("changeSelection", function() { + this.curOp || this.startOperation(); + this.curOp.selectionChanged = true; + }.bind(this), true); + } + + this.curOp = null; + this.prevOp = {}; + this.startOperation = function(commadEvent) { + if (this.curOp) { + if (!commadEvent || this.curOp.command) + return; + this.prevOp = this.curOp; + } + if (!commadEvent) { + this.previousCommand = null; + commadEvent = {}; + } + + this.$opResetTimer.schedule(); + this.curOp = { + command: commadEvent.command || {}, + args: commadEvent.args + }; + + this.selections.push(this.selection.toJSON()); + }; + + this.endOperation = function() { + if (this.curOp) { + this.prevOp = this.curOp; + this.curOp = null; + } + }; + this.$historyTracker = function(e) { if (!this.$mergeUndoDeltas) return; From 52e0b2d16312ecc28a1814f3d82ecad675bb2fdb Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 16:35:22 +0400 Subject: [PATCH 06/10] pressing ctrl+home second time should restore selection --- lib/ace/commands/default_commands.js | 12 ++++++++---- lib/ace/editor.js | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index 9d3bc401..24e03a90 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -118,13 +118,15 @@ exports.commands = [{ bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"), exec: function(editor) { editor.getSelection().selectFileStart(); }, multiSelectAction: "forEach", - readOnly: true + readOnly: true, + group: "fileJump" }, { name: "gotostart", bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"), exec: function(editor) { editor.navigateFileStart(); }, multiSelectAction: "forEach", - readOnly: true + readOnly: true, + group: "fileJump" }, { name: "selectup", bindKey: bindKey("Shift-Up", "Shift-Up"), @@ -142,13 +144,15 @@ exports.commands = [{ bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"), exec: function(editor) { editor.getSelection().selectFileEnd(); }, multiSelectAction: "forEach", - readOnly: true + readOnly: true, + group: "fileJump" }, { name: "gotoend", bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"), exec: function(editor) { editor.navigateFileEnd(); }, multiSelectAction: "forEach", - readOnly: true + readOnly: true, + group: "fileJump" }, { name: "selectdown", bindKey: bindKey("Shift-Down", "Shift-Down"), diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 6771c0dd..572425ea 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -107,11 +107,27 @@ var Editor = function(renderer, session) { this.commands.on("exec", function(e) { this.startOperation(e); + var command = e.command; + if (command.group == "fileJump") { + var prev = this.prevOp; + if (!prev || prev.command.group != "fileJump") { + this.lastFileJumpPos = last(this.selections) + } + } else { + this.lastFileJumpPos = null; + } }.bind(this), true); this.commands.on("afterExec", function(e) { - this.endOperation(e); + var command = e.command; + if (command.group == "fileJump") { + if (this.lastFileJumpPos && !this.curOp.selectionChanged) { + this.selection.fromJSON(this.lastFileJumpPos); + return + } + } + this.endOperation(e); }.bind(this), true); this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this)); From db4953c976ebd05c2fbf311a456ad200137b08f3 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 16:38:00 +0400 Subject: [PATCH 07/10] use operations for undo coalescence --- lib/ace/editor.js | 22 +++++++++++----------- lib/ace/selection.js | 2 ++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 572425ea..ff3bb3cd 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -175,38 +175,38 @@ var Editor = function(renderer, session) { this.$historyTracker = function(e) { if (!this.$mergeUndoDeltas) return; - var previous = this.previousCommand || {command:{}}; + + + var prev = this.prevOp; var mergeableCommands = ["backspace", "del", "insertstring"]; + // previous command was the same + var shouldMerge = prev.command && (e.command.name == prev.command.name); if (e.command.name == "insertstring") { var text = e.args; if (this.mergeNextCommand === undefined) this.mergeNextCommand = true; - var shouldMerge = e.command.name == previous.command.name // previous command was the same - && !this.cursorMoved // cursor was not moved since last command + shouldMerge = shouldMerge && this.mergeNextCommand // previous command allows to coalesce with - && (!/\s/.test(text) || /\s/.test(previous.args)) // previous insertion was of same type + && (!/\s/.test(text) || /\s/.test(prev.args)) // previous insertion was of same type this.mergeNextCommand = true; } else { - var shouldMerge = e.command.name == previous.command.name // previous command was the same + shouldMerge = shouldMerge && mergeableCommands.indexOf(e.command.name) !== -1// the command is mergeable - && !this.cursorMoved // cursor was not moved since last command } - + if ( this.$mergeUndoDeltas != "always" - && Date.now() - this.sequenceStartTime < 2000 + && Date.now() - this.sequenceStartTime > 2000 ) { shouldMerge = false; // the sequence is too long } - + if (shouldMerge) this.session.mergeUndoDeltas = true; else if (mergeableCommands.indexOf(e.command.name) !== -1) this.sequenceStartTime = Date.now(); - this.cursorMoved = false; - this.previousCommand = e; }; /** diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 68a22a5c..50a5bd0e 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -258,6 +258,8 @@ var Selection = function(session) { this.setSelectionAnchor(range.start.row, range.start.column); this.selectTo(range.end.row, range.end.column); } + if (this.getRange().isEmpty()) + this.$isEmpty = true; this.$desiredColumn = null; }; From 9197cc983cd7fafa39bc32c80c701ad6228a371a Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 16:38:27 +0400 Subject: [PATCH 08/10] enable undo coalescence by default --- lib/ace/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index ff3bb3cd..b786d5b7 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -2363,7 +2363,7 @@ config.defineOptions(Editor.prototype, "editor", { }, mergeUndoDeltas: { values: [false, true, "always"], - initialValue: false + initialValue: true }, behavioursEnabled: {initialValue: true}, wrapBehavioursEnabled: {initialValue: true}, From 0ba027c8bf046f7b00b5cd3118923a7a370032a5 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 17 Jun 2013 16:54:43 +0400 Subject: [PATCH 09/10] fix failing test --- lib/ace/selection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 50a5bd0e..4f4dfb39 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -879,7 +879,7 @@ var Selection = function(session) { this.fromJSON = function(data) { if (data.start == undefined) { - if (this.toSingleRange) { + if (this.rangeList) { this.toSingleRange(data[0]); for (var i = data.length; i--; ) { var r = Range.fromPoints(data[i].start, data[i].end); @@ -891,7 +891,7 @@ var Selection = function(session) { } else data = data[0]; } - if (this.toSingleRange) + if (this.rangeList) this.toSingleRange(data); this.setSelectionRange(data, data.isBackwards); }; From 09d0044e1ec0193c09a5680f0826cdb8efbc8488 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 11 Jul 2013 17:35:29 +0400 Subject: [PATCH 10/10] fix undo selection range after delete --- lib/ace/edit_session.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index cc34ce6f..69938b6f 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1216,7 +1216,7 @@ var EditSession = function(text, mode) { var range, point; var lastDeltaIsInsert = false; if (isInsert(delta)) { - range = delta.range.clone(); + range = Range.fromPoints(delta.range.start, delta.range.end); lastDeltaIsInsert = true; } else { range = Range.fromPoints(delta.range.start, delta.range.start); @@ -1248,6 +1248,11 @@ var EditSession = function(text, mode) { // Check if this range and the last undo range has something in common. // If true, merge the ranges. if (lastUndoRange != null) { + if (Range.comparePoints(lastUndoRange.start, range.start) == 0) { + lastUndoRange.start.column += range.end.column - range.start.column; + lastUndoRange.end.column += range.end.column - range.start.column; + } + var cmp = lastUndoRange.compareRange(range); if (cmp == 1) { range.setStart(lastUndoRange.start);