Quellcode durchsuchen

Clean up the handler and registry

Steven Silvester vor 9 Jahren
Ursprung
Commit
e2ec54b0eb

+ 12 - 8
examples/filebrowser/src/index.ts

@@ -65,11 +65,9 @@ function main(): void {
 
   let fbModel = new FileBrowserModel(contentsManager, sessionsManager);
   let registry = new FileHandlerRegistry();
-  let fileHandler = new FileHandler(contentsManager, widget => {
-    dock.insertTabAfter(widget);
-    widgets.push(widget);
-  });
-  registry.addDefault(fileHandler);
+  let fileHandler = new FileHandler(contentsManager);
+
+  registry.addDefaultHandler(fileHandler);
 
   let fbWidget = new FileBrowserWidget(fbModel, registry);
 
@@ -79,6 +77,12 @@ function main(): void {
     'New Directory', dirCreator.createNew.bind(dirCreator));
   registry.addCreator('New File', fileCreator.createNew.bind(fileCreator));
 
+  let widgets: CodeMirrorWidget[] = [];
+  registry.opened.connect((r, widget) => {
+    dock.insertTabAfter(widget);
+    widgets.push(widget as CodeMirrorWidget);
+  });
+
   let panel = new SplitPanel();
   panel.id = 'main';
   panel.addChild(fbWidget);
@@ -88,8 +92,7 @@ function main(): void {
   SplitPanel.setStretch(dock, 1);
   dock.spacing = 8;
 
-  let widgets: Widget[] = [];
-  let activeWidget: Widget;
+  let activeWidget: CodeMirrorWidget;
 
   document.addEventListener('focus', event => {
     for (let i = 0; i < widgets.length; i++) {
@@ -127,7 +130,8 @@ function main(): void {
     sequence: ['Accel S'],
     selector: '.jp-CodeMirrorWidget',
     handler: () => {
-      fileHandler.save(activeWidget as any);
+      let path = fileHandler.findModel(activeWidget).path;
+      fileHandler.save(path);
       return true;
     }
   }]);

+ 3 - 3
src/filebrowser/browser.ts

@@ -170,7 +170,7 @@ class FileBrowserWidget extends Widget {
         continue;
       }
       if (item.type !== 'directory') {
-        this._registry.revert(item);
+        this._registry.revert(item.path);
       }
     }
   }
@@ -185,7 +185,7 @@ class FileBrowserWidget extends Widget {
         continue;
       }
       if (item.type !== 'directory') {
-        this._registry.save(item);
+        this._registry.save(item.path);
       }
     }
   }
@@ -200,7 +200,7 @@ class FileBrowserWidget extends Widget {
         continue;
       }
       if (item.type !== 'directory') {
-        this._registry.close(item);
+        this._registry.close(item.path);
       }
     }
   }

+ 3 - 5
src/filebrowser/listing.ts

@@ -1031,11 +1031,9 @@ class DirListing extends Widget {
     });
     this._drag.mimeData.setData(utils.CONTENTS_MIME, null);
     if (item && item.type !== 'directory') {
-      if (this._registry.canOpen(item)) {
-        this._drag.mimeData.setData(FACTORY_MIME, () => {
-          this._registry.open(item);
-        });
-      }
+      this._drag.mimeData.setData(FACTORY_MIME, () => {
+        this._registry.open(item);
+      });
     }
 
     // Start the drag and remove the mousemove listener.

+ 1 - 1
src/filehandler/default.ts

@@ -58,7 +58,7 @@ class FileHandler extends AbstractFileHandler<CodeMirrorWidget> {
     let widget = new CodeMirrorWidget();
     widget.addClass(EDITOR_CLASS);
     CodeMirror.on(widget.editor.getDoc(), 'change', () => {
-      this.setDirty(widget);
+      this.setDirty(model.path);
     });
     return widget;
   }

+ 59 - 34
src/filehandler/handler.ts

@@ -42,12 +42,16 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
    * Construct a new source file handler.
    *
    * @param manager - The contents manager used to save/load files.
-   *
-   * @param cb - The function called when a widget is created.
    */
-  constructor(manager: IContentsManager, cb: (widget: T) => void) {
+  constructor(manager: IContentsManager) {
     this._manager = manager;
-    this._cb = cb;
+  }
+
+  /**
+   * A signal emitted when a file opens.
+   */
+  get opened(): ISignal<AbstractFileHandler<T>, T> {
+    return Private.openedSignal.bind(this);
   }
 
   /**
@@ -74,7 +78,7 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
   /**
    * Find a widget given a path.
    */
-  findByPath(path: string): T {
+  findWidget(path: string): T {
     for (let w of this._widgets) {
       let model = this._getModel(w);
       if (model.path === path) {
@@ -83,11 +87,19 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
     }
   }
 
+  /**
+   * Find a model given a widget.  The model itself will have a
+   * null `content` field.
+   */
+  findModel(widget: T): IContentsModel {
+    return Private.modelProperty.get(widget);
+  }
+
   /**
    * Open a contents model and return a widget.
    */
   open(model: IContentsModel): T {
-    let widget = this.findByPath(model.path);
+    let widget = this.findWidget(model.path);
     if (!widget) {
       widget = this.createWidget(model);
       widget.title.closable = true;
@@ -102,10 +114,9 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
       widget.title.text = this.getTitleText(model);
       return this.populateWidget(widget, contents);
     }).then(contents => {
-      this.clearDirty(widget);
+      this.clearDirty(model.path);
     });
-    let cb = this._cb;
-    if (cb) cb(widget);
+    this.opened.emit(widget);
     return widget;
   }
 
@@ -113,12 +124,12 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
    * Rename a file.
    */
   rename(oldPath: string, newPath: string): boolean {
-    let widget = this.findByPath(oldPath);
+    let widget = this.findWidget(oldPath);
     if (widget === void 0) {
       return false;
     }
     if (newPath === void 0) {
-      this.clearDirty(widget);
+      this.clearDirty(oldPath);
       widget.close();
       return true;
     }
@@ -131,16 +142,17 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
   }
 
   /**
-   * Save widget contents.
+   * Save contents.
    *
-   * @param widget - The widget to save (defaults to current active widget).
+   * @param path - The path of the file to save.
    *
-   * returns A promise that resolves to the contents of the widget.
+   * returns A promise that resolves to the contents of the path.
    *
    * #### Notes
-   * This clears the dirty state of the widget after a successful save.
+   * This clears the dirty state of the file after a successful save.
    */
-  save(widget: T): Promise<IContentsModel> {
+  save(path: string): Promise<IContentsModel> {
+    let widget = this.findWidget(path);
     if (!widget) {
       return Promise.resolve(void 0);
     }
@@ -148,22 +160,23 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
     return this.getSaveOptions(widget, model).then(opts => {
       return this.manager.save(model.path, opts);
     }).then(contents => {
-      this.clearDirty(widget);
+      this.clearDirty(path);
       return contents;
     });
   }
 
   /**
-   * Revert widget contents.
+   * Revert contents.
    *
-   * @param widget - The widget to revert (defaults to current active widget).
+   * @param path - The path of the file to revert.
    *
-   * returns A promise that resolves to the new contents of the widget.
+   * returns A promise that resolves to the new contents of the path.
    *
    * #### Notes
-   * This clears the dirty state of the widget after a successful revert.
+   * This clears the dirty state of the file after a successful revert.
    */
-  revert(widget: T): Promise<IContentsModel> {
+  revert(path: string): Promise<IContentsModel> {
+    let widget = this.findWidget(path);
     if (!widget) {
       return Promise.resolve(void 0);
     }
@@ -172,23 +185,24 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
     return this.manager.get(model.path, opts).then(contents => {
       return this.populateWidget(widget, contents);
     }).then(contents => {
-      this.clearDirty(widget);
+      this.clearDirty(path);
       return contents;
     });
   }
 
   /**
-   * Close a widget.
+   * Close a file.
    *
-   * @param widget - The widget to close (defaults to current active widget).
+   * @param path - The path of the file to close.
    *
-   * returns A boolean indicating whether the widget was closed.
+   * returns A boolean indicating whether the file was closed.
    */
-  close(widget: T): Promise<boolean> {
+  close(path: string): Promise<boolean> {
+    let widget = this.findWidget(path);
     if (!widget) {
       return Promise.resolve(false);
     }
-    if (this.isDirty(widget)) {
+    if (this.isDirty(path)) {
       return this._maybeClose(widget);
     }
     this._close(widget);
@@ -196,7 +210,7 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
   }
 
   /**
-   * Close all widgets.
+   * Close all files.
    */
   closeAll(): void {
     for (let w of this._widgets) {
@@ -205,23 +219,26 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
   }
 
   /**
-   * Get whether a widget is dirty (defaults to current active widget).
+   * Get whether a file is dirty.
    */
-  isDirty(widget: T): boolean {
+  isDirty(path: string): boolean {
+    let widget = this.findWidget(path);
     return Private.dirtyProperty.get(widget);
   }
 
   /**
    * Set the dirty state of a widget (defaults to current active widget).
    */
-  setDirty(widget: T): void {
+  setDirty(path: string): void {
+    let widget = this.findWidget(path);
     Private.dirtyProperty.set(widget, true);
   }
 
   /**
    * Clear the dirty state of a widget (defaults to current active widget).
    */
-  clearDirty(widget: T): void {
+  clearDirty(path: string): void {
+    let widget = this.findWidget(path);
     Private.dirtyProperty.set(widget, false);
   }
 
@@ -231,7 +248,8 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
   filterMessage(handler: IMessageHandler, msg: Message): boolean {
     let widget = handler as T;
     if (msg.type === 'close-request' && widget) {
-      this.close(widget);
+      let path = this.findModel(widget).path;
+      this.close(path);
       return true;
     }
     return false;
@@ -310,6 +328,7 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
    * Actually close the file.
    */
   private _close(widget: T): void {
+    let model = Private.modelProperty.get(widget);
     widget.dispose();
     let index = this._widgets.indexOf(widget);
     this._widgets.splice(index, 1);
@@ -325,6 +344,12 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
  * A private namespace for AbstractFileHandler data.
  */
 namespace Private {
+  /**
+   * A signal emitted when a model is opened.
+   */
+  export
+  const openedSignal = new Signal<AbstractFileHandler<Widget>, Widget>();
+
   /**
    * An attached property with the widget model.
    */

+ 88 - 23
src/filehandler/registry.ts

@@ -6,6 +6,10 @@ import {
   IContentsModel
 } from 'jupyter-js-services';
 
+import {
+  ISignal, Signal
+} from 'phosphor-signaling';
+
 import {
   Widget
 } from 'phosphor-widget';
@@ -20,21 +24,35 @@ import {
  */
 export
 class FileHandlerRegistry {
+  /**
+   * A signal emitted when a file is opened.
+   */
+  get opened(): ISignal<FileHandlerRegistry, Widget> {
+    return Private.openedSignal.bind(this);
+  }
+
+  /**
+   * A signal emitted when a file is created.
+   */
+  get created(): ISignal<FileHandlerRegistry, IContentsModel> {
+    return Private.createdSignal.bind(this);
+  }
+
   /**
    * Register a file handler.
    */
-  add(handler: AbstractFileHandler<Widget>): void {
+  addHandler(handler: AbstractFileHandler<Widget>): void {
     this._handlers.push(handler);
   }
 
   /**
    * Register a default file handler.
    */
-  addDefault(handler: AbstractFileHandler<Widget>): void {
+  addDefaultHandler(handler: AbstractFileHandler<Widget>): void {
     if (this._default) {
       throw Error('Default handler already registered');
     }
-    this.add(handler);
+    this.addHandler(handler);
     this._default = handler;
   }
 
@@ -58,19 +76,14 @@ class FileHandlerRegistry {
   createNew(name: string, path: string): Promise<IContentsModel> {
     let creator = this._creators[name];
     if (creator) {
-      return creator(path);
+      return creator(path).then(model => {
+        this.created.emit(model);
+        return model;
+      });
     }
     return Promise.reject(new Error(`No handler named ${name}`));
   }
 
-  /**
-   * Test whether a model can be opened.
-   */
-  canOpen(model: IContentsModel): boolean {
-    let handler = this.findHandler(model);
-    return handler !== void 0;
-  }
-
   /**
    * Open a contents model by name.
    */
@@ -79,7 +92,8 @@ class FileHandlerRegistry {
     if (handler === void 0) {
       return;
     }
-    handler.open(model);
+    let widget = handler.open(model);
+    this.opened.emit(widget);
   }
 
   /**
@@ -87,7 +101,7 @@ class FileHandlerRegistry {
    */
   rename(oldPath: string, newPath: string): boolean {
     for (let h of this._handlers) {
-      if (h.findByPath(oldPath)) {
+      if (h.findWidget(oldPath)) {
         return h.rename(oldPath, newPath);
       }
     }
@@ -97,11 +111,11 @@ class FileHandlerRegistry {
   /**
    * Save a file.
    */
-  save(model: IContentsModel): Promise<IContentsModel> {
+  save(path: string): Promise<IContentsModel> {
     for (let h of this._handlers) {
-      let w = h.findByPath(model.path);
+      let w = h.findWidget(path);
       if (w !== void 0) {
-        return h.save(w);
+        return h.save(path);
       }
     }
   }
@@ -109,11 +123,11 @@ class FileHandlerRegistry {
   /**
    * Revert a file.
    */
-  revert(model: IContentsModel): Promise<IContentsModel> {
+  revert(path: string): Promise<IContentsModel> {
     for (let h of this._handlers) {
-      let w = h.findByPath(model.path);
+      let w = h.findWidget(path);
       if (w !== void 0) {
-        return h.revert(w);
+        return h.revert(path);
       }
     }
   }
@@ -121,11 +135,44 @@ class FileHandlerRegistry {
   /**
    * Close a file.
    */
-  close(model: IContentsModel): Promise<boolean> {
+  close(path: string): Promise<boolean> {
+    for (let h of this._handlers) {
+      let w = h.findWidget(path);
+      if (w !== void 0) {
+        return h.close(path);
+      }
+    }
+  }
+
+  /**
+   * Close all files.
+   */
+  closeAll(): void {
     for (let h of this._handlers) {
-      let w = h.findByPath(model.path);
+      h.closeAll();
+    }
+  }
+
+  /**
+   * Find the model for a given widget.
+   */
+  findModel(widget: Widget): IContentsModel {
+    for (let h of this._handlers) {
+      let model = h.findModel(widget);
+      if (model) {
+        return model;
+      }
+    }
+  }
+
+  /**
+   * Find the widget for a given file.
+   */
+  findWidget(path: string): Widget {
+    for (let h of this._handlers) {
+      let w = h.findWidget(path);
       if (w !== void 0) {
-        return h.close(w);
+        return w;
       }
     }
   }
@@ -166,3 +213,21 @@ class FileHandlerRegistry {
   private _default: AbstractFileHandler<Widget> = null;
   private _creators: { [key: string]: (path: string) => Promise<IContentsModel> } = Object.create(null);
 }
+
+
+/**
+ * A private namespace for FileHandlerRegistry data.
+ */
+namespace Private {
+  /**
+   * A signal emitted when a file is opened.
+   */
+  export
+  const openedSignal = new Signal<FileHandlerRegistry, Widget>();
+
+  /**
+   * A signal emitted when a file is created.
+   */
+  export
+  const createdSignal = new Signal<FileHandlerRegistry, IContentsModel>();
+}