@jupyter/chat 0.3.1 → 0.5.0
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/lib/active-cell-manager.d.ts +3 -0
- package/lib/components/chat-input.d.ts +4 -0
- package/lib/components/chat-input.js +50 -18
- package/lib/components/chat-messages.d.ts +31 -1
- package/lib/components/chat-messages.js +55 -19
- package/lib/components/chat.js +1 -1
- package/lib/components/code-blocks/code-toolbar.js +50 -16
- package/lib/components/input/cancel-button.d.ts +12 -0
- package/lib/components/input/cancel-button.js +27 -0
- package/lib/components/input/send-button.d.ts +18 -0
- package/lib/components/input/send-button.js +143 -0
- package/lib/components/mui-extras/tooltipped-button.d.ts +41 -0
- package/lib/components/mui-extras/tooltipped-button.js +43 -0
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/model.d.ts +68 -8
- package/lib/model.js +57 -12
- package/lib/selection-watcher.d.ts +62 -0
- package/lib/selection-watcher.js +134 -0
- package/lib/types.d.ts +22 -0
- package/lib/utils.d.ts +11 -0
- package/lib/utils.js +37 -0
- package/package.json +3 -15
- package/src/active-cell-manager.ts +3 -0
- package/src/components/chat-input.tsx +71 -32
- package/src/components/chat-messages.tsx +106 -32
- package/src/components/chat.tsx +1 -1
- package/src/components/code-blocks/code-toolbar.tsx +55 -17
- package/src/components/input/cancel-button.tsx +47 -0
- package/src/components/input/send-button.tsx +210 -0
- package/src/components/mui-extras/tooltipped-button.tsx +92 -0
- package/src/icons.ts +6 -0
- package/src/index.ts +1 -0
- package/src/model.ts +102 -13
- package/src/selection-watcher.ts +221 -0
- package/src/types.ts +25 -0
- package/src/utils.ts +47 -0
- package/style/chat.css +13 -0
- package/style/icons/include-selection.svg +5 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
3
|
+
export type TooltippedButtonProps = {
|
|
4
|
+
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
5
|
+
tooltip: string;
|
|
6
|
+
children: JSX.Element;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
placement?: TooltipProps['placement'];
|
|
9
|
+
/**
|
|
10
|
+
* The offset of the tooltip popup.
|
|
11
|
+
*
|
|
12
|
+
* The expected syntax is defined by the Popper library:
|
|
13
|
+
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
14
|
+
*/
|
|
15
|
+
offset?: [number, number];
|
|
16
|
+
'aria-label'?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Props passed directly to the MUI `Button` component.
|
|
19
|
+
*/
|
|
20
|
+
buttonProps?: ButtonProps;
|
|
21
|
+
/**
|
|
22
|
+
* Styles applied to the MUI `Button` component.
|
|
23
|
+
*/
|
|
24
|
+
sx?: SxProps;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A component that renders an MUI `Button` with a high-contrast tooltip
|
|
28
|
+
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
29
|
+
* defaults in the following ways:
|
|
30
|
+
*
|
|
31
|
+
* - Shows the tooltip on hover even if disabled.
|
|
32
|
+
* - Renders the tooltip above the button by default.
|
|
33
|
+
* - Renders the tooltip closer to the button by default.
|
|
34
|
+
* - Lowers the opacity of the Button when disabled.
|
|
35
|
+
* - Renders the Button with `line-height: 0` to avoid showing extra
|
|
36
|
+
* vertical space in SVG icons.
|
|
37
|
+
*
|
|
38
|
+
* NOTE TO DEVS: Please keep this component's features synchronized with
|
|
39
|
+
* features available to `TooltippedIconButton`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function TooltippedButton(props: TooltippedButtonProps): JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { Button } from '@mui/material';
|
|
7
|
+
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
8
|
+
/**
|
|
9
|
+
* A component that renders an MUI `Button` with a high-contrast tooltip
|
|
10
|
+
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
11
|
+
* defaults in the following ways:
|
|
12
|
+
*
|
|
13
|
+
* - Shows the tooltip on hover even if disabled.
|
|
14
|
+
* - Renders the tooltip above the button by default.
|
|
15
|
+
* - Renders the tooltip closer to the button by default.
|
|
16
|
+
* - Lowers the opacity of the Button when disabled.
|
|
17
|
+
* - Renders the Button with `line-height: 0` to avoid showing extra
|
|
18
|
+
* vertical space in SVG icons.
|
|
19
|
+
*
|
|
20
|
+
* NOTE TO DEVS: Please keep this component's features synchronized with
|
|
21
|
+
* features available to `TooltippedIconButton`.
|
|
22
|
+
*/
|
|
23
|
+
export function TooltippedButton(props) {
|
|
24
|
+
var _a;
|
|
25
|
+
return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_a = props.placement) !== null && _a !== void 0 ? _a : 'top', slotProps: {
|
|
26
|
+
popper: {
|
|
27
|
+
modifiers: [
|
|
28
|
+
{
|
|
29
|
+
name: 'offset',
|
|
30
|
+
options: {
|
|
31
|
+
offset: [0, -8]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
} },
|
|
37
|
+
React.createElement("span", { style: { cursor: 'default' } },
|
|
38
|
+
React.createElement(Button, { ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, sx: {
|
|
39
|
+
lineHeight: 0,
|
|
40
|
+
...(props.disabled && { opacity: 0.5 }),
|
|
41
|
+
...props.sx
|
|
42
|
+
}, "aria-label": props['aria-label'] }, props.children))));
|
|
43
|
+
}
|
package/lib/icons.d.ts
CHANGED
package/lib/icons.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// This file is based on iconimports.ts in @jupyterlab/ui-components, but is manually generated.
|
|
6
6
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
7
7
|
import chatSvgStr from '../style/icons/chat.svg';
|
|
8
|
+
import includeSelectionIconStr from '../style/icons/include-selection.svg';
|
|
8
9
|
import readSvgStr from '../style/icons/read.svg';
|
|
9
10
|
import replaceCellSvg from '../style/icons/replace-cell.svg';
|
|
10
11
|
export const chatIcon = new LabIcon({
|
|
@@ -19,3 +20,7 @@ export const replaceCellIcon = new LabIcon({
|
|
|
19
20
|
name: 'jupyter-ai::replace-cell',
|
|
20
21
|
svgstr: replaceCellSvg
|
|
21
22
|
});
|
|
23
|
+
export const includeSelectionIcon = new LabIcon({
|
|
24
|
+
name: 'jupyter-chat::include',
|
|
25
|
+
svgstr: includeSelectionIconStr
|
|
26
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './model';
|
|
|
3
3
|
export * from './registry';
|
|
4
4
|
export * from './types';
|
|
5
5
|
export * from './active-cell-manager';
|
|
6
|
+
export * from './selection-watcher';
|
|
6
7
|
export * from './widgets/chat-error';
|
|
7
8
|
export * from './widgets/chat-sidebar';
|
|
8
9
|
export * from './widgets/chat-widget';
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './model';
|
|
|
7
7
|
export * from './registry';
|
|
8
8
|
export * from './types';
|
|
9
9
|
export * from './active-cell-manager';
|
|
10
|
+
export * from './selection-watcher';
|
|
10
11
|
export * from './widgets/chat-error';
|
|
11
12
|
export * from './widgets/chat-sidebar';
|
|
12
13
|
export * from './widgets/chat-widget';
|
package/lib/model.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { IDisposable } from '@lumino/disposable';
|
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
4
|
import { IChatHistory, INewMessage, IChatMessage, IConfig, IUser } from './types';
|
|
5
5
|
import { IActiveCellManager } from './active-cell-manager';
|
|
6
|
+
import { ISelectionWatcher } from './selection-watcher';
|
|
6
7
|
/**
|
|
7
8
|
* The chat model interface.
|
|
8
9
|
*/
|
|
@@ -35,6 +36,10 @@ export interface IChatModel extends IDisposable {
|
|
|
35
36
|
* Get the active cell manager.
|
|
36
37
|
*/
|
|
37
38
|
readonly activeCellManager: IActiveCellManager | null;
|
|
39
|
+
/**
|
|
40
|
+
* Get the selection watcher.
|
|
41
|
+
*/
|
|
42
|
+
readonly selectionWatcher: ISelectionWatcher | null;
|
|
38
43
|
/**
|
|
39
44
|
* A signal emitting when the messages list is updated.
|
|
40
45
|
*/
|
|
@@ -51,6 +56,14 @@ export interface IChatModel extends IDisposable {
|
|
|
51
56
|
* A signal emitting when the viewport change.
|
|
52
57
|
*/
|
|
53
58
|
readonly viewportChanged?: ISignal<IChatModel, number[]>;
|
|
59
|
+
/**
|
|
60
|
+
* A signal emitting when the writers change.
|
|
61
|
+
*/
|
|
62
|
+
readonly writersChanged?: ISignal<IChatModel, IUser[]>;
|
|
63
|
+
/**
|
|
64
|
+
* A signal emitting when the focus is requested on the input.
|
|
65
|
+
*/
|
|
66
|
+
readonly focusInputSignal?: ISignal<IChatModel, void>;
|
|
54
67
|
/**
|
|
55
68
|
* Send a message, to be defined depending on the chosen technology.
|
|
56
69
|
* Default to no-op.
|
|
@@ -58,7 +71,7 @@ export interface IChatModel extends IDisposable {
|
|
|
58
71
|
* @param message - the message to send.
|
|
59
72
|
* @returns whether the message has been sent or not, or nothing if not needed.
|
|
60
73
|
*/
|
|
61
|
-
|
|
74
|
+
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
62
75
|
/**
|
|
63
76
|
* Optional, to update a message from the chat panel.
|
|
64
77
|
*
|
|
@@ -104,6 +117,18 @@ export interface IChatModel extends IDisposable {
|
|
|
104
117
|
* @param count - the number of messages to delete.
|
|
105
118
|
*/
|
|
106
119
|
messagesDeleted(index: number, count: number): void;
|
|
120
|
+
/**
|
|
121
|
+
* Update the current writers list.
|
|
122
|
+
*/
|
|
123
|
+
updateWriters(writers: IUser[]): void;
|
|
124
|
+
/**
|
|
125
|
+
* Function to request the focus on the input of the chat.
|
|
126
|
+
*/
|
|
127
|
+
focusInput(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Function called by the input on key pressed.
|
|
130
|
+
*/
|
|
131
|
+
inputChanged?(input?: string): void;
|
|
107
132
|
}
|
|
108
133
|
/**
|
|
109
134
|
* The default chat model implementation.
|
|
@@ -115,11 +140,6 @@ export declare class ChatModel implements IChatModel {
|
|
|
115
140
|
* Create a new chat model.
|
|
116
141
|
*/
|
|
117
142
|
constructor(options?: ChatModel.IOptions);
|
|
118
|
-
/**
|
|
119
|
-
* The chat messages list.
|
|
120
|
-
*/
|
|
121
|
-
get messages(): IChatMessage[];
|
|
122
|
-
get activeCellManager(): IActiveCellManager | null;
|
|
123
143
|
/**
|
|
124
144
|
* The chat model id.
|
|
125
145
|
*/
|
|
@@ -130,6 +150,18 @@ export declare class ChatModel implements IChatModel {
|
|
|
130
150
|
*/
|
|
131
151
|
get name(): string;
|
|
132
152
|
set name(value: string);
|
|
153
|
+
/**
|
|
154
|
+
* The chat messages list.
|
|
155
|
+
*/
|
|
156
|
+
get messages(): IChatMessage[];
|
|
157
|
+
/**
|
|
158
|
+
* Get the active cell manager.
|
|
159
|
+
*/
|
|
160
|
+
get activeCellManager(): IActiveCellManager | null;
|
|
161
|
+
/**
|
|
162
|
+
* Get the selection watcher.
|
|
163
|
+
*/
|
|
164
|
+
get selectionWatcher(): ISelectionWatcher | null;
|
|
133
165
|
/**
|
|
134
166
|
* Timestamp of the last read message in local storage.
|
|
135
167
|
*/
|
|
@@ -166,6 +198,14 @@ export declare class ChatModel implements IChatModel {
|
|
|
166
198
|
* A signal emitting when the viewport change.
|
|
167
199
|
*/
|
|
168
200
|
get viewportChanged(): ISignal<IChatModel, number[]>;
|
|
201
|
+
/**
|
|
202
|
+
* A signal emitting when the writers change.
|
|
203
|
+
*/
|
|
204
|
+
get writersChanged(): ISignal<IChatModel, IUser[]>;
|
|
205
|
+
/**
|
|
206
|
+
* A signal emitting when the focus is requested on the input.
|
|
207
|
+
*/
|
|
208
|
+
get focusInputSignal(): ISignal<IChatModel, void>;
|
|
169
209
|
/**
|
|
170
210
|
* Send a message, to be defined depending on the chosen technology.
|
|
171
211
|
* Default to no-op.
|
|
@@ -173,7 +213,7 @@ export declare class ChatModel implements IChatModel {
|
|
|
173
213
|
* @param message - the message to send.
|
|
174
214
|
* @returns whether the message has been sent or not.
|
|
175
215
|
*/
|
|
176
|
-
|
|
216
|
+
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
177
217
|
/**
|
|
178
218
|
* Dispose the chat model.
|
|
179
219
|
*/
|
|
@@ -207,6 +247,19 @@ export declare class ChatModel implements IChatModel {
|
|
|
207
247
|
* @param count - the number of messages to delete.
|
|
208
248
|
*/
|
|
209
249
|
messagesDeleted(index: number, count: number): void;
|
|
250
|
+
/**
|
|
251
|
+
* Update the current writers list.
|
|
252
|
+
* This implementation only propagate the list via a signal.
|
|
253
|
+
*/
|
|
254
|
+
updateWriters(writers: IUser[]): void;
|
|
255
|
+
/**
|
|
256
|
+
* Function to request the focus on the input of the chat.
|
|
257
|
+
*/
|
|
258
|
+
focusInput(): void;
|
|
259
|
+
/**
|
|
260
|
+
* Function called by the input on key pressed.
|
|
261
|
+
*/
|
|
262
|
+
inputChanged?(input?: string): void;
|
|
210
263
|
/**
|
|
211
264
|
* Add unread messages to the list.
|
|
212
265
|
* @param indexes - list of new indexes.
|
|
@@ -231,11 +284,14 @@ export declare class ChatModel implements IChatModel {
|
|
|
231
284
|
private _isDisposed;
|
|
232
285
|
private _commands?;
|
|
233
286
|
private _activeCellManager;
|
|
287
|
+
private _selectionWatcher;
|
|
234
288
|
private _notificationId;
|
|
235
289
|
private _messagesUpdated;
|
|
236
290
|
private _configChanged;
|
|
237
291
|
private _unreadChanged;
|
|
238
292
|
private _viewportChanged;
|
|
293
|
+
private _writersChanged;
|
|
294
|
+
private _focusInputSignal;
|
|
239
295
|
}
|
|
240
296
|
/**
|
|
241
297
|
* The chat model namespace.
|
|
@@ -254,8 +310,12 @@ export declare namespace ChatModel {
|
|
|
254
310
|
*/
|
|
255
311
|
commands?: CommandRegistry;
|
|
256
312
|
/**
|
|
257
|
-
* Active cell manager
|
|
313
|
+
* Active cell manager.
|
|
258
314
|
*/
|
|
259
315
|
activeCellManager?: IActiveCellManager | null;
|
|
316
|
+
/**
|
|
317
|
+
* Selection watcher.
|
|
318
|
+
*/
|
|
319
|
+
selectionWatcher?: ISelectionWatcher | null;
|
|
260
320
|
}
|
|
261
321
|
}
|
package/lib/model.js
CHANGED
|
@@ -13,7 +13,7 @@ export class ChatModel {
|
|
|
13
13
|
* Create a new chat model.
|
|
14
14
|
*/
|
|
15
15
|
constructor(options = {}) {
|
|
16
|
-
var _a, _b;
|
|
16
|
+
var _a, _b, _c;
|
|
17
17
|
this._messages = [];
|
|
18
18
|
this._unreadMessages = [];
|
|
19
19
|
this._messagesInViewport = [];
|
|
@@ -24,20 +24,18 @@ export class ChatModel {
|
|
|
24
24
|
this._configChanged = new Signal(this);
|
|
25
25
|
this._unreadChanged = new Signal(this);
|
|
26
26
|
this._viewportChanged = new Signal(this);
|
|
27
|
+
this._writersChanged = new Signal(this);
|
|
28
|
+
this._focusInputSignal = new Signal(this);
|
|
27
29
|
const config = (_a = options.config) !== null && _a !== void 0 ? _a : {};
|
|
28
30
|
// Stack consecutive messages from the same user by default.
|
|
29
|
-
this._config = {
|
|
31
|
+
this._config = {
|
|
32
|
+
stackMessages: true,
|
|
33
|
+
sendTypingNotification: true,
|
|
34
|
+
...config
|
|
35
|
+
};
|
|
30
36
|
this._commands = options.commands;
|
|
31
37
|
this._activeCellManager = (_b = options.activeCellManager) !== null && _b !== void 0 ? _b : null;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The chat messages list.
|
|
35
|
-
*/
|
|
36
|
-
get messages() {
|
|
37
|
-
return this._messages;
|
|
38
|
-
}
|
|
39
|
-
get activeCellManager() {
|
|
40
|
-
return this._activeCellManager;
|
|
38
|
+
this._selectionWatcher = (_c = options.selectionWatcher) !== null && _c !== void 0 ? _c : null;
|
|
41
39
|
}
|
|
42
40
|
/**
|
|
43
41
|
* The chat model id.
|
|
@@ -57,6 +55,24 @@ export class ChatModel {
|
|
|
57
55
|
set name(value) {
|
|
58
56
|
this._name = value;
|
|
59
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The chat messages list.
|
|
60
|
+
*/
|
|
61
|
+
get messages() {
|
|
62
|
+
return this._messages;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the active cell manager.
|
|
66
|
+
*/
|
|
67
|
+
get activeCellManager() {
|
|
68
|
+
return this._activeCellManager;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the selection watcher.
|
|
72
|
+
*/
|
|
73
|
+
get selectionWatcher() {
|
|
74
|
+
return this._selectionWatcher;
|
|
75
|
+
}
|
|
60
76
|
/**
|
|
61
77
|
* Timestamp of the last read message in local storage.
|
|
62
78
|
*/
|
|
@@ -172,6 +188,18 @@ export class ChatModel {
|
|
|
172
188
|
get viewportChanged() {
|
|
173
189
|
return this._viewportChanged;
|
|
174
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A signal emitting when the writers change.
|
|
193
|
+
*/
|
|
194
|
+
get writersChanged() {
|
|
195
|
+
return this._writersChanged;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A signal emitting when the focus is requested on the input.
|
|
199
|
+
*/
|
|
200
|
+
get focusInputSignal() {
|
|
201
|
+
return this._focusInputSignal;
|
|
202
|
+
}
|
|
175
203
|
/**
|
|
176
204
|
* Send a message, to be defined depending on the chosen technology.
|
|
177
205
|
* Default to no-op.
|
|
@@ -179,7 +207,7 @@ export class ChatModel {
|
|
|
179
207
|
* @param message - the message to send.
|
|
180
208
|
* @returns whether the message has been sent or not.
|
|
181
209
|
*/
|
|
182
|
-
|
|
210
|
+
sendMessage(message) { }
|
|
183
211
|
/**
|
|
184
212
|
* Dispose the chat model.
|
|
185
213
|
*/
|
|
@@ -268,6 +296,23 @@ export class ChatModel {
|
|
|
268
296
|
this._messages.splice(index, count);
|
|
269
297
|
this._messagesUpdated.emit();
|
|
270
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Update the current writers list.
|
|
301
|
+
* This implementation only propagate the list via a signal.
|
|
302
|
+
*/
|
|
303
|
+
updateWriters(writers) {
|
|
304
|
+
this._writersChanged.emit(writers);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Function to request the focus on the input of the chat.
|
|
308
|
+
*/
|
|
309
|
+
focusInput() {
|
|
310
|
+
this._focusInputSignal.emit();
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Function called by the input on key pressed.
|
|
314
|
+
*/
|
|
315
|
+
inputChanged(input) { }
|
|
271
316
|
/**
|
|
272
317
|
* Add unread messages to the list.
|
|
273
318
|
* @param indexes - list of new indexes.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
3
|
+
import { Widget } from '@lumino/widgets';
|
|
4
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
5
|
+
/**
|
|
6
|
+
* The selection watcher namespace.
|
|
7
|
+
*/
|
|
8
|
+
export declare namespace SelectionWatcher {
|
|
9
|
+
/**
|
|
10
|
+
* The constructor options.
|
|
11
|
+
*/
|
|
12
|
+
interface IOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The current shell of the application.
|
|
15
|
+
*/
|
|
16
|
+
shell: JupyterFrontEnd.IShell;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The selection type.
|
|
20
|
+
*/
|
|
21
|
+
type Selection = CodeEditor.ITextSelection & {
|
|
22
|
+
/**
|
|
23
|
+
* The text within the selection as a string.
|
|
24
|
+
*/
|
|
25
|
+
text: string;
|
|
26
|
+
/**
|
|
27
|
+
* Number of lines contained by the text selection.
|
|
28
|
+
*/
|
|
29
|
+
numLines: number;
|
|
30
|
+
/**
|
|
31
|
+
* The ID of the document widget in which the selection was made.
|
|
32
|
+
*/
|
|
33
|
+
widgetId: string;
|
|
34
|
+
/**
|
|
35
|
+
* The ID of the cell in which the selection was made, if the original widget
|
|
36
|
+
* was a notebook.
|
|
37
|
+
*/
|
|
38
|
+
cellId?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The selection watcher interface.
|
|
43
|
+
*/
|
|
44
|
+
export interface ISelectionWatcher {
|
|
45
|
+
readonly selection: SelectionWatcher.Selection | null;
|
|
46
|
+
readonly selectionChanged: ISignal<ISelectionWatcher, SelectionWatcher.Selection | null>;
|
|
47
|
+
replaceSelection(selection: SelectionWatcher.Selection): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The selection watcher, read/write selected text in a DocumentWidget.
|
|
51
|
+
*/
|
|
52
|
+
export declare class SelectionWatcher {
|
|
53
|
+
constructor(options: SelectionWatcher.IOptions);
|
|
54
|
+
get selection(): SelectionWatcher.Selection | null;
|
|
55
|
+
get selectionChanged(): ISignal<this, SelectionWatcher.Selection | null>;
|
|
56
|
+
replaceSelection(selection: SelectionWatcher.Selection): void;
|
|
57
|
+
protected _poll(): void;
|
|
58
|
+
protected _shell: JupyterFrontEnd.IShell;
|
|
59
|
+
protected _mainAreaDocumentWidget: Widget | null;
|
|
60
|
+
protected _selection: SelectionWatcher.Selection | null;
|
|
61
|
+
protected _selectionChanged: Signal<this, SelectionWatcher.Selection | null>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
6
|
+
import { Notebook } from '@jupyterlab/notebook';
|
|
7
|
+
import { find } from '@lumino/algorithm';
|
|
8
|
+
import { Signal } from '@lumino/signaling';
|
|
9
|
+
import { getCellIndex, getEditor } from './utils';
|
|
10
|
+
/**
|
|
11
|
+
* The selection watcher, read/write selected text in a DocumentWidget.
|
|
12
|
+
*/
|
|
13
|
+
export class SelectionWatcher {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
var _a;
|
|
16
|
+
this._mainAreaDocumentWidget = null;
|
|
17
|
+
this._selection = null;
|
|
18
|
+
this._selectionChanged = new Signal(this);
|
|
19
|
+
this._shell = options.shell;
|
|
20
|
+
(_a = this._shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect((sender, args) => {
|
|
21
|
+
var _a;
|
|
22
|
+
// Do not change the main area widget if the new one has no editor, for example
|
|
23
|
+
// a chat panel. However, the selected text is only available if the main area
|
|
24
|
+
// widget is visible. (to avoid confusion in inclusion/replacement).
|
|
25
|
+
const widget = args.newValue;
|
|
26
|
+
// if there is no main area widget, set it to null.
|
|
27
|
+
if (widget === null) {
|
|
28
|
+
this._mainAreaDocumentWidget = null;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const editor = getEditor(widget);
|
|
32
|
+
if (widget instanceof DocumentWidget &&
|
|
33
|
+
(editor || widget.content instanceof Notebook)) {
|
|
34
|
+
// if the new widget is a DocumentWidget and has an editor, set it.
|
|
35
|
+
// NOTE: special case for notebook which do not has an active cell at that stage,
|
|
36
|
+
// and so the editor can't be retrieved too.
|
|
37
|
+
this._mainAreaDocumentWidget = widget;
|
|
38
|
+
}
|
|
39
|
+
else if ((_a = this._mainAreaDocumentWidget) === null || _a === void 0 ? void 0 : _a.isDisposed) {
|
|
40
|
+
// if the previous document widget has been closed, set it to null.
|
|
41
|
+
this._mainAreaDocumentWidget = null;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
setInterval(this._poll.bind(this), 200);
|
|
45
|
+
}
|
|
46
|
+
get selection() {
|
|
47
|
+
return this._selection;
|
|
48
|
+
}
|
|
49
|
+
get selectionChanged() {
|
|
50
|
+
return this._selectionChanged;
|
|
51
|
+
}
|
|
52
|
+
replaceSelection(selection) {
|
|
53
|
+
// unfortunately shell.currentWidget doesn't update synchronously after
|
|
54
|
+
// shell.activateById(), which is why we have to get a reference to the
|
|
55
|
+
// widget manually.
|
|
56
|
+
const widget = find(this._shell.widgets(), widget => widget.id === selection.widgetId);
|
|
57
|
+
// Do not allow replacement on non visible widget (to avoid confusion).
|
|
58
|
+
if (!(widget === null || widget === void 0 ? void 0 : widget.isVisible) || !(widget instanceof DocumentWidget)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// activate the widget if not already active
|
|
62
|
+
this._shell.activateById(selection.widgetId);
|
|
63
|
+
// activate notebook cell if specified
|
|
64
|
+
if (widget.content instanceof Notebook && selection.cellId) {
|
|
65
|
+
const cellIndex = getCellIndex(widget.content, selection.cellId);
|
|
66
|
+
if (cellIndex !== -1) {
|
|
67
|
+
widget.content.activeCellIndex = cellIndex;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// get editor instance
|
|
71
|
+
const editor = getEditor(widget);
|
|
72
|
+
if (!editor) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
editor.model.sharedModel.updateSource(editor.getOffsetAt(selection.start), editor.getOffsetAt(selection.end), selection.text);
|
|
76
|
+
const newPosition = editor.getPositionAt(editor.getOffsetAt(selection.start) + selection.text.length);
|
|
77
|
+
editor.setSelection({ start: newPosition, end: newPosition });
|
|
78
|
+
}
|
|
79
|
+
_poll() {
|
|
80
|
+
var _a;
|
|
81
|
+
let currSelection = null;
|
|
82
|
+
const prevSelection = this._selection;
|
|
83
|
+
// Do not return selected text if the main area widget is hidden.
|
|
84
|
+
if ((_a = this._mainAreaDocumentWidget) === null || _a === void 0 ? void 0 : _a.isVisible) {
|
|
85
|
+
currSelection = getTextSelection(this._mainAreaDocumentWidget);
|
|
86
|
+
}
|
|
87
|
+
if ((prevSelection === null || prevSelection === void 0 ? void 0 : prevSelection.text) !== (currSelection === null || currSelection === void 0 ? void 0 : currSelection.text)) {
|
|
88
|
+
this._selection = currSelection;
|
|
89
|
+
this._selectionChanged.emit(currSelection);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets a Selection object from a document widget. Returns `null` if unable.
|
|
95
|
+
*/
|
|
96
|
+
function getTextSelection(widget) {
|
|
97
|
+
var _a;
|
|
98
|
+
const editor = getEditor(widget);
|
|
99
|
+
// widget type check is redundant but hints the type to TypeScript
|
|
100
|
+
if (!editor || !(widget instanceof DocumentWidget)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
let cellId = undefined;
|
|
104
|
+
if (widget.content instanceof Notebook) {
|
|
105
|
+
cellId = (_a = widget.content.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
|
|
106
|
+
}
|
|
107
|
+
const selectionObj = editor.getSelection();
|
|
108
|
+
let { start, end } = selectionObj;
|
|
109
|
+
const startOffset = editor.getOffsetAt(start);
|
|
110
|
+
const endOffset = editor.getOffsetAt(end);
|
|
111
|
+
const text = editor.model.sharedModel
|
|
112
|
+
.getSource()
|
|
113
|
+
.substring(startOffset, endOffset);
|
|
114
|
+
// Do not return a Selection object if no text is selected
|
|
115
|
+
if (!text) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// ensure start <= end
|
|
119
|
+
// required for editor.model.sharedModel.updateSource()
|
|
120
|
+
if (startOffset > endOffset) {
|
|
121
|
+
[start, end] = [end, start];
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
...selectionObj,
|
|
125
|
+
start,
|
|
126
|
+
end,
|
|
127
|
+
text,
|
|
128
|
+
numLines: text.split('\n').length,
|
|
129
|
+
widgetId: widget.id,
|
|
130
|
+
...(cellId && {
|
|
131
|
+
cellId
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface IConfig {
|
|
|
33
33
|
* Whether to enable or not the code toolbar.
|
|
34
34
|
*/
|
|
35
35
|
enableCodeToolbar?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to send typing notification.
|
|
38
|
+
*/
|
|
39
|
+
sendTypingNotification?: boolean;
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* The chat message description.
|
|
@@ -72,6 +76,24 @@ export interface ISettings {
|
|
|
72
76
|
export type AutocompleteCommand = {
|
|
73
77
|
label: string;
|
|
74
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Representation of a selected text.
|
|
81
|
+
*/
|
|
82
|
+
export type TextSelection = {
|
|
83
|
+
type: 'text';
|
|
84
|
+
source: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Representation of a selected cell.
|
|
88
|
+
*/
|
|
89
|
+
export type CellSelection = {
|
|
90
|
+
type: 'cell';
|
|
91
|
+
source: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Selection object (text or cell).
|
|
95
|
+
*/
|
|
96
|
+
export type Selection = TextSelection | CellSelection;
|
|
75
97
|
/**
|
|
76
98
|
* The properties of the autocompletion.
|
|
77
99
|
*
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
2
|
+
import { Notebook } from '@jupyterlab/notebook';
|
|
3
|
+
import { Widget } from '@lumino/widgets';
|
|
4
|
+
/**
|
|
5
|
+
* Gets the editor instance used by a document widget. Returns `null` if unable.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getEditor(widget: Widget | null): CodeMirrorEditor | null | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Gets the index of the cell associated with `cellId`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCellIndex(notebook: Notebook, cellId: string): number;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
6
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
7
|
+
import { FileEditor } from '@jupyterlab/fileeditor';
|
|
8
|
+
import { Notebook } from '@jupyterlab/notebook';
|
|
9
|
+
/**
|
|
10
|
+
* Gets the editor instance used by a document widget. Returns `null` if unable.
|
|
11
|
+
*/
|
|
12
|
+
export function getEditor(widget) {
|
|
13
|
+
var _a;
|
|
14
|
+
if (!(widget instanceof DocumentWidget)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
let editor;
|
|
18
|
+
const { content } = widget;
|
|
19
|
+
if (content instanceof FileEditor) {
|
|
20
|
+
editor = content.editor;
|
|
21
|
+
}
|
|
22
|
+
else if (content instanceof Notebook) {
|
|
23
|
+
editor = (_a = content.activeCell) === null || _a === void 0 ? void 0 : _a.editor;
|
|
24
|
+
}
|
|
25
|
+
if (!(editor instanceof CodeMirrorEditor)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return editor;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Gets the index of the cell associated with `cellId`.
|
|
32
|
+
*/
|
|
33
|
+
export function getCellIndex(notebook, cellId) {
|
|
34
|
+
var _a;
|
|
35
|
+
const idx = (_a = notebook.model) === null || _a === void 0 ? void 0 : _a.sharedModel.cells.findIndex(cell => cell.getId() === cellId);
|
|
36
|
+
return idx === undefined ? -1 : idx;
|
|
37
|
+
}
|