Afshin Darian 8 роки тому
батько
коміт
80dadb4ba8
2 змінених файлів з 128 додано та 31 видалено
  1. 94 4
      src/tooltip/plugin.ts
  2. 34 27
      src/tooltip/widget.ts

+ 94 - 4
src/tooltip/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  Kernel, KernelMessage
+} from '@jupyterlab/services';
+
 import {
   Widget
 } from 'phosphor/lib/ui/widget';
@@ -9,6 +13,10 @@ import {
   JupyterLab, JupyterLabPlugin
 } from '../application';
 
+import {
+  CodeEditor
+} from '../codeeditor';
+
 import {
   IConsoleTracker
 } from '../console';
@@ -18,7 +26,11 @@ import {
 } from '../notebook';
 
 import {
-  CommandIDs, ITooltipManager, TooltipModel, TooltipWidget
+  RenderMime
+} from '../rendermime';
+
+import {
+  CommandIDs, ITooltipManager, TooltipWidget
 } from './';
 
 
@@ -32,13 +44,19 @@ const service: JupyterLabPlugin<ITooltipManager> = {
   activate: (app: JupyterLab): ITooltipManager => {
     let tooltip: TooltipWidget | null = null;
     return {
-      invoke(options: ITooltipManager.IOptions): void {
+      invoke(options: ITooltipManager.IOptions): Promise<void> {
+        const detail: 0 | 1 = 0;
         const { anchor, editor, kernel, rendermime  } = options;
 
         if (tooltip) {
           tooltip.dispose();
           tooltip = null;
         }
+
+        return Private.fetch({ detail, editor, kernel }).then(bundle => {
+          tooltip = new TooltipWidget({ anchor, bundle, editor, rendermime });
+          Widget.attach(tooltip, document.body);
+        }).catch(() => { /* Fails silently. */ });
       }
     };
   }
@@ -69,7 +87,7 @@ const consolePlugin: JupyterLabPlugin<void> = {
 
         // If all components necessary for rendering exist, create a tooltip.
         if (!!editor && !!kernel && !!rendermime) {
-          manager.invoke({ anchor, editor, kernel, rendermime });
+          return manager.invoke({ anchor, editor, kernel, rendermime });
         }
       }
     });
@@ -102,7 +120,7 @@ const notebookPlugin: JupyterLabPlugin<void> = {
 
         // If all components necessary for rendering exist, create a tooltip.
         if (!!editor && !!kernel && !!rendermime) {
-          manager.invoke({ anchor, editor, kernel, rendermime });
+          return manager.invoke({ anchor, editor, kernel, rendermime });
         }
       }
     });
@@ -118,3 +136,75 @@ const plugins: JupyterLabPlugin<any>[] = [
   service, consolePlugin, notebookPlugin
 ];
 export default plugins;
+
+
+/**
+ * A namespace for private data.
+ */
+namespace Private {
+  /**
+   * A counter for outstanding requests.
+   */
+  let pending = 0;
+
+  export
+  interface IFetchOptions {
+    /**
+     * The detail level requested from the API.
+     *
+     * #### Notes
+     * The only acceptable values are 0 and 1. The default value is 0.
+     * @see http://jupyter-client.readthedocs.io/en/latest/messaging.html#introspection
+     */
+    detail?: 0 | 1;
+
+    /**
+     * The referent editor for the tooltip.
+     */
+    editor: CodeEditor.IEditor;
+
+    /**
+     * The kernel against which the API request will be made.
+     */
+    kernel: Kernel.IKernel;
+  }
+
+  /**
+   * Fetch a tooltip's content from the API server.
+   */
+  export
+  function fetch(options: IFetchOptions): Promise<RenderMime.MimeMap<string>> {
+    let { detail, editor, kernel } = options;
+    let code = editor.model.value.text;
+    let position = editor.getCursorPosition();
+    let offset = editor.getOffsetAt(position);
+
+    // Clear hints if the new text value is empty or kernel is unavailable.
+    if (!code || !kernel) {
+      return Promise.reject(void 0);
+    }
+
+    let contents: KernelMessage.IInspectRequest = {
+      code,
+      cursor_pos: offset,
+      detail_level: detail || 0
+    };
+    let current = ++pending;
+
+    return kernel.requestInspect(contents).then(msg => {
+      let value = msg.content;
+
+      // If a newer request is pending, bail.
+      if (current !== pending) {
+        return Promise.reject(void 0);
+      }
+
+      // If request fails or returns negative results, bail.
+      if (value.status !== 'ok' || !value.found) {
+        return Promise.reject(void 0);
+      }
+
+      return Promise.resolve(value.data as RenderMime.MimeMap<string>);
+    });
+  }
+}

+ 34 - 27
src/tooltip/widget.ts

@@ -13,13 +13,17 @@ import {
   Widget
 } from 'phosphor/lib/ui/widget';
 
+import {
+  CodeEditor
+} from '../codeeditor';
+
 import {
   HoverBox
 } from '../common/hoverbox';
 
 import {
-  TooltipModel
-} from './model';
+  IRenderMime, RenderMime
+} from '../rendermime';
 
 
 /**
@@ -57,12 +61,22 @@ class TooltipWidget extends Widget {
    */
   constructor(options: TooltipWidget.IOptions) {
     super();
+
     this.layout = new PanelLayout();
     this.anchor = options.anchor;
-    this.model = options.model;
-    this.model.contentChanged.connect(this._onContentChanged, this);
+
     this.addClass(TOOLTIP_CLASS);
     this.anchor.addClass(ANCHOR_CLASS);
+
+    this._editor = options.editor;
+    this._rendermime = options.rendermime;
+    this._content = this._rendermime.render({
+      bundle: options.bundle,
+      trusted: true
+    });
+    if (this._content) {
+      (this.layout as PanelLayout).addWidget(this._content);
+    }
   }
 
   /**
@@ -70,11 +84,6 @@ class TooltipWidget extends Widget {
    */
   readonly anchor: Widget;
 
-  /**
-   * The tooltip widget's data model.
-   */
-  readonly model: TooltipModel;
-
   /**
    * Dispose of the resources held by the widget.
    */
@@ -131,7 +140,7 @@ class TooltipWidget extends Widget {
     document.addEventListener('keydown', this, USE_CAPTURE);
     document.addEventListener('mousedown', this, USE_CAPTURE);
     this.anchor.node.addEventListener('scroll', this, USE_CAPTURE);
-    this.model.fetch();
+    this.update();
   }
 
   /**
@@ -168,26 +177,12 @@ class TooltipWidget extends Widget {
     this.update();
   }
 
-  /**
-   * Handle model content changes.
-   */
-  private _onContentChanged(): void {
-    if (this._content) {
-      this._content.dispose();
-    }
-    this._content = this.model.content;
-    if (this._content) {
-      (this.layout as PanelLayout).addWidget(this._content);
-    }
-    this.update();
-  }
-
   /**
    * Set the geometry of the tooltip widget.
    */
   private _setGeometry():  void {
     let node = this.node;
-    let editor = this.model.editor;
+    let editor = this._editor;
     let { charWidth, lineHeight } = editor;
     let coords = editor.getCoordinate(editor.getCursorPosition());
 
@@ -203,6 +198,8 @@ class TooltipWidget extends Widget {
   }
 
   private _content: Widget | null = null;
+  private _editor: CodeEditor.IEditor;
+  private _rendermime: IRenderMime;
 }
 
 /**
@@ -221,8 +218,18 @@ namespace TooltipWidget {
     anchor: Widget;
 
     /**
-     * The data model for the tooltip widget.
+     * The data that populates the tooltip widget.
+     */
+    bundle: RenderMime.MimeMap<string>;
+
+    /**
+     * The editor referent of the tooltip model.
+     */
+    editor: CodeEditor.IEditor;
+
+    /**
+     * The rendermime instance used by the tooltip model.
      */
-    model: TooltipModel;
+    rendermime: IRenderMime;
   }
 }