commit
aded11265f
7 changed files with 288 additions and 66 deletions
|
|
@ -118,13 +118,15 @@ exports.commands = [{
|
||||||
bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"),
|
bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"),
|
||||||
exec: function(editor) { editor.getSelection().selectFileStart(); },
|
exec: function(editor) { editor.getSelection().selectFileStart(); },
|
||||||
multiSelectAction: "forEach",
|
multiSelectAction: "forEach",
|
||||||
readOnly: true
|
readOnly: true,
|
||||||
|
group: "fileJump"
|
||||||
}, {
|
}, {
|
||||||
name: "gotostart",
|
name: "gotostart",
|
||||||
bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"),
|
bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"),
|
||||||
exec: function(editor) { editor.navigateFileStart(); },
|
exec: function(editor) { editor.navigateFileStart(); },
|
||||||
multiSelectAction: "forEach",
|
multiSelectAction: "forEach",
|
||||||
readOnly: true
|
readOnly: true,
|
||||||
|
group: "fileJump"
|
||||||
}, {
|
}, {
|
||||||
name: "selectup",
|
name: "selectup",
|
||||||
bindKey: bindKey("Shift-Up", "Shift-Up"),
|
bindKey: bindKey("Shift-Up", "Shift-Up"),
|
||||||
|
|
@ -142,13 +144,15 @@ exports.commands = [{
|
||||||
bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"),
|
bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"),
|
||||||
exec: function(editor) { editor.getSelection().selectFileEnd(); },
|
exec: function(editor) { editor.getSelection().selectFileEnd(); },
|
||||||
multiSelectAction: "forEach",
|
multiSelectAction: "forEach",
|
||||||
readOnly: true
|
readOnly: true,
|
||||||
|
group: "fileJump"
|
||||||
}, {
|
}, {
|
||||||
name: "gotoend",
|
name: "gotoend",
|
||||||
bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"),
|
bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"),
|
||||||
exec: function(editor) { editor.navigateFileEnd(); },
|
exec: function(editor) { editor.navigateFileEnd(); },
|
||||||
multiSelectAction: "forEach",
|
multiSelectAction: "forEach",
|
||||||
readOnly: true
|
readOnly: true,
|
||||||
|
group: "fileJump"
|
||||||
}, {
|
}, {
|
||||||
name: "selectdown",
|
name: "selectdown",
|
||||||
bindKey: bindKey("Shift-Down", "Shift-Down"),
|
bindKey: bindKey("Shift-Down", "Shift-Down"),
|
||||||
|
|
|
||||||
|
|
@ -406,10 +406,11 @@ var EditSession = function(text, mode) {
|
||||||
if (self.$deltas.length > 0) {
|
if (self.$deltas.length > 0) {
|
||||||
undoManager.execute({
|
undoManager.execute({
|
||||||
action: "aceupdate",
|
action: "aceupdate",
|
||||||
args: [self.$deltas, self]
|
args: [self.$deltas, self],
|
||||||
|
merge: self.mergeUndoDeltas
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
self.mergeUndoDeltas = false;
|
||||||
self.$deltas = [];
|
self.$deltas = [];
|
||||||
}
|
}
|
||||||
this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager);
|
this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager);
|
||||||
|
|
@ -1220,7 +1221,7 @@ var EditSession = function(text, mode) {
|
||||||
var range, point;
|
var range, point;
|
||||||
var lastDeltaIsInsert = false;
|
var lastDeltaIsInsert = false;
|
||||||
if (isInsert(delta)) {
|
if (isInsert(delta)) {
|
||||||
range = delta.range.clone();
|
range = Range.fromPoints(delta.range.start, delta.range.end);
|
||||||
lastDeltaIsInsert = true;
|
lastDeltaIsInsert = true;
|
||||||
} else {
|
} else {
|
||||||
range = Range.fromPoints(delta.range.start, delta.range.start);
|
range = Range.fromPoints(delta.range.start, delta.range.start);
|
||||||
|
|
@ -1252,6 +1253,11 @@ var EditSession = function(text, mode) {
|
||||||
// Check if this range and the last undo range has something in common.
|
// Check if this range and the last undo range has something in common.
|
||||||
// If true, merge the ranges.
|
// If true, merge the ranges.
|
||||||
if (lastUndoRange != null) {
|
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);
|
var cmp = lastUndoRange.compareRange(range);
|
||||||
if (cmp == 1) {
|
if (cmp == 1) {
|
||||||
range.setStart(lastUndoRange.start);
|
range.setStart(lastUndoRange.start);
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,6 @@ var defaultCommands = require("./commands/default_commands").commands;
|
||||||
var config = require("./config");
|
var config = require("./config");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
*
|
|
||||||
* The main entry point into the Ace functionality.
|
* 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.
|
* The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
|
||||||
|
|
@ -88,6 +86,11 @@ var Editor = function(renderer, session) {
|
||||||
wrap: true
|
wrap: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$historyTracker = this.$historyTracker.bind(this);
|
||||||
|
this.commands.on("exec", this.$historyTracker);
|
||||||
|
|
||||||
|
this.$initOperationListeners();
|
||||||
|
|
||||||
this.setSession(session || new EditSession(""));
|
this.setSession(session || new EditSession(""));
|
||||||
config.resetOptions(this);
|
config.resetOptions(this);
|
||||||
config._emit("editor", this);
|
config._emit("editor", this);
|
||||||
|
|
@ -97,6 +100,115 @@ var Editor = function(renderer, session) {
|
||||||
|
|
||||||
oop.implement(this, EventEmitter);
|
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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
shouldMerge = shouldMerge
|
||||||
|
&& this.mergeNextCommand // previous command allows to coalesce with
|
||||||
|
&& (!/\s/.test(text) || /\s/.test(prev.args)) // previous insertion was of same type
|
||||||
|
|
||||||
|
this.mergeNextCommand = true;
|
||||||
|
} else {
|
||||||
|
shouldMerge = shouldMerge
|
||||||
|
&& mergeableCommands.indexOf(e.command.name) !== -1// the command is mergeable
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a new key handler, such as "vim" or "windows".
|
* Sets a new key handler, such as "vim" or "windows".
|
||||||
* @param {String} keyboardHandler The new key handler
|
* @param {String} keyboardHandler The new key handler
|
||||||
|
|
@ -701,8 +813,14 @@ var Editor = function(renderer, session) {
|
||||||
if (this.getBehavioursEnabled()) {
|
if (this.getBehavioursEnabled()) {
|
||||||
// Get a transform if the current mode wants one.
|
// Get a transform if the current mode wants one.
|
||||||
var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
|
var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
|
||||||
if (transform)
|
if (transform) {
|
||||||
|
if (text !== transform.text) {
|
||||||
|
this.session.mergeUndoDeltas = false;
|
||||||
|
this.$mergeNextCommand = false;
|
||||||
|
}
|
||||||
text = transform.text;
|
text = transform.text;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text == "\t")
|
if (text == "\t")
|
||||||
|
|
@ -2242,6 +2360,10 @@ config.defineOptions(Editor.prototype, "editor", {
|
||||||
values: ["ace", "slim", "smooth", "wide"],
|
values: ["ace", "slim", "smooth", "wide"],
|
||||||
initialValue: "ace"
|
initialValue: "ace"
|
||||||
},
|
},
|
||||||
|
mergeUndoDeltas: {
|
||||||
|
values: [false, true, "always"],
|
||||||
|
initialValue: true
|
||||||
|
},
|
||||||
behavioursEnabled: {initialValue: true},
|
behavioursEnabled: {initialValue: true},
|
||||||
wrapBehavioursEnabled: {initialValue: true},
|
wrapBehavioursEnabled: {initialValue: true},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,30 @@ module.exports = {
|
||||||
selection.addRange(range2);
|
selection.addRange(range2);
|
||||||
editor.execCommand('insertfoo');
|
editor.execCommand('insertfoo');
|
||||||
assert.equal('l1foo\nl2foo', editor.getValue());
|
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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,8 @@ var Selection = function(session) {
|
||||||
this.setSelectionAnchor(range.start.row, range.start.column);
|
this.setSelectionAnchor(range.start.row, range.start.column);
|
||||||
this.selectTo(range.end.row, range.end.column);
|
this.selectTo(range.end.row, range.end.column);
|
||||||
}
|
}
|
||||||
|
if (this.getRange().isEmpty())
|
||||||
|
this.$isEmpty = true;
|
||||||
this.$desiredColumn = null;
|
this.$desiredColumn = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -861,6 +863,52 @@ var Selection = function(session) {
|
||||||
return range;
|
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.rangeList) {
|
||||||
|
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.rangeList)
|
||||||
|
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);
|
}).call(Selection.prototype);
|
||||||
|
|
||||||
exports.Selection = Selection;
|
exports.Selection = Selection;
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
"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\
|
var session = new EditSession("function (a) {\n \n}");
|
||||||
\n\
|
|
||||||
}");
|
|
||||||
var selection = session.getSelection();
|
var selection = session.getSelection();
|
||||||
|
|
||||||
selection.moveCursorTo(2, 0);
|
selection.moveCursorTo(2, 0);
|
||||||
|
|
@ -445,16 +443,33 @@ module.exports = {
|
||||||
assert.position(selection.getCursor(), 1, 0);
|
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() {
|
"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\
|
var session = new EditSession("function (a) {\n \n}");
|
||||||
\n\
|
|
||||||
}");
|
|
||||||
var selection = session.getSelection();
|
var selection = session.getSelection();
|
||||||
|
|
||||||
selection.moveCursorTo(0, 14);
|
selection.moveCursorTo(0, 14);
|
||||||
selection.moveCursorDown();
|
selection.moveCursorDown();
|
||||||
|
|
||||||
assert.position(selection.getCursor(), 1, 4);
|
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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ var UndoManager = function() {
|
||||||
this.execute = function(options) {
|
this.execute = function(options) {
|
||||||
var deltas = options.args[0];
|
var deltas = options.args[0];
|
||||||
this.$doc = options.args[1];
|
this.$doc = options.args[1];
|
||||||
|
if (options.merge && this.hasUndo()){
|
||||||
|
deltas = this.$undoStack.pop().concat(deltas);
|
||||||
|
}
|
||||||
this.$undoStack.push(deltas);
|
this.$undoStack.push(deltas);
|
||||||
this.$redoStack = [];
|
this.$redoStack = [];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue