Skip to content

Commit

Permalink
Server: Add tooltips with help for Python functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jitseniesen committed Dec 24, 2020
1 parent 7ddae5a commit def8d55
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
1 change: 1 addition & 0 deletions spyder_notebook/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@jupyterlab/services": "^4.2.0",
"@jupyterlab/theme-light-extension": "^1.2.1",
"@jupyterlab/theme-dark-extension": "^1.2.1",
"@jupyterlab/tooltip": "^1.2.1",
"@phosphor/commands": "^1.7.0",
"@phosphor/widgets": "^1.9.0",
"es6-promise": "~4.2.6"
Expand Down
25 changes: 25 additions & 0 deletions spyder_notebook/server/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
NotebookSearchProvider
} from '@jupyterlab/documentsearch';

import { dismissTooltip, invokeTooltip } from './tooltip';

/**
* The map of command ids used by the notebook.
*/
Expand All @@ -21,6 +23,8 @@ const cmdIds = {
select: 'completer:select',
invokeNotebook: 'completer:invoke-notebook',
selectNotebook: 'completer:select-notebook',
dismissTooltip: 'tooltip:dismiss',
invokeTooltip: 'tooltip:invoke',
startSearch: 'documentsearch:start-search',
findNext: 'documentsearch:find-next',
findPrevious: 'documentsearch:find-previous',
Expand Down Expand Up @@ -361,6 +365,17 @@ export const SetupCommands = (
execute: () => nbWidget.context.session.selectKernel()
});

// Tooltip commands
commands.addCommand(cmdIds.dismissTooltip, {
label: 'Dismiss Tooltip',
execute: () => dismissTooltip()
});

commands.addCommand(cmdIds.invokeTooltip, {
label: 'Invoke Tooltip',
execute: () => invokeTooltip(nbWidget)
});

// Add other commands.
commands.addCommand(cmdIds.invoke, {
label: 'Completer: Invoke',
Expand Down Expand Up @@ -554,6 +569,16 @@ export const SetupCommands = (
keys: ['Enter'],
command: cmdIds.selectNotebook
},
{
selector: 'body.jp-mod-tooltip .jp-Notebook',
keys: ['Escape'],
command: cmdIds.dismissTooltip
},
{
selector: '.jp-Notebook.jp-mod-editMode .jp-InputArea-editor:not(.jp-mod-has-primary-selection):not(.jp-mod-in-leading-whitespace)',
keys: ['Shift Tab'],
command: cmdIds.invokeTooltip
},
{
selector: '.jp-Notebook',
keys: ['Shift Enter'],
Expand Down
1 change: 1 addition & 0 deletions spyder_notebook/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '@jupyterlab/application/style/index.css';
import '@jupyterlab/codemirror/style/index.css';
import '@jupyterlab/completer/style/index.css';
import '@jupyterlab/notebook/style/index.css';
import '@jupyterlab/tooltip/style/index.css';

if (PageConfig.getOption('darkTheme') == 'true') {
require('@jupyterlab/theme-dark-extension/style/index.css');
Expand Down
111 changes: 111 additions & 0 deletions spyder_notebook/server/src/tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Jupyter Development Team, Spyder Project Contributors.
// Distributed under the terms of the Modified BSD License.

// Adapted from jupyterlab/packages/tooltip-extension/src/index.ts

import { CodeEditor } from '@jupyterlab/codeeditor';
import { Text } from '@jupyterlab/coreutils';
import { NotebookPanel } from '@jupyterlab/notebook';
import { Kernel, KernelMessage } from '@jupyterlab/services';
import { Tooltip } from '@jupyterlab/tooltip';
import { Widget } from '@phosphor/widgets';
import { JSONObject } from '@phosphor/coreutils';

let tooltip: Tooltip | null = null;

export function dismissTooltip() {
if (tooltip) {
tooltip.dispose();
tooltip = null;
}
}

export function invokeTooltip(nbWidget: NotebookPanel) {
const detail: 0 | 1 = 0;
const parent = nbWidget.context;
const anchor = nbWidget.content;
const editor = anchor.activeCell.editor;
const kernel = parent.session.kernel;
const rendermime = anchor.rendermime;

// If some components necessary for rendering don't exist, stop
if (!editor || !kernel || !rendermime) {
return;
}

if (tooltip) {
tooltip.dispose();
tooltip = null;
}

return fetchTooltip({ detail, editor, kernel })
.then(bundle => {
tooltip = new Tooltip({ anchor, bundle, editor, rendermime });
Widget.attach(tooltip, document.body);
})
.catch(() => {
/* Fails silently. */
});
}

// A counter for outstanding requests.
let pending = 0;

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.IKernelConnection;
}

/**
* Fetch a tooltip's content from the API server.
*/
function fetchTooltip(options: IFetchOptions): Promise<JSONObject> {
let { detail, editor, kernel } = options;
let code = editor.model.value.text;
let position = editor.getCursorPosition();
let offset = Text.jsIndexToCharIndex(editor.getOffsetAt(position), code);

// Clear hints if the new text value is empty or kernel is unavailable.
if (!code || !kernel) {
return Promise.reject(void 0);
}

let contents: KernelMessage.IInspectRequestMsg['content'] = {
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) as Promise<JSONObject>;
}

// If request fails or returns negative results, bail.
if (value.status !== 'ok' || !value.found) {
return Promise.reject(void 0) as Promise<JSONObject>;
}

return Promise.resolve(value.data);
});
}

0 comments on commit def8d55

Please sign in to comment.