@jupyterlab/console 4.0.0-alpha.9 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/history.ts ADDED
@@ -0,0 +1,370 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { ISessionContext } from '@jupyterlab/apputils';
5
+ import { CodeEditor } from '@jupyterlab/codeeditor';
6
+ import { KernelMessage } from '@jupyterlab/services';
7
+ import { IDisposable } from '@lumino/disposable';
8
+ import { Signal } from '@lumino/signaling';
9
+
10
+ /**
11
+ * The definition of a console history manager object.
12
+ */
13
+ export interface IConsoleHistory extends IDisposable {
14
+ /**
15
+ * The session context used by the foreign handler.
16
+ */
17
+ readonly sessionContext: ISessionContext | null;
18
+
19
+ /**
20
+ * The current editor used by the history widget.
21
+ */
22
+ editor: CodeEditor.IEditor | null;
23
+
24
+ /**
25
+ * The placeholder text that a history session began with.
26
+ */
27
+ readonly placeholder: string;
28
+
29
+ /**
30
+ * Get the previous item in the console history.
31
+ *
32
+ * @param placeholder - The placeholder string that gets temporarily added
33
+ * to the history only for the duration of one history session. If multiple
34
+ * placeholders are sent within a session, only the first one is accepted.
35
+ *
36
+ * @returns A Promise for console command text or `undefined` if unavailable.
37
+ */
38
+ back(placeholder: string): Promise<string>;
39
+
40
+ /**
41
+ * Get the next item in the console history.
42
+ *
43
+ * @param placeholder - The placeholder string that gets temporarily added
44
+ * to the history only for the duration of one history session. If multiple
45
+ * placeholders are sent within a session, only the first one is accepted.
46
+ *
47
+ * @returns A Promise for console command text or `undefined` if unavailable.
48
+ */
49
+ forward(placeholder: string): Promise<string>;
50
+
51
+ /**
52
+ * Add a new item to the bottom of history.
53
+ *
54
+ * @param item The item being added to the bottom of history.
55
+ *
56
+ * #### Notes
57
+ * If the item being added is undefined or empty, it is ignored. If the item
58
+ * being added is the same as the last item in history, it is ignored as well
59
+ * so that the console's history will consist of no contiguous repetitions.
60
+ */
61
+ push(item: string): void;
62
+
63
+ /**
64
+ * Reset the history navigation state, i.e., start a new history session.
65
+ */
66
+ reset(): void;
67
+ }
68
+
69
+ /**
70
+ * A console history manager object.
71
+ */
72
+ export class ConsoleHistory implements IConsoleHistory {
73
+ /**
74
+ * Construct a new console history object.
75
+ */
76
+ constructor(options: ConsoleHistory.IOptions) {
77
+ const { sessionContext } = options;
78
+ if (sessionContext) {
79
+ this.sessionContext = sessionContext;
80
+ void this._handleKernel();
81
+ this.sessionContext.kernelChanged.connect(this._handleKernel, this);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * The client session used by the foreign handler.
87
+ */
88
+ readonly sessionContext: ISessionContext | null;
89
+
90
+ /**
91
+ * The current editor used by the history manager.
92
+ */
93
+ get editor(): CodeEditor.IEditor | null {
94
+ return this._editor;
95
+ }
96
+ set editor(value: CodeEditor.IEditor | null) {
97
+ if (this._editor === value) {
98
+ return;
99
+ }
100
+
101
+ const prev = this._editor;
102
+ if (prev) {
103
+ prev.edgeRequested.disconnect(this.onEdgeRequest, this);
104
+ prev.model.sharedModel.changed.disconnect(this.onTextChange, this);
105
+ }
106
+
107
+ this._editor = value;
108
+
109
+ if (value) {
110
+ value.edgeRequested.connect(this.onEdgeRequest, this);
111
+ value.model.sharedModel.changed.connect(this.onTextChange, this);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * The placeholder text that a history session began with.
117
+ */
118
+ get placeholder(): string {
119
+ return this._placeholder;
120
+ }
121
+
122
+ /**
123
+ * Get whether the console history manager is disposed.
124
+ */
125
+ get isDisposed(): boolean {
126
+ return this._isDisposed;
127
+ }
128
+
129
+ /**
130
+ * Dispose of the resources held by the console history manager.
131
+ */
132
+ dispose(): void {
133
+ this._isDisposed = true;
134
+ this._history.length = 0;
135
+ Signal.clearData(this);
136
+ }
137
+
138
+ /**
139
+ * Get the previous item in the console history.
140
+ *
141
+ * @param placeholder - The placeholder string that gets temporarily added
142
+ * to the history only for the duration of one history session. If multiple
143
+ * placeholders are sent within a session, only the first one is accepted.
144
+ *
145
+ * @returns A Promise for console command text or `undefined` if unavailable.
146
+ */
147
+ back(placeholder: string): Promise<string> {
148
+ if (!this._hasSession) {
149
+ this._hasSession = true;
150
+ this._placeholder = placeholder;
151
+ // Filter the history with the placeholder string.
152
+ this.setFilter(placeholder);
153
+ this._cursor = this._filtered.length - 1;
154
+ }
155
+
156
+ --this._cursor;
157
+ this._cursor = Math.max(0, this._cursor);
158
+ const content = this._filtered[this._cursor];
159
+ return Promise.resolve(content);
160
+ }
161
+
162
+ /**
163
+ * Get the next item in the console history.
164
+ *
165
+ * @param placeholder - The placeholder string that gets temporarily added
166
+ * to the history only for the duration of one history session. If multiple
167
+ * placeholders are sent within a session, only the first one is accepted.
168
+ *
169
+ * @returns A Promise for console command text or `undefined` if unavailable.
170
+ */
171
+ forward(placeholder: string): Promise<string> {
172
+ if (!this._hasSession) {
173
+ this._hasSession = true;
174
+ this._placeholder = placeholder;
175
+ // Filter the history with the placeholder string.
176
+ this.setFilter(placeholder);
177
+ this._cursor = this._filtered.length;
178
+ }
179
+
180
+ ++this._cursor;
181
+ this._cursor = Math.min(this._filtered.length - 1, this._cursor);
182
+ const content = this._filtered[this._cursor];
183
+ return Promise.resolve(content);
184
+ }
185
+
186
+ /**
187
+ * Add a new item to the bottom of history.
188
+ *
189
+ * @param item The item being added to the bottom of history.
190
+ *
191
+ * #### Notes
192
+ * If the item being added is undefined or empty, it is ignored. If the item
193
+ * being added is the same as the last item in history, it is ignored as well
194
+ * so that the console's history will consist of no contiguous repetitions.
195
+ */
196
+ push(item: string): void {
197
+ if (item && item !== this._history[this._history.length - 1]) {
198
+ this._history.push(item);
199
+ }
200
+ this.reset();
201
+ }
202
+
203
+ /**
204
+ * Reset the history navigation state, i.e., start a new history session.
205
+ */
206
+ reset(): void {
207
+ this._cursor = this._history.length;
208
+ this._hasSession = false;
209
+ this._placeholder = '';
210
+ }
211
+
212
+ /**
213
+ * Populate the history collection on history reply from a kernel.
214
+ *
215
+ * @param value The kernel message history reply.
216
+ *
217
+ * #### Notes
218
+ * History entries have the shape:
219
+ * [session: number, line: number, input: string]
220
+ * Contiguous duplicates are stripped out of the API response.
221
+ */
222
+ protected onHistory(value: KernelMessage.IHistoryReplyMsg): void {
223
+ this._history.length = 0;
224
+ let last = '';
225
+ let current = '';
226
+ if (value.content.status === 'ok') {
227
+ for (let i = 0; i < value.content.history.length; i++) {
228
+ current = (value.content.history[i] as string[])[2];
229
+ if (current !== last) {
230
+ this._history.push((last = current));
231
+ }
232
+ }
233
+ }
234
+ // Reset the history navigation cursor back to the bottom.
235
+ this._cursor = this._history.length;
236
+ }
237
+
238
+ /**
239
+ * Handle a text change signal from the editor.
240
+ */
241
+ protected onTextChange(): void {
242
+ if (this._setByHistory) {
243
+ this._setByHistory = false;
244
+ return;
245
+ }
246
+ this.reset();
247
+ }
248
+
249
+ /**
250
+ * Handle an edge requested signal.
251
+ */
252
+ protected onEdgeRequest(
253
+ editor: CodeEditor.IEditor,
254
+ location: CodeEditor.EdgeLocation
255
+ ): void {
256
+ const sharedModel = editor.model.sharedModel;
257
+ const source = sharedModel.getSource();
258
+
259
+ if (location === 'top' || location === 'topLine') {
260
+ void this.back(source).then(value => {
261
+ if (this.isDisposed || !value) {
262
+ return;
263
+ }
264
+ if (sharedModel.getSource() === value) {
265
+ return;
266
+ }
267
+ this._setByHistory = true;
268
+ sharedModel.setSource(value);
269
+ let columnPos = 0;
270
+ columnPos = value.indexOf('\n');
271
+ if (columnPos < 0) {
272
+ columnPos = value.length;
273
+ }
274
+ editor.setCursorPosition({ line: 0, column: columnPos });
275
+ });
276
+ } else {
277
+ void this.forward(source).then(value => {
278
+ if (this.isDisposed) {
279
+ return;
280
+ }
281
+ const text = value || this.placeholder;
282
+ if (sharedModel.getSource() === text) {
283
+ return;
284
+ }
285
+ this._setByHistory = true;
286
+ sharedModel.setSource(text);
287
+ const pos = editor.getPositionAt(text.length);
288
+ if (pos) {
289
+ editor.setCursorPosition(pos);
290
+ }
291
+ });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Handle the current kernel changing.
297
+ */
298
+ private async _handleKernel(): Promise<void> {
299
+ const kernel = this.sessionContext?.session?.kernel;
300
+ if (!kernel) {
301
+ this._history.length = 0;
302
+ return;
303
+ }
304
+
305
+ return kernel.requestHistory(Private.initialRequest).then(v => {
306
+ this.onHistory(v);
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Set the filter data.
312
+ *
313
+ * @param filterStr - The string to use when filtering the data.
314
+ */
315
+ protected setFilter(filterStr: string = ''): void {
316
+ // Apply the new filter and remove contiguous duplicates.
317
+ this._filtered.length = 0;
318
+
319
+ let last = '';
320
+ let current = '';
321
+
322
+ for (let i = 0; i < this._history.length; i++) {
323
+ current = this._history[i];
324
+ if (
325
+ current !== last &&
326
+ filterStr === current.slice(0, filterStr.length)
327
+ ) {
328
+ this._filtered.push((last = current));
329
+ }
330
+ }
331
+
332
+ this._filtered.push(filterStr);
333
+ }
334
+
335
+ private _cursor = 0;
336
+ private _hasSession = false;
337
+ private _history: string[] = [];
338
+ private _placeholder: string = '';
339
+ private _setByHistory = false;
340
+ private _isDisposed = false;
341
+ private _editor: CodeEditor.IEditor | null = null;
342
+ private _filtered: string[] = [];
343
+ }
344
+
345
+ /**
346
+ * A namespace for ConsoleHistory statics.
347
+ */
348
+ export namespace ConsoleHistory {
349
+ /**
350
+ * The initialization options for a console history object.
351
+ */
352
+ export interface IOptions {
353
+ /**
354
+ * The client session used by the foreign handler.
355
+ */
356
+ sessionContext?: ISessionContext;
357
+ }
358
+ }
359
+
360
+ /**
361
+ * A namespace for private data.
362
+ */
363
+ namespace Private {
364
+ export const initialRequest: KernelMessage.IHistoryRequestMsg['content'] = {
365
+ output: false,
366
+ raw: true,
367
+ hist_access_type: 'tail',
368
+ n: 500
369
+ };
370
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+ /**
4
+ * @packageDocumentation
5
+ * @module console
6
+ */
7
+
8
+ export * from './foreign';
9
+ export * from './history';
10
+ export * from './panel';
11
+ export * from './tokens';
12
+ export * from './widget';
package/src/panel.ts ADDED
@@ -0,0 +1,336 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import {
5
+ ISessionContext,
6
+ MainAreaWidget,
7
+ SessionContext,
8
+ SessionContextDialogs
9
+ } from '@jupyterlab/apputils';
10
+ import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
11
+ import { PathExt, Time, URLExt } from '@jupyterlab/coreutils';
12
+ import {
13
+ IRenderMimeRegistry,
14
+ RenderMimeRegistry
15
+ } from '@jupyterlab/rendermime';
16
+ import { ServiceManager } from '@jupyterlab/services';
17
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
18
+ import { consoleIcon } from '@jupyterlab/ui-components';
19
+ import { Token, UUID } from '@lumino/coreutils';
20
+ import { IDisposable } from '@lumino/disposable';
21
+ import { Message } from '@lumino/messaging';
22
+ import { Panel } from '@lumino/widgets';
23
+ import { CodeConsole } from './widget';
24
+
25
+ /**
26
+ * The class name added to console panels.
27
+ */
28
+ const PANEL_CLASS = 'jp-ConsolePanel';
29
+
30
+ /**
31
+ * A panel which contains a console and the ability to add other children.
32
+ */
33
+ export class ConsolePanel extends MainAreaWidget<Panel> {
34
+ /**
35
+ * Construct a console panel.
36
+ */
37
+ constructor(options: ConsolePanel.IOptions) {
38
+ super({ content: new Panel() });
39
+ this.addClass(PANEL_CLASS);
40
+ let {
41
+ rendermime,
42
+ mimeTypeService,
43
+ path,
44
+ basePath,
45
+ name,
46
+ manager,
47
+ modelFactory,
48
+ sessionContext,
49
+ translator
50
+ } = options;
51
+ this.translator = translator ?? nullTranslator;
52
+ const trans = this.translator.load('jupyterlab');
53
+
54
+ const contentFactory = (this.contentFactory = options.contentFactory);
55
+ const count = Private.count++;
56
+ if (!path) {
57
+ path = URLExt.join(basePath || '', `console-${count}-${UUID.uuid4()}`);
58
+ }
59
+
60
+ sessionContext = this._sessionContext =
61
+ sessionContext ??
62
+ new SessionContext({
63
+ sessionManager: manager.sessions,
64
+ specsManager: manager.kernelspecs,
65
+ path,
66
+ name: name || trans.__('Console %1', count),
67
+ type: 'console',
68
+ kernelPreference: options.kernelPreference,
69
+ setBusy: options.setBusy
70
+ });
71
+
72
+ const resolver = new RenderMimeRegistry.UrlResolver({
73
+ path,
74
+ contents: manager.contents
75
+ });
76
+ rendermime = rendermime.clone({ resolver });
77
+
78
+ this.console = contentFactory.createConsole({
79
+ rendermime,
80
+ sessionContext: sessionContext,
81
+ mimeTypeService,
82
+ contentFactory,
83
+ modelFactory,
84
+ translator
85
+ });
86
+ this.content.addWidget(this.console);
87
+
88
+ void sessionContext.initialize().then(async value => {
89
+ if (value) {
90
+ await (
91
+ options.sessionDialogs ?? new SessionContextDialogs({ translator })
92
+ ).selectKernel(sessionContext!);
93
+ }
94
+ this._connected = new Date();
95
+ this._updateTitlePanel();
96
+ });
97
+
98
+ this.console.executed.connect(this._onExecuted, this);
99
+ this._updateTitlePanel();
100
+ sessionContext.kernelChanged.connect(this._updateTitlePanel, this);
101
+ sessionContext.propertyChanged.connect(this._updateTitlePanel, this);
102
+
103
+ this.title.icon = consoleIcon;
104
+ this.title.closable = true;
105
+ this.id = `console-${count}`;
106
+ }
107
+
108
+ /**
109
+ * The content factory used by the console panel.
110
+ */
111
+ readonly contentFactory: ConsolePanel.IContentFactory;
112
+
113
+ /**
114
+ * The console widget used by the panel.
115
+ */
116
+ console: CodeConsole;
117
+
118
+ /**
119
+ * The session used by the panel.
120
+ */
121
+ get sessionContext(): ISessionContext {
122
+ return this._sessionContext;
123
+ }
124
+
125
+ /**
126
+ * Dispose of the resources held by the widget.
127
+ */
128
+ dispose(): void {
129
+ this.sessionContext.dispose();
130
+ this.console.dispose();
131
+ super.dispose();
132
+ }
133
+
134
+ /**
135
+ * Handle `'activate-request'` messages.
136
+ */
137
+ protected onActivateRequest(msg: Message): void {
138
+ const prompt = this.console.promptCell;
139
+ if (prompt) {
140
+ prompt.editor!.focus();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Handle `'close-request'` messages.
146
+ */
147
+ protected onCloseRequest(msg: Message): void {
148
+ super.onCloseRequest(msg);
149
+ this.dispose();
150
+ }
151
+
152
+ /**
153
+ * Handle a console execution.
154
+ */
155
+ private _onExecuted(sender: CodeConsole, args: Date) {
156
+ this._executed = args;
157
+ this._updateTitlePanel();
158
+ }
159
+
160
+ /**
161
+ * Update the console panel title.
162
+ */
163
+ private _updateTitlePanel(): void {
164
+ Private.updateTitle(this, this._connected, this._executed, this.translator);
165
+ }
166
+
167
+ translator: ITranslator;
168
+ private _executed: Date | null = null;
169
+ private _connected: Date | null = null;
170
+ private _sessionContext: ISessionContext;
171
+ }
172
+
173
+ /**
174
+ * A namespace for ConsolePanel statics.
175
+ */
176
+ export namespace ConsolePanel {
177
+ /**
178
+ * The initialization options for a console panel.
179
+ */
180
+ export interface IOptions {
181
+ /**
182
+ * The rendermime instance used by the panel.
183
+ */
184
+ rendermime: IRenderMimeRegistry;
185
+
186
+ /**
187
+ * The content factory for the panel.
188
+ */
189
+ contentFactory: IContentFactory;
190
+
191
+ /**
192
+ * The service manager used by the panel.
193
+ */
194
+ manager: ServiceManager.IManager;
195
+
196
+ /**
197
+ * The path of an existing console.
198
+ */
199
+ path?: string;
200
+
201
+ /**
202
+ * The base path for a new console.
203
+ */
204
+ basePath?: string;
205
+
206
+ /**
207
+ * The name of the console.
208
+ */
209
+ name?: string;
210
+
211
+ /**
212
+ * A kernel preference.
213
+ */
214
+ kernelPreference?: ISessionContext.IKernelPreference;
215
+
216
+ /**
217
+ * An existing session context to use.
218
+ */
219
+ sessionContext?: ISessionContext;
220
+
221
+ /**
222
+ * Session dialogs to use.
223
+ */
224
+ sessionDialogs?: ISessionContext.IDialogs;
225
+
226
+ /**
227
+ * The model factory for the console widget.
228
+ */
229
+ modelFactory?: CodeConsole.IModelFactory;
230
+
231
+ /**
232
+ * The service used to look up mime types.
233
+ */
234
+ mimeTypeService: IEditorMimeTypeService;
235
+
236
+ /**
237
+ * The application language translator.
238
+ */
239
+ translator?: ITranslator;
240
+
241
+ /**
242
+ * A function to call when the kernel is busy.
243
+ */
244
+ setBusy?: () => IDisposable;
245
+ }
246
+
247
+ /**
248
+ * The console panel renderer.
249
+ */
250
+ export interface IContentFactory extends CodeConsole.IContentFactory {
251
+ /**
252
+ * Create a new console panel.
253
+ */
254
+ createConsole(options: CodeConsole.IOptions): CodeConsole;
255
+ }
256
+
257
+ /**
258
+ * Default implementation of `IContentFactory`.
259
+ */
260
+ export class ContentFactory
261
+ extends CodeConsole.ContentFactory
262
+ implements IContentFactory
263
+ {
264
+ /**
265
+ * Create a new console panel.
266
+ */
267
+ createConsole(options: CodeConsole.IOptions): CodeConsole {
268
+ return new CodeConsole(options);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * A namespace for the console panel content factory.
274
+ */
275
+ export namespace ContentFactory {
276
+ /**
277
+ * Options for the code console content factory.
278
+ */
279
+ export interface IOptions extends CodeConsole.ContentFactory.IOptions {}
280
+ }
281
+
282
+ /**
283
+ * The console renderer token.
284
+ */
285
+ export const IContentFactory = new Token<IContentFactory>(
286
+ '@jupyterlab/console:IContentFactory'
287
+ );
288
+ }
289
+
290
+ /**
291
+ * A namespace for private data.
292
+ */
293
+ namespace Private {
294
+ /**
295
+ * The counter for new consoles.
296
+ */
297
+ export let count = 1;
298
+
299
+ /**
300
+ * Update the title of a console panel.
301
+ */
302
+ export function updateTitle(
303
+ panel: ConsolePanel,
304
+ connected: Date | null,
305
+ executed: Date | null,
306
+ translator?: ITranslator
307
+ ): void {
308
+ translator = translator || nullTranslator;
309
+ const trans = translator.load('jupyterlab');
310
+
311
+ const sessionContext = panel.console.sessionContext.session;
312
+ if (sessionContext) {
313
+ // FIXME:
314
+ let caption =
315
+ trans.__('Name: %1\n', sessionContext.name) +
316
+ trans.__('Directory: %1\n', PathExt.dirname(sessionContext.path)) +
317
+ trans.__('Kernel: %1', panel.console.sessionContext.kernelDisplayName);
318
+
319
+ if (connected) {
320
+ caption += trans.__(
321
+ '\nConnected: %1',
322
+ Time.format(connected.toISOString())
323
+ );
324
+ }
325
+
326
+ if (executed) {
327
+ caption += trans.__('\nLast Execution: %1');
328
+ }
329
+ panel.title.label = sessionContext.name;
330
+ panel.title.caption = caption;
331
+ } else {
332
+ panel.title.label = trans.__('Console');
333
+ panel.title.caption = '';
334
+ }
335
+ }
336
+ }