UNPKG

9.84 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { nullTranslator } from '@jupyterlab/translation';
4import { Signal } from '@lumino/signaling';
5/**
6 * A console history manager object.
7 */
8export class NotebookHistory {
9 /**
10 * Construct a new console history object.
11 */
12 constructor(options) {
13 /**
14 * The number of history items to increase a batch size by per subsequent request.
15 */
16 this._requestBatchSize = 10;
17 this._cursor = 0;
18 this._hasSession = false;
19 this._history = [];
20 this._placeholder = '';
21 this._kernelSession = '';
22 this._setByHistory = false;
23 this._isDisposed = false;
24 this._editor = null;
25 this._filtered = [];
26 this._kernel = null;
27 this._sessionContext = options.sessionContext;
28 this._trans = (options.translator || nullTranslator).load('jupyterlab');
29 void this._handleKernel().then(() => {
30 this._sessionContext.kernelChanged.connect(this._handleKernel, this);
31 });
32 this._toRequest = this._requestBatchSize;
33 }
34 /**
35 * The current editor used by the history manager.
36 */
37 get editor() {
38 return this._editor;
39 }
40 set editor(value) {
41 if (this._editor === value) {
42 return;
43 }
44 const prev = this._editor;
45 if (prev) {
46 prev.model.sharedModel.changed.disconnect(this.onTextChange, this);
47 }
48 this._editor = value;
49 if (value) {
50 value.model.sharedModel.changed.connect(this.onTextChange, this);
51 }
52 }
53 /**
54 * The placeholder text that a history session began with.
55 */
56 get placeholder() {
57 return this._placeholder;
58 }
59 /**
60 * Kernel session number for filtering
61 */
62 get kernelSession() {
63 return this._kernelSession;
64 }
65 /**
66 * Get whether the notebook history manager is disposed.
67 */
68 get isDisposed() {
69 return this._isDisposed;
70 }
71 /**
72 * Dispose of the resources held by the notebook history manager.
73 */
74 dispose() {
75 this._isDisposed = true;
76 this._history.length = 0;
77 Signal.clearData(this);
78 }
79 /**
80 * Set placeholder and editor. Start session if one is not already started.
81 *
82 * @param activeCell - The currently selected Cell in the notebook.
83 */
84 async checkSession(activeCell) {
85 var _a;
86 if (!this._hasSession) {
87 await this._retrieveHistory();
88 this._hasSession = true;
89 this.editor = activeCell.editor;
90 this._placeholder = ((_a = this._editor) === null || _a === void 0 ? void 0 : _a.model.sharedModel.getSource()) || '';
91 // Filter the history with the placeholder string.
92 this.setFilter(this._placeholder);
93 this._cursor = this._filtered.length - 1;
94 }
95 }
96 /**
97 * Get the previous item in the notebook history.
98 *
99 * @param activeCell - The currently selected Cell in the notebook.
100 *
101 * @returns A Promise resolving to the historical cell content text.
102 */
103 async back(activeCell) {
104 await this.checkSession(activeCell);
105 --this._cursor;
106 if (this._cursor < 0) {
107 await this.fetchBatch();
108 }
109 this._cursor = Math.max(0, this._cursor);
110 const content = this._filtered[this._cursor];
111 // This shouldn't ever be undefined as `setFilter` will always be run first
112 return content;
113 }
114 /**
115 * Get the next item in the notebook history.
116 *
117 * @param activeCell - The currently selected Cell in the notebook.
118 *
119 * @returns A Promise resolving to the historical cell content text.
120 */
121 async forward(activeCell) {
122 await this.checkSession(activeCell);
123 ++this._cursor;
124 this._cursor = Math.min(this._filtered.length - 1, this._cursor);
125 const content = this._filtered[this._cursor];
126 // This shouldn't ever be undefined as `setFilter` will always be run first
127 return content;
128 }
129 /**
130 * Update the editor of the cell with provided text content.
131 *
132 * @param activeCell - The currently selected Cell in the notebook.
133 * @param content - the result from back or forward
134 */
135 updateEditor(activeCell, content) {
136 var _a, _b;
137 if (activeCell) {
138 const model = (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.model;
139 const source = model === null || model === void 0 ? void 0 : model.sharedModel.getSource();
140 if (this.isDisposed || !content) {
141 return;
142 }
143 if (source === content) {
144 return;
145 }
146 this._setByHistory = true;
147 model === null || model === void 0 ? void 0 : model.sharedModel.setSource(content);
148 let columnPos = 0;
149 columnPos = content.indexOf('\n');
150 if (columnPos < 0) {
151 columnPos = content.length;
152 }
153 (_b = activeCell.editor) === null || _b === void 0 ? void 0 : _b.setCursorPosition({ line: 0, column: columnPos });
154 }
155 }
156 /**
157 * Reset the history navigation state, i.e., start a new history session.
158 */
159 reset() {
160 this._hasSession = false;
161 this._placeholder = '';
162 this._toRequest = this._requestBatchSize;
163 }
164 /**
165 * Fetches a subsequent batch of history. Updates the filtered history and cursor to correct place in history,
166 * accounting for potentially new history items above it.
167 */
168 async fetchBatch() {
169 this._toRequest += this._requestBatchSize;
170 let oldFilteredReversed = this._filtered.slice().reverse();
171 let oldHistory = this._history.slice();
172 await this._retrieveHistory().then(() => {
173 this.setFilter(this._placeholder);
174 let cursorOffset = 0;
175 let filteredReversed = this._filtered.slice().reverse();
176 for (let i = 0; i < oldFilteredReversed.length; i++) {
177 let item = oldFilteredReversed[i];
178 for (let ij = i + cursorOffset; ij < filteredReversed.length; ij++) {
179 if (item === filteredReversed[ij]) {
180 break;
181 }
182 else {
183 cursorOffset += 1;
184 }
185 }
186 }
187 this._cursor =
188 this._filtered.length - (oldFilteredReversed.length + 1) - cursorOffset;
189 });
190 if (this._cursor < 0) {
191 if (this._history.length > oldHistory.length) {
192 await this.fetchBatch();
193 }
194 }
195 }
196 /**
197 * Populate the history collection on history reply from a kernel.
198 *
199 * @param value The kernel message history reply.
200 *
201 * #### Notes
202 * History entries have the shape:
203 * [session: number, line: number, input: string]
204 * Contiguous duplicates are stripped out of the API response.
205 */
206 onHistory(value, cell) {
207 this._history.length = 0;
208 let last = ['', '', ''];
209 let current = ['', '', ''];
210 let kernelSession = '';
211 if (value.content.status === 'ok') {
212 for (let i = 0; i < value.content.history.length; i++) {
213 current = value.content.history[i];
214 if (current !== last) {
215 kernelSession = value.content.history[i][0];
216 this._history.push((last = current));
217 }
218 }
219 // set the kernel session for filtering
220 if (!this.kernelSession) {
221 if (current[2] == (cell === null || cell === void 0 ? void 0 : cell.model.sharedModel.getSource())) {
222 this._kernelSession = kernelSession;
223 }
224 }
225 }
226 }
227 /**
228 * Handle a text change signal from the editor.
229 */
230 onTextChange() {
231 if (this._setByHistory) {
232 this._setByHistory = false;
233 return;
234 }
235 this.reset();
236 }
237 /**
238 * Handle the current kernel changing.
239 */
240 async _handleKernel() {
241 var _a;
242 this._kernel = (_a = this._sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel;
243 if (!this._kernel) {
244 this._history.length = 0;
245 return;
246 }
247 await this._retrieveHistory().catch();
248 return;
249 }
250 /**
251 * retrieve the history from the kernel
252 *
253 * @param cell - The string to use when filtering the data.
254 */
255 async _retrieveHistory(cell) {
256 var _a;
257 return await ((_a = this._kernel) === null || _a === void 0 ? void 0 : _a.requestHistory(request(this._toRequest)).then(v => {
258 this.onHistory(v, cell);
259 }).catch(() => {
260 console.warn(this._trans.__('History was unable to be retrieved'));
261 }));
262 }
263 /**
264 * Set the filter data.
265 *
266 * @param filterStr - The string to use when filtering the data.
267 */
268 setFilter(filterStr = '') {
269 // Apply the new filter and remove contiguous duplicates.
270 this._filtered.length = 0;
271 let last = '';
272 let current = '';
273 for (let i = 0; i < this._history.length; i++) {
274 current = this._history[i][2];
275 if (current !== last && filterStr !== current) {
276 this._filtered.push((last = current));
277 }
278 }
279 this._filtered.push(filterStr);
280 }
281}
282function request(n) {
283 return {
284 output: false,
285 raw: true,
286 hist_access_type: 'tail',
287 n: n
288 };
289}
290//# sourceMappingURL=history.js.map
\No newline at end of file