livebook/assets/js/hooks/session/cursor_history.js

133 lines
3.1 KiB
JavaScript

/**
* Allows for recording a sequence of focused cells with the focused line
* and navigate inside this stack.
*/
export default class CursorHistory {
/** @private */
entries = [];
/** @private */
index = -1;
/**
* Adds a new cell to the stack.
*
* If the stack length is greater than the stack limit,
* it will remove the oldest entries.
*/
push(cellId, line, offset) {
const entry = { cellId, line, offset };
if (this.isSameCell(cellId)) {
this.entries[this.index] = entry;
} else {
if (this.entries[this.index + 1] !== undefined) {
this.entries = this.entries.slice(0, this.index + 1);
}
this.entries.push(entry);
this.index++;
}
if (this.entries.length > 20) {
this.entries.shift();
this.index--;
}
}
/**
* Removes all matching cells with given id from the stack.
*/
removeAllFromCell(cellId) {
// We need to make sure the last entry from history
// doesn't belong to the given cell id that we need
// to remove from the entries list.
let cellIdCount = 0;
for (let i = 0; i <= this.index; i++) {
const entry = this.get(i);
if (entry.cellId === cellId) cellIdCount++;
}
this.entries = this.entries.filter((entry) => entry.cellId !== cellId);
this.index = this.index - cellIdCount;
if (this.index === -1 && this.entries.length > 0) this.index = 0;
}
/**
* Checks if the current stack is available to navigate back.
*/
canGoBack() {
return this.canGetFromHistory(-1);
}
/**
* Navigates back in the current stack.
*
* If the navigation succeeds, it will return the entry from current index.
* Otherwise, returns null;
*/
goBack() {
return this.getFromHistory(-1);
}
/**
* Checks if the current stack is available to navigate forward.
*/
canGoForward() {
return this.canGetFromHistory(1);
}
/**
* Navigates forward in the current stack.
*
* If the navigation succeeds, it will return the entry from current index.
* Otherwise, returns null;
*/
goForward() {
return this.getFromHistory(1);
}
/**
* Gets the entry in the current stack.
*
* If the stack have at least one entry, it will return the entry from current index.
* Otherwise, returns null;
*/
getCurrent() {
return this.get(this.index);
}
/** @private **/
getEntries() {
return this.entries;
}
/** @private **/
get(index) {
if (this.entries.length <= 0) return null;
return this.entries[index];
}
/** @private **/
getFromHistory(direction) {
if (!this.canGetFromHistory(direction)) return null;
this.index = Math.max(0, this.index + direction);
return this.entries[this.index];
}
/** @private **/
canGetFromHistory(direction) {
if (this.entries.length === 0) return false;
const index = this.index + direction;
return 0 <= index && index < this.entries.length;
}
/** @private **/
isSameCell(cellId) {
const lastEntry = this.get(this.index);
return lastEntry !== null && cellId === lastEntry.cellId;
}
}