浏览代码

Rewrite how default widgets in the docmanager work.

I also changed the wildcard extension from .* to just *, which seems to make more sense.
Jason Grout 8 年之前
父节点
当前提交
b3a805cc3e

+ 2 - 2
examples/filebrowser/src/index.ts

@@ -88,8 +88,8 @@ function createApp(manager: IServiceManager): void {
   docRegistry.addWidgetFactory(wFactory, {
     displayName: 'Editor',
     modelName: 'text',
-    fileExtensions: ['.*'],
-    defaultFor: ['.*'],
+    fileExtensions: ['*'],
+    defaultFor: ['*'],
     preferKernel: false,
     canStartKernel: true
   });

+ 29 - 19
src/docmanager/manager.ts

@@ -30,6 +30,26 @@ import {
   DocumentWidgetManager
 } from './widgetmanager';
 
+/**
+ * Return the extension, given a path.
+ *
+ * @param path - the file path.
+ *
+ * @returns the extension for the filepath, with the '.' included, or '' if no extension.
+ *
+ * #### TODO
+ * This should call the jupyter-js-services to get the extension
+ */
+function extname(path: string): string {
+  let parts = path.split('.');
+  let ext: string;
+  if (parts.length === 1 || (parts[0] === '' && parts.length === 2)) {
+    ext = '';
+  } else {
+    ext = '.' + parts.pop().toLowerCase();
+  }
+  return ext;
+}
 
 /**
  * The document manager.
@@ -112,21 +132,14 @@ class DocumentManager implements IDisposable {
    *
    * @param path - The file path to open.
    *
-   * @param widgetName - The name of the widget factory to use.
+   * @param widgetName - The name of the widget factory to use. 'default' will use the default widget.
    *
    * @param kernel - An optional kernel name/id to override the default.
    */
   open(path: string, widgetName='default', kernel?: IKernel.IModel): Widget {
     let registry = this._registry;
     if (widgetName === 'default') {
-      let parts = path.split('.');
-      let ext: string;
-      if (parts.length === 1 || (parts[0] === '' && parts.length === 2)) {
-        ext = '';
-      } else {
-        ext = '.' + parts.pop().toLowerCase();
-      }
-      widgetName = registry.listWidgetFactories(ext)[0];
+      widgetName = registry.defaultWidgetFactory(extname(path));
     }
     let mFactory = registry.getModelFactory(widgetName);
     if (!mFactory) {
@@ -153,21 +166,14 @@ class DocumentManager implements IDisposable {
    *
    * @param path - The file path to use.
    *
-   * @param widgetName - The name of the widget factory to use.
+   * @param widgetName - The name of the widget factory to use. 'default' will use the default widget.
    *
    * @param kernel - An optional kernel name/id to override the default.
    */
   createNew(path: string, widgetName='default', kernel?: IKernel.IModel): Widget {
     let registry = this._registry;
     if (widgetName === 'default') {
-      let parts = path.split('.');
-      let ext: string;
-      if (parts.length === 1 || (parts[0] === '' && parts.length === 2)) {
-        ext = '';
-      } else {
-        ext = '.' + parts.pop().toLowerCase();
-      }
-      widgetName = registry.listWidgetFactories(ext)[0];
+      widgetName = registry.defaultWidgetFactory(extname(path));
     }
     let mFactory = registry.getModelFactory(widgetName);
     if (!mFactory) {
@@ -211,13 +217,17 @@ class DocumentManager implements IDisposable {
   /**
    * See if a widget already exists for the given path and widget name.
    *
+   * @param path - The file path to use.
+   *
+   * @param widgetName - The name of the widget factory to use. 'default' will use the default widget.
+   *
    * #### Notes
    * This can be used to use an existing widget instead of opening
    * a new widget.
    */
   findWidget(path: string, widgetName='default'): Widget {
     if (widgetName === 'default') {
-      widgetName = this._registry.defaultWidgetFactory;
+      widgetName = this._registry.defaultWidgetFactory(extname(path));
     }
     return this._widgetManager.findWidget(path, widgetName);
   }

+ 4 - 2
src/docregistry/interfaces.ts

@@ -229,7 +229,7 @@ interface IWidgetFactoryOptions {
    * The file extensions the widget can view.
    *
    * #### Notes
-   * Use ".*" to denote all files.
+   * Use "*" to denote all files. Specific file extensions must be preceded with '.', like '.png', '.txt', etc.
    */
   fileExtensions: string[];
 
@@ -247,7 +247,9 @@ interface IWidgetFactoryOptions {
    * The file extensions for which the factory should be the default.
    *
    * #### Notes
-   * Use ".*" to denote all files.
+   * Use "*" to denote all files. Specific file extensions must be preceded with '.', like '.png', '.txt', etc. Entries in this attribute must also be included in the fileExtensions attribute.
+   *
+   * **See also:** [[fileExtensions]].
    */
   defaultFor?: string[];
 

+ 69 - 53
src/docregistry/registry.ts

@@ -24,16 +24,6 @@ import {
  */
 export
 class DocumentRegistry implements IDisposable {
-  /**
-   * The name of the default widget factory.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get defaultWidgetFactory(): string {
-    return this._defaultWidgetFactory;
-  }
-
   /**
    * Get whether the document registry has been disposed.
    */
@@ -70,7 +60,7 @@ class DocumentRegistry implements IDisposable {
    * #### Notes
    * If a factory with the given `displayName` is already registered,
    * an error will be thrown.
-   * If `'.*'` is given as a default extension, the factory will be registered
+   * If `'*'` is given as a default extension, the factory will be registered
    * as the global default.
    * If a factory is already registered as a default for a given extension or
    * as the global default, this factory will override the existing default.
@@ -84,23 +74,38 @@ class DocumentRegistry implements IDisposable {
     }
     this._widgetFactories[name] = exOpt;
     if (options.defaultFor) {
-      for (let option of options.defaultFor) {
-        if (option === '.*') {
+      for (let ext of options.defaultFor) {
+        if (options.fileExtensions.indexOf(ext) === -1) {
+          continue;
+        }
+        if (ext === '*') {
           this._defaultWidgetFactory = name;
-        } else if (options.fileExtensions.indexOf(option) !== -1) {
-          this._defaultWidgetFactories[option] = name;
+        } else {
+          this._defaultWidgetFactories[ext] = name;
         }
       }
     }
+    // For convenience, store a mapping of ext -> name
+    for (let ext of options.fileExtensions) {
+      if (!this._widgetFactoryExtensions[ext]) {
+        this._widgetFactoryExtensions[ext] = new Set<string>();
+      }
+      this._widgetFactoryExtensions[ext].add(name);
+    }
     return new DisposableDelegate(() => {
       delete this._widgetFactories[name];
       if (this._defaultWidgetFactory === name) {
         this._defaultWidgetFactory = '';
       }
-      for (let opt of Object.keys(this._defaultWidgetFactories)) {
-        let n = this._defaultWidgetFactories[opt];
-        if (n === name) {
-          delete this._defaultWidgetFactories[opt];
+      for (let ext of Object.keys(this._defaultWidgetFactories)) {
+        if (this._defaultWidgetFactories[ext] === name) {
+          delete this._defaultWidgetFactories[ext];
+        }
+      }
+      for (let ext of Object.keys(this._widgetFactoryExtensions)) {
+        this._widgetFactoryExtensions[ext].delete(name);
+        if (this._widgetFactoryExtensions[ext].size === 0) {
+          delete this._widgetFactoryExtensions[ext];
         }
       }
     });
@@ -208,47 +213,57 @@ class DocumentRegistry implements IDisposable {
    * @returns A new array of registered widget factory names.
    *
    * #### Notes
-   * The first item in the list is considered the default.
+   * The first item in the list is considered the default. The returned list
+   * has factories in the following order:
+   * - extension-specific default factory
+   * - extension-specific factories
+   * - last registered global default factory
+   * - all other global default factories
    */
-  listWidgetFactories(ext?: string): string[] {
-    ext = ext || '';
-    let factories: string[] = [];
-    let options: Private.IWidgetFactoryEx;
-    let name = '';
-    // If an extension was given, filter by extension.
-    // Make sure the modelFactory is registered.
+  listWidgetFactories(ext: string = '*'): string[] {
+    let factories = new Set<string>();
     if (ext.length > 1) {
       if (ext in this._defaultWidgetFactories) {
-        name = this._defaultWidgetFactories[ext];
-        options = this._widgetFactories[name];
-        if (options.modelName in this._modelFactories) {
-          factories.push(name);
-        }
-      }
-    }
-    // Add the rest of the valid widgetFactories that can open the path.
-    for (name in this._widgetFactories) {
-      if (factories.indexOf(name) !== -1) {
-        continue;
-      }
-      options = this._widgetFactories[name];
-      if (!(options.modelName in this._modelFactories)) {
-        continue;
+        factories.add(this._defaultWidgetFactories[ext]);
       }
-      let exts = options.fileExtensions;
-      if ((exts.indexOf(ext) !== -1) || (exts.indexOf('.*') !== -1)) {
-        factories.push(name);
+
+      // Add extension-specific factories in registration order
+      if (ext in this._widgetFactoryExtensions) {
+        this._widgetFactoryExtensions[ext].forEach(n => { factories.add(n); });
       }
     }
-    // Add the default widget if it was not already added.
-    name = this._defaultWidgetFactory;
-    if (name && factories.indexOf(name) === -1) {
-      options = this._widgetFactories[name];
-      if (options.modelName in this._modelFactories) {
-        factories.push(name);
-      }
+
+    // Add the gobal default factory
+    if (this._defaultWidgetFactory) {
+      factories.add(this._defaultWidgetFactory);
+    }
+
+    // Add the rest of the global factories, in registration order
+    if ('*' in this._widgetFactoryExtensions) {
+      this._widgetFactoryExtensions['*'].forEach(n => { factories.add(n); });
     }
-    return factories;
+
+    // Construct the return list, checking to make sure the corresponding
+    // model factories are registered.
+    let factoryList: string[] = [];
+    factories.forEach(name => {
+      if (this._widgetFactories[name].modelName in this._modelFactories) {
+        factoryList.push(name);
+      }
+    });
+
+    return factoryList;
+  }
+
+  /**
+   * Return the name of the default widget factory for a given extension.
+   *
+   * @param ext - An optional file extension.
+   *
+   * @returns The default widget factory name for the extension (if given) or the global default.
+   */
+  defaultWidgetFactory(ext: string = '*') {
+    return this.listWidgetFactories(ext)[0];
   }
 
   /**
@@ -379,6 +394,7 @@ class DocumentRegistry implements IDisposable {
   private _widgetFactories: { [key: string]: Private.IWidgetFactoryEx } = Object.create(null);
   private _defaultWidgetFactory = '';
   private _defaultWidgetFactories: { [key: string]: string } = Object.create(null);
+  private _widgetFactoryExtensions: {[key: string]: Set<string> } = Object.create(null);
   private _fileTypes: IFileType[] = [];
   private _creators: IFileCreator[] = [];
   private _extenders: { [key: string] : IWidgetExtension<Widget, IDocumentModel>[] } = Object.create(null);

+ 2 - 2
src/editorwidget/plugin.ts

@@ -95,10 +95,10 @@ function activateEditorHandler(app: Application, registry: DocumentRegistry, mai
   mainMenu.addItem(editorMenu, {rank: 30});
   registry.addWidgetFactory(new EditorWidgetFactory(),
   {
-    fileExtensions: ['.*'],
+    fileExtensions: ['*'],
     displayName: 'Editor',
     modelName: 'text',
-    defaultFor: ['.*'],
+    defaultFor: ['*'],
     preferKernel: false,
     canStartKernel: false
   });

+ 2 - 1
src/typings.d.ts

@@ -1,3 +1,4 @@
+/// <reference path="../typings/es6-collections/es6-collections.d.ts"/>
 /// <reference path="../typings/es6-promise/es6-promise.d.ts"/>
 /// <reference path="../typings/codemirror/codemirror.d.ts"/>
 /// <reference path="../typings/moment/moment.d.ts"/>
@@ -8,5 +9,5 @@
 /// <reference path="../typings/require/require.d.ts"/>
 /// <reference path="../typings/xterm/xterm.d.ts"/>
 /// <reference path="../typings/marked/marked.d.ts"/>
-/// <reference path="../typings/d3-dsv/d3-dsv.d.ts"/>
 /// <reference path="../typings/leaflet/leaflet.d.ts"/>
+/// <reference path="../typings/d3-dsv/d3-dsv.d.ts"/>

+ 113 - 0
typings/es6-collections/es6-collections.d.ts

@@ -0,0 +1,113 @@
+// Type definitions for es6-collections v0.5.1
+// Project: https://github.com/WebReflection/es6-collections/
+// Definitions by: Ron Buckton <http://github.com/rbuckton>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+/* *****************************************************************************
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+
+THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+
+See the Apache Version 2.0 License for specific language governing permissions
+and limitations under the License.
+***************************************************************************** */
+
+interface IteratorResult<T> {
+    done: boolean;
+    value?: T;
+}
+
+interface Iterator<T> {
+    next(value?: any): IteratorResult<T>;
+    return?(value?: any): IteratorResult<T>;
+    throw?(e?: any): IteratorResult<T>;
+}
+
+interface ForEachable<T> {
+    forEach(callbackfn: (value: T) => void): void;
+}
+
+interface Map<K, V> {
+    clear(): void;
+    delete(key: K): boolean;
+    forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
+    get(key: K): V;
+    has(key: K): boolean;
+    set(key: K, value?: V): Map<K, V>;
+    entries(): Iterator<[K, V]>;
+    keys(): Iterator<K>;
+    values(): Iterator<V>;
+    size: number;
+}
+
+interface MapConstructor {
+    new <K, V>(): Map<K, V>;
+    new <K, V>(iterable: ForEachable<[K, V]>): Map<K, V>;
+    prototype: Map<any, any>;
+}
+
+declare var Map: MapConstructor;
+
+interface Set<T> {
+    add(value: T): Set<T>;
+    clear(): void;
+    delete(value: T): boolean;
+    forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
+    has(value: T): boolean;
+    entries(): Iterator<[T, T]>;
+    keys(): Iterator<T>;
+    values(): Iterator<T>;
+    size: number;
+}
+
+interface SetConstructor {
+    new <T>(): Set<T>;
+    new <T>(iterable: ForEachable<T>): Set<T>;
+    prototype: Set<any>;
+}
+
+declare var Set: SetConstructor;
+
+interface WeakMap<K, V> {
+    delete(key: K): boolean;
+	clear(): void;
+    get(key: K): V;
+    has(key: K): boolean;
+    set(key: K, value?: V): WeakMap<K, V>;
+}
+
+interface WeakMapConstructor {
+    new <K, V>(): WeakMap<K, V>;
+    new <K, V>(iterable: ForEachable<[K, V]>): WeakMap<K, V>;
+    prototype: WeakMap<any, any>;
+}
+
+declare var WeakMap: WeakMapConstructor;
+
+interface WeakSet<T> {
+    delete(value: T): boolean;
+	clear(): void;
+    add(value: T): WeakSet<T>;
+    has(value: T): boolean;
+}
+
+interface WeakSetConstructor {
+    new <T>(): WeakSet<T>;
+    new <T>(iterable: ForEachable<T>): WeakSet<T>;
+    prototype: WeakSet<any>;
+}
+
+declare var WeakSet: WeakSetConstructor;
+
+declare module "es6-collections" {
+    var Map: MapConstructor;
+    var Set: SetConstructor;
+    var WeakMap: WeakMapConstructor;
+    var WeakSet: WeakSetConstructor;
+}

+ 2 - 11
typings/es6-promise/es6-promise.d.ts

@@ -16,6 +16,8 @@ See the Apache Version 2.0 License for specific language governing permissions
 and limitations under the License.
 ***************************************************************************** */
 
+/// <reference path="../es6-collections/es6-collections.d.ts"/>
+
 interface Symbol {
     /** Returns a string representation of an object. */
     toString(): string;
@@ -122,17 +124,6 @@ interface SymbolConstructor {
 }
 declare var Symbol: SymbolConstructor;
 
-interface IteratorResult<T> {
-    done: boolean;
-    value?: T;
-}
-
-interface Iterator<T> {
-    next(value?: any): IteratorResult<T>;
-    return?(value?: any): IteratorResult<T>;
-    throw?(e?: any): IteratorResult<T>;
-}
-
 interface Iterable<T> {
     //[Symbol.iterator](): Iterator<T>;
 }