@@ -8,13 +8,13 @@ import {
88 IEditorRange
99} from '../editor_integration/codemirror' ;
1010import { FeatureSettings , IFeatureLabIntegration } from '../feature' ;
11- import { EditorTooltipManager } from '../components/free_tooltip' ;
11+ import { EditorTooltipManager , FreeTooltip } from '../components/free_tooltip' ;
1212import {
1313 JupyterFrontEnd ,
1414 JupyterFrontEndPlugin
1515} from '@jupyterlab/application' ;
1616import { IRenderMimeRegistry } from '@jupyterlab/rendermime' ;
17- import { ILSPAdapterManager , ILSPFeatureManager , PLUGIN_ID } from '../tokens' ;
17+ import { ILSPFeatureManager , PLUGIN_ID } from '../tokens' ;
1818import { ISettingRegistry } from '@jupyterlab/settingregistry' ;
1919import { CodeHover as LSPHoverSettings , ModifierKey } from '../_hover' ;
2020import { LabIcon } from '@jupyterlab/ui-components' ;
@@ -86,8 +86,9 @@ export class HoverCM extends CodeMirrorIntegration {
8686 protected cache : ResponseCache ;
8787
8888 private debounced_get_hover : Throttler < Promise < lsProtocol . Hover > > ;
89+ private tooltip : FreeTooltip ;
8990
90- get modifierKey ( ) : ModifierKey {
91+ protected get modifierKey ( ) : ModifierKey {
9192 return this . settings . composite . modifierKey ;
9293 }
9394
@@ -114,31 +115,34 @@ export class HoverCM extends CodeMirrorIntegration {
114115
115116 register ( ) : void {
116117 this . cache = new ResponseCache ( this . settings . composite . cacheSize ) ;
117- this . wrapper_handlers . set ( 'mousemove' , this . handleMouseOver ) ;
118- this . wrapper_handlers . set ( 'mouseleave' , this . remove_range_highlight ) ;
119- // show hover after pressing the modifier key
120- this . wrapper_handlers . set ( 'keydown' , ( event : KeyboardEvent ) => {
121- if (
122- getModifierState ( event , this . modifierKey ) &&
123- typeof this . last_hover_character !== 'undefined'
124- ) {
125- const document = this . virtual_editor . document_at_root_position (
126- this . last_hover_character
127- ) ;
128- const virtual_position = this . virtual_editor . root_position_to_virtual_position (
129- this . last_hover_character
130- ) ;
131- let response_data = this . restore_from_cache (
132- this . last_hover_character ,
133- document ,
134- virtual_position
135- ) ;
136- if ( response_data == null ) {
137- return ;
138- }
139- this . handleResponse ( response_data , this . last_hover_character , true ) ;
140- }
118+
119+ this . wrapper_handlers . set ( 'mousemove' , event => {
120+ // as CodeMirror.Editor does not support mouseleave nor mousemove,
121+ // we simulate the mouseleave for the editor in wrapper's mousemove;
122+ // this is used to hide the tooltip on leaving cells in notebook
123+ this . updateUnderlineAndTooltip ( event )
124+ . then ( keep_tooltip => {
125+ if ( ! keep_tooltip ) {
126+ this . maybeHideTooltip ( event . target ) ;
127+ }
128+ } )
129+ . catch ( console . warn ) ;
141130 } ) ;
131+ this . wrapper_handlers . set ( 'mouseleave' , this . onMouseLeave ) ;
132+
133+ // show hover after pressing the modifier key
134+ // TODO: when the editor (notebook or file editor) is not focused, the keydown event is not getting to us
135+ // (probably getting captured by lab); this gives subpar experience when using hover in two editors open
136+ // side-by-side, BUT this does not happen for mousemove which properly reads keyModifier from the event
137+ // (so this is no too bad as most of the time the user will get the desired outcome - they just need to
138+ // budge the mice when holding ctrl if looking at a document which is not active).
139+ // whether the editor is focused
140+ this . wrapper_handlers . set ( 'keydown' , this . onKeyDown ) ;
141+ // or just the wrapper (e.g. the notebook but no cell active)
142+ this . editor_handlers . set ( 'keydown' , ( instance , event : KeyboardEvent ) =>
143+ this . onKeyDown ( event )
144+ ) ;
145+
142146 this . debounced_get_hover = this . create_throttler ( ) ;
143147
144148 this . settings . changed . connect ( ( ) => {
@@ -148,7 +152,45 @@ export class HoverCM extends CodeMirrorIntegration {
148152 super . register ( ) ;
149153 }
150154
151- create_throttler ( ) {
155+ protected onKeyDown = ( event : KeyboardEvent ) => {
156+ if (
157+ getModifierState ( event , this . modifierKey ) &&
158+ typeof this . last_hover_character !== 'undefined'
159+ ) {
160+ const document = this . virtual_editor . document_at_root_position (
161+ this . last_hover_character
162+ ) ;
163+ const virtual_position = this . virtual_editor . root_position_to_virtual_position (
164+ this . last_hover_character
165+ ) ;
166+ let response_data = this . restore_from_cache (
167+ this . last_hover_character ,
168+ document ,
169+ virtual_position
170+ ) ;
171+ if ( response_data == null ) {
172+ return ;
173+ }
174+ event . stopPropagation ( ) ;
175+ this . handleResponse ( response_data , this . last_hover_character , true ) ;
176+ }
177+ } ;
178+
179+ protected onMouseLeave = ( event : MouseEvent ) => {
180+ this . remove_range_highlight ( ) ;
181+ this . maybeHideTooltip ( event . relatedTarget ) ;
182+ } ;
183+
184+ protected maybeHideTooltip ( mouse_target : EventTarget ) {
185+ if (
186+ typeof this . tooltip !== 'undefined' &&
187+ mouse_target !== this . tooltip . node
188+ ) {
189+ this . tooltip . dispose ( ) ;
190+ }
191+ }
192+
193+ protected create_throttler ( ) {
152194 return new Throttler < Promise < lsProtocol . Hover > > ( this . on_hover , {
153195 limit : this . settings . composite . throttlerDelay ,
154196 edge : 'trailing'
@@ -203,11 +245,17 @@ export class HoverCM extends CodeMirrorIntegration {
203245 }
204246 }
205247
248+ /**
249+ * Underlines the word if a tooltip is available.
250+ * Displays tooltip if asked to do so.
251+ *
252+ * Returns true is the tooltip was shown.
253+ */
206254 public handleResponse = (
207255 response_data : IResponseData ,
208256 root_position : IRootPosition ,
209257 show_tooltip : boolean
210- ) => {
258+ ) : boolean => {
211259 let response = response_data . response ;
212260
213261 // testing for object equality because the response will likely be reused from cache
@@ -228,13 +276,16 @@ export class HoverCM extends CodeMirrorIntegration {
228276 root_position
229277 ) ;
230278
231- this . lab_integration . tooltip . create ( {
279+ this . tooltip = this . lab_integration . tooltip . create ( {
232280 markup,
233281 position : editor_position ,
234282 ce_editor : response_data . ce_editor ,
283+ adapter : this . adapter ,
235284 className : 'lsp-hover'
236285 } ) ;
286+ return true ;
237287 }
288+ return false ;
238289 } ;
239290
240291 protected is_token_empty ( token : CodeMirror . Token ) {
@@ -255,7 +306,12 @@ export class HoverCM extends CodeMirrorIntegration {
255306 ) ;
256307 }
257308
258- protected async _handleMouseOver ( event : MouseEvent ) {
309+ /**
310+ * Returns true if the tooltip should stay.
311+ */
312+ protected async _updateUnderlineAndTooltip (
313+ event : MouseEvent
314+ ) : Promise < boolean > {
259315 const show_tooltip = getModifierState ( event , this . modifierKey ) ;
260316
261317 // currently the events are coming from notebook panel; ideally these would be connected to individual cells,
@@ -332,13 +388,15 @@ export class HoverCM extends CodeMirrorIntegration {
332388 }
333389 }
334390
335- this . handleResponse ( response_data , root_position , show_tooltip ) ;
391+ return this . handleResponse ( response_data , root_position , show_tooltip ) ;
392+ } else {
393+ return true ;
336394 }
337395 }
338396
339- protected handleMouseOver = ( event : MouseEvent ) => {
397+ protected updateUnderlineAndTooltip = ( event : MouseEvent ) => {
340398 try {
341- return this . _handleMouseOver ( event ) ;
399+ return this . _updateUnderlineAndTooltip ( event ) ;
342400 } catch ( e ) {
343401 if (
344402 ! (
@@ -435,37 +493,29 @@ class HoverLabIntegration implements IFeatureLabIntegration {
435493 constructor (
436494 app : JupyterFrontEnd ,
437495 settings : FeatureSettings < any > ,
438- renderMimeRegistry : IRenderMimeRegistry ,
439- adapterManager : ILSPAdapterManager
496+ renderMimeRegistry : IRenderMimeRegistry
440497 ) {
441- this . tooltip = new EditorTooltipManager ( renderMimeRegistry , adapterManager ) ;
498+ this . tooltip = new EditorTooltipManager ( renderMimeRegistry ) ;
442499 }
443500}
444501
445502const FEATURE_ID = PLUGIN_ID + ':hover' ;
446503
447504export const HOVER_PLUGIN : JupyterFrontEndPlugin < void > = {
448505 id : FEATURE_ID ,
449- requires : [
450- ILSPFeatureManager ,
451- ISettingRegistry ,
452- IRenderMimeRegistry ,
453- ILSPAdapterManager
454- ] ,
506+ requires : [ ILSPFeatureManager , ISettingRegistry , IRenderMimeRegistry ] ,
455507 autoStart : true ,
456508 activate : (
457509 app : JupyterFrontEnd ,
458510 featureManager : ILSPFeatureManager ,
459511 settingRegistry : ISettingRegistry ,
460- renderMimeRegistry : IRenderMimeRegistry ,
461- adapterManager : ILSPAdapterManager
512+ renderMimeRegistry : IRenderMimeRegistry
462513 ) => {
463514 const settings = new FeatureSettings ( settingRegistry , FEATURE_ID ) ;
464515 const labIntegration = new HoverLabIntegration (
465516 app ,
466517 settings ,
467- renderMimeRegistry ,
468- adapterManager
518+ renderMimeRegistry
469519 ) ;
470520
471521 featureManager . register ( {
0 commit comments