@@ -15,7 +15,7 @@ import * as lsProtocol from 'vscode-languageserver-protocol';
15
15
16
16
import { WidgetAdapter } from '../adapters/adapter' ;
17
17
import { PositionConverter } from '../converter' ;
18
- import { IEditorPosition } from '../positioning' ;
18
+ import { IEditorPosition , is_equal } from '../positioning' ;
19
19
20
20
const MIN_HEIGHT = 20 ;
21
21
const MAX_HEIGHT = 250 ;
@@ -41,6 +41,8 @@ interface IFreeTooltipOptions extends Tooltip.IOptions {
41
41
hideOnKeyPress ?: boolean ;
42
42
}
43
43
44
+ type Bundle = { 'text/plain' : string } | { 'text/markdown' : string } ;
45
+
44
46
/**
45
47
* Tooltip which can be placed at any character, not only at the current position (derived from getCursorPosition)
46
48
*/
@@ -50,8 +52,10 @@ export class FreeTooltip extends Tooltip {
50
52
constructor ( protected options : IFreeTooltipOptions ) {
51
53
super ( options ) ;
52
54
this . _setGeometry ( ) ;
53
- // TODO: remove once https://github.com/jupyterlab/jupyterlab/pull/11010 is merged & released
54
- const model = new MimeModel ( { data : options . bundle } ) ;
55
+ }
56
+
57
+ setBundle ( bundle : Bundle ) {
58
+ const model = new MimeModel ( { data : bundle } ) ;
55
59
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
56
60
// @ts -ignore
57
61
const content : IRenderMime . IRenderer = this . _content ;
@@ -99,35 +103,34 @@ export class FreeTooltip extends Tooltip {
99
103
this . options . position == null
100
104
? editor . getCursorPosition ( )
101
105
: this . options . position ;
102
-
103
- const end = editor . getOffsetAt ( cursor ) ;
104
- const line = editor . getLine ( cursor . line ) ;
105
-
106
- if ( ! line ) {
107
- return ;
108
- }
109
-
110
106
let position : CodeEditor . IPosition | undefined ;
111
107
112
- switch ( this . options . alignment ) {
113
- case 'start' : {
114
- const tokens = line . substring ( 0 , end ) . split ( / \W + / ) ;
115
- const last = tokens [ tokens . length - 1 ] ;
116
- const start = last ? end - last . length : end ;
117
- position = editor . getPositionAt ( start ) ;
118
- break ;
119
- }
120
- case 'end' : {
121
- const tokens = line . substring ( 0 , end ) . split ( / \W + / ) ;
122
- const last = tokens [ tokens . length - 1 ] ;
123
- const start = last ? end - last . length : end ;
124
- position = editor . getPositionAt ( start ) ;
125
- break ;
108
+ if ( this . options . alignment ) {
109
+ const end = editor . getOffsetAt ( cursor ) ;
110
+ const line = editor . getLine ( cursor . line ) ;
111
+
112
+ if ( ! line ) {
113
+ return ;
126
114
}
127
- default : {
128
- position = cursor ;
129
- break ;
115
+
116
+ switch ( this . options . alignment ) {
117
+ case 'start' : {
118
+ const tokens = line . substring ( 0 , end ) . split ( / \W + / ) ;
119
+ const last = tokens [ tokens . length - 1 ] ;
120
+ const start = last ? end - last . length : end ;
121
+ position = editor . getPositionAt ( start ) ;
122
+ break ;
123
+ }
124
+ case 'end' : {
125
+ const tokens = line . substring ( 0 , end ) . split ( / \W + / ) ;
126
+ const last = tokens [ tokens . length - 1 ] ;
127
+ const start = last ? end - last . length : end ;
128
+ position = editor . getPositionAt ( start ) ;
129
+ break ;
130
+ }
130
131
}
132
+ } else {
133
+ position = cursor ;
131
134
}
132
135
133
136
if ( ! position ) {
@@ -155,9 +158,23 @@ export class FreeTooltip extends Tooltip {
155
158
node : this . node ,
156
159
offset : { horizontal : - 1 * paddingLeft } ,
157
160
privilege : this . options . privilege || 'below' ,
158
- style : style
161
+ style : style ,
162
+ // TODO: remove `ts-ignore` once minimum version is >=3.5
163
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
164
+ // @ts -ignore
165
+ outOfViewDisplay : {
166
+ left : 'stick-inside' ,
167
+ right : 'stick-outside' ,
168
+ top : 'stick-outside' ,
169
+ bottom : 'stick-inside'
170
+ }
159
171
} ) ;
160
172
}
173
+
174
+ setPosition ( position : CodeEditor . IPosition ) {
175
+ this . options . position = position ;
176
+ this . _setGeometry ( ) ;
177
+ }
161
178
}
162
179
163
180
export namespace EditorTooltip {
@@ -172,6 +189,12 @@ export namespace EditorTooltip {
172
189
}
173
190
}
174
191
192
+ function markupToBundle ( markup : lsProtocol . MarkupContent ) : Bundle {
193
+ return markup . kind === 'plaintext'
194
+ ? { 'text/plain' : markup . value }
195
+ : { 'text/markdown' : markup . value } ;
196
+ }
197
+
175
198
export class EditorTooltipManager {
176
199
private currentTooltip : FreeTooltip | null = null ;
177
200
private currentOptions : EditorTooltip . IOptions | null ;
@@ -183,10 +206,7 @@ export class EditorTooltipManager {
183
206
this . currentOptions = options ;
184
207
let { markup, position, adapter } = options ;
185
208
let widget = adapter . widget ;
186
- const bundle : { 'text/plain' : string } | { 'text/markdown' : string } =
187
- markup . kind === 'plaintext'
188
- ? { 'text/plain' : markup . value }
189
- : { 'text/markdown' : markup . value } ;
209
+ const bundle = markupToBundle ( markup ) ;
190
210
const tooltip = new FreeTooltip ( {
191
211
...( options . tooltip || { } ) ,
192
212
anchor : widget . content ,
@@ -204,6 +224,43 @@ export class EditorTooltipManager {
204
224
return tooltip ;
205
225
}
206
226
227
+ showOrCreate ( options : EditorTooltip . IOptions ) : FreeTooltip {
228
+ const samePosition =
229
+ this . currentOptions &&
230
+ is_equal ( this . currentOptions . position , options . position ) ;
231
+ const sameMarkup =
232
+ this . currentOptions &&
233
+ this . currentOptions . markup . value === options . markup . value &&
234
+ this . currentOptions . markup . kind === options . markup . kind ;
235
+ if (
236
+ this . currentTooltip !== null &&
237
+ ! this . currentTooltip . isDisposed &&
238
+ this . currentOptions &&
239
+ this . currentOptions . adapter === options . adapter &&
240
+ ( samePosition || sameMarkup ) &&
241
+ this . currentOptions . ce_editor === options . ce_editor &&
242
+ this . currentOptions . id === options . id
243
+ ) {
244
+ // we only allow either position or markup change, because if both changed,
245
+ // then we may get into problematic race condition in sizing after bundle update.
246
+ if ( ! sameMarkup ) {
247
+ this . currentOptions . markup = options . markup ;
248
+ this . currentTooltip . setBundle ( markupToBundle ( options . markup ) ) ;
249
+ }
250
+ if ( ! samePosition ) {
251
+ // setting geometry only works when visible
252
+ this . currentTooltip . setPosition (
253
+ PositionConverter . cm_to_ce ( options . position )
254
+ ) ;
255
+ }
256
+ this . show ( ) ;
257
+ return this . currentTooltip ;
258
+ } else {
259
+ this . remove ( ) ;
260
+ return this . create ( options ) ;
261
+ }
262
+ }
263
+
207
264
get position ( ) : IEditorPosition {
208
265
return this . currentOptions ! . position ;
209
266
}
@@ -219,6 +276,18 @@ export class EditorTooltipManager {
219
276
) ;
220
277
}
221
278
279
+ hide ( ) {
280
+ if ( this . currentTooltip !== null ) {
281
+ this . currentTooltip . hide ( ) ;
282
+ }
283
+ }
284
+
285
+ show ( ) {
286
+ if ( this . currentTooltip !== null ) {
287
+ this . currentTooltip . show ( ) ;
288
+ }
289
+ }
290
+
222
291
remove ( ) {
223
292
if ( this . currentTooltip !== null ) {
224
293
this . currentTooltip . dispose ( ) ;
0 commit comments