@jupyterlab/notebook
Version:
JupyterLab - Notebook
290 lines • 9.84 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { nullTranslator } from '@jupyterlab/translation';
import { Signal } from '@lumino/signaling';
/**
* A console history manager object.
*/
export class NotebookHistory {
/**
* Construct a new console history object.
*/
constructor(options) {
/**
* The number of history items to increase a batch size by per subsequent request.
*/
this._requestBatchSize = 10;
this._cursor = 0;
this._hasSession = false;
this._history = [];
this._placeholder = '';
this._kernelSession = '';
this._setByHistory = false;
this._isDisposed = false;
this._editor = null;
this._filtered = [];
this._kernel = null;
this._sessionContext = options.sessionContext;
this._trans = (options.translator || nullTranslator).load('jupyterlab');
void this._handleKernel().then(() => {
this._sessionContext.kernelChanged.connect(this._handleKernel, this);
});
this._toRequest = this._requestBatchSize;
}
/**
* The current editor used by the history manager.
*/
get editor() {
return this._editor;
}
set editor(value) {
if (this._editor === value) {
return;
}
const prev = this._editor;
if (prev) {
prev.model.sharedModel.changed.disconnect(this.onTextChange, this);
}
this._editor = value;
if (value) {
value.model.sharedModel.changed.connect(this.onTextChange, this);
}
}
/**
* The placeholder text that a history session began with.
*/
get placeholder() {
return this._placeholder;
}
/**
* Kernel session number for filtering
*/
get kernelSession() {
return this._kernelSession;
}
/**
* Get whether the notebook history manager is disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Dispose of the resources held by the notebook history manager.
*/
dispose() {
this._isDisposed = true;
this._history.length = 0;
Signal.clearData(this);
}
/**
* Set placeholder and editor. Start session if one is not already started.
*
* @param activeCell - The currently selected Cell in the notebook.
*/
async checkSession(activeCell) {
var _a;
if (!this._hasSession) {
await this._retrieveHistory();
this._hasSession = true;
this.editor = activeCell.editor;
this._placeholder = ((_a = this._editor) === null || _a === void 0 ? void 0 : _a.model.sharedModel.getSource()) || '';
// Filter the history with the placeholder string.
this.setFilter(this._placeholder);
this._cursor = this._filtered.length - 1;
}
}
/**
* Get the previous item in the notebook history.
*
* @param activeCell - The currently selected Cell in the notebook.
*
* @returns A Promise resolving to the historical cell content text.
*/
async back(activeCell) {
await this.checkSession(activeCell);
--this._cursor;
if (this._cursor < 0) {
await this.fetchBatch();
}
this._cursor = Math.max(0, this._cursor);
const content = this._filtered[this._cursor];
// This shouldn't ever be undefined as `setFilter` will always be run first
return content;
}
/**
* Get the next item in the notebook history.
*
* @param activeCell - The currently selected Cell in the notebook.
*
* @returns A Promise resolving to the historical cell content text.
*/
async forward(activeCell) {
await this.checkSession(activeCell);
++this._cursor;
this._cursor = Math.min(this._filtered.length - 1, this._cursor);
const content = this._filtered[this._cursor];
// This shouldn't ever be undefined as `setFilter` will always be run first
return content;
}
/**
* Update the editor of the cell with provided text content.
*
* @param activeCell - The currently selected Cell in the notebook.
* @param content - the result from back or forward
*/
updateEditor(activeCell, content) {
var _a, _b;
if (activeCell) {
const model = (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.model;
const source = model === null || model === void 0 ? void 0 : model.sharedModel.getSource();
if (this.isDisposed || !content) {
return;
}
if (source === content) {
return;
}
this._setByHistory = true;
model === null || model === void 0 ? void 0 : model.sharedModel.setSource(content);
let columnPos = 0;
columnPos = content.indexOf('\n');
if (columnPos < 0) {
columnPos = content.length;
}
(_b = activeCell.editor) === null || _b === void 0 ? void 0 : _b.setCursorPosition({ line: 0, column: columnPos });
}
}
/**
* Reset the history navigation state, i.e., start a new history session.
*/
reset() {
this._hasSession = false;
this._placeholder = '';
this._toRequest = this._requestBatchSize;
}
/**
* Fetches a subsequent batch of history. Updates the filtered history and cursor to correct place in history,
* accounting for potentially new history items above it.
*/
async fetchBatch() {
this._toRequest += this._requestBatchSize;
let oldFilteredReversed = this._filtered.slice().reverse();
let oldHistory = this._history.slice();
await this._retrieveHistory().then(() => {
this.setFilter(this._placeholder);
let cursorOffset = 0;
let filteredReversed = this._filtered.slice().reverse();
for (let i = 0; i < oldFilteredReversed.length; i++) {
let item = oldFilteredReversed[i];
for (let ij = i + cursorOffset; ij < filteredReversed.length; ij++) {
if (item === filteredReversed[ij]) {
break;
}
else {
cursorOffset += 1;
}
}
}
this._cursor =
this._filtered.length - (oldFilteredReversed.length + 1) - cursorOffset;
});
if (this._cursor < 0) {
if (this._history.length > oldHistory.length) {
await this.fetchBatch();
}
}
}
/**
* Populate the history collection on history reply from a kernel.
*
* @param value The kernel message history reply.
*
* #### Notes
* History entries have the shape:
* [session: number, line: number, input: string]
* Contiguous duplicates are stripped out of the API response.
*/
onHistory(value, cell) {
this._history.length = 0;
let last = ['', '', ''];
let current = ['', '', ''];
let kernelSession = '';
if (value.content.status === 'ok') {
for (let i = 0; i < value.content.history.length; i++) {
current = value.content.history[i];
if (current !== last) {
kernelSession = value.content.history[i][0];
this._history.push((last = current));
}
}
// set the kernel session for filtering
if (!this.kernelSession) {
if (current[2] == (cell === null || cell === void 0 ? void 0 : cell.model.sharedModel.getSource())) {
this._kernelSession = kernelSession;
}
}
}
}
/**
* Handle a text change signal from the editor.
*/
onTextChange() {
if (this._setByHistory) {
this._setByHistory = false;
return;
}
this.reset();
}
/**
* Handle the current kernel changing.
*/
async _handleKernel() {
var _a;
this._kernel = (_a = this._sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel;
if (!this._kernel) {
this._history.length = 0;
return;
}
await this._retrieveHistory().catch();
return;
}
/**
* retrieve the history from the kernel
*
* @param cell - The string to use when filtering the data.
*/
async _retrieveHistory(cell) {
var _a;
return await ((_a = this._kernel) === null || _a === void 0 ? void 0 : _a.requestHistory(request(this._toRequest)).then(v => {
this.onHistory(v, cell);
}).catch(() => {
console.warn(this._trans.__('History was unable to be retrieved'));
}));
}
/**
* Set the filter data.
*
* @param filterStr - The string to use when filtering the data.
*/
setFilter(filterStr = '') {
// Apply the new filter and remove contiguous duplicates.
this._filtered.length = 0;
let last = '';
let current = '';
for (let i = 0; i < this._history.length; i++) {
current = this._history[i][2];
if (current !== last && filterStr !== current) {
this._filtered.push((last = current));
}
}
this._filtered.push(filterStr);
}
}
function request(n) {
return {
output: false,
raw: true,
hist_access_type: 'tail',
n: n
};
}
//# sourceMappingURL=history.js.map