Parcourir la source

Add a kernel selector dialog and facilities to create new simple files

Steven Silvester il y a 9 ans
Parent
commit
2d57738f06

+ 1 - 1
src/codemirror/widget.ts

@@ -26,7 +26,7 @@ import {
 /**
  * The class name added to CodeMirrorWidget instances.
  */
-const EDITOR_CLASS = 'p-CodeMirrorWidget';
+const EDITOR_CLASS = 'jp-CodeMirrorWidget';
 
 
 /**

+ 15 - 5
src/docmanager/context.ts

@@ -71,6 +71,16 @@ class Context implements IDocumentContext {
     return this._id;
   }
 
+  /**
+   * Get the model associated with the document.
+   *
+   * #### Notes
+   * This is a read-only property
+   */
+  get model(): IDocumentModel {
+    return this._manager.getModel(this._id);
+  }
+
   /**
    * The current kernel associated with the document.
    *
@@ -108,8 +118,8 @@ class Context implements IDocumentContext {
    * #### Notes
    * This is a read-only property.
    */
-  get kernelSpecs(): IKernelSpecIds {
-    return this._manager.getKernelSpecs();
+  get kernelspecs(): IKernelSpecIds {
+    return this._manager.getKernelspecs();
   }
 
   /**
@@ -193,11 +203,11 @@ class ContextManager implements IDisposable {
   /**
    * Construct a new context manager.
    */
-  constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager,  kernelSpecs: IKernelSpecIds, opener: (id: string, widget: Widget) => IDisposable) {
+  constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager,  kernelspecs: IKernelSpecIds, opener: (id: string, widget: Widget) => IDisposable) {
     this._contentsManager = contentsManager;
     this._sessionManager = sessionManager;
     this._opener = opener;
-    this._kernelspecids = kernelSpecs;
+    this._kernelspecids = kernelspecs;
   }
 
   /**
@@ -367,7 +377,7 @@ class ContextManager implements IDisposable {
   /**
    * Get the current kernelspec information.
    */
-  getKernelSpecs(): IKernelSpecIds {
+  getKernelspecs(): IKernelSpecIds {
     return this._kernelspecids;
   }
 

+ 4 - 2
src/docmanager/editor.ts

@@ -36,7 +36,7 @@ const DIRTY_CLASS = 'jp-mod-dirty';
 /**
  * The class name added to a jupyter code mirror widget.
  */
-const EDITOR_CLASS = 'jp-CodeMirrorWidget';
+const EDITOR_CLASS = 'jp-EditorWidget';
 
 
 /**
@@ -93,7 +93,9 @@ class EditorWidgetFactory extends ABCWidgetFactory implements IWidgetFactory<Edi
    * Create a new widget given a document model and a context.
    */
   createNew(model: IDocumentModel, context: IDocumentContext, kernel?: IKernelId): EditorWidget {
-    // TODO: use the kernel.
+    if (kernel) {
+      context.changeKernel(kernel);
+    }
     return new EditorWidget(model, context);
   }
 }

+ 36 - 1
src/docmanager/interfaces.ts

@@ -114,6 +114,14 @@ export interface IDocumentContext extends IDisposable {
    */
   id: string;
 
+  /**
+   * Get the model associated with the document.
+   *
+   * #### Notes
+   * This is a read-only property
+   */
+  model: IDocumentModel;
+
   /**
    * The current kernel associated with the document.
    *
@@ -145,7 +153,7 @@ export interface IDocumentContext extends IDisposable {
    * #### Notes
    * This is a read-only property.
    */
-  kernelSpecs: IKernelSpecIds;
+  kernelspecs: IKernelSpecIds;
 
   /**
    * A signal emitted when the kernel changes.
@@ -344,3 +352,30 @@ interface IFileType {
    */
   icon?: string;
 }
+
+
+/**
+ * An interface for a "Create New" item.
+ */
+export
+interface IFileCreator {
+  /**
+   * The display name of the item.
+   */
+  name: string;
+
+  /**
+   * The contents options used to create the file.
+   */
+  options: IContentsOpts;
+
+  /**
+   * The optional widget name.
+   */
+  widgetName?: string;
+
+  /**
+   * The optional kernel name.
+   */
+  kernelName?: string;
+}

+ 202 - 0
src/docmanager/kernelselector.ts

@@ -0,0 +1,202 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  ISessionId, IKernelId, IKernelSpecIds
+} from 'jupyter-js-services';
+
+import {
+  showDialog
+} from '../dialog';
+
+
+/**
+ * Bring up a dialog to select the kernel for a path.
+ */
+export
+function selectKernel(host: HTMLElement, path: string, specs: IKernelSpecIds, running: ISessionId[], preferredLanguage?: string, existing?: IKernelId): Promise<IKernelId> {
+  let body = document.createElement('div');
+  let text = document.createElement('pre');
+  text.textContent = `Select kernel for "${path}"`;
+  body.appendChild(text);
+  if (existing) {
+    let displayName = specs.kernelspecs[existing.name].spec.display_name;
+    text.textContent += `\nCurrent: ${displayName}`;
+    text.title = `Path: ${path}\n` +
+    `Kernel Name: ${displayName}\n` +
+    `Kernel Id: ${existing.id}`;
+  }
+  let selector = document.createElement('select');
+  populateKernels(selector, specs, running, preferredLanguage);
+  return showDialog({
+    title: 'Select Kernel',
+    body,
+    host,
+    okText: 'SELECT'
+  }).then(result => {
+    if (result.text === 'SELECT') {
+      return JSON.parse(selector.value);
+    }
+    return void 0;
+  });
+}
+
+
+/**
+ * Populate a kernel dropdown list.
+ *
+ * @param node - The host html element.
+ *
+ * @param specs - The available kernel spec information.
+ *
+ * @param running - The list of running session ids.
+ *
+ * @param preferredLanguage - The preferred language for the kernel.
+ *
+ * #### Notes
+ * Populates the list with separated sections:
+ *   - Kernels matching the preferred language (display names).
+ *   - The remaining kernels.
+ *   - Sessions matching the preferred language (file names).
+ *   - The remaining sessions.
+ *   - "None" signifying no kernel.
+ * If no preferred language is given or no kernels are found using
+ * the preferred language, the default kernel is used in the first
+ * section.  Kernels are sorted by display name.  Sessions display the
+ * base name of the file with an ellipsis overflow and a tooltip with
+ * the explicit session information.
+ */
+export
+function populateKernels(node: HTMLSelectElement, specs: IKernelSpecIds, running: ISessionId[], preferredLanguage?: string): void {
+  // Clear any existing options.
+  while (node.firstChild) {
+    node.removeChild(node.firstChild);
+  }
+  let maxLength = 10;
+  // Create mappings of display names and languages for kernel name.
+  let displayNames: { [key: string]: string } = Object.create(null);
+  let languages: { [key: string]: string } = Object.create(null);
+  for (let name in specs.kernelspecs) {
+    displayNames[name] = specs.kernelspecs[name].spec.display_name;
+    maxLength = Math.max(maxLength, displayNames[name].length);
+    languages[name] = specs.kernelspecs[name].spec.language;
+  }
+  // Handle a preferred kernel language in order of display name.
+  let names: string[] = [];
+  if (preferredLanguage) {
+    for (let name in specs.kernelspecs) {
+      if (languages[name] === preferredLanguage) {
+        names.push(name);
+      }
+    }
+    names.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
+    for (let name of names) {
+      node.appendChild(optionForName(name, displayNames[name]));
+    }
+  }
+  // Use the default kernel if no preferred language or none were found.
+  if (!names) {
+    let name = specs.default;
+    node.appendChild(optionForName(name, displayNames[name]));
+  }
+  // Add a separator.
+  node.appendChild(createSeparatorOption(maxLength));
+  // Add the rest of the kernel names in alphabetical order.
+  let otherNames: string[] = [];
+  for (let name in specs.kernelspecs) {
+    if (names.indexOf(name) !== -1) {
+      continue;
+    }
+    otherNames.push(name);
+  }
+  otherNames.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
+  for (let name of otherNames) {
+    node.appendChild(optionForName(name, displayNames[name]));
+  }
+  // Add a separator option if there were any other names.
+  if (otherNames.length) {
+    node.appendChild(createSeparatorOption(maxLength));
+  }
+  // Add the sessions using the preferred language first.
+  let matchingSessions: ISessionId[] = [];
+  if (preferredLanguage) {
+    for (let session of running) {
+      if (languages[session.kernel.name] === preferredLanguage) {
+        matchingSessions.push(session);
+      }
+    }
+    if (matchingSessions) {
+      matchingSessions.sort((a, b) => {
+        return a.notebook.path.localeCompare(b.notebook.path);
+      });
+      for (let session of matchingSessions) {
+        let name = displayNames[session.kernel.name];
+        node.appendChild(optionForSession(session, name, maxLength));
+      }
+      node.appendChild(createSeparatorOption(maxLength));
+    }
+  }
+  // Add the other remaining sessions.
+  let otherSessions: ISessionId[] = [];
+  for (let session of running) {
+    if (matchingSessions.indexOf(session) === -1) {
+      otherSessions.push(session);
+    }
+  }
+  if (otherSessions) {
+    otherSessions.sort((a, b) => {
+      return a.notebook.path.localeCompare(b.notebook.path);
+    });
+    for (let session of otherSessions) {
+      let name = displayNames[session.kernel.name];
+      node.appendChild(optionForSession(session, name, maxLength));
+    }
+    node.appendChild(createSeparatorOption(maxLength));
+  }
+  // Add the option to have no kernel.
+  let option = document.createElement('option');
+  option.text = 'None';
+  option.value = 'null';
+  node.appendChild(option);
+  node.selectedIndex = 0;
+}
+
+
+/**
+ * Create a separator option.
+ */
+function createSeparatorOption(length: number): HTMLOptionElement {
+  let option = document.createElement('option');
+  option.disabled = true;
+  option.text = Array(length).join('─');
+  return option;
+}
+
+/**
+ * Create an option element for a kernel name.
+ */
+function optionForName(name: string, displayName: string): HTMLOptionElement {
+  let option = document.createElement('option');
+  option.text = displayName;
+  option.value = JSON.stringify({ name });
+  return option;
+}
+
+
+/**
+ * Create an option element for a session.
+ */
+function optionForSession(session: ISessionId, displayName: string, maxLength: number): HTMLOptionElement {
+  let option = document.createElement('option');
+  let sessionName = session.notebook.path.split('/').pop();
+  if (sessionName.length > maxLength) {
+    sessionName = sessionName.slice(0, maxLength - 3) + '...';
+  }
+  option.text = sessionName;
+  option.value = JSON.stringify({ id: session.kernel.id });
+  option.title = `Path: ${session.notebook.path}\n` +
+    `Kernel Name: ${displayName}\n` +
+    `Kernel Id: ${session.kernel.id}`;
+  return option;
+}

+ 59 - 10
src/docmanager/manager.ts

@@ -8,6 +8,9 @@ import {
   IKernelSpecIds, ISessionId
 } from 'jupyter-js-services';
 
+import * as utils
+  from 'jupyter-js-utils';
+
 import {
   IDisposable, DisposableDelegate
 } from 'phosphor-disposable';
@@ -40,9 +43,13 @@ import {
   ContextManager
 } from './context';
 
+import {
+  selectKernel
+} from './kernelselector';
+
 import {
   IDocumentContext, IModelFactory, IWidgetFactory, IWidgetFactoryOptions,
-  IFileType, IKernelPreference
+  IFileType, IKernelPreference, IFileCreator
 } from './interfaces';
 
 
@@ -67,11 +74,11 @@ class DocumentManager implements IDisposable {
   /**
    * Construct a new document manager.
    */
-  constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager, kernelSpecs: IKernelSpecIds, opener: IWidgetOpener) {
+  constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager, kernelspecs: IKernelSpecIds, opener: IWidgetOpener) {
     this._contentsManager = contentsManager;
     this._sessionManager = sessionManager;
-    this._specs = kernelSpecs;
-    this._contextManager = new ContextManager(contentsManager, sessionManager, kernelSpecs, (id: string, widget: Widget) => {
+    this._specs = kernelspecs;
+    this._contextManager = new ContextManager(contentsManager, sessionManager, kernelspecs, (id: string, widget: Widget) => {
       let parent = this._createWidget('', id);
       parent.setContent(widget);
       opener.open(parent);
@@ -87,7 +94,7 @@ class DocumentManager implements IDisposable {
    * #### Notes
    * This is a read-only property.
    */
-  get kernelSpecs(): IKernelSpecIds {
+  get kernelspecs(): IKernelSpecIds {
     return this._specs;
   }
 
@@ -144,7 +151,7 @@ class DocumentManager implements IDisposable {
    */
   registerWidgetFactory(factory: IWidgetFactory<Widget>, options: IWidgetFactoryOptions): IDisposable {
     let name = options.displayName;
-    let exOpt = options as Private.IWidgetFactoryEx;
+    let exOpt = utils.copy(options) as Private.IWidgetFactoryEx;
     exOpt.factory = factory;
     if (this._widgetFactories[name]) {
       throw new Error(`Duplicate registered factory ${name}`);
@@ -210,6 +217,37 @@ class DocumentManager implements IDisposable {
     });
   }
 
+  /**
+   * Register a Create New handler.
+   *
+   * @params creator - The file creator object.
+   *
+   * @params after - The optional item name to insert after.
+   *
+   * #### Notes
+   * If `after` is not given or not already registered, it will be moved
+   * to the end.
+   */
+  registerCreator(creator: IFileCreator, after?: string): IDisposable {
+    let added = false;
+    if (after) {
+      for (let existing of this._creators) {
+        if (existing.name === after) {
+          let index = this._creators.indexOf(existing);
+          this._creators.splice(index, 0, creator);
+          added = true;
+        }
+      }
+    }
+    if (!added) {
+      this._creators.push(creator);
+    }
+    return new DisposableDelegate(() => {
+      let index = this._creators.indexOf(creator);
+      this._creators.splice(index, 1);
+    });
+  }
+
   /**
    * Get the list of registered widget factory display names.
    *
@@ -266,6 +304,13 @@ class DocumentManager implements IDisposable {
     return this._fileTypes.slice();
   }
 
+  /**
+   * Get the ordered list of file creator names.
+   */
+  listCreators(): IFileCreator[] {
+    return this._creators.slice();
+  }
+
   /**
    * Get the kernel preference.
    */
@@ -489,6 +534,7 @@ class DocumentManager implements IDisposable {
   private _contextManager: ContextManager = null;
   private _specs: IKernelSpecIds = null;
   private _fileTypes: IFileType[] = [];
+  private _creators: IFileCreator[] = [];
 }
 
 
@@ -550,10 +596,13 @@ class DocumentWidget extends Widget {
    * Bring up a dialog to select a kernel.
    */
   selectKernel(): Promise<IKernel> {
-    // TODO: the dialog should take kernel information only,
-    // and return kernel information.  We then change the
-    // kernel in the context.
-    return void 0;
+    let context = this.context;
+    let path = context.path;
+    let specs = context.kernelspecs;
+    let lang = this.context.model.defaultKernelLanguage;
+    return context.listSessions().then(running => {
+      return selectKernel(this.node, path, specs, running, lang, context.kernel);
+    });
   }
 
   /**

+ 6 - 161
src/filebrowser/buttons.ts

@@ -18,6 +18,10 @@ import {
   DocumentManager, IKernelPreference
 } from '../docmanager';
 
+import {
+  populateKernels
+} from '../docmanager/kernelselector';
+
 import {
   FileBrowserModel
 } from './model';
@@ -305,7 +309,7 @@ class OpenWithHandler extends Widget {
   protected widgetChanged(): void {
     let widgetName = this.widgetDropdown.value;
     let preference = this._manager.getKernelPreference(this._ext, widgetName);
-    updateKernels(preference, this.kernelDropdown, this._manager.kernelSpecs, this._sessions);
+    updateKernels(preference, this.kernelDropdown, this._manager.kernelspecs, this._sessions);
   }
 
   private _ext = '';
@@ -497,7 +501,7 @@ class CreateNewHandler extends Widget {
     let ext = this.ext;
     let widgetName = this.widgetDropdown.value;
     let preference = this._manager.getKernelPreference(ext, widgetName);
-    updateKernels(preference, this.kernelDropdown, this._manager.kernelSpecs, this._sessions);
+    updateKernels(preference, this.kernelDropdown, this._manager.kernelspecs, this._sessions);
   }
 
   private _model: FileBrowserModel = null;
@@ -530,165 +534,6 @@ function updateKernels(preference: IKernelPreference, node: HTMLSelectElement, s
 }
 
 
-/**
- * Populate a kernel dropdown list.
- *
- * @param node - The host html element.
- *
- * @param specs - The available kernel spec information.
- *
- * @param running - The list of running session ids.
- *
- * @param preferredLanguage - The preferred language for the kernel.
- *
- * #### Notes
- * Populates the list with separated sections:
- *   - Kernels matching the preferred language (display names).
- *   - The remaining kernels.
- *   - Sessions matching the preferred language (file names).
- *   - The remaining sessions.
- *   - "None" signifying no kernel.
- * If no preferred language is given or no kernels are found using
- * the preferred language, the default kernel is used in the first
- * section.  Kernels are sorted by display name.  Sessions display the
- * base name of the file with an ellipsis overflow and a tooltip with
- * the explicit session information.
- */
-export
-function populateKernels(node: HTMLSelectElement, specs: IKernelSpecIds, running: ISessionId[], preferredLanguage?: string): void {
-  // Clear any existing options.
-  while (node.firstChild) {
-    node.removeChild(node.firstChild);
-  }
-  let maxLength = 10;
-  // Create mappings of display names and languages for kernel name.
-  let displayNames: { [key: string]: string } = Object.create(null);
-  let languages: { [key: string]: string } = Object.create(null);
-  for (let name in specs.kernelspecs) {
-    displayNames[name] = specs.kernelspecs[name].spec.display_name;
-    maxLength = Math.max(maxLength, displayNames[name].length);
-    languages[name] = specs.kernelspecs[name].spec.language;
-  }
-  // Handle a preferred kernel language in order of display name.
-  let names: string[] = [];
-  if (preferredLanguage) {
-    for (let name in specs.kernelspecs) {
-      if (languages[name] === preferredLanguage) {
-        names.push(name);
-      }
-    }
-    names.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
-    for (let name of names) {
-      node.appendChild(optionForName(name, displayNames[name]));
-    }
-  }
-  // Use the default kernel if no preferred language or none were found.
-  if (!names) {
-    let name = specs.default;
-    node.appendChild(optionForName(name, displayNames[name]));
-  }
-  // Add a separator.
-  node.appendChild(createSeparatorOption(maxLength));
-  // Add the rest of the kernel names in alphabetical order.
-  let otherNames: string[] = [];
-  for (let name in specs.kernelspecs) {
-    if (names.indexOf(name) !== -1) {
-      continue;
-    }
-    otherNames.push(name);
-  }
-  otherNames.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
-  for (let name of otherNames) {
-    node.appendChild(optionForName(name, displayNames[name]));
-  }
-  // Add a separator option if there were any other names.
-  if (otherNames.length) {
-    node.appendChild(createSeparatorOption(maxLength));
-  }
-  // Add the sessions using the preferred language first.
-  let matchingSessions: ISessionId[] = [];
-  if (preferredLanguage) {
-    for (let session of running) {
-      if (languages[session.kernel.name] === preferredLanguage) {
-        matchingSessions.push(session);
-      }
-    }
-    if (matchingSessions) {
-      matchingSessions.sort((a, b) => {
-        return a.notebook.path.localeCompare(b.notebook.path);
-      });
-      for (let session of matchingSessions) {
-        let name = displayNames[session.kernel.name];
-        node.appendChild(optionForSession(session, name, maxLength));
-      }
-      node.appendChild(createSeparatorOption(maxLength));
-    }
-  }
-  // Add the other remaining sessions.
-  let otherSessions: ISessionId[] = [];
-  for (let session of running) {
-    if (matchingSessions.indexOf(session) === -1) {
-      otherSessions.push(session);
-    }
-  }
-  if (otherSessions) {
-    otherSessions.sort((a, b) => {
-      return a.notebook.path.localeCompare(b.notebook.path);
-    });
-    for (let session of otherSessions) {
-      let name = displayNames[session.kernel.name];
-      node.appendChild(optionForSession(session, name, maxLength));
-    }
-    node.appendChild(createSeparatorOption(maxLength));
-  }
-  // Add the option to have no kernel.
-  let option = document.createElement('option');
-  option.text = 'None';
-  option.value = 'null';
-  node.appendChild(option);
-  node.selectedIndex = 0;
-}
-
-
-/**
- * Create a separator option.
- */
-function createSeparatorOption(length: number): HTMLOptionElement {
-  let option = document.createElement('option');
-  option.disabled = true;
-  option.text = Array(length).join('─');
-  return option;
-}
-
-/**
- * Create an option element for a kernel name.
- */
-function optionForName(name: string, displayName: string): HTMLOptionElement {
-  let option = document.createElement('option');
-  option.text = displayName;
-  option.value = JSON.stringify({ name });
-  return option;
-}
-
-
-/**
- * Create an option element for a session.
- */
-function optionForSession(session: ISessionId, displayName: string, maxLength: number): HTMLOptionElement {
-  let option = document.createElement('option');
-  let sessionName = session.notebook.path.split('/').pop();
-  if (sessionName.length > maxLength) {
-    sessionName = sessionName.slice(0, maxLength - 3) + '...';
-  }
-  option.text = sessionName;
-  option.value = JSON.stringify({ id: session.kernel.id });
-  option.title = `Path: ${session.notebook.path}\n` +
-    `Kernel Name: ${displayName}\n` +
-    `Kernel Id: ${session.kernel.id}`;
-  return option;
-}
-
-
 /**
  * The namespace for the `FileButtons` private data.
  */