@@ -15,7 +15,7 @@ import * as lsProtocol from 'vscode-languageserver-protocol';
1515
1616import { WidgetAdapter } from '../adapters/adapter' ;
1717import { PositionConverter } from '../converter' ;
18- import { IEditorPosition } from '../positioning' ;
18+ import { IEditorPosition , is_equal } from '../positioning' ;
1919
2020const MIN_HEIGHT = 20 ;
2121const MAX_HEIGHT = 250 ;
@@ -41,6 +41,8 @@ interface IFreeTooltipOptions extends Tooltip.IOptions {
4141 hideOnKeyPress ?: boolean ;
4242}
4343
44+ type Bundle = { 'text/plain' : string } | { 'text/markdown' : string } ;
45+
4446/**
4547 * Tooltip which can be placed at any character, not only at the current position (derived from getCursorPosition)
4648 */
@@ -50,8 +52,10 @@ export class FreeTooltip extends Tooltip {
5052 constructor ( protected options : IFreeTooltipOptions ) {
5153 super ( options ) ;
5254 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 } ) ;
5559 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5660 // @ts -ignore
5761 const content : IRenderMime . IRenderer = this . _content ;
@@ -99,35 +103,34 @@ export class FreeTooltip extends Tooltip {
99103 this . options . position == null
100104 ? editor . getCursorPosition ( )
101105 : 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-
110106 let position : CodeEditor . IPosition | undefined ;
111107
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 ;
126114 }
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+ }
130131 }
132+ } else {
133+ position = cursor ;
131134 }
132135
133136 if ( ! position ) {
@@ -155,9 +158,23 @@ export class FreeTooltip extends Tooltip {
155158 node : this . node ,
156159 offset : { horizontal : - 1 * paddingLeft } ,
157160 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+ }
159171 } ) ;
160172 }
173+
174+ setPosition ( position : CodeEditor . IPosition ) {
175+ this . options . position = position ;
176+ this . _setGeometry ( ) ;
177+ }
161178}
162179
163180export namespace EditorTooltip {
@@ -172,6 +189,12 @@ export namespace EditorTooltip {
172189 }
173190}
174191
192+ function markupToBundle ( markup : lsProtocol . MarkupContent ) : Bundle {
193+ return markup . kind === 'plaintext'
194+ ? { 'text/plain' : markup . value }
195+ : { 'text/markdown' : markup . value } ;
196+ }
197+
175198export class EditorTooltipManager {
176199 private currentTooltip : FreeTooltip | null = null ;
177200 private currentOptions : EditorTooltip . IOptions | null ;
@@ -183,10 +206,7 @@ export class EditorTooltipManager {
183206 this . currentOptions = options ;
184207 let { markup, position, adapter } = options ;
185208 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 ) ;
190210 const tooltip = new FreeTooltip ( {
191211 ...( options . tooltip || { } ) ,
192212 anchor : widget . content ,
@@ -204,6 +224,43 @@ export class EditorTooltipManager {
204224 return tooltip ;
205225 }
206226
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+
207264 get position ( ) : IEditorPosition {
208265 return this . currentOptions ! . position ;
209266 }
@@ -219,6 +276,18 @@ export class EditorTooltipManager {
219276 ) ;
220277 }
221278
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+
222291 remove ( ) {
223292 if ( this . currentTooltip !== null ) {
224293 this . currentTooltip . dispose ( ) ;
0 commit comments