UNPKG

@jupyterlab/notebook

Version:
290 lines 9.84 kB
// 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