Skip to content

Commit

Permalink
editor listening to edit-session operation events
Browse files Browse the repository at this point in the history
  • Loading branch information
sokomari committed Oct 18, 2024
1 parent 19d1e11 commit 649f83c
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 75 deletions.
10 changes: 10 additions & 0 deletions ace-internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,16 @@ export namespace Ace {
**/
"changeScrollLeft": (scrollLeft: number) => void;
"changeEditor": (e: { editor: Editor }) => void;
/**
* Emitted after operation starts.
* @param commandEvent event causing the operation
*/
"startOperation": (commandEvent) => void;
/**
* Emitted after operation finishes.
* @param e event causing the finish
*/
"endOperation": (e) => void;
}

interface EditorEvents {
Expand Down
76 changes: 53 additions & 23 deletions src/edit_session.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class EditSession {
this.$backMarkers = {};
this.$markerId = 1;
this.$undoSelect = true;
this.curOp = null;
this.prevOp = {};

/** @type {FoldLine[]} */
this.$foldData = [];
Expand All @@ -71,11 +73,9 @@ class EditSession {
this.setDocument(text);

this.selection = new Selection(this);
const onSelectionChange = () => {
this._signal("changeSelection");
};
this.selection.on("changeSelection", onSelectionChange);
this.selection.on("changeCursor", onSelectionChange);
this.$onSelectionChange = this.onSelectionChange.bind(this);
this.selection.on("changeSelection", this.$onSelectionChange);
this.selection.on("changeCursor", this.$onSelectionChange);

this.$bidiHandler = new BidiHandler(this);

Expand All @@ -89,56 +89,77 @@ class EditSession {

$initOperationListeners() {
this.on("change", () => {
this.startOperation();
if (!this.curOp) {
this.startOperation();
this.curOp.selectionBefore = this.$lastSel;
}
this.curOp.docChanged = true;
});
}, true);
this.on("changeSelection", () => {
this.startOperation();
if (!this.curOp) {
this.startOperation();
this.curOp.selectionBefore = this.$lastSel;
}
this.curOp.selectionChanged = true;
});
}, true);

// Fallback mechanism in case current operation doesn't finish more explicitly.
// Triggered, for example, when a consumer makes programmatic changes without invoking endOperation afterwards.
this.$operationResetTimer = lang.delayedCall(() => {
if (this.curOp && this.curOp.command && this.curOp.command.name === "mouse") {
// When current operation is mousedown, we wait for the mouseup to end the operation.
// So during a user selection, we would only end the operation when the final selection is known.
return;
}
this.endOperation();
});
this.$operationResetTimer = lang.delayedCall(this.endOperation.bind(this, true));
}

/**
* Start an Ace operation, which will then batch all the subsequent changes (to either content or selection) under a single atomic operation.
* @param {{command?: {name?: string}}|undefined} [commandEvent] Optional name for the operation
* @param {{command?: {name?: string}, args?: any}|undefined} [commandEvent] Optional name for the operation
*/
startOperation(commandEvent) {
if (this.curOp) {
if (!commandEvent || this.curOp.command) {
return;
}
this.prevOp = this.curOp;
}
if (!commandEvent) {
commandEvent = {};
}

this.$operationResetTimer.schedule();
this.curOp = {
command: commandEvent.command || {}
command: commandEvent.command || {},
args: commandEvent.args,

Check failure on line 129 in src/edit_session.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Unexpected trailing comma
};
this.curOp.selectionBefore = this.selection.toJSON();
this._signal("startOperation", commandEvent);
}

/**
* End the current Ace operation.
* End current Ace operation.
* Emits "beforeEndOperation" event just before clearing everything, where the current operation can be accessed through `curOp` property.
* @param {any} e
*/
endOperation() {
this.$operationResetTimer.cancel();
endOperation(e) {
if (this.curOp) {
if (e && e.returnValue === false) {
this.curOp = null;
this._signal("endOperation", e);
return;
}
if (e == true && this.curOp.command && this.curOp.command.name == "mouse") {
// When current operation is mousedown, we wait for the mouseup to end the operation.
// So during a user selection, we would only end the operation when the final selection is known.
return;
}

const currentSelection = this.selection.toJSON();
this.curOp.selectionAfter = currentSelection;
this.$lastSel = this.selection.toJSON();
this.getUndoManager().addSelection(currentSelection);

this._signal("beforeEndOperation");
this.prevOp = this.curOp;
this.curOp = null;
this._signal("endOperation", e);
}
this.curOp = null;
}

/**
Expand Down Expand Up @@ -248,6 +269,10 @@ class EditSession {
this._signal("change", delta);
}

onSelectionChange() {
this._signal("changeSelection");
}

/**
* Sets the session text.
* @param {String} text The new text to place
Expand Down Expand Up @@ -2448,11 +2473,16 @@ class EditSession {
this.bgTokenizer.cleanup();
this.destroyed = true;
}
this.endOperation();
this.$stopWorker();
this.removeAllListeners();
if (this.doc) {
this.doc.off("change", this.$onChange);
}
if (this.selection) {
this.selection.off("changeCursor", this.$onSelectionChange);
this.selection.off("changeSelection", this.$onSelectionChange);
}
this.selection.detach();
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/edit_session_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1164,26 +1164,34 @@ module.exports = {
session.insert({row: 0, column : 0}, "both");
session.endOperation();
assert.equal(beforeEndOperationSpy.length, 1);
assert.deepEqual(beforeEndOperationSpy[0], {command: {name: "inserting-both"}, docChanged: true, selectionChanged: true});
assert.equal(beforeEndOperationSpy[0].command.name, "inserting-both");
assert.equal(beforeEndOperationSpy[0].docChanged, true);
assert.equal(beforeEndOperationSpy[0].selectionChanged, true);

// When only start operation is invoked
session.startOperation({command: {name: "inserting-start"}});
session.insert({row: 0, column : 0}, "start");
setTimeout(() => {
assert.equal(beforeEndOperationSpy.length, 2);
assert.deepEqual(beforeEndOperationSpy[1], {command: {name: "inserting-start"}, docChanged: true, selectionChanged: true});
assert.equal(beforeEndOperationSpy[1].command.name, "inserting-start");
assert.equal(beforeEndOperationSpy[1].docChanged, true);
assert.equal(beforeEndOperationSpy[1].selectionChanged, true);

// When only end operation is invoked
session.insert({row: 0, column : 0}, "end");
session.endOperation();
assert.equal(beforeEndOperationSpy.length, 3);
assert.deepEqual(beforeEndOperationSpy[2], {command: {}, docChanged: true, selectionChanged: true});
assert.deepEqual(beforeEndOperationSpy[2].command, {});
assert.equal(beforeEndOperationSpy[2].docChanged, true);
assert.equal(beforeEndOperationSpy[2].selectionChanged, true);

// When nothing is invoked
session.insert({row: 0, column : 0}, "none");
setTimeout(() => {
assert.equal(beforeEndOperationSpy.length, 4);
assert.deepEqual(beforeEndOperationSpy[3], {command: {}, docChanged: true, selectionChanged: true});
assert.deepEqual(beforeEndOperationSpy[3].command, {});
assert.equal(beforeEndOperationSpy[3].docChanged, true);
assert.equal(beforeEndOperationSpy[3].selectionChanged, true);

done();
}, 10);
Expand Down
81 changes: 33 additions & 48 deletions src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,63 +98,45 @@ class Editor {
$initOperationListeners() {
this.commands.on("exec", this.startOperation.bind(this), true);
this.commands.on("afterExec", this.endOperation.bind(this), true);

this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this, true));

// todo: add before change events?
this.on("change", function() {
if (!this.curOp) {
this.startOperation();
this.curOp.selectionBefore = this.$lastSel;
}
this.curOp.docChanged = true;
}.bind(this), true);

this.on("changeSelection", function() {
if (!this.curOp) {
this.startOperation();
this.curOp.selectionBefore = this.$lastSel;
}
this.curOp.selectionChanged = true;
}.bind(this), true);
}

startOperation(commandEvent) {
if (this.curOp) {
if (!commandEvent || this.curOp.command)
return;
this.prevOp = this.curOp;
this.session.startOperation(commandEvent);
}

/**
* @arg e
*/
endOperation(e) {
this.session.endOperation(e);
}

onStartOperation(commandEvent) {
this.curOp = this.session.curOp = {
...this.session.curOp,
// scrollTop is kept inside of EditSession only for backwards compatibility reasons
scrollTop: this.renderer.scrollTop
}
this.prevOp = this.session.prevOp;

if (!commandEvent) {
this.previousCommand = null;
commandEvent = {};
}

this.session.startOperation(commandEvent);
this.$opResetTimer.schedule();
/**
* @type {{[key: string]: any;}}
*/
this.curOp = this.session.curOp = {
command: commandEvent.command || (this.session.curOp && this.session.curOp.command) || {},
args: commandEvent.args,
scrollTop: this.renderer.scrollTop
};
this.curOp.selectionBefore = this.selection.toJSON();
}

/**
* @arg e
*/
endOperation(e) {
if (this.curOp && this.session) {
if (e && e.returnValue === false || !this.session)
return (this.curOp = this.session.curOp = null);
if (e == true && this.curOp.command && this.curOp.command.name == "mouse")
onEndOperation(e) {
if (this.curOp) {
if (e && e.returnValue === false) {
this.curOp = null;
return;
this.session.endOperation();
}

this._signal("beforeEndOperation");
if (!this.curOp) return;

var command = this.curOp.command;
var scrollIntoView = command && command.scrollIntoView;
if (scrollIntoView) {
Expand Down Expand Up @@ -182,12 +164,8 @@ class Editor {
if (scrollIntoView == "animate")
this.renderer.animateScrolling(this.curOp.scrollTop);
}
var sel = this.selection.toJSON();
this.curOp.selectionAfter = sel;
this.$lastSel = this.selection.toJSON();

// console.log(this.$lastSel+" endOP")
this.session.getUndoManager().addSelection(sel);

this.$lastSel = this.session.selection.toJSON();
this.prevOp = this.curOp;
this.curOp = null;
}
Expand Down Expand Up @@ -292,6 +270,8 @@ class Editor {
this.session.off("changeOverwrite", this.$onCursorChange);
this.session.off("changeScrollTop", this.$onScrollTopChange);
this.session.off("changeScrollLeft", this.$onScrollLeftChange);
this.session.off("startOperation", this.$onStartOperation);
this.session.off("endOperation", this.$onEndOperation);

var selection = this.session.getSelection();
selection.off("changeCursor", this.$onCursorChange);
Expand Down Expand Up @@ -348,6 +328,11 @@ class Editor {

this.$onSelectionChange = this.onSelectionChange.bind(this);
this.selection.on("changeSelection", this.$onSelectionChange);

this.$onStartOperation = this.onStartOperation.bind(this);
this.session.on("startOperation", this.$onStartOperation);
this.$onEndOperation = this.onEndOperation.bind(this);
this.session.on("endOperation", this.$onEndOperation);

this.onChangeMode();

Expand Down

0 comments on commit 649f83c

Please sign in to comment.