Ver código fonte

Add initial content

Steven Silvester 9 anos atrás
pai
commit
5ed1159ac4

+ 56 - 0
package.json

@@ -0,0 +1,56 @@
+{
+  "name": "jupyter-js-ui",
+  "version": "0.0.1",
+  "description": "JavaScript UI Components for Jupyter",
+  "main": "lib/index.js",
+  "typings": "lib/index.d.ts",
+  "dependencies": {
+  },
+  "devDependencies": {
+    "css-loader": "^0.23.1",
+    "expect.js": "^0.3.1",
+    "file-loader": "^0.8.5",
+    "fs-extra": "^0.26.4",
+    "istanbul-instrumenter-loader": "^0.1.3",
+    "json-loader": "^0.5.4",
+    "karma": "^0.13.19",
+    "karma-chrome-launcher": "^0.2.2",
+    "karma-coverage": "^0.5.3",
+    "karma-firefox-launcher": "^0.1.7",
+    "karma-ie-launcher": "^0.2.0",
+    "karma-mocha": "^0.2.1",
+    "karma-mocha-reporter": "^1.1.5",
+    "mocha": "^2.3.4",
+    "rimraf": "^2.5.0",
+    "style-loader": "^0.13.0",
+    "typedoc": "^0.3.12",
+    "typescript": "^1.7.5",
+    "url-loader": "^0.5.7",
+    "webpack": "^1.12.11"
+  },
+  "scripts": {
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/jupyter/jupyter-js-ui"
+  },
+  "keywords": [
+    "jupyter",
+    "filebrowser",
+    "terminal"
+  ],
+  "files": [
+    "lib/*.css",
+    "lib/*.d.ts",
+    "lib/*.js",
+    "lib/**/*.css",
+    "lib/**/*.d.ts",
+    "lib/**/*.js"
+  ],
+  "author": "Project Jupyter",
+  "license": "BSD-3-Clause",
+  "bugs": {
+    "url": "https://github.com/jupyter/jupyter-js-ui/issues"
+  },
+  "homepage": "https://github.com/jupyter/jupyter-js-ui"
+}

+ 64 - 0
src/clipboard/index.ts

@@ -0,0 +1,64 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+/**
+ * Copy text to the system clipboard.
+ *
+ * #### Notes
+ * This can only be called in response to a user input event.
+ */
+export
+function copyToClipboard(text: string): void {
+  let node = document.body;
+  let handler = (event: ClipboardEvent) => {
+    let data = event.clipboardData || (window as any).clipboardData;
+    data.setData('text', text);
+    event.preventDefault();
+    node.removeEventListener('copy', handler);
+  };
+  node.addEventListener('copy', handler);
+  generateClipboardEvent(node);
+}
+
+
+/**
+ * Generate a clipboard event on a node.
+ *
+ * @param node - The element on which to generate the event.
+ *
+ * @param type - The type of event to generate: `'copy'` or `'cut'`.
+ *   `'paste'` events cannot be programmatically generated.
+ *
+ * #### Notes
+ * This can only be called in response to a user input event.
+ */
+export
+function generateClipboardEvent(node: HTMLElement, type='copy'): void {
+  // http://stackoverflow.com/a/5210367
+
+  // Identify selected text.
+  var sel = window.getSelection();
+
+  // Save the current selection.
+  var savedRanges: any[] = [];
+  for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+    savedRanges[i] = sel.getRangeAt(i).cloneRange();
+  }
+
+  // Select the node content.
+  var range = document.createRange();
+  range.selectNodeContents(node);
+  sel.removeAllRanges();
+  sel.addRange(range);
+
+  // Execute the command.
+  document.execCommand(type);
+
+  // Restore the previous selection.
+  sel = window.getSelection();
+  sel.removeAllRanges();
+  for (var i = 0, len = savedRanges.length; i < len; ++i) {
+    sel.addRange(savedRanges[i]);
+  }
+}

+ 61 - 0
src/dialog/dialog.css

@@ -0,0 +1,61 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) 2014-2016, Jupyter Development Team.
+|
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+.jp-Dialog {
+  position: absolute;
+  z-index: 10000;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  top: 0px;
+  left: 0px;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+}
+
+
+.jp-Dialog-content {
+  margin-left: auto;
+  margin-right: auto;
+  text-align: center;
+  vertical-align: middle;
+}
+
+
+.jp-Dialog-footer {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  margin: 0;
+  padding: 0;
+}
+
+
+.jp-Dialog-button {
+  flex: 1 1 auto;
+}
+
+
+.jp-Dialog-header {
+  display: flex;
+  flex-direction: row;
+}
+
+.jp-Dialog-title {
+  flex: 1 1 auto;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+
+.jp-Dialog-close {
+  flex: 0 0 auto;
+}
+

+ 252 - 0
src/dialog/dialog.ts

@@ -0,0 +1,252 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.'use strict';
+
+import './dialog.css';
+
+
+/**
+ * The class name added to dialog instances.
+ */
+const DIALOG_CLASS = 'jp-Dialog';
+
+/**
+ * The class name added to dialog content node.
+ */
+const CONTENT_CLASS = 'jp-Dialog-content';
+
+/**
+ * The class name added to dialog header node.
+ */
+const HEADER_CLASS = 'jp-Dialog-header';
+
+/**
+ * The class name added to dialog title node.
+ */
+const TITLE_CLASS = 'jp-Dialog-title';
+
+/**
+ * The class name added to dialog close icon node.
+ */
+const CLOSE_ICON_CLASS = 'jp-Dialog-close';
+
+/**
+ * The class name added to dialog body node.
+ */
+const BODY_CLASS = 'jp-Dialog-body';
+
+/**
+ * The class name added to a dialog content node.
+ */
+const FOOTER_CLASS = 'jp-Dialog-footer';
+
+/**
+ * The class name added to a dialog button node.
+ */
+const BUTTON_CLASS = 'jp-Dialog-button';
+
+/**
+ * The class name added to a dialog button icon node.
+ */
+const BUTTON_ICON_CLASS = 'jp-Dialog-buttonIcon';
+
+/**
+ * The class name added to a dialog button text node.
+ */
+const BUTTON_TEXT_CLASS = 'jp-Dialog-buttonText';
+
+/*
+ * The class name added to dialog OK buttons.
+ */
+const OK_BUTTON_CLASS = 'jp-Dialog-okButton';
+
+/**
+ * The class name added to dialog Cancel buttons.
+ */
+const CANCEL_BUTTON_CLASS = 'jp-Dialog-cancelButton';
+
+
+/**
+ * A button applied to a dialog.
+ */
+export
+interface IButtonItem {
+  /**
+   * The text for the button.
+   */
+  text: string;
+
+  /**
+   * The icon class for the button.
+   */
+  icon?: string;
+
+  /**
+   * The extra class name to associate with the button.
+   */
+  className?: string;
+
+  /**
+   * The handler for the button.
+   */
+  handler?: (args: any) => void;
+
+  /**
+   * The arguments to pass to the handler.
+   */
+  args?: any;
+}
+
+
+/**
+ * A default "OK" button.
+ */
+export
+const okButton: IButtonItem = {
+  text: 'OK',
+  className: OK_BUTTON_CLASS
+}
+
+
+/**
+ * A default "Cancel" button.
+ */
+export
+const cancelButton: IButtonItem = {
+  text: 'Cancel',
+  className: CANCEL_BUTTON_CLASS
+}
+
+
+/**
+ * The options used to create a dialog.
+ */
+export
+interface IDialogOptions {
+  /**
+   * The tope level text for the dialog (defaults to an empty string).
+   */
+  title?: string;
+
+  /**
+   * The main body element for the dialog or a message to display,
+   * which is wrapped in a <p> tag.
+   */
+  body?: HTMLElement | string;
+
+  /**
+   * The host element for the dialog (defaults to `document.body`).
+   */
+  host?: HTMLElement;
+
+  /**
+   * A list of button times to display (defaults to [[okButton]] and
+   *   [[cancelButton]]).
+   */
+  buttons?: IButtonItem[];
+}
+
+
+/**
+ * Create a dialog and show it.
+ *
+ * @param options - The dialog setup options.
+ *
+ * @returns The button item that was selected.
+ */
+export
+function showDialog(options: IDialogOptions): Promise<IButtonItem>{
+  let host = options.host || document.body;
+  let buttons = options.buttons || [okButton, cancelButton];
+  let buttonNodes = buttons.map(createButton);
+  let dialog = createDialog(options, buttonNodes);
+  host.appendChild(dialog);
+  buttonNodes[0].focus();
+
+  return new Promise<IButtonItem>((resolve, reject) => {
+    buttonNodes.map(node => {
+      node.addEventListener('click', evt => {
+        if (node.contains(evt.target as HTMLElement)) {
+          host.removeChild(dialog);
+          let button = buttons[buttonNodes.indexOf(node)];
+          if (button.handler) button.handler(button.args);
+          resolve(button);
+        }
+      });
+    });
+    dialog.addEventListener('keydown', evt => {
+      // Check for escape key
+      if (evt.keyCode === 27) {
+        host.removeChild(dialog);
+        resolve(null);
+      }
+    });
+    dialog.addEventListener('contextmenu', evt => {
+      evt.preventDefault();
+      evt.stopPropagation();
+    });
+    let close = dialog.getElementsByClassName(CLOSE_ICON_CLASS)[0];
+    close.addEventListener('click', () => {
+      host.removeChild(dialog);
+      resolve(null);
+    });
+  });
+}
+
+
+/**
+ * Create the dialog node.
+ */
+function createDialog(options: IDialogOptions, buttonNodes: HTMLElement[]): HTMLElement {
+  // Create the dialog nodes (except for the buttons).
+  let node = document.createElement('div');
+  let content = document.createElement('div');
+  let header = document.createElement('div');
+  let body = document.createElement('div');
+  let footer = document.createElement('div');
+  let title = document.createElement('span');
+  let close = document.createElement('span');
+  node.className = DIALOG_CLASS;
+  content.className = CONTENT_CLASS;
+  header.className = HEADER_CLASS;
+  body.className = BODY_CLASS;
+  footer.className = FOOTER_CLASS;
+  title.className = TITLE_CLASS;
+  close.className = CLOSE_ICON_CLASS;
+  node.appendChild(content);
+  content.appendChild(header);
+  content.appendChild(body);
+  content.appendChild(footer);
+  header.appendChild(title);
+  header.appendChild(close);
+
+  // Populate the nodes.
+  title.textContent = options.title || '';
+  if (options.body && typeof options.body === 'string') {
+    let span = document.createElement('span');
+    span.innerHTML = options.body as string;
+    body.appendChild(span);
+  } else if (options.body) {
+    body.appendChild(options.body as HTMLElement);
+  }
+  buttonNodes.map(buttonNode => { footer.appendChild(buttonNode); });
+  return node;
+}
+
+
+/**
+ * Create a node for a button item.
+ */
+function createButton(item: IButtonItem): HTMLElement {
+  let button = document.createElement('button');
+  button.className = BUTTON_CLASS;
+  if (item.className) button.classList.add(item.className);
+  let icon = document.createElement('span');
+  icon.className = BUTTON_ICON_CLASS;
+  if (item.icon) icon.classList.add(item.icon);
+  let text = document.createElement('span');
+  text.className = BUTTON_TEXT_CLASS;
+  text.textContent = item.text;
+  button.appendChild(icon);
+  button.appendChild(text);
+  return button;
+}

+ 460 - 0
src/docmanager/handler.ts

@@ -0,0 +1,460 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import * as CodeMirror
+  from 'codemirror';
+
+import {
+  IContentsModel, IContentsManager, IContentsOpts
+} from 'jupyter-js-services';
+
+import * as arrays
+  from 'phosphor-arrays';
+
+import {
+  IMessageFilter, IMessageHandler, Message, installMessageFilter,
+  removeMessageFilter
+} from 'phosphor-messaging';
+
+import {
+  Property
+} from 'phosphor-properties';
+
+import {
+  ISignal, Signal
+} from 'phosphor-signaling';
+
+import {
+  Widget, Title
+} from 'phosphor-widget';
+
+import {
+  JupyterCodeMirrorWidget as CodeMirrorWidget
+} from './widget';
+
+import {
+  loadModeByFileName
+} from './utils';
+
+
+/**
+ * The class name added to a dirty documents.
+ */
+const DIRTY_CLASS = 'jp-mod-dirty';
+
+
+/**
+ * An implementation of a file handler.
+ */
+export
+abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
+
+  /**
+   * Construct a new source file handler.
+   */
+  constructor(manager: IContentsManager) {
+    this._manager = manager;
+    document.addEventListener('focus', this._onFocus, true);
+  }
+
+  /**
+   * Get the list of file extensions explicitly supported by the handler.
+   */
+  get fileExtensions(): string[] {
+    return []
+  }
+
+  /**
+   * Get the list of mime types explicitly supported by the handler.
+   */
+  get mimeTypes(): string[] {
+    return []
+  }
+
+  /**
+   * Get the contents manager used by the handler.
+   *
+   * #### Notes
+   * This is a read-only property
+   */
+  get manager(): IContentsManager {
+    return this._manager;
+  }
+
+  /**
+   * Get the active widget.
+   *
+   * #### Notes
+   * This is a read-only property.  It will be `null` if there is no
+   * active widget.
+   */
+  get activeWidget(): T {
+    return this._activeWidget;
+  }
+
+  /**
+   * A signal emitted when the file handler has finished loading the
+   * contents of the widget.
+   */
+  get finished(): ISignal<AbstractFileHandler<T>, IContentsModel> {
+    return Private.finishedSignal.bind(this);
+  }
+
+  /**
+   * A signal emitted when the file handler is activated.
+   */
+  get activated(): ISignal<AbstractFileHandler<T>, void> {
+    return Private.activatedSignal.bind(this);
+  }
+
+  /**
+   * Deactivate the handler.
+   */
+  deactivate(): void {
+    this._activeWidget = null;
+  }
+
+  /**
+   * Get the number of widgets managed by the handler.
+   *
+   * @returns The number of widgets managed by the handler.
+   */
+  widgetCount(): number {
+    return this._widgets.length;
+  }
+
+  /**
+   * Get the widget at the specified index.
+   *
+   * @param index - The index of the widget of interest.
+   *
+   * @returns The widget at the specified index, or `undefined`.
+   */
+  widgetAt(index: number): T {
+    return this._widgets[index];
+  }
+
+  /**
+   * Open a contents model and return a widget.
+   */
+  open(model: IContentsModel): T {
+    let widget = this.findWidgetByModel(model);
+    if (!widget) {
+      widget = this.createWidget(model);
+      widget.title.closable = true;
+      this._setModel(widget, model);
+      this._widgets.push(widget);
+      installMessageFilter(widget, this);
+    }
+
+    // Fetch the contents and populate the widget asynchronously.
+    let opts = this.getFetchOptions(model);
+    this.manager.get(model.path, opts).then(contents => {
+      widget.title.text = this.getTitleText(model);
+      return this.populateWidget(widget, contents);
+    }).then(() => {
+      this.clearDirty(widget);
+      this.finished.emit(model);
+    });
+    return widget;
+  }
+
+  /**
+   * Rename a file.
+   */
+  rename(oldPath: string, newPath: string): void {
+    for (let w of this._widgets) {
+      let model = this._getModel(w);
+      if (model.path === oldPath) {
+        this._setModel(w, model);
+        w.title.text = this.getTitleText(model);
+        return;
+      }
+    }
+  }
+
+  /**
+   * Save widget contents.
+   *
+   * @param widget - The widget to save (defaults to current active widget).
+   *
+   * returns A promise that resolves to the contents of the widget.
+   *
+   * #### Notes
+   * This clears the dirty state of the widget after a successful save.
+   */
+  save(widget?: T): Promise<IContentsModel> {
+    widget = this.resolveWidget(widget);
+    if (!widget) {
+      return Promise.resolve(void 0);
+    }
+    let model = this._getModel(widget);
+    return this.getSaveOptions(widget, model).then(opts => {
+      return this.manager.save(model.path, opts);
+    }).then(contents => {
+      this.clearDirty(widget);
+      return contents;
+    });
+  }
+
+  /**
+   * Revert widget contents.
+   *
+   * @param widget - The widget to revert (defaults to current active widget).
+   *
+   * returns A promise that resolves to the new contents of the widget.
+   *
+   * #### Notes
+   * This clears the dirty state of the widget after a successful revert.
+   */
+  revert(widget?: T): Promise<IContentsModel> {
+    widget = this.resolveWidget(widget);
+    if (!widget) {
+      return Promise.resolve(void 0);
+    }
+    let model = this._getModel(widget);
+    let opts = this.getFetchOptions(model);
+    return this.manager.get(model.path, opts).then(contents => {
+      return this.populateWidget(widget, contents);
+    }).then(contents => {
+      this.clearDirty(widget);
+      return contents;
+    });
+  }
+
+  /**
+   * Close a widget.
+   *
+   * @param widget - The widget to close (defaults to current active widget).
+   *
+   * returns A boolean indicating whether the widget was closed.
+   */
+  close(widget?: T): Promise<boolean> {
+    widget = this.resolveWidget(widget);
+    if (!widget) {
+      return Promise.resolve(false);
+    }
+    if (widget.hasClass(DIRTY_CLASS)) {
+      // TODO: implement a dialog here.
+      console.log('CLOSING DIRTY FILE');
+    }
+    widget.dispose();
+    let index = this._widgets.indexOf(widget);
+    this._widgets.splice(index, 1);
+    if (widget === this.activeWidget) {
+      this._activeWidget = null;
+    }
+    return Promise.resolve(true);
+  }
+
+  /**
+   * Close all widgets.
+   */
+  closeAll(): void {
+    for (let w of this._widgets) {
+      w.close();
+    }
+    this._activeWidget = null;
+  }
+
+  /**
+   * Get whether a widget is dirty (defaults to current active widget).
+   */
+  isDirty(widget?: T): boolean {
+    return Private.dirtyProperty.get(this.resolveWidget(widget));
+  }
+
+  /**
+   * Set the dirty state of a widget (defaults to current active widget).
+   */
+  setDirty(widget?: T): void {
+    Private.dirtyProperty.set(this.resolveWidget(widget), true);
+  }
+
+  /**
+   * Clear the dirty state of a widget (defaults to current active widget).
+   */
+  clearDirty(widget?: T): void {
+    Private.dirtyProperty.set(this.resolveWidget(widget), false);
+  }
+
+  /**
+   * Filter messages on the widget.
+   */
+  filterMessage(handler: IMessageHandler, msg: Message): boolean {
+    let widget = this.resolveWidget(handler as T);
+    if (msg.type == 'close-request' && widget) {
+      this.close(widget);
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Get options use to fetch the model contents from disk.
+   *
+   * #### Notes
+   * Subclasses are free to use any or none of the information in
+   * the model.
+   */
+  protected getFetchOptions(model: IContentsModel): IContentsOpts {
+    return { type: 'file', format: 'text' };
+  }
+
+  /**
+   * Get the options used to save the widget content.
+   */
+  protected abstract getSaveOptions(widget: T, model: IContentsModel): Promise<IContentsOpts>;
+
+  /**
+   * Create the widget from a model.
+   */
+  protected abstract createWidget(model: IContentsModel): T;
+
+  /**
+   * Populate a widget from an `IContentsModel`.
+   *
+   * #### Notes
+   * Subclasses are free to use any or none of the information in
+   * the model.  It is up to subclasses to handle setting dirty state when
+   * the widget contents change.  See [[AbstractFileHandler.dirtyProperty]].
+   */
+  protected abstract populateWidget(widget: T, model: IContentsModel): Promise<IContentsModel>;
+
+  /**
+   * Set the appropriate title text based on a model.
+   */
+  protected getTitleText(model: IContentsModel): string {
+    return model.name;
+  }
+
+  /**
+   * Get the model for a given widget.
+   */
+  private _getModel(widget: T): IContentsModel {
+    return Private.modelProperty.get(widget);
+  }
+
+  /**
+   * Set the model for a widget.
+   */
+  private _setModel(widget: T, model: IContentsModel) {
+    Private.modelProperty.set(widget, model);
+  }
+
+  /**
+   * Resolve a given widget.
+   */
+  protected resolveWidget(widget: T): T {
+    widget = widget || this.activeWidget;
+    if (this._widgets.indexOf(widget) === -1) {
+      return;
+    }
+    return widget;
+  }
+
+  /**
+   * Find a widget given a model.
+   */
+  protected findWidgetByModel(model: IContentsModel): T {
+    return arrays.find(this._widgets, widget => this._getModel(widget).path === model.path);
+  }
+
+  /**
+   * Handle a focus events.
+   */
+  private _onFocus = (event: Event) => {
+    let target = event.target as HTMLElement;
+    let prev = this.activeWidget;
+    let widget = arrays.find(this._widgets,
+      w => w.isVisible && w.node.contains(target));
+    if (widget) {
+      this._activeWidget = widget;
+      if (!prev) this.activated.emit(void 0);
+    }
+  }
+
+  private _activeWidget: T = null;
+  private _manager: IContentsManager = null;
+  private _widgets: T[] = [];
+}
+
+
+/**
+ * An implementation of a file handler.
+ */
+export
+class FileHandler extends AbstractFileHandler<CodeMirrorWidget> {
+  /**
+   * Get the options used to save the widget content.
+   */
+  protected getSaveOptions(widget: CodeMirrorWidget, model: IContentsModel): Promise<IContentsOpts> {
+    let name = model.path.split('/').pop();
+    name = name.split('.')[0];
+    let content = (widget as CodeMirrorWidget).editor.getDoc().getValue();
+    return Promise.resolve({ path: model.path, content, name,
+                             type: 'file', format: 'text' });
+  }
+
+  /**
+   * Create the widget from an `IContentsModel`.
+   */
+  protected createWidget(model: IContentsModel): CodeMirrorWidget {
+    let widget = new CodeMirrorWidget();
+    widget.editor.on('change', () => this.setDirty(widget));
+    return widget;
+  }
+
+  /**
+   * Populate a widget from an `IContentsModel`.
+   */
+  protected populateWidget(widget: CodeMirrorWidget, model: IContentsModel): Promise<IContentsModel> {
+    widget.editor.getDoc().setValue(model.content);
+    loadModeByFileName(widget.editor, model.name);
+    return Promise.resolve(model);
+  }
+
+}
+
+/** 
+ * A private namespace for AbstractFileHandler data.
+ */
+namespace Private {
+  /**
+   * An attached property with the widget model.
+   */
+  export
+  const modelProperty = new Property<Widget, IContentsModel>({
+    name: 'model',
+    value: null
+  });
+
+  /**
+   * An attached property with the widget dirty state.
+   */
+  export
+  const dirtyProperty = new Property<Widget, boolean>({
+    name: 'dirty',
+    value: false,
+    changed: (widget: Widget, oldValue: boolean, newValue: boolean) => {
+      if (newValue) {
+        widget.title.className += ` ${DIRTY_CLASS}`;
+      } else {
+        widget.title.className = widget.title.className.replace(DIRTY_CLASS, '');
+      }
+    }
+  });
+
+  /**
+   * A signal emitted when a file handler is activated.
+   */
+  export
+  const activatedSignal = new Signal<AbstractFileHandler<Widget>, void>();
+
+  /**
+   * A signal emitted when a file handler has finished populating a widget.
+   */
+  export
+  const finishedSignal = new Signal<AbstractFileHandler<Widget>, IContentsModel>();
+}

+ 6 - 0
src/docmanager/index.ts

@@ -0,0 +1,6 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+export * from './handler';
+export * from './manager';

+ 159 - 0
src/docmanager/manager.ts

@@ -0,0 +1,159 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  IContentsModel
+} from 'jupyter-js-services';
+
+import * as arrays
+ from 'phosphor-arrays';
+
+import {
+  ISignal, Signal
+} from 'phosphor-signaling';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  AbstractFileHandler
+} from './handler';
+
+/**
+ * The class name added to document widgets.
+ */
+export
+const DOCUMENT_CLASS = 'jp-Document';
+
+
+/**
+ * A document manager for Jupyter.
+ */
+export
+class DocumentManager {
+
+  /**
+   * Register a file handler.
+   */
+  register(handler: AbstractFileHandler<Widget>): void {
+    this._handlers.push(handler);
+    handler.activated.connect(this._onActivated, this);
+  }
+
+  /**
+   * Register a default file handler.
+   */
+  registerDefault(handler: AbstractFileHandler<Widget>): void {
+    if (this._defaultHandler) {
+      throw Error('Default handler already registered');
+    }
+    this.register(handler);
+    this._defaultHandler = handler;
+  }
+
+  /**
+   * Open a contents model and return a widget.
+   */
+  open(model: IContentsModel): Widget {
+    if (this._handlers.length === 0) {
+      return;
+    }
+    let path = model.path;
+    let ext = '.' + path.split('.').pop();
+    let handlers: AbstractFileHandler<Widget>[] = [];
+    // Look for matching file extensions.
+    for (let h of this._handlers) {
+      if (h.fileExtensions.indexOf(ext) !== -1) handlers.push(h);
+    }
+    let widget: Widget;
+    // If there was only one match, use it.
+    if (handlers.length === 1) {
+      widget = this._open(handlers[0], model);
+
+    // If there were no matches, use default handler.
+    } else if (handlers.length === 0) {
+      if (this._defaultHandler) {
+        widget = this._open(this._defaultHandler, model);
+      } else {
+        throw new Error(`Could not open file '${path}'`);
+      }
+
+    // There are more than one possible handlers.
+    } else {
+      // TODO: Ask the user to choose one.
+      widget = this._open(handlers[0], model);
+    }
+    widget.addClass(DOCUMENT_CLASS);
+    return widget;
+  }
+
+  /**
+   * Save the active document.
+   *
+   * returns A promise that resolves to the contents of the widget.
+   */
+  save(): Promise<IContentsModel>  {
+    if (this._activeHandler) {
+      return this._activeHandler.save();
+    }
+  }
+
+  /**
+   * Revert the active document.
+   *
+   * returns A promise that resolves to the new contents of the widget.
+   */
+  revert(): Promise<IContentsModel> {
+    if (this._activeHandler) {
+      return this._activeHandler.revert();
+    }
+  }
+
+  /**
+   * Close the active document.
+   *
+   * returns A promise that resolves with a boolean indicating whether the
+      widget was closed.
+   */
+  close(): Promise<boolean> {
+    if (this._activeHandler) {
+      return this._activeHandler.close();
+    }
+    return Promise.resolve(false);
+  }
+
+  /**
+   * Close all documents.
+   */
+  closeAll(): void {
+    this._handlers.map(handler => handler.closeAll());
+  }
+
+  /**
+   * Open a file and emit an `openRequested` signal.
+   */
+  private _open(handler: AbstractFileHandler<Widget>, model: IContentsModel): Widget {
+    let widget = handler.open(model);
+    // Clear all other active widgets.
+    for (let h of this._handlers) {
+      if (h !== handler) handler.deactivate();
+    }
+    return widget;
+  }
+
+  /**
+   * A handler for handler activated signals.
+   */
+  private _onActivated(handler: AbstractFileHandler<Widget>) {
+    this._activeHandler = handler;
+    for (let h of this._handlers) {
+      if (h !== handler) h.deactivate();
+    }
+  }
+
+  private _handlers: AbstractFileHandler<Widget>[] = [];
+  private _defaultHandler: AbstractFileHandler<Widget> = null;
+  private _activeHandler: AbstractFileHandler<Widget> = null;
+}

+ 17 - 0
src/docmanager/tsconfig.json

@@ -0,0 +1,17 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "ES5",
+    "outDir": "../lib"
+  },
+  "files": [
+    "../typings/codemirror/codemirror.d.ts",
+    "../typings/require/require.d.ts",
+    "../typings/es6-promise.d.ts",
+    "index.ts"
+  ]
+}

+ 61 - 0
src/docmanager/utils.ts

@@ -0,0 +1,61 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import * as CodeMirror
+  from 'codemirror';
+
+// Bundle common modes
+import 'codemirror/mode/python/python';
+import 'codemirror/mode/javascript/javascript';
+import 'codemirror/mode/css/css';
+import 'codemirror/mode/julia/julia';
+import 'codemirror/mode/r/r';
+import 'codemirror/mode/markdown/markdown';
+import 'codemirror/mode/gfm/gfm';
+
+
+/**
+ * Load a codemirror mode by file name.
+ */
+export
+function loadModeByFileName(editor: CodeMirror.Editor, filename: string): void {
+  loadInfo(editor, CodeMirror.findModeByFileName(filename));
+}
+
+
+/**
+ * Load a codemirror mode by mime type.
+ */
+export
+function loadModeByMIME(editor: CodeMirror.Editor, mimetype: string): void {
+  loadInfo(editor, CodeMirror.findModeByMIME(mimetype));
+}
+
+
+
+/**
+ * Load a codemirror mode by mode name.
+ */
+export
+function loadModeByName(editor: CodeMirror.Editor, mode: string): void {
+  loadInfo(editor, CodeMirror.findModeByName(mode));
+}
+
+
+/**
+ * Load a CodeMirror mode based on a mode spec.
+ */
+function loadInfo(editor: CodeMirror.Editor, info: CodeMirror.modespec): void {
+  if (!info) {
+    editor.setOption('mode', 'null');
+    return;
+  }
+  if (CodeMirror.modes.hasOwnProperty(info.mode)) {
+    editor.setOption('mode', info.mime);
+  } else {
+    require([`codemirror/mode/${info.mode}/${info.mode}`], () => {
+      editor.setOption('mode', info.mime);
+    });
+  }
+}

+ 48 - 0
src/docmanager/widget.ts

@@ -0,0 +1,48 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  CodeMirrorWidget
+} from 'phosphor-codemirror';
+
+import {
+  Message
+} from 'phosphor-messaging';
+
+
+/**
+ * The class name added to a jupyter code mirror widget.
+ */
+const EDITOR_CLASS = 'jp-CodeMirrorWidget';
+
+
+/**
+ * A Jupyter-specific code mirror widget.
+ */
+export
+class JupyterCodeMirrorWidget extends CodeMirrorWidget {
+  /**
+   * Construct a new jupyter code mirror widget.
+   */
+  constructor() {
+    super();
+    this.addClass(EDITOR_CLASS);
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  protected onAfterAttach(msg: Message): void {
+    super.onAfterAttach(msg);
+    this.editor.focus();
+  }
+
+  /**
+   * A message handler invoked on an `'after-show'` message.
+   */
+  protected onAfterShow(msg: Message): void {
+    super.onAfterShow(msg);
+    this.editor.focus();
+  }
+}

+ 279 - 0
src/filebrowser/browser.ts

@@ -0,0 +1,279 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  IContentsModel
+} from 'jupyter-js-services';
+
+import {
+  Message
+} from 'phosphor-messaging';
+
+import {
+  PanelLayout
+} from 'phosphor-panel';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  FileButtons
+} from './buttons';
+
+import {
+  BreadCrumbs
+} from './crumbs';
+
+import {
+  DirListing
+} from './listing';
+
+import {
+  FileBrowserModel
+} from './model';
+
+import {
+  FILE_BROWSER_CLASS, showErrorMessage
+} from './utils';
+
+
+/**
+ * The class name added to the filebrowser crumbs node.
+ */
+const CRUMBS_CLASS = 'jp-FileBrowser-crumbs';
+
+/**
+ * The class name added to the filebrowser buttons node.
+ */
+const BUTTON_CLASS = 'jp-FileBrowser-buttons';
+
+/**
+ * The class name added to the filebrowser listing node.
+ */
+const LISTING_CLASS = 'jp-FileBrowser-listing';
+
+/**
+ * The duration of auto-refresh in ms.
+ */
+const REFRESH_DURATION = 30000;
+
+
+/**
+ * A widget which hosts a file browser.
+ *
+ * The widget uses the Jupyter Contents API to retreive contents,
+ * and presents itself as a flat list of files and directories with
+ * breadcrumbs.
+ */
+export
+class FileBrowserWidget extends Widget {
+  /**
+   * Construct a new file browser.
+   *
+   * @param model - The file browser view model.
+   */
+  constructor(model: FileBrowserModel) {
+    super();
+    this.addClass(FILE_BROWSER_CLASS);
+    this._model = model;
+    this._model.refreshed.connect(this._handleRefresh, this)
+    this._crumbs = new BreadCrumbs(model);
+    this._buttons = new FileButtons(model);
+    this._listing = new DirListing(model);
+
+    this._crumbs.addClass(CRUMBS_CLASS);
+    this._buttons.addClass(BUTTON_CLASS);
+    this._listing.addClass(LISTING_CLASS);
+
+    let layout = new PanelLayout();
+    layout.addChild(this._crumbs);
+    layout.addChild(this._buttons);
+    layout.addChild(this._listing);
+
+    this.layout = layout;
+  }
+
+  /**
+   * Dispose of the resources held by the file browser.
+   */
+  dispose() {
+    this._model = null;
+    this._crumbs = null;
+    this._buttons = null;
+    this._listing = null;
+    super.dispose();
+  }
+
+  /**
+   * Get the model used by the file browser.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get model(): FileBrowserModel {
+    return this._model;
+  }
+
+  /**
+   * Get the widget factory for the widget.
+   */
+  get widgetFactory(): (model: IContentsModel) => Widget {
+    return this._listing.widgetFactory;
+  }
+
+  /**
+   * Set the widget factory for the widget.
+   */
+  set widgetFactory(factory: (model: IContentsModel) => Widget) {
+    this._listing.widgetFactory = factory;
+  }
+
+  /**
+   * Change directory.
+   */
+  cd(path: string): Promise<void> {
+    return this._model.cd(path);
+  }
+
+  /**
+   * Open the currently selected item(s).
+   *
+   * Changes to the first directory encountered.
+   * Emits [[openRequested]] signals for files.
+   */
+  open(): void {
+    let foundDir = false;
+    let items = this._model.sortedItems;
+    for (let item of items) {
+      if (!this._model.isSelected(item.name)) {
+        continue;
+      }
+      if (item.type === 'directory' && !foundDir) {
+        foundDir = true;
+        this._model.open(item.name).catch(error =>
+          showErrorMessage(this, 'Open directory', error)
+        );
+      } else {
+        this.model.open(item.name);
+      }
+    }
+  }
+
+  /**
+   * Create a new untitled file or directory in the current directory.
+   */
+  newUntitled(type: string, ext?: string): Promise<IContentsModel> {
+    return this.model.newUntitled(type, ext);
+  }
+
+  /**
+   * Rename the first currently selected item.
+   */
+  rename(): Promise<string> {
+    return this._listing.rename();
+  }
+
+  /**
+   * Cut the selected items.
+   */
+  cut(): void {
+    this._listing.cut();
+  }
+
+  /**
+   * Copy the selected items.
+   */
+  copy(): void {
+    this._listing.copy();
+  }
+
+  /**
+   * Paste the items from the clipboard.
+   */
+  paste(): Promise<void> {
+    return this._listing.paste();
+  }
+
+  /**
+   * Delete the currently selected item(s).
+   */
+  delete(): Promise<void> {
+    return this._listing.delete();
+  }
+
+  /**
+   * Duplicate the currently selected item(s).
+   */
+  duplicate(): Promise<void> {
+    return this._listing.duplicate();
+  }
+
+  /**
+   * Download the currently selected item(s).
+   */
+  download(): Promise<void> {
+    return this._listing.download();
+  }
+
+  /**
+   * Shut down kernels on the applicable currently selected items.
+   */
+  shutdownKernels(): Promise<void> {
+    return this._listing.shutdownKernels();
+  }
+
+  /**
+   * Refresh the current directory.
+   */
+  refresh(): Promise<void> {
+    return this._model.refresh().catch(
+      error => showErrorMessage(this, 'Refresh Error', error)
+    );
+  }
+
+  /**
+   * Select next item.
+   */
+  selectNext(): void {
+    this._listing.selectNext();
+  }
+
+  /**
+   * Select previous item.
+   */
+  selectPrevious(): void {
+    this._listing.selectPrevious();
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  protected onAfterAttach(msg: Message): void {
+    super.onAfterAttach(msg);
+    this.refresh();
+  }
+
+  /**
+   * A message handler invoked on an `'after-show'` message.
+   */
+  protected onAfterShow(msg: Message): void {
+    super.onAfterShow(msg);
+    this.refresh();
+  }
+
+  /**
+   * Handle a model refresh.
+   */
+  private _handleRefresh(): void {
+    clearTimeout(this._timeoutId);
+    this._timeoutId = setTimeout(() => this.refresh(), REFRESH_DURATION);
+  }
+
+  private _model: FileBrowserModel = null;
+  private _crumbs: BreadCrumbs = null;
+  private _buttons: FileButtons = null;
+  private _listing: DirListing = null;
+  private _timeoutId = -1;
+}

+ 374 - 0
src/filebrowser/buttons.ts

@@ -0,0 +1,374 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  IContentsModel, IKernelSpecId
+} from 'jupyter-js-services';
+
+import {
+  showDialog
+} from 'jupyter-js-domutils';
+
+import {
+  Menu, MenuItem
+} from 'phosphor-menus';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  FileBrowserModel
+} from './model';
+
+import * as utils
+  from './utils';
+
+
+/**
+ * The class name added to a file buttons widget.
+ */
+const FILE_BUTTONS_CLASS = 'jp-FileButtons';
+
+/**
+ * The class name added to a button node.
+ */
+const BUTTON_CLASS = 'jp-FileButtons-button';
+
+/**
+ * The class name added to a button content node.
+ */
+const CONTENT_CLASS = 'jp-FileButtons-buttonContent';
+
+/**
+ * The class name added to a button icon node.
+ */
+const ICON_CLASS = 'jp-FileButtons-buttonIcon';
+
+/**
+ * The class name added to a dropdown icon.
+ */
+const DROPDOWN_CLASS = 'jp-FileButtons-dropdownIcon';
+
+/**
+ * The class name added to the create button.
+ */
+const CREATE_CLASS = 'jp-id-create';
+
+/**
+ * The class name added to the upload button.
+ */
+const UPLOAD_CLASS = 'jp-id-upload';
+
+/**
+ * The class name added to the refresh button.
+ */
+const REFRESH_CLASS = 'jp-id-refresh';
+
+/**
+ * The class name added to an active create button.
+ */
+const ACTIVE_CLASS = 'jp-mod-active';
+
+
+/**
+ * A widget which hosts the file browser buttons.
+ */
+export
+class FileButtons extends Widget {
+  /**
+   * Construct a new file browser buttons widget.
+   *
+   * @param model - The file browser view model.
+   */
+  constructor(model: FileBrowserModel) {
+    super();
+    this.addClass(FILE_BUTTONS_CLASS);
+    this._model = model;
+
+    this._buttons.create.onmousedown = this._onCreateButtonPressed;
+    this._buttons.upload.onclick = this._onUploadButtonClicked;
+    this._buttons.refresh.onclick = this._onRefreshButtonClicked;
+    this._input.onchange = this._onInputChanged;
+
+    let node = this.node;
+    node.appendChild(this._buttons.create);
+    node.appendChild(this._buttons.upload);
+    node.appendChild(this._buttons.refresh);
+  }
+
+  /**
+   * Dispose of the resources held by the widget.
+   */
+  dispose(): void {
+    this._model = null;
+    this._buttons = null;
+    this._input = null;
+    super.dispose();
+  }
+
+  /**
+   * Get the model used by the widget.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get model(): FileBrowserModel {
+    return this._model;
+  }
+
+  /**
+   * The 'mousedown' handler for the create button.
+   */
+  private _onCreateButtonPressed = (event: MouseEvent) => {
+    // Do nothing if nothing if it's not a left press.
+    if (event.button !== 0) {
+      return;
+    }
+
+    // Do nothing if the create button is already active.
+    let button = this._buttons.create;
+    if (button.classList.contains(ACTIVE_CLASS)) {
+      return;
+    }
+
+    // Create a new dropdown menu and snap the button geometry.
+    let dropdown = Private.createDropdownMenu(this);
+    let rect = button.getBoundingClientRect();
+
+    // Mark the button as active.
+    button.classList.add(ACTIVE_CLASS);
+
+    // Setup the `closed` signal handler. The menu is disposed on an
+    // animation frame to allow a mouse press event which closed the
+    // menu to run its course. This keeps the button from re-opening.
+    dropdown.closed.connect(() => {
+      requestAnimationFrame(() => { dropdown.dispose(); });
+    });
+
+    // Setup the `disposed` signal handler. This restores the button
+    // to the non-active state and allows a new menu to be opened.
+    dropdown.disposed.connect(() => {
+      button.classList.remove(ACTIVE_CLASS);
+    });
+
+    // Popup the menu aligned with the bottom of the create button.
+    dropdown.popup(rect.left, rect.bottom, false, true);
+  };
+
+  /**
+   * The 'click' handler for the upload button.
+   */
+  private _onUploadButtonClicked = (event: MouseEvent) => {
+    if (event.button !== 0) return;
+    this._input.click();
+  };
+
+  /**
+   * The 'click' handler for the refresh button.
+   */
+  private _onRefreshButtonClicked = (event: MouseEvent) => {
+    if (event.button !== 0) return;
+    this._model.refresh();
+  };
+
+  /**
+   * The 'change' handler for the input field.
+   */
+  private _onInputChanged = () => {
+    let files = Array.prototype.slice.call(this._input.files);
+    Private.uploadFiles(this, files as File[]);
+  };
+
+  private _model: FileBrowserModel;
+  private _buttons = Private.createButtons();
+  private _input = Private.createUploadInput();
+}
+
+
+/**
+ * The namespace for the `FileButtons` private data.
+ */
+namespace Private {
+  /**
+   * An object which holds the button nodes for a file buttons widget.
+   */
+  export
+  interface IButtonGroup {
+    create: HTMLButtonElement;
+    upload: HTMLButtonElement;
+    refresh: HTMLButtonElement;
+  }
+
+  /**
+   * Create the button group for a file buttons widget.
+   */
+  export
+  function createButtons(): IButtonGroup {
+    let create = document.createElement('button');
+    let upload = document.createElement('button');
+    let refresh = document.createElement('button');
+
+    let createContent = document.createElement('span');
+    let uploadContent = document.createElement('span');
+    let refreshContent = document.createElement('span');
+
+    let createIcon = document.createElement('span');
+    let uploadIcon = document.createElement('span');
+    let refreshIcon = document.createElement('span');
+    let dropdownIcon = document.createElement('span');
+
+    create.type = 'button';
+    upload.type = 'button';
+    refresh.type = 'button';
+
+    create.title = 'Create New...';
+    upload.title = 'Upload File(s)';
+    refresh.title = 'Refresh File List';
+
+    create.className = `${BUTTON_CLASS} ${CREATE_CLASS}`;
+    upload.className = `${BUTTON_CLASS} ${UPLOAD_CLASS}`;
+    refresh.className = `${BUTTON_CLASS} ${REFRESH_CLASS}`;
+
+    createContent.className = CONTENT_CLASS;
+    uploadContent.className = CONTENT_CLASS;
+    refreshContent.className = CONTENT_CLASS;
+
+    // TODO make these icons configurable.
+    createIcon.className = ICON_CLASS + ' fa fa-plus';
+    uploadIcon.className = ICON_CLASS + ' fa fa-upload';
+    refreshIcon.className = ICON_CLASS + ' fa fa-refresh';
+    dropdownIcon.className = DROPDOWN_CLASS + ' fa fa-caret-down';
+
+    createContent.appendChild(createIcon);
+    createContent.appendChild(dropdownIcon);
+    uploadContent.appendChild(uploadIcon);
+    refreshContent.appendChild(refreshIcon);
+
+    create.appendChild(createContent);
+    upload.appendChild(uploadContent);
+    refresh.appendChild(refreshContent);
+
+    return { create, upload, refresh };
+  }
+
+  /**
+   * Create the upload input node for a file buttons widget.
+   */
+  export
+  function createUploadInput(): HTMLInputElement {
+    let input = document.createElement('input');
+    input.type = 'file';
+    input.multiple = true;
+    return input;
+  }
+
+  /**
+   * Create a new untitled file.
+   */
+  export
+  function createNewFile(widget: FileButtons): void {
+    widget.model.newUntitled('file').then(contents => {
+      widget.model.refresh().then(() => widget.model.open(contents.name));
+    }).catch(error => {
+      utils.showErrorMessage(widget, 'New File Error', error);
+    });
+  }
+
+  /**
+   * Create a new untitled folder.
+   */
+  export
+  function createNewFolder(widget: FileButtons): void {
+    widget.model.newUntitled('directory').then(contents => {
+      widget.model.refresh();
+    }).catch(error => {
+      utils.showErrorMessage(widget, 'New Folder Error', error);
+    });
+  }
+
+  /**
+   * Create a new untitled notebook.
+   */
+  export
+  function createNewNotebook(widget: FileButtons, spec: IKernelSpecId): void {
+    widget.model.newUntitled('notebook').then(contents => {
+      let started = widget.model.startSession(contents.path, spec.name);
+      return started.then(() => contents);
+    }).then(contents => {
+      widget.model.refresh().then(() => widget.model.open(contents.name));
+    }).catch(error => {
+      utils.showErrorMessage(widget, 'New Notebook Error', error);
+    });
+  }
+
+  /**
+   * Create a new dropdown menu for the create new button.
+   */
+  export
+  function createDropdownMenu(widget: FileButtons): Menu {
+    let items = [
+      new MenuItem({
+        text: 'Text File',
+        handler: () => { createNewFile(widget); }
+      }),
+      new MenuItem({
+        text: 'Folder',
+        handler: () => { createNewFolder(widget); }
+      }),
+      new MenuItem({
+        type: MenuItem.Separator
+      })
+    ];
+    // TODO the kernels below are suffixed with "Notebook" as a
+    // temporary measure until we can update the Menu widget to
+    // show text in a separator for a "Notebooks" group.
+    let extra = widget.model.kernelSpecs.map(spec => {
+      return new MenuItem({
+        text: `${spec.spec.display_name} Notebook`,
+        handler: () => { createNewNotebook(widget, spec); }
+      });
+    });
+    return new Menu(items.concat(extra));
+  }
+
+  /**
+   * Upload an array of files to the server.
+   */
+  export
+  function uploadFiles(widget: FileButtons, files: File[]): void {
+    let pending = files.map(file => uploadFile(widget, file));
+    Promise.all(pending).then(() => {
+      widget.model.refresh();
+    }).catch(error => {
+      utils.showErrorMessage(widget, 'Upload Error', error);
+    });
+  }
+
+  /**
+   * Upload a file to the server.
+   */
+  function uploadFile(widget: FileButtons, file: File): Promise<any> {
+    return widget.model.upload(file).catch(error => {
+      let exists = error.message.indexOf('already exists') !== -1;
+      if (exists) return uploadFileOverride(widget, file);
+      throw error;
+    });
+  }
+
+  /**
+   * Upload a file to the server checking for override.
+   */
+  function uploadFileOverride(widget: FileButtons, file: File): Promise<any> {
+    let options = {
+      title: 'Overwrite File?',
+      host: this.parent.node,
+      body: `"${file.name}" already exists, overwrite?`
+    };
+    return showDialog(options).then(button => {
+      if (button.text !== 'Ok') return;
+      return widget.model.upload(file, true);
+    });
+  }
+}

+ 356 - 0
src/filebrowser/crumbs.ts

@@ -0,0 +1,356 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  IContentsModel
+} from 'jupyter-js-services';
+
+import {
+  showDialog
+} from 'jupyter-js-domutils';
+
+import {
+  DropAction, IDragEvent
+} from 'phosphor-dragdrop';
+
+import {
+  Message
+} from 'phosphor-messaging';
+
+import {
+  IChangedArgs
+} from 'phosphor-properties';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  FileBrowserModel
+} from './model';
+
+import * as utils
+  from './utils';
+
+
+/**
+ * The class name added to the breadcrumb node.
+ */
+const BREADCRUMB_CLASS = 'jp-BreadCrumbs';
+
+/**
+ * The class name added to the breadcrumb node.
+ */
+const BREADCRUMB_ITEM_CLASS = 'jp-BreadCrumbs-item';
+
+/**
+ * Bread crumb paths.
+ */
+const BREAD_CRUMB_PATHS = ['/', '../../', '../', ''];
+
+
+/**
+ * A class which hosts folder breadcrumbs.
+ */
+export
+class BreadCrumbs extends Widget {
+
+  /**
+   * Construct a new file browser crumb widget.
+   *
+   * @param model - The file browser view model.
+   */
+  constructor(model: FileBrowserModel) {
+    super();
+    this._model = model;
+    this.addClass(BREADCRUMB_CLASS);
+    this._crumbs = Private.createCrumbs();
+    this._crumbSeps = Private.createCrumbSeparators();
+    this.node.appendChild(this._crumbs[Private.Crumb.Home]);
+    this._model.refreshed.connect(this.update, this);
+  }
+
+  /**
+   * Handle the DOM events for the bread crumbs.
+   *
+   * @param event - The DOM event sent to the widget.
+   *
+   * #### Notes
+   * This method implements the DOM `EventListener` interface and is
+   * called in response to events on the panel's DOM node. It should
+   * not be called directly by user code.
+   */
+  handleEvent(event: Event): void {
+    switch (event.type) {
+    case 'click':
+      this._evtClick(event as MouseEvent);
+      break;
+    case 'p-dragenter':
+      this._evtDragEnter(event as IDragEvent);
+      break;
+    case 'p-dragleave':
+      this._evtDragLeave(event as IDragEvent);
+      break;
+    case 'p-dragover':
+      this._evtDragOver(event as IDragEvent);
+      break;
+    case 'p-drop':
+      this._evtDrop(event as IDragEvent);
+      break;
+    }
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  protected onAfterAttach(msg: Message): void {
+    super.onAfterAttach(msg);
+    let node = this.node;
+    node.addEventListener('click', this);
+    node.addEventListener('p-dragenter', this);
+    node.addEventListener('p-dragleave', this);
+    node.addEventListener('p-dragover', this);
+    node.addEventListener('p-drop', this);
+  }
+
+  /**
+   * A message handler invoked on a `'before-detach'` message.
+   */
+  protected onBeforeDetach(msg: Message): void {
+    super.onBeforeDetach(msg);
+    let node = this.node;
+    node.removeEventListener('click', this);
+    node.removeEventListener('p-dragenter', this);
+    node.removeEventListener('p-dragleave', this);
+    node.removeEventListener('p-dragover', this);
+    node.removeEventListener('p-drop', this);
+  }
+
+  /**
+   * A handler invoked on an `'update-request'` message.
+   */
+  protected onUpdateRequest(msg: Message): void {
+    // Update the breadcrumb list.
+    Private.updateCrumbs(this._crumbs, this._crumbSeps, this._model.path);
+  }
+
+  /**
+   * Handle the `'click'` event for the widget.
+   */
+  private _evtClick(event: MouseEvent) {
+    // Do nothing if it's not a left mouse press.
+    if (event.button !== 0) {
+      return;
+    }
+
+    // Find a valid click target.
+    let node = event.target as HTMLElement;
+    while (node && node !== this.node) {
+      if (node.classList.contains(BREADCRUMB_ITEM_CLASS)) {
+        let index = this._crumbs.indexOf(node);
+        this._model.cd(BREAD_CRUMB_PATHS[index]).catch(error =>
+          utils.showErrorMessage(this, 'Open Error', error)
+        );
+
+        // Stop the event propagation.
+        event.preventDefault();
+        event.stopPropagation();
+        return;
+      }
+      node = node.parentElement;
+    }
+  }
+
+  /**
+   * Handle the `'p-dragenter'` event for the widget.
+   */
+  private _evtDragEnter(event: IDragEvent): void {
+    if (event.mimeData.hasData(utils.CONTENTS_MIME)) {
+      let index = utils.hitTestNodes(this._crumbs, event.clientX, event.clientY);
+      if (index !== -1) {
+        if (index !== Private.Crumb.Current) {
+          this._crumbs[index].classList.add(utils.DROP_TARGET_CLASS);
+          event.preventDefault();
+          event.stopPropagation();
+        }
+      }
+    }
+  }
+
+  /**
+   * Handle the `'p-dragleave'` event for the widget.
+   */
+  private _evtDragLeave(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    let dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS);
+    if (dropTarget) dropTarget.classList.remove(utils.DROP_TARGET_CLASS);
+  }
+
+  /**
+   * Handle the `'p-dragover'` event for the widget.
+   */
+  private _evtDragOver(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    event.dropAction = event.proposedAction;
+    let dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS);
+    if (dropTarget) dropTarget.classList.remove(utils.DROP_TARGET_CLASS);
+    let index = utils.hitTestNodes(this._crumbs, event.clientX, event.clientY);
+    if (index !== -1) this._crumbs[index].classList.add(utils.DROP_TARGET_CLASS);
+  }
+
+  /**
+   * Handle the `'p-drop'` event for the widget.
+   */
+  private _evtDrop(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    if (event.proposedAction === DropAction.None) {
+      event.dropAction = DropAction.None;
+      return;
+    }
+    if (!event.mimeData.hasData(utils.CONTENTS_MIME)) {
+      return;
+    }
+    event.dropAction = event.proposedAction;
+
+    let target = event.target as HTMLElement;
+    while (target && target.parentElement) {
+      if (target.classList.contains(utils.DROP_TARGET_CLASS)) {
+        target.classList.remove(utils.DROP_TARGET_CLASS);
+        break;
+      }
+      target = target.parentElement;
+    }
+
+    // Get the path based on the target node.
+    let index = this._crumbs.indexOf(target);
+    if (index == -1) return;
+    var path = BREAD_CRUMB_PATHS[index];
+
+    // Move all of the items.
+    let promises: Promise<void>[] = [];
+    let items = this._model.sortedItems;
+    for (let item of items) {
+      var name = item.name;
+      if (!this._model.isSelected(name)) {
+        continue;
+      }
+      var newPath = path + name;
+      promises.push(this._model.rename(name, newPath).catch(error => {
+        if (error.xhr) {
+          error.message = `${error.xhr.status}: error.statusText`;
+        }
+        if (error.message.indexOf('409') !== -1) {
+          let options = {
+            title: 'Overwrite file?',
+            host: this.parent.node,
+            body: `"${newPath}" already exists, overwrite?`
+          }
+          return showDialog(options).then(button => {
+            if (button.text === 'OK') {
+              return this._model.delete(newPath).then(() => {
+                return this._model.rename(name, newPath).then(() => {
+                  return this._model.refresh();
+                });
+              });
+            }
+          });
+        }
+      }));
+    }
+    Promise.all(promises).then(
+      () => this._model.refresh(),
+      err => utils.showErrorMessage(this, 'Move Error', err)
+    );
+  }
+
+  private _model: FileBrowserModel = null;
+  private _crumbs: HTMLElement[] = [];
+  private _crumbSeps: HTMLElement[] = [];
+}
+
+
+/**
+ * The namespace for the crumbs private data.
+ */
+namespace Private {
+
+  /**
+   * Breadcrumb item list enum.
+   */
+  export
+  enum Crumb {
+    Home,
+    Ellipsis,
+    Parent,
+    Current
+  }
+
+  /**
+   * Populate the breadcrumb node.
+   */
+  export
+  function updateCrumbs(breadcrumbs: HTMLElement[], separators: HTMLElement[], path: string) {
+    let node = breadcrumbs[0].parentNode;
+
+    // Remove all but the home node.
+    while (node.firstChild.nextSibling) {
+      node.removeChild(node.firstChild.nextSibling);
+    }
+
+    let parts = path.split('/');
+    if (parts.length > 2) {
+      node.appendChild(separators[0]);
+      node.appendChild(breadcrumbs[Crumb.Ellipsis]);
+      let grandParent = parts.slice(0, parts.length - 2).join('/');
+      breadcrumbs[Crumb.Ellipsis].title = grandParent
+    }
+
+    if (path) {
+      if (parts.length >= 2) {
+        node.appendChild(separators[1]);
+        breadcrumbs[Crumb.Parent].textContent = parts[parts.length - 2];
+        node.appendChild(breadcrumbs[Crumb.Parent]);
+        let parent = parts.slice(0, parts.length - 1).join('/');
+        breadcrumbs[Crumb.Parent].title = parent;
+      }
+      node.appendChild(separators[2]);
+      breadcrumbs[Crumb.Current].textContent = parts[parts.length - 1];
+      node.appendChild(breadcrumbs[Crumb.Current]);
+      breadcrumbs[Crumb.Current].title = path;
+    }
+  }
+
+  /**
+   * Create the breadcrumb nodes.
+   */
+  export
+  function createCrumbs(): HTMLElement[] {
+    let home = document.createElement('i');
+    home.className = 'fa fa-home ' + BREADCRUMB_ITEM_CLASS;
+    let ellipsis = document.createElement('i');
+    ellipsis.className = 'fa fa-ellipsis-h ' + BREADCRUMB_ITEM_CLASS;
+    let parent = document.createElement('span');
+    parent.className = BREADCRUMB_ITEM_CLASS;
+    let current = document.createElement('span');
+    current.className = BREADCRUMB_ITEM_CLASS;
+    return [home, ellipsis, parent, current];
+  }
+
+  /**
+   * Create the breadcrumb separator nodes.
+   */
+  export
+  function createCrumbSeparators(): HTMLElement[] {
+    let items: HTMLElement[] = [];
+    for (let i = 0; i < 3; i++) {
+      let item = document.createElement('i');
+      item.className = 'fa fa-angle-right';
+      items.push(item);
+    }
+    return items;
+  }
+}

+ 69 - 0
src/filebrowser/index.css

@@ -0,0 +1,69 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+.jp-FileBrowser {
+  display: flex;
+  flex-direction: column;
+}
+
+
+.jp-BreadCrumbs {
+  flex: 0 0 auto;
+}
+
+
+.jp-FileButtons {
+  flex: 0 0 auto;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+}
+
+
+.jp-FileButtons-buttonContent {
+  display: flex;
+  flex-direction: row;
+  align-items: baseline;
+}
+
+
+.jp-FileButtons-buttonIcon {
+  margin-left: auto;
+  margin-right: auto;
+}
+
+
+.jp-FileButtons-dropdownIcon {
+  margin-left: -0.5em;
+}
+
+
+.jp-DirListing {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+}
+
+
+.jp-DirListing-header {
+  flex: 0 0 auto;
+  display: flex;
+  flex-direction: row;
+  overflow: hidden;
+}
+
+
+.jp-DirListing-content {
+  flex: 1 1 auto;
+  margin: 0;
+  padding: 0;
+  list-style-type: none;
+  overflow: auto;
+}
+
+
+.jp-DirListing-item {
+  display: flex;
+  flex-direction: row;
+}

+ 8 - 0
src/filebrowser/index.ts

@@ -0,0 +1,8 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+export * from './model';
+export * from './browser';
+
+import './index.css';

+ 1307 - 0
src/filebrowser/listing.ts

@@ -0,0 +1,1307 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  showDialog
+} from 'jupyter-js-domutils';
+
+import {
+  IContentsModel
+} from 'jupyter-js-services';
+
+import * as moment
+  from 'moment';
+
+import * as arrays
+  from 'phosphor-arrays';
+
+import {
+  IDisposable
+} from 'phosphor-disposable';
+
+import {
+  Drag, DropAction, DropActions, IDragEvent, MimeData
+} from 'phosphor-dragdrop';
+
+import {
+  Message
+} from 'phosphor-messaging';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  FileBrowserModel
+} from './model';
+
+import * as utils
+  from './utils';
+
+import {
+  SELECTED_CLASS
+} from './utils';
+
+
+/**
+ * The class name added to DirListing widget.
+ */
+const DIR_LISTING_CLASS = 'jp-DirListing';
+
+/**
+ * The class name added to a dir listing header node.
+ */
+const HEADER_CLASS = 'jp-DirListing-header';
+
+/**
+ * The class name added to a dir listing list header cell.
+ */
+const HEADER_ITEM_CLASS = 'jp-DirListing-headerItem';
+
+/**
+ * The class name added to a header cell text node.
+ */
+const HEADER_ITEM_TEXT_CLASS = 'jp-DirListing-headerItemText';
+
+/**
+ * The class name added to a header cell icon node.
+ */
+const HEADER_ITEM_ICON_CLASS = 'jp-DirListing-headerItemIcon';
+
+/**
+ * The class name added to the dir listing content node.
+ */
+const CONTENT_CLASS = 'jp-DirListing-content';
+
+/**
+ * The class name added to dir listing content item.
+ */
+const ITEM_CLASS = 'jp-DirListing-item';
+
+/**
+ * The class name added to the listing item text cell.
+ */
+const ITEM_TEXT_CLASS = 'jp-DirListing-itemText';
+
+/**
+ * The class name added to the listing item icon cell.
+ */
+const ITEM_ICON_CLASS = 'jp-DirListing-itemIcon';
+
+/**
+ * The class name added to the listing item modified cell.
+ */
+const ITEM_MODIFIED_CLASS = 'jp-DirListing-itemModified';
+
+/**
+ * The class name added to the dir listing editor node.
+ */
+const EDITOR_CLASS = 'jp-DirListing-editor';
+
+/**
+ * The class name added to the name column header cell.
+ */
+const NAME_ID_CLASS = 'jp-id-name';
+
+/**
+ * The class name added to the modified column header cell.
+ */
+const MODIFIED_ID_CLASS = 'jp-id-modified';
+
+/**
+ * The class name added to a file type content item.
+ */
+const FILE_TYPE_CLASS = 'jp-type-file';
+
+/**
+ * The class name added to a folder type content item.
+ */
+const FOLDER_TYPE_CLASS = 'jp-type-folder';
+
+/**
+ * The class name added to a notebook type content item.
+ */
+const NOTEBOOK_TYPE_CLASS = 'jp-type-notebook';
+
+/**
+ * The class name added to the widget when there are items on the clipboard.
+ */
+const CLIPBOARD_CLASS = 'jp-mod-clipboard';
+
+/**
+ * The class name added to cut rows.
+ */
+const CUT_CLASS = 'jp-mod-cut';
+
+/**
+ * The class name added when there are more than one selected rows.
+ */
+const MULTI_SELECTED_CLASS = 'jp-mod-multiSelected';
+
+/**
+ * The class name added to indicate running notebook.
+ */
+const RUNNING_CLASS = 'jp-mod-running';
+
+/**
+ * The class name added for a decending sort.
+ */
+const DESCENDING_CLASS = 'jp-mod-descending';
+
+/**
+ * The minimum duration for a rename select in ms.
+ */
+const RENAME_DURATION = 500;
+
+/**
+ * The threshold in pixels to start a drag event.
+ */
+const DRAG_THRESHOLD = 5;
+
+/**
+ * The factory MIME type supported by phosphor dock panels.
+ */
+const FACTORY_MIME = 'application/x-phosphor-widget-factory';
+
+
+/**
+ * A widget which hosts a file list area.
+ */
+export
+class DirListing extends Widget {
+  /**
+   * Create the DOM node for a dir listing.
+   */
+  static createNode(): HTMLElement {
+    let node = document.createElement('div');
+    let content = document.createElement('ul');
+    let header = this.createHeaderNode();
+    content.className = CONTENT_CLASS;
+    node.appendChild(header);
+    node.appendChild(content);
+    node.tabIndex = 1;
+    return node;
+  }
+
+  /**
+   * Create the header node for a dir listing.
+   *
+   * @returns A new DOM node to use as the dir listing header.
+   *
+   * #### Notes
+   * This method may be reimplemented to create custom headers.
+   */
+  static createHeaderNode(): HTMLElement {
+    let node = document.createElement('div');
+    let name = createItemNode('Name');
+    let modified = createItemNode('Last Modified');
+    node.className = HEADER_CLASS;
+    name.classList.add(NAME_ID_CLASS);
+    name.classList.add(SELECTED_CLASS);
+    modified.classList.add(MODIFIED_ID_CLASS);
+    node.appendChild(name);
+    node.appendChild(modified);
+    return node;
+
+    function createItemNode(label: string): HTMLElement {
+      let node = document.createElement('div');
+      let text = document.createElement('span');
+      let icon = document.createElement('span');
+      node.className = HEADER_ITEM_CLASS;
+      text.className = HEADER_ITEM_TEXT_CLASS;
+      icon.className = HEADER_ITEM_ICON_CLASS;
+      text.textContent = label;
+      node.appendChild(text);
+      node.appendChild(icon);
+      return node;
+    }
+  }
+
+  /**
+   * Create a new item node for a dir listing.
+   *
+   * @returns A new DOM node to use as a content item.
+   *
+   * #### Notes
+   * This method may be reimplemented to create custom items.
+   */
+  static createItemNode(): HTMLElement {
+    let node = document.createElement('li');
+    let icon = document.createElement('span');
+    let text = document.createElement('span');
+    let modified = document.createElement('span');
+    node.className = ITEM_CLASS;
+    icon.className = ITEM_ICON_CLASS;
+    text.className = ITEM_TEXT_CLASS;
+    modified.className = ITEM_MODIFIED_CLASS;
+    node.appendChild(icon);
+    node.appendChild(text);
+    node.appendChild(modified);
+    return node;
+  }
+
+  /**
+   * Update an item node to reflect the current state of a model.
+   *
+   * @param node - A node created by a call to [[createItemNode]].
+   *
+   * @param model - The model object to use for the item state.
+   *
+   * #### Notes
+   * This is called automatically when the item should be updated.
+   *
+   * If the [[createItemNode]] method is reimplemented, this method
+   * should also be reimplemented so that the item state is properly
+   * updated.
+   */
+  static updateItemNode(node: HTMLElement, model: IContentsModel) {
+    let icon = node.firstChild as HTMLElement;
+    let text = icon.nextSibling as HTMLElement;
+    let modified = text.nextSibling as HTMLElement;
+
+    let type: string;
+    switch (model.type) {
+    case 'directory':
+      type = FOLDER_TYPE_CLASS;
+      break;
+    case 'notebook':
+      type = NOTEBOOK_TYPE_CLASS;
+      break;
+    default:
+      type = FILE_TYPE_CLASS;
+      break;
+    }
+
+    let modText = '';
+    let modTitle = '';
+    if (model.last_modified) {
+      let time = moment(model.last_modified).fromNow();
+      modText = time === 'a few seconds ago' ? 'seconds ago' : time;
+      modTitle = moment(model.last_modified).format("YYYY-MM-DD HH:mm");
+    }
+
+    node.className = `${ITEM_CLASS} ${type}`;
+    text.textContent = model.name;
+    modified.textContent = modText;
+    modified.title = modTitle;
+  }
+
+  /**
+   * Construct a new file browser directory listing widget.
+   *
+   * @param model - The file browser view model.
+   */
+  constructor(model: FileBrowserModel) {
+    super();
+    this.addClass(DIR_LISTING_CLASS);
+    this._model = model;
+    this._model.refreshed.connect(this._onModelRefreshed, this);
+    this._model.selectionChanged.connect(this._onSelectionChanged, this);
+    this._editNode = document.createElement('input');
+    this._editNode.className = EDITOR_CLASS;
+  }
+
+  /**
+   * Dispose of the resources held by the directory listing.
+   */
+  dispose(): void {
+    this._model = null;
+    this._items = null;
+    this._editNode = null;
+    this._drag = null;
+    this._dragData = null;
+    super.dispose();
+  }
+
+  /**
+   * Get the widget factory for the widget.
+   */
+  get widgetFactory(): (model: IContentsModel) => Widget {
+    return this._widgetFactory;
+  }
+
+  /**
+   * Set the widget factory for the widget.
+   */
+  set widgetFactory(factory: (model: IContentsModel) => Widget) {
+    this._widgetFactory = factory;
+  }
+
+  /**
+   * Get the dir listing header node.
+   *
+   * #### Notes
+   * This is the node which holds the header cells.
+   *
+   * Modifying this node directly can lead to undefined behavior.
+   *
+   * This is a read-only property.
+   */
+  get headerNode(): HTMLElement {
+    return utils.findElement(this.node, HEADER_CLASS);
+  }
+
+  /**
+   * Get the dir listing content node.
+   *
+   * #### Notes
+   * This is the node which holds the item nodes.
+   *
+   * Modifying this node directly can lead to undefined behavior.
+   *
+   * This is a read-only property.
+   */
+  get contentNode(): HTMLElement {
+    return utils.findElement(this.node, CONTENT_CLASS);
+  }
+
+  /**
+   * Rename the first currently selected item.
+   */
+  rename(): Promise<string> {
+    return this._doRename();
+  }
+
+  /**
+   * Cut the selected items.
+   */
+  cut(): void {
+    this._isCut = true;
+    this._copy();
+  }
+
+  /**
+   * Copy the selected items.
+   */
+  copy(): void {
+    this._copy();
+  }
+
+  /**
+   * Paste the items from the clipboard.
+   */
+  paste(): Promise<void> {
+    if (!this._clipboard.length) {
+      return;
+    }
+    let promises: Promise<IContentsModel>[] = [];
+    for (let path of this._clipboard) {
+      if (this._isCut) {
+        let parts = path.split('/');
+        let name = parts[parts.length - 1];
+        promises.push(this._model.rename(path, name));
+      } else {
+        promises.push(this._model.copy(path, '.'));
+      }
+    }
+    // Remove any cut modifiers.
+    for (let item of this._items) {
+      item.classList.remove(CUT_CLASS);
+    }
+
+    this._clipboard = [];
+    this._isCut = false;
+    this.removeClass(CLIPBOARD_CLASS);
+    return Promise.all(promises).then(
+      () => this._model.refresh(),
+      error => utils.showErrorMessage(this, 'Paste Error', error)
+    );
+  }
+
+  /**
+   * Delete the currently selected item(s).
+   */
+  delete(): Promise<void> {
+    let promises: Promise<void>[] = [];
+    let items = this._model.sortedItems;
+    if (this._softSelection) {
+      promises.push(this._model.delete(this._softSelection));
+    } else {
+      for (let item of items) {
+        if (this._model.isSelected(item.name)) {
+          promises.push(this._model.delete(item.name));
+        }
+      }
+    }
+
+    return Promise.all(promises).then(
+      () => this._model.refresh(),
+      error => utils.showErrorMessage(this, 'Delete file', error)
+    );
+  }
+
+  /**
+   * Duplicate the currently selected item(s).
+   */
+  duplicate(): Promise<void> {
+    let promises: Promise<IContentsModel>[] = [];
+    for (let item of this._getSelectedItems()) {
+      if (item.type !== 'directory') {
+        promises.push(this._model.copy(item.path, this._model.path));
+      }
+    }
+    return Promise.all(promises).then(
+      () => this._model.refresh(),
+      error => utils.showErrorMessage(this, 'Duplicate file', error)
+    );
+  }
+
+  /**
+   * Download the currently selected item(s).
+   */
+  download(): Promise<void> {
+    for (let item of this._getSelectedItems()) {
+      if (item.type !== 'directory') {
+        return this._model.download(item.path).catch(error =>
+          utils.showErrorMessage(this, 'Download file', error)
+        );
+      }
+    }
+  }
+
+  /**
+   * Shut down kernels on the applicable currently selected items.
+   */
+  shutdownKernels(): Promise<void> {
+    let promises: Promise<void>[] = [];
+    let items = this._model.sortedItems;
+    let paths = items.map(item => item.path);
+    for (let sessionId of this._model.sessionIds) {
+      let index = paths.indexOf(sessionId.notebook.path);
+      if (!this._softSelection && this._model.isSelected(items[index].name)) {
+        promises.push(this._model.shutdown(sessionId));
+      } else if (this._softSelection === items[index].name) {
+        promises.push(this._model.shutdown(sessionId));
+      }
+    }
+    return Promise.all(promises).then(
+      () => this._model.refresh(),
+      error => utils.showErrorMessage(this, 'Shutdown kernel', error)
+    );
+  }
+
+  /**
+   * Select next item.
+   *
+   * @param keepExisting - Whether to keep the current selection and add to it.
+   */
+  selectNext(keepExisting = false): void {
+    let index = -1;
+    let selected = this._model.getSelected();
+    let items = this._model.sortedItems;
+    if (selected.length === 1 || keepExisting) {
+      // Select the next item.
+      let name = selected[selected.length - 1];
+      index = arrays.findIndex(items, (value, index) => value.name === name);
+      index += 1;
+      if (index === this._items.length) index = 0;
+    } else if (selected.length === 0) {
+      // Select the first item.
+      index = 0;
+    } else {
+      // Select the last selected item.
+      let name = selected[selected.length - 1];
+      index = arrays.findIndex(items, (value, index) => value.name === name);
+    }
+    if (index !== -1) this._selectItem(index, keepExisting);
+  }
+
+  /**
+   * Select previous item.
+   *
+   * @param keepExisting - Whether to keep the current selection and add to it.
+   */
+  selectPrevious(keepExisting = false): void {
+    let index = -1;
+    let selected = this._model.getSelected();
+    let items = this._model.sortedItems;
+    if (selected.length === 1 || keepExisting) {
+      // Select the previous item.
+      let name = selected[0];
+      index = arrays.findIndex(items, (value, index) => value.name === name);
+      index -= 1;
+      if (index === -1) index = this._items.length - 1;
+    } else if (selected.length === 0) {
+      // Select the last item.
+      index = this._items.length - 1;
+    } else {
+      // Select the first selected item.
+      let name = selected[0];
+      index = arrays.findIndex(items, (value, index) => value.name === name);
+    }
+    if (index !== -1) this._selectItem(index, keepExisting);
+  }
+
+  /**
+   * Handle the DOM events for the directory listing.
+   *
+   * @param event - The DOM event sent to the widget.
+   *
+   * #### Notes
+   * This method implements the DOM `EventListener` interface and is
+   * called in response to events on the panel's DOM node. It should
+   * not be called directly by user code.
+   */
+  handleEvent(event: Event): void {
+    switch (event.type) {
+    case 'mousedown':
+      this._evtMousedown(event as MouseEvent);
+      break;
+    case 'mouseup':
+      this._evtMouseup(event as MouseEvent);
+      break;
+    case 'mousemove':
+      this._evtMousemove(event as MouseEvent);
+      break;
+    case 'keydown':
+      this._evtKeydown(event as KeyboardEvent);
+      break;
+    case 'click':
+      this._evtClick(event as MouseEvent);
+      break
+    case 'dblclick':
+      this._evtDblClick(event as MouseEvent);
+      break;
+    case 'scroll':
+      this._evtScroll(event as MouseEvent);
+      break;
+    case 'p-dragenter':
+      this._evtDragEnter(event as IDragEvent);
+      break;
+    case 'p-dragleave':
+      this._evtDragLeave(event as IDragEvent);
+      break;
+    case 'p-dragover':
+      this._evtDragOver(event as IDragEvent);
+      break;
+    case 'p-drop':
+      this._evtDrop(event as IDragEvent);
+      break;
+    }
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  protected onAfterAttach(msg: Message): void {
+    super.onAfterAttach(msg);
+    let node = this.node;
+    let content = utils.findElement(node, CONTENT_CLASS);
+    node.addEventListener('mousedown', this);
+    node.addEventListener('keydown', this);
+    node.addEventListener('click', this);
+    node.addEventListener('dblclick', this);
+    content.addEventListener('scroll', this);
+    content.addEventListener('p-dragenter', this);
+    content.addEventListener('p-dragleave', this);
+    content.addEventListener('p-dragover', this);
+    content.addEventListener('p-drop', this);
+  }
+
+  /**
+   * A message handler invoked on a `'before-detach'` message.
+   */
+  protected onBeforeDetach(msg: Message): void {
+    super.onBeforeDetach(msg);
+    let node = this.node;
+    let content = utils.findElement(node, CONTENT_CLASS);
+    node.removeEventListener('mousedown', this);
+    node.removeEventListener('keydown', this);
+    node.removeEventListener('click', this);
+    node.removeEventListener('dblclick', this);
+    content.removeEventListener('scroll', this);
+    content.removeEventListener('p-dragenter', this);
+    content.removeEventListener('p-dragleave', this);
+    content.removeEventListener('p-dragover', this);
+    content.removeEventListener('p-drop', this);
+    document.removeEventListener('mousemove', this, true);
+    document.removeEventListener('mouseup', this, true);
+  }
+
+  /**
+   * A handler invoked on an `'update-request'` message.
+   */
+  protected onUpdateRequest(msg: Message): void {
+    // Fetch common variables.
+    let items = this._model.sortedItems;
+    let nodes = this._items;
+    let content = utils.findElement(this.node, CONTENT_CLASS);
+    let subtype = this.constructor as typeof DirListing;
+
+    this.removeClass(MULTI_SELECTED_CLASS);
+    this.removeClass(SELECTED_CLASS);
+
+    // Remove any excess item nodes.
+    while (nodes.length > items.length) {
+      let node = nodes.pop();
+      content.removeChild(node);
+    }
+
+    // Add any missing item nodes.
+    while (nodes.length < items.length) {
+      let node = subtype.createItemNode();
+      nodes.push(node);
+      content.appendChild(node);
+    }
+
+    // Update the node states to match the model contents.
+    for (let i = 0, n = items.length; i < n; ++i) {
+      subtype.updateItemNode(nodes[i], items[i]);
+      if (this._model.isSelected(items[i].name)) {
+        nodes[i].classList.add(SELECTED_CLASS);
+        if (this._isCut && this._model.path === this._prevPath) {
+          nodes[i].classList.add(CUT_CLASS);
+        }
+      }
+    }
+
+    // Handle the selectors on the widget node.
+    let selectedNames = this._model.getSelected();
+    if (selectedNames.length > 1) {
+      this.addClass(MULTI_SELECTED_CLASS);
+    }
+    if (selectedNames.length) {
+      this.addClass(SELECTED_CLASS);
+    }
+
+    // Handle notebook session statuses.
+    let paths = items.map(item => item.path);
+    for (let sessionId of this._model.sessionIds) {
+      let index = paths.indexOf(sessionId.notebook.path);
+      let node = this._items[index];
+      node.classList.add(RUNNING_CLASS);
+      node.title = sessionId.kernel.name;
+    }
+
+    this._prevPath = this._model.path;
+  }
+
+  /**
+   * Handle the `'click'` event for the widget.
+   */
+  private _evtClick(event: MouseEvent) {
+    this._softSelection = '';
+    let target = event.target as HTMLElement;
+
+    let header = this.headerNode;
+    if (header.contains(target)) {
+
+      let children = header.getElementsByClassName(HEADER_ITEM_CLASS);
+      let name = children[0] as HTMLElement;
+      let modified = children[1] as HTMLElement;
+
+      if (name.contains(target)) {
+        if (this._model.sortKey === 'name') {
+          let flag = !this._model.sortAscending;
+          this._model.sortAscending = flag;
+          if (flag) name.classList.remove(DESCENDING_CLASS);
+          else name.classList.add(DESCENDING_CLASS);
+        } else {
+          this._model.sortKey = 'name';
+          this._model.sortAscending = true;
+          name.classList.remove(DESCENDING_CLASS);
+        }
+        name.classList.add(SELECTED_CLASS);
+        modified.classList.remove(SELECTED_CLASS);
+        modified.classList.remove(DESCENDING_CLASS);
+      } else if (modified.contains(target)) {
+        if (this._model.sortKey === 'last_modified') {
+          let flag = !this._model.sortAscending;
+          this._model.sortAscending = flag;
+          if (flag) modified.classList.remove(DESCENDING_CLASS);
+          else modified.classList.add(DESCENDING_CLASS);
+        } else {
+          this._model.sortKey = 'last_modified';
+          this._model.sortAscending = true;
+          modified.classList.remove(DESCENDING_CLASS);
+        }
+        modified.classList.add(SELECTED_CLASS);
+        name.classList.remove(SELECTED_CLASS);
+        name.classList.remove(DESCENDING_CLASS);
+      }
+      this.update();
+      return;
+    }
+
+    let content = this.contentNode;
+    if (content.contains(target)) {
+      this._handleFileSelect(event);
+    }
+
+  }
+
+  /**
+   * Handle the `'scroll'` event for the widget.
+   */
+  private _evtScroll(event: MouseEvent): void {
+    this.headerNode.scrollLeft = this.contentNode.scrollLeft;
+  }
+
+  /**
+   * Handle the `'mousedown'` event for the widget.
+   */
+  private _evtMousedown(event: MouseEvent): void {
+
+    // Blur the edit node if necessary.
+    if (this._editNode.parentNode) {
+      if (this._editNode !== event.target as HTMLElement) {
+        this._editNode.focus();
+        this._editNode.blur();
+        clearTimeout(this._selectTimer);
+      } else {
+        return;
+      }
+    }
+
+    let index = utils.hitTestNodes(this._items, event.clientX, event.clientY);
+    if (index == -1) {
+      return;
+    }
+    this._softSelection = '';
+    let items = this._model.sortedItems;
+    let selected = this._model.getSelected();
+    if (selected.indexOf(items[index].name) == -1) {
+      this._softSelection = items[index].name;
+    }
+
+    // Left mouse press for drag start.
+    if (event.button === 0) {
+      this._dragData = { pressX: event.clientX, pressY: event.clientY,
+                         index: index };
+      document.addEventListener('mouseup', this, true);
+      document.addEventListener('mousemove', this, true);
+    }
+
+    if (event.button !== 0) {
+      clearTimeout(this._selectTimer);
+    }
+  }
+
+  /**
+   * Handle the `'mouseup'` event for the widget.
+   */
+  private _evtMouseup(event: MouseEvent): void {
+    if (event.button !== 0 || !this._drag) {
+      document.removeEventListener('mousemove', this, true);
+      document.removeEventListener('mouseup', this, true);
+      return;
+    }
+    event.preventDefault();
+    event.stopPropagation();
+  }
+
+  /**
+   * Handle the `'mousemove'` event for the widget.
+   */
+  private _evtMousemove(event: MouseEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+
+    // Bail if we are the one dragging.
+    if (this._drag) {
+      return;
+    }
+
+    // Check for a drag initialization.
+    let data = this._dragData;
+    let dx = Math.abs(event.clientX - data.pressX);
+    let dy = Math.abs(event.clientY - data.pressY);
+    if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) {
+      return;
+    }
+
+    this._startDrag(data.index, event.clientX, event.clientY);
+  }
+
+  /**
+   * Handle the `'keydown'` event for the widget.
+   */
+  private _evtKeydown(event: KeyboardEvent): void {
+    switch (event.keyCode) {
+    case 38: // Up arrow
+      this.selectPrevious(event.shiftKey);
+      event.stopPropagation();
+      event.preventDefault();
+      break;
+    case 40: // Down arrow
+      this.selectNext(event.shiftKey);
+      event.stopPropagation();
+      event.preventDefault();
+      break;
+    }
+  }
+
+  /**
+   * Handle the `'dblclick'` event for the widget.
+   */
+  private _evtDblClick(event: MouseEvent): void {
+    // Do nothing if it's not a left mouse press.
+    if (event.button !== 0) {
+      return;
+    }
+
+    // Do nothing if any modifier keys are pressed.
+    if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
+      return;
+    }
+
+    // Stop the event propagation.
+    event.preventDefault();
+    event.stopPropagation();
+
+    clearTimeout(this._selectTimer);
+    this._noSelectTimer = setTimeout(() => {
+      this._noSelectTimer = -1;
+    }, RENAME_DURATION);
+
+    this._editNode.blur();
+
+    // Find a valid double click target.
+    let target = event.target as HTMLElement;
+    let i = arrays.findIndex(this._items, node => node.contains(target));
+    if (i === -1) {
+      return;
+    }
+
+    let item = this._model.sortedItems[i];
+    this._model.open(item.name).catch(error =>
+        utils.showErrorMessage(this, 'File Open Error', error)
+    );
+  }
+
+
+  /**
+   * Handle the `'p-dragenter'` event for the widget.
+   */
+  private _evtDragEnter(event: IDragEvent): void {
+    if (event.mimeData.hasData(utils.CONTENTS_MIME)) {
+      let index = utils.hitTestNodes(this._items, event.clientX, event.clientY);
+      if (index === -1) {
+        return;
+      }
+      let target = this._items[index];
+      if (!target.classList.contains(FOLDER_TYPE_CLASS)) {
+        return;
+      }
+      if (target.classList.contains(SELECTED_CLASS)) {
+        return;
+      }
+      target.classList.add(utils.DROP_TARGET_CLASS);
+      event.preventDefault();
+      event.stopPropagation();
+    }
+  }
+
+  /**
+   * Handle the `'p-dragleave'` event for the widget.
+   */
+  private _evtDragLeave(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    let dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS);
+    if (dropTarget) dropTarget.classList.remove(utils.DROP_TARGET_CLASS);
+  }
+
+  /**
+   * Handle the `'p-dragover'` event for the widget.
+   */
+  private _evtDragOver(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    event.dropAction = event.proposedAction;
+    let dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS);
+    if (dropTarget) dropTarget.classList.remove(utils.DROP_TARGET_CLASS);
+    let index = utils.hitTestNodes(this._items, event.clientX, event.clientY);
+    this._items[index].classList.add(utils.DROP_TARGET_CLASS);
+  }
+
+  /**
+   * Handle the `'p-drop'` event for the widget.
+   */
+  private _evtDrop(event: IDragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    if (event.proposedAction === DropAction.None) {
+      event.dropAction = DropAction.None;
+      return;
+    }
+    if (!event.mimeData.hasData(utils.CONTENTS_MIME)) {
+      return;
+    }
+    event.dropAction = event.proposedAction;
+
+    let target = event.target as HTMLElement;
+    while (target && target.parentElement) {
+      if (target.classList.contains(utils.DROP_TARGET_CLASS)) {
+        target.classList.remove(utils.DROP_TARGET_CLASS);
+        break;
+      }
+      target = target.parentElement;
+    }
+
+    // Get the path based on the target node.
+    let index = this._items.indexOf(target);
+    let items = this._model.sortedItems;
+    var path = items[index].name + '/';
+
+    // Move all of the items.
+    let promises: Promise<IContentsModel>[] = [];
+    for (let item of items) {
+      if (!this._softSelection && !this._model.isSelected(item.name)) {
+        continue;
+      }
+      if (this._softSelection !== item.name) {
+        continue;
+      }
+      var name = item.name;
+      var newPath = path + name;
+      promises.push(this._model.rename(name, newPath).catch(error => {
+        if (error.xhr) {
+          error.message = `${error.xhr.statusText} ${error.xhr.status}`;
+        }
+        if (error.message.indexOf('409') !== -1) {
+          let options = {
+            title: 'Overwrite file?',
+            host: this.parent.node,
+            body: `"${newPath}" already exists, overwrite?`
+          }
+          return showDialog(options).then(button => {
+            if (button.text === 'OK') {
+              return this._model.delete(newPath).then(() => {
+                return this._model.rename(name, newPath);
+              });
+            }
+          });
+        }
+      }));
+    }
+    Promise.all(promises).then(
+      () => this._model.refresh(),
+      error => utils.showErrorMessage(this, 'Move Error', error)
+    );
+  }
+
+  /**
+   * Start a drag event.
+   */
+  private _startDrag(index: number, clientX: number, clientY: number): void {
+    let selectedNames = this._model.getSelected();
+    let source = this._items[index];
+    let items = this._model.sortedItems;
+    let item: IContentsModel = null;
+
+    // If the source node is not selected, use just that node.
+    if (!source.classList.contains(SELECTED_CLASS)) {
+      item = items[index];
+      selectedNames = [item.name];
+    } else if (selectedNames.length === 1) {
+      let name = selectedNames[0];
+      item = arrays.find(items, (value, index) => value.name === name);
+    }
+
+    // Create the drag image.
+    var dragImage = source.cloneNode(true) as HTMLElement;
+    dragImage.removeChild(dragImage.lastChild);
+    if (selectedNames.length > 1) {
+      let text = utils.findElement(dragImage, ITEM_TEXT_CLASS);
+      text.textContent = '(' + selectedNames.length + ')'
+    }
+
+    // Set up the drag event.
+    this._drag = new Drag({
+      dragImage: dragImage,
+      mimeData: new MimeData(),
+      supportedActions: DropActions.Move,
+      proposedAction: DropAction.Move
+    });
+    this._drag.mimeData.setData(utils.CONTENTS_MIME, null);
+    if (this._widgetFactory && item && item.type !== 'directory') {
+      this._drag.mimeData.setData(FACTORY_MIME, () => {
+        return this._widgetFactory(item);
+      });
+    }
+
+    // Start the drag and remove the mousemove listener.
+    this._drag.start(clientX, clientY).then(action => {
+      console.log('action', action);
+      this._drag = null;
+    });
+    document.removeEventListener('mousemove', this, true);
+  }
+
+  /**
+   * Handle selection on a file node.
+   */
+  private _handleFileSelect(event: MouseEvent): void {
+    // Fetch common variables.
+    let items = this._model.sortedItems;
+    let nodes = this._items;
+    let index = utils.hitTestNodes(this._items, event.clientX, event.clientY);
+
+    clearTimeout(this._selectTimer);
+
+    let name = items[index].name;
+    let selected = this._model.getSelected();
+
+    // Handle toggling.
+    if (event.metaKey || event.ctrlKey) {
+      if (this._model.isSelected(name)) {
+        this._model.deselect(name);
+      } else {
+        this._model.select(name);
+      }
+
+    // Handle multiple select.
+    } else if (event.shiftKey) {
+      this._handleMultiSelect(selected, index);
+
+    // Default to selecting the only the item.
+    } else {
+      // Handle a rename.
+      if (selected.length === 1 && selected[0] === name) {
+        this._selectTimer = setTimeout(() => {
+          if (this._noSelectTimer === -1) {
+            this._doRename();
+          }
+        }, RENAME_DURATION);
+      }
+      this._model.clearSelected();
+      this._model.select(name);
+    }
+    this._isCut = false;
+    this.update();
+  }
+
+  /**
+   * Handle a multiple select on a file item node.
+   */
+  private _handleMultiSelect(selected: string[], index: number): void {
+    // Find the "nearest selected".
+    let items = this._model.sortedItems;
+    let nearestIndex = -1;
+    for (let i = 0; i < this._items.length; i++) {
+      if (i === index) {
+        continue;
+      }
+      let name = items[i].name;
+      if (selected.indexOf(name) !== -1) {
+        if (nearestIndex === -1) {
+          nearestIndex = i;
+        } else {
+          if (Math.abs(index - i) < Math.abs(nearestIndex - i)) {
+            nearestIndex = i;
+          }
+        }
+      }
+    }
+
+    // Default to the first element (and fill down).
+    if (nearestIndex === -1) {
+      nearestIndex = 0;
+    }
+
+    // Select the rows between the current and the nearest selected.
+    for (let i = 0; i < this._items.length; i++) {
+      if (nearestIndex >= i && index <= i ||
+          nearestIndex <= i && index >= i) {
+        this._model.select(items[i].name);
+      }
+    }
+  }
+
+  /**
+   * Get the currently selected items.
+   */
+  private _getSelectedItems(): IContentsModel[] {
+    let items = this._model.sortedItems;
+    if (!this._softSelection) {
+      return items.filter(item => this._model.isSelected(item.name));
+    }
+    return items.filter(item => item.name === this._softSelection);
+  }
+
+  /**
+   * Copy the selected items, and optionally cut as well.
+   */
+  private _copy(): void {
+    this._clipboard = []
+    for (var item of this._getSelectedItems()) {
+      let row = arrays.find(this._items, row => {
+        let text = utils.findElement(row, ITEM_TEXT_CLASS);
+        return text.textContent === item.name;
+      });
+      if (item.type !== 'directory') {
+        // Store the absolute path of the item.
+        this._clipboard.push('/' + item.path)
+      }
+    }
+    this.update();
+  }
+
+  /**
+   * Allow the user to rename item on a given row.
+   */
+  private _doRename(): Promise<string> {
+    let listing = utils.findElement(this.node, CONTENT_CLASS);
+    let items = this._model.sortedItems;
+    let name = this._model.getSelected()[0];
+    let index = arrays.findIndex(items, (value, index) => value.name === name);
+    let row = this._items[index];
+    let fileCell = utils.findElement(row, FILE_TYPE_CLASS);
+    let text = utils.findElement(row, ITEM_TEXT_CLASS);
+    let original = text.textContent;
+
+    if (!fileCell) {
+      return;
+    }
+
+    return Private.doRename(fileCell as HTMLElement, text, this._editNode).then(changed => {
+      if (!changed) {
+        return original;
+      }
+      let newPath = text.textContent;
+      return this._model.rename(original, newPath).catch(error => {
+        if (error.xhr) {
+          error.message = `${error.xhr.status}: error.statusText`;
+        }
+        if (error.message.indexOf('409') !== -1 ||
+            error.message.indexOf('already exists') !== -1) {
+          let options = {
+            title: 'Overwrite file?',
+            host: this.parent.node,
+            body: `"${newPath}" already exists, overwrite?`
+          }
+          return showDialog(options).then(button => {
+            if (button.text === 'OK') {
+              return this._model.delete(newPath).then(() => {
+                return this._model.rename(original, newPath).then(() => {
+                  this._model.refresh();
+                });
+              });
+            } else {
+              text.textContent = original;
+            }
+          });
+        }
+      }).catch(error => {
+        utils.showErrorMessage(this, 'Rename Error', error);
+        return original;
+      }).then(() => {
+        this._model.refresh();
+        return text.textContent;
+      });
+    });
+  }
+
+  /**
+   * Select a given item.
+   */
+  private _selectItem(index: number, keepExisting: boolean) {
+    // Selected the given row(s)
+    let items = this._model.sortedItems;
+    if (!keepExisting) {
+      this._model.clearSelected();
+    }
+    let name = items[index].name;
+    this._model.select(name);
+    Private.scrollIfNeeded(this.contentNode, this._items[index]);
+    this._isCut = false;
+  }
+
+  /**
+   * Handle the `refreshed` signal from the model.
+   */
+  private _onModelRefreshed(): void {
+    this.update();
+  }
+
+  /**
+   * Handle the `selectionChanged` signal from the model.
+   */
+  private _onSelectionChanged(): void {
+    this.update();
+  }
+
+  private _model: FileBrowserModel = null;
+  private _editNode: HTMLInputElement = null;
+  private _items: HTMLElement[] = [];
+  private _drag: Drag = null;
+  private _dragData: { pressX: number, pressY: number, index: number } = null;
+  private _selectTimer = -1;
+  private _noSelectTimer = -1;
+  private _isCut = false;
+  private _prevPath = '';
+  private _clipboard: string[] = [];
+  private _widgetFactory: (model: IContentsModel) => Widget = null;
+  private _softSelection = '';
+}
+
+
+/**
+ * The namespace for the listing private data.
+ */
+namespace Private {
+  /**
+   * Handle editing text on a node.
+   *
+   * @returns Boolean indicating whether the name changed.
+   */
+  export
+  function doRename(parent: HTMLElement, text: HTMLElement, edit: HTMLInputElement): Promise<boolean> {
+    let changed = true;
+    parent.replaceChild(edit, text);
+    edit.value = text.textContent;
+    edit.focus();
+    let index = edit.value.lastIndexOf('.');
+    if (index === -1) {
+      edit.setSelectionRange(0, edit.value.length);
+    } else {
+      edit.setSelectionRange(0, index);
+    }
+
+    return new Promise<boolean>((resolve, reject) => {
+      edit.onblur = () => {
+        parent.replaceChild(text, edit);
+        if (text.textContent === edit.value) {
+          changed = false;
+        }
+        if (changed) text.textContent = edit.value;
+        resolve(changed);
+      }
+      edit.onkeydown = (event: KeyboardEvent) => {
+        switch (event.keyCode) {
+        case 13:  // Enter
+          event.stopPropagation();
+          event.preventDefault();
+          edit.blur();
+          break;
+        case 27:  // Escape
+          event.stopPropagation();
+          event.preventDefault();
+          changed = false;
+          edit.blur();
+          break;
+        }
+      }
+    });
+  }
+
+  /**
+   * Scroll an element into view if needed.
+   *
+   * @param area - The scroll area element.
+   *
+   * @param elem - The element of interest.
+   */
+  export
+  function scrollIfNeeded(area: HTMLElement, elem: HTMLElement): void {
+    let ar = area.getBoundingClientRect();
+    let er = elem.getBoundingClientRect();
+    if (er.top < ar.top) {
+      area.scrollTop -= ar.top - er.top;
+    } else if (er.bottom > ar.bottom) {
+      area.scrollTop += er.bottom - ar.bottom;
+    }
+  }
+}

+ 653 - 0
src/filebrowser/model.ts

@@ -0,0 +1,653 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  IContentsManager, IContentsModel, IContentsOpts, INotebookSessionManager,
+  INotebookSession, ISessionId, KernelStatus, getKernelSpecs, IKernelSpecId
+} from 'jupyter-js-services';
+
+import * as arrays
+  from 'phosphor-arrays';
+
+import {
+  IDisposable
+} from 'phosphor-disposable';
+
+import {
+  IChangedArgs
+} from 'phosphor-properties';
+
+import {
+  ISignal, Signal, clearSignalData
+} from 'phosphor-signaling';
+
+import * as utils
+  from './utils';
+
+
+/**
+ * An implementation of a file browser view model.
+ *
+ * #### Notes
+ * All paths parameters without a leading `'/'` are interpreted as relative to
+ * the current directory.  Supports `'../'` syntax.
+ */
+export
+class FileBrowserModel implements IDisposable {
+  /**
+   * Construct a new file browser view model.
+   */
+  constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager) {
+    this._contentsManager = contentsManager;
+    this._sessionManager = sessionManager;
+    this._model = { path: '', name: '/', type: 'directory', content: [] };
+    this._getKernelSpecs();
+    this.cd();
+  }
+
+  /**
+   * Get the refreshed signal.
+   */
+  get refreshed(): ISignal<FileBrowserModel, void> {
+    return Private.refreshedSignal.bind(this);
+  }
+
+  /**
+   * Get the file open requested signal.
+   */
+  get openRequested(): ISignal<FileBrowserModel, IContentsModel> {
+    return Private.openRequestedSignal.bind(this);
+  }
+
+  /**
+   * Get the file path changed signal.
+   */
+  get fileChanged(): ISignal<FileBrowserModel, IChangedArgs<string>> {
+    return Private.fileChangedSignal.bind(this);
+  }
+
+  /**
+   * Get the current path.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get path(): string {
+    return this._model.path;
+  }
+
+  /**
+   * Get the selection changed signal.
+   */
+  get selectionChanged(): ISignal<FileBrowserModel, void> {
+    return Private.selectionChangedSignal.bind(this);
+  }
+
+  /**
+   * Get whether the view model is disposed.
+   */
+  get isDisposed(): boolean {
+    return this._model === null;
+  }
+
+  /**
+   * Get the session ids for active notebooks.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get sessionIds(): ISessionId[] {
+    return this._sessionIds.slice();
+  }
+
+  /**
+   * Get a the list of available kernel specs.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get kernelSpecs(): IKernelSpecId[] {
+    return this._kernelSpecs.slice();
+  }
+
+  /**
+   * Get whether the items are sorted in ascending order.
+   */
+  get sortAscending(): boolean {
+    return this._ascending;
+  }
+
+  /**
+   * Set whether the items are sorted in ascending order.
+   */
+  set sortAscending(value: boolean) {
+    this._ascending = value;
+    this._sort();
+  }
+
+  /**
+   * Get which key the items are sorted on.
+   */
+  get sortKey(): string {
+    return this._sortKey;
+  }
+
+  /**
+   * Set which key the items are sorted on.
+   */
+  set sortKey(value: string) {
+    this._sortKey = value;
+    this._sort();
+  }
+
+  /**
+   * Get the sorted list of items.
+   *
+   * #### Notes
+   * This is a read-only property and should be treated as immutable.
+   */
+  get sortedItems(): IContentsModel[] {
+    return this._model.content;
+  }
+
+  /**
+   * Select an item by name.
+   *
+   * #### Notes
+   * This is a no-op if the name is not valid or already selected.
+   */
+  select(name: string): void {
+    if (!this._selection[name]) {
+      this._selection[name] = true;
+      this.selectionChanged.emit(void 0);
+    }
+  }
+
+  /**
+   * De-select an item by name.
+   *
+   * #### Notes
+   * This is a no-op if the name is not valid or not selected.
+   */
+  deselect(name: string): void {
+    if (this._selection[name]) {
+      delete this._selection[name];
+      this.selectionChanged.emit(void 0);
+    }
+  }
+
+  /**
+   * Check whether an item is selected.
+   *
+   * #### Notes
+   * Returns `true` for a valid name that is selected, `false` otherwise.
+   */
+  isSelected(name: string): boolean {
+    return !!this._selection[name];
+  }
+
+  /**
+   * Get the list of selected names.
+   */
+  getSelected(): string[] {
+    return Object.keys(this._selection);
+  }
+
+  /**
+   * Clear the selected items.
+   */
+  clearSelected(): void {
+    this._selection = Object.create(null);
+    this.selectionChanged.emit(void 0);
+  }
+
+  /**
+   * Dispose of the resources held by the view model.
+   */
+  dispose(): void {
+    this._model = null;
+    this._contentsManager = null;
+    this._selection = null;
+    clearSignalData(this);
+  }
+
+  /**
+   * Change directory.
+   *
+   * @param path - The path to the file or directory.
+   *
+   * @returns A promise with the contents of the directory.
+   */
+  cd(path = ''): Promise<void> {
+    if (path !== '') {
+      path = Private.normalizePath(this._model.path, path);
+    }
+    let changed = false;
+    let previous = this._selection;
+    if (path !== this.path) {
+      previous = Object.create(null);
+    }
+    let selection = Object.create(null);
+    return this._contentsManager.get(path, {}).then(contents => {
+      this._model = contents;
+      let content = contents.content as IContentsModel[];
+      let names = content.map((value, index) => value.name);
+      for (let name of names) {
+        if (previous[name]) {
+          selection[name] = true;
+        }
+      }
+      this._unsortedNames = content.map((value, index) => value.name);
+      if (this._sortKey !== 'name' || !this._ascending) this._sort();
+      return this._findSessions();
+    }).then(() => {
+      this.selectionChanged.emit(void 0);
+      this.refreshed.emit(void 0);
+    });
+  }
+
+  /**
+   * Refresh the current directory.
+   */
+  refresh(): Promise<void> {
+    // Refresh the list of kernelspecs and our directory listing.
+    this._getKernelSpecs();
+    return this.cd('.');
+  }
+
+  /**
+   * Copy a file.
+   *
+   * @param fromFile - The path of the original file.
+   *
+   * @param toDir - The path to the target directory.
+   *
+   * @returns A promise which resolves to the contents of the file.
+   */
+  copy(fromFile: string, toDir: string): Promise<IContentsModel> {
+    let normalizePath = Private.normalizePath;
+    fromFile = normalizePath(this._model.path, fromFile);
+    toDir = normalizePath(this._model.path, toDir);
+    return this._contentsManager.copy(fromFile, toDir);
+  }
+
+  /**
+   * Delete a file.
+   *
+   * @param: path - The path to the file to be deleted.
+   *
+   * @returns A promise which resolves when the file is deleted.
+   */
+  delete(path: string): Promise<void> {
+    let normalizePath = Private.normalizePath;
+    path = normalizePath(this._model.path, path);
+    return this._contentsManager.delete(path).then(() => {
+      this.fileChanged.emit({
+        name: 'file',
+        oldValue: path,
+        newValue: void 0
+      });
+    });
+  }
+
+  /**
+   * Download a file.
+   *
+   * @param - path - The path of the file to be downloaded.
+   *
+   * @returns - A promise which resolves to the file contents.
+   */
+  download(path: string): Promise<IContentsModel> {
+    let normalizePath = Private.normalizePath;
+    path = normalizePath(this._model.path, path);
+    return this._contentsManager.get(path, {}).then(contents => {
+      let element = document.createElement('a');
+      element.setAttribute('href', 'data:text/text;charset=utf-8,' +      encodeURI(contents.content));
+      element.setAttribute('download', contents.name);
+      element.click();
+      return contents;
+    });
+  }
+
+  /**
+   * Create a new untitled file or directory in the current directory.
+   *
+   * @param type - The type of file object to create. One of
+   *  `['file', 'notebook', 'directory']`.
+   *
+   * @param ext - Optional extension for `'file'` types (defaults to `'.txt'`).
+   *
+   * @returns A promise containing the new file contents model.
+   */
+  newUntitled(type: string, ext?: string): Promise<IContentsModel> {
+    if (type === 'file') {
+      ext = ext || '.txt';
+    } else {
+      ext = '';
+    }
+    return this._contentsManager.newUntitled(this._model.path,
+      { type: type, ext: ext }
+    );
+  }
+
+  /**
+   * Open a file in the current by name.
+   */
+  open(name: string): Promise<void> {
+    let item = arrays.find(this.sortedItems, value => value.name === name);
+    if (!item) {
+      return Promise.reject(new Error(`No file named: '${name}'`));
+    }
+    if (item.type === 'directory') {
+      return this.cd(name);
+    } else {
+      this.openRequested.emit(item);
+      return Promise.resolve(void 0);
+    }
+  }
+
+  /**
+   * Rename a file or directory.
+   *
+   * @param path - The path to the original file.
+   *
+   * @param newPath - The path to the new file.
+   *
+   * @returns A promise containing the new file contents model.
+   */
+  rename(path: string, newPath: string): Promise<IContentsModel> {
+    // Handle relative paths.
+    let normalizePath = Private.normalizePath;
+    path = normalizePath(this._model.path, path);
+    newPath = normalizePath(this._model.path, newPath);
+    return this._contentsManager.rename(path, newPath).then(contents => {
+      this.fileChanged.emit({
+        name: 'file',
+        oldValue: path,
+        newValue: newPath
+      });
+      return contents;
+    })
+  }
+
+  /**
+   * Upload a `File` object.
+   *
+   * @param file - The `File` object to upload.
+   *
+   * @param overwrite - Whether to overwrite an existing file.
+   *
+   * @returns A promise containing the new file contents model.
+   *
+   * #### Notes
+   * This will fail to upload files that are too big to be sent in one
+   * request to the server.
+   */
+  upload(file: File, overwrite?: boolean): Promise<IContentsModel> {
+    // Skip large files with a warning.
+    if (file.size > this._max_upload_size_mb * 1024 * 1024) {
+      let msg = `Cannot upload file (>${this._max_upload_size_mb} MB) `;
+      msg += `"${file.name}"`
+      console.warn(msg);
+      return Promise.reject(new Error(msg));
+    }
+
+    if (overwrite) {
+      return this._upload(file);
+    }
+
+    return this._contentsManager.get(file.name, {}).then(() => {
+      return Private.typedThrow<IContentsModel>(`"${file.name}" already exists`);
+    }, () => {
+      return this._upload(file);
+    });
+  }
+
+  /**
+   * Shut down a notebook session by session id.
+   */
+  shutdown(sessionId: ISessionId): Promise<void> {
+    return this._sessionManager.connectTo(sessionId.id).then(session => {
+      return session.shutdown();
+    });
+  }
+
+  /**
+   * Start a new session on a notebook.
+   */
+  startSession(path: string, kernel: string): Promise<INotebookSession> {
+    return this._sessionManager.startNew({
+      notebookPath: path,
+      kernelName: kernel
+    });
+  }
+
+  /**
+   * Sort the model items.
+   */
+  private _sort(): void {
+    if (!this._unsortedNames) {
+      return;
+    }
+    let items = this._model.content.slice() as IContentsModel[];
+    if (this._sortKey === 'name') {
+      items.sort((a, b) => {
+        let indexA = this._unsortedNames.indexOf(a.name);
+        let indexB = this._unsortedNames.indexOf(b.name);
+        return indexA - indexB;
+      });
+    } else if (this._sortKey === 'last_modified') {
+      items.sort((a, b) => {
+        let valA = new Date(a.last_modified).getTime();
+        let valB = new Date(b.last_modified).getTime();
+        return valB - valA;
+      });
+    }
+
+    // Reverse the order if descending.
+    if (!this._ascending) {
+      items.reverse();
+    }
+    this._model.content = items;
+  }
+
+  /**
+   * Perform the actual upload.
+   */
+  private _upload(file: File): Promise<IContentsModel> {
+    // Gather the file model parameters.
+    let path = this._model.path
+    path = path ? path + '/' + file.name : file.name;
+    let name = file.name;
+    let isNotebook = file.name.indexOf('.ipynb') !== -1;
+    let type = isNotebook ? 'notebook' : 'file';
+    let format = isNotebook ? 'json' : 'base64';
+
+    // Get the file content.
+    let reader = new FileReader();
+    if (isNotebook) {
+      reader.readAsText(file);
+    } else {
+      reader.readAsArrayBuffer(file);
+    }
+
+    return new Promise<IContentsModel>((resolve, reject) => {
+      reader.onload = (event: Event) => {
+        let model: IContentsOpts = {
+          type: type,
+          format: format,
+          name: name,
+          content: Private.getContent(reader)
+        }
+        resolve(this._contentsManager.save(path, model));
+      }
+
+      reader.onerror = (event: Event) => {
+        reject(Error(`Failed to upload "${file.name}":` + event));
+      }
+    });
+
+  }
+
+  /**
+   * Get the notebook sessions for the current directory.
+   */
+  private _findSessions(): Promise<void> {
+    this._sessionIds = [];
+    let notebooks = this._model.content.filter((content: IContentsModel) => { return content.type === 'notebook'; });
+    if (!notebooks.length) {
+      return Promise.resolve(void 0);
+    }
+
+    return this._sessionManager.listRunning().then(sessionIds => {
+      if (!sessionIds.length) {
+        return;
+      }
+      let promises: Promise<void>[] = [];
+      let paths = notebooks.map((notebook: IContentsModel) => {
+        return notebook.path;
+      });
+      for (var sessionId of sessionIds) {
+        let index = paths.indexOf(sessionId.notebook.path);
+        if (index !== -1) {
+          promises.push(this._sessionManager.connectTo(sessionId.id).then(session => {
+            if (session.status === KernelStatus.Idle || session.status === KernelStatus.Idle) {
+              this._sessionIds.push(sessionId);
+              return void 0;
+            }
+          }));
+        }
+      }
+      return Promise.all(promises).then(() => {});
+    });
+  }
+
+  /**
+   * Load the list of kernel specs.
+   */
+  private _getKernelSpecs(): void {
+    this._sessionManager.getSpecs().then(specs => {
+      let kernelSpecs: IKernelSpecId[] = [];
+      for (let key in specs.kernelspecs) {
+        kernelSpecs.push(specs.kernelspecs[key]);
+      }
+      kernelSpecs.sort((a, b) => {
+        return a.spec.display_name.localeCompare(b.spec.display_name);
+      });
+      this._kernelSpecs = kernelSpecs;
+    }, error => console.error(error));
+  }
+
+  private _max_upload_size_mb = 15;
+  private _contentsManager: IContentsManager = null;
+  private _sessionIds: ISessionId[] = [];
+  private _sessionManager: INotebookSessionManager = null;
+  private _model: IContentsModel;
+  private _kernelSpecs: IKernelSpecId[] = [];
+  private _selection: { [key: string]: boolean; } = Object.create(null);
+  private _sortKey = 'name';
+  private _ascending = true;
+  private _unsortedNames: string[] = [];
+}
+
+
+/**
+ * The namespace for the file browser model private data.
+ */
+namespace Private {
+  /**
+   * A signal emitted when a model refresh occurs.
+   */
+  export
+  const refreshedSignal = new Signal<FileBrowserModel, void>();
+
+  /**
+   * A signal emitted when a file open is requested.
+   */
+  export
+  const openRequestedSignal = new Signal<FileBrowserModel, IContentsModel>();
+
+  /**
+   * A signal emitted when the a file changes path.
+   */
+  export
+  const fileChangedSignal = new Signal<FileBrowserModel, IChangedArgs<string>>();
+
+  /**
+   * A signal emitted when the selection changes.
+   */
+  export
+  const selectionChangedSignal = new Signal<FileBrowserModel, void>();
+
+  /**
+   * Parse the content of a `FileReader`.
+   *
+   * If the result is an `ArrayBuffer`, return a Base64-encoded string.
+   * Otherwise, return the JSON parsed result.
+   */
+  export
+  function getContent(reader: FileReader): any {
+    if (reader.result instanceof ArrayBuffer) {
+      // Base64-encode binary file data.
+      let bytes = '';
+      let buf = new Uint8Array(reader.result);
+      let nbytes = buf.byteLength;
+      for (let i = 0; i < nbytes; i++) {
+        bytes += String.fromCharCode(buf[i]);
+      }
+      return btoa(bytes);
+    } else {
+      return JSON.parse(reader.result);
+    }
+  }
+
+  /**
+   * Normalize a path based on a root directory, accounting for relative paths.
+   */
+  export
+  function normalizePath(root: string, path: string): string {
+    // Current directory
+    if (path === '.') {
+      return root;
+    }
+    // Root path.
+    if (path.indexOf('/') === 0) {
+      path = path.slice(1, path.length);
+      root = ''
+    // Current directory.
+    } else if (path.indexOf('./') === 0) {
+      path = path.slice(2, path.length);
+    // Grandparent directory.
+    } else if (path.indexOf('../../') === 0) {
+      let parts = root.split('/');
+      root = parts.splice(0, parts.length - 2).join('/');
+      path = path.slice(6, path.length);
+    // Parent directory.
+    } else if (path.indexOf('../') === 0) {
+      let parts = root.split('/');
+      root = parts.splice(0, parts.length - 1).join('/');
+      path = path.slice(3, path.length);
+    } else {
+      // Current directory.
+    }
+    if (path[path.length - 1] === '/') {
+      path = path.slice(0, path.length - 1);
+    }
+    // Combine the root and the path if necessary.
+    if (root && path) {
+      path = root + '/' + path;
+    } else if (root) {
+      path = root;
+    }
+    return path;
+  }
+
+  /**
+   * Work around TS 1.8 type inferencing in promises which only throw.
+   */
+  export
+  function typedThrow<T>(msg: string): T {
+    throw new Error(msg);
+  }
+}

+ 16 - 0
src/filebrowser/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "ES5",
+    "outDir": "../lib"
+  },
+  "files": [
+    "../typings/es6-promise.d.ts",
+    "../typings/moment/moment.d.ts",
+    "index.ts"
+  ]
+}

+ 86 - 0
src/filebrowser/utils.ts

@@ -0,0 +1,86 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  okButton, showDialog
+} from 'jupyter-js-domutils';
+
+import {
+  hitTest
+} from 'phosphor-domutil';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+
+/**
+ * The class name added to FileBrowser instances.
+ */
+export
+const FILE_BROWSER_CLASS = 'jp-FileBrowser';
+
+/**
+ * The class name added to drop targets.
+ */
+export
+const DROP_TARGET_CLASS = 'jp-mod-dropTarget';
+
+/**
+ * The class name added to selected rows.
+ */
+export
+const SELECTED_CLASS = 'jp-mod-selected';
+
+/**
+ * The mime type for a contents drag object.
+ */
+export
+const CONTENTS_MIME = 'application/x-jupyter-icontents';
+
+
+/**
+ * An error message dialog to show in the filebrowser widget.
+ */
+export
+function showErrorMessage(host: Widget, title: string, error: Error): Promise<void> {
+  console.error(error);
+  if (!host.isAttached) {
+    return;
+  }
+  // Find the file browser node.
+  let node = host.node;
+  while (!node.classList.contains(FILE_BROWSER_CLASS)) {
+    node = node.parentElement;
+  }
+  let options = {
+    title: title,
+    host: node,
+    body: error.message,
+    buttons: [okButton]
+  }
+  return showDialog(options).then(() => {});
+}
+
+
+/**
+ * Get the index of the node at a client position, or `-1`.
+ */
+export
+function hitTestNodes(nodes: HTMLElement[] | NodeList, x: number, y: number): number {
+  for (let i = 0, n = nodes.length; i < n; ++i) {
+    if (hitTest(nodes[i] as HTMLElement, x, y)) return i;
+  }
+  return -1;
+}
+
+
+/**
+ * Find the first element matching a class name.
+ */
+export
+function findElement(parent: HTMLElement, className: string): HTMLElement {
+  let elements = parent.getElementsByClassName(className);
+  if (elements.length) return elements[0] as HTMLElement;
+}

+ 12 - 0
src/terminal/index.css

@@ -0,0 +1,12 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+.jp-TerminalWidget {
+  padding: 0;
+  margin: 0;
+}
+
+.jp-TerminalWidget-body {
+  font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
+}

+ 401 - 0
src/terminal/index.ts

@@ -0,0 +1,401 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  getWsUrl
+} from 'jupyter-js-utils';
+
+import {
+  Message, sendMessage
+} from 'phosphor-messaging';
+
+import {
+  ResizeMessage, Widget
+} from 'phosphor-widget';
+
+import {
+  Terminal, ITerminalConfig
+} from 'term.js';
+
+import './index.css';
+
+
+/**
+ * The class name added to a terminal widget.
+ */
+const TERMINAL_CLASS = 'jp-TerminalWidget';
+
+/**
+ * The class name added to a terminal body.
+ */
+const TERMINAL_BODY_CLASS = 'jp-TerminalWidget-body';
+
+
+/**
+ * Options for the terminal widget.
+ */
+export
+interface ITerminalOptions {
+  /**
+   * The base websocket url.
+   */
+  baseUrl?: string;
+
+  /**
+   * The font size of the terminal in pixels.
+   */
+  fontSize?: number;
+
+  /**
+   * The background color of the terminal.
+   */
+  background?: string;
+
+  /**
+   * The text color of the terminal.
+   */
+  color?: string;
+
+  /**
+   * Whether to blink the cursor.  Can only be set at startup.
+   */
+  cursorBlink?: boolean;
+
+  /**
+   * Whether to show a bell in the terminal.
+   */
+  visualBell?: boolean;
+
+  /**
+   * Whether to focus on a bell event.
+   */
+  popOnBell?: boolean;
+
+  /**
+   * The size of the scrollback buffer in the terminal.
+   */
+  scrollback?: number;
+}
+
+
+/**
+ * A widget which manages a terminal session.
+ */
+export
+class TerminalWidget extends Widget {
+
+  /**
+   * The number of terminals started.  Used to ensure unique sessions.
+   */
+  static nterms = 0;
+
+  /**
+   * Construct a new terminal widget.
+   *
+   * @param options - The terminal configuration options.
+   */
+  constructor(options?: ITerminalOptions) {
+    super();
+    options = options || {};
+    this.addClass(TERMINAL_CLASS);
+    let baseUrl = options.baseUrl || getWsUrl();
+
+    TerminalWidget.nterms += 1;
+    let url = baseUrl + 'terminals/websocket/' + TerminalWidget.nterms;
+    this._ws = new WebSocket(url);
+    this.id = `jp-TerminalWidget-${TerminalWidget.nterms}`;
+
+    // Set the default title.
+    this.title.text = 'Terminal ' + TerminalWidget.nterms;
+
+    Terminal.brokenBold = true;
+
+    this._term = new Terminal(getConfig(options));
+    this._term.open(this.node);
+    this._term.element.classList.add(TERMINAL_BODY_CLASS)
+
+    this._dummyTerm = createDummyTerm();
+    this.fontSize = options.fontSize || 11;
+    this.background = options.background || 'white';
+    this.color = options.color || 'black';
+
+    this._term.on('data', (data: string) => {
+      this._ws.send(JSON.stringify(['stdin', data]));
+    });
+
+    this._term.on('title', (title: string) => {
+        this.title.text = title;
+    });
+
+    this._ws.onmessage = (event: MessageEvent) => {
+      var json_msg = JSON.parse(event.data);
+      switch (json_msg[0]) {
+      case 'stdout':
+        this._term.write(json_msg[1]);
+        break;
+      case 'disconnect':
+        this._term.write('\r\n\r\n[Finished... Term Session]\r\n');
+        break;
+      }
+    };
+
+    this._sheet = document.createElement('style');
+    this.node.appendChild(this._sheet);
+  }
+
+  /**
+   * Get the font size of the terminal in pixels.
+   */
+  get fontSize(): number {
+    return this._fontSize;
+  }
+
+  /**
+   * Set the font size of the terminal in pixels.
+   */
+  set fontSize(size: number) {
+    this._fontSize = size;
+    this._term.element.style.fontSize = `${size}px`;
+    this._snapTermSizing();
+  }
+
+  /**
+   * Get the background color of the terminal.
+   */
+  get background(): string {
+    return this._background
+  }
+
+  /**
+   * Set the background color of the terminal.
+   */
+  set background(value: string) {
+    this._background = value;
+    this.update();
+  }
+
+  /**
+   * Get the text color of the terminal.
+   */
+  get color(): string {
+    return this._color;
+  }
+
+  /**
+   * Set the text color of the terminal.
+   */
+  set color(value: string) {
+    this._color = value;
+    this.update();
+  }
+
+  /**
+   * Get whether the bell is shown.
+   */
+  get visualBell(): boolean {
+    return this._term.visualBell;
+  }
+
+  /**
+   * Set whether the bell is shown.
+   */
+  set visualBell(value: boolean) {
+    this._term.visualBell = value;
+  }
+
+  /**
+   * Get whether to focus on a bell event.
+   */
+  get popOnBell(): boolean {
+    return this._term.popOnBell;
+  }
+
+  /**
+   * Set whether to focus on a bell event.
+   */
+  set popOnBell(value: boolean) {
+    this._term.popOnBell = value;
+  }
+
+  /**
+   * Get the size of the scrollback buffer in the terminal.
+   */
+  get scrollback(): number {
+    return this._term.scrollback;
+  }
+
+  /**
+   * Set the size of the scrollback buffer in the terminal.
+   */
+  set scrollback(value: number) {
+    this._term.scrollback = value;
+  }
+
+  /**
+   * Dispose of the resources held by the terminal widget.
+   */
+  dispose(): void {
+    this._term.destroy();
+    this._sheet = null;
+    this._ws = null;
+    this._term = null;
+    this._dummyTerm = null;
+    super.dispose();
+  }
+
+  /**
+   * Process a message sent to the widget.
+   *
+   * @param msg - The message sent to the widget.
+   *
+   * #### Notes
+   * Subclasses may reimplement this method as needed.
+   */
+  processMessage(msg: Message): void {
+    super.processMessage(msg);
+    switch (msg.type) {
+      case 'fit-request':
+        this.onFitRequest(msg);
+        break;
+    }
+  }
+
+  /**
+   * Set the size of the terminal when attached if dirty.
+   */
+  protected onAfterAttach(msg: Message): void {
+    if (this._dirty) {
+      this._snapTermSizing();
+    }
+  }
+
+  /**
+   * Set the size of the terminal when shown if dirty.
+   */
+  protected onAfterShow(msg: Message): void {
+    if (this._dirty) {
+      this._snapTermSizing();
+    }
+  }
+
+  /**
+   * On resize, use the computed row and column sizes to resize the terminal.
+   */
+  protected onResize(msg: ResizeMessage): void {
+    if (!this.isAttached || !this.isVisible) {
+      return;
+    }
+    let width = msg.width;
+    let height = msg.height;
+    if (width < 0 || height < 0) {
+      let rect = this.node.getBoundingClientRect();
+      if (width < 0) width = rect.width;
+      if (height < 0) height = rect.height;
+    }
+    this._width = width;
+    this._height = height;
+    this._resizeTerminal();
+  }
+
+  /**
+   * A message handler invoked on an `'update-request'` message.
+   */
+  protected onUpdateRequest(msg: Message): void {
+    // Set the fg and bg colors of the terminal and cursor.
+    this._term.element.style.backgroundColor = this.background;
+    this._term.element.style.color = this.color;
+    this._sheet.innerHTML = (".terminal-cursor {background:" + this.color +
+                             ";color:" + this.background + ";}");
+  }
+
+  /**
+   * A message handler invoked on an `'fit-request'` message.
+   */
+  protected onFitRequest(msg: Message): void {
+    let resize = ResizeMessage.UnknownSize;
+    sendMessage(this, resize);
+  }
+
+  /**
+   * Use the dummy terminal to measure the row and column sizes.
+   */
+  private _snapTermSizing(): void {
+    if (!this.isAttached || !this.isVisible) {
+      this._dirty = true;
+      return;
+    }
+    let node = this._dummyTerm;
+    this._term.element.appendChild(node);
+    this._row_height = node.offsetHeight;
+    this._col_width = node.offsetWidth / 80;
+    this._term.element.removeChild(node);
+    this._dirty = false;
+    if (this._width !== -1) {
+      this._resizeTerminal();
+    }
+  }
+
+  /**
+   * Resize the terminal based on the computed geometry.
+   */
+  private _resizeTerminal() {
+    var rows = Math.max(2, Math.round(this._height / this._row_height) - 1);
+    var cols = Math.max(3, Math.round(this._width / this._col_width) - 1);
+    this._term.resize(cols, rows);
+  }
+
+  private _term: Terminal = null;
+  private _ws: WebSocket = null;
+  private _row_height = -1;
+  private _col_width = -1;
+  private _sheet: HTMLElement = null;
+  private _dummyTerm: HTMLElement = null;
+  private _fontSize = -1;
+  private _dirty = false;
+  private _width = -1;
+  private _height = -1;
+  private _background = ''
+  private _color = '';
+}
+
+
+/**
+ * Get term.js options from ITerminalOptions.
+ */
+function getConfig(options: ITerminalOptions): ITerminalConfig {
+  let config: ITerminalConfig = {};
+  if (options.cursorBlink !== void 0) {
+    config.cursorBlink = options.cursorBlink;
+  }
+  if (options.visualBell !== void 0) {
+    config.visualBell = options.visualBell;
+  }
+  if (options.popOnBell !== void 0) {
+    config.popOnBell = options.popOnBell;
+  }
+  if (options.scrollback !== void 0) {
+    config.scrollback = options.scrollback;
+  }
+  return config;
+}
+
+
+/**
+ * Create a dummy terminal element used to measure text size.
+ */
+function createDummyTerm(): HTMLElement {
+  let node = document.createElement('div');
+  node.innerHTML = (
+    '01234567890123456789' +
+    '01234567890123456789' +
+    '01234567890123456789' +
+    '01234567890123456789'
+  );
+  node.style.visibility = 'hidden';
+  node.style.position = 'absolute';
+  node.style.height = 'auto';
+  node.style.width = 'auto';
+  (node.style as any)['white-space'] = 'nowrap';
+  return node;
+}

+ 16 - 0
src/terminal/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "ES5",
+    "outDir": "../lib"
+  },
+  "files": [
+    "../typings/term.js.d.ts",
+    "../typings/es6-promise.d.ts",
+    "index.ts"
+  ]
+}

+ 1123 - 0
src/typings/codemirror/codemirror.d.ts

@@ -0,0 +1,1123 @@
+// Type definitions for CodeMirror
+// Project: https://github.com/marijnh/CodeMirror
+// Definitions by: mihailik <https://github.com/mihailik>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare function CodeMirror(host: HTMLElement, options?: CodeMirror.EditorConfiguration): CodeMirror.Editor;
+declare function CodeMirror(callback: (host: HTMLElement) => void , options?: CodeMirror.EditorConfiguration): CodeMirror.Editor;
+
+declare module CodeMirror {
+    export var Doc : CodeMirror.DocConstructor;
+    export var Pos: CodeMirror.PositionConstructor;
+    export var Pass: any;
+
+    function fromTextArea(host: HTMLTextAreaElement, options?: EditorConfiguration): CodeMirror.EditorFromTextArea;
+
+
+    // findMode* functions are from loading the codemirror/mode/meta module
+    interface modespec {
+      name: string;
+      mode: string;
+      mime: string;
+    }
+    function findModeByName(name: string): modespec;
+    function findModeByFileName(name: string): modespec;
+    function findModeByMIME(mime: string): modespec;
+
+    var modes: {
+      [key: string]: any;
+    };
+
+    var mimeModes: {
+        [key: string]: any;
+    }
+
+    var version: string;
+
+    /** If you want to define extra methods in terms of the CodeMirror API, it is possible to use defineExtension.
+    This will cause the given value(usually a method) to be added to all CodeMirror instances created from then on. */
+    function defineExtension(name: string, value: any): void;
+
+    /** Like defineExtension, but the method will be added to the interface for Doc objects instead. */
+    function defineDocExtension(name: string, value: any): void;
+
+    /** Similarly, defineOption can be used to define new options for CodeMirror.
+    The updateFunc will be called with the editor instance and the new value when an editor is initialized,
+    and whenever the option is modified through setOption. */
+    function defineOption(name: string, default_: any, updateFunc: Function): void;
+
+    /** If your extention just needs to run some code whenever a CodeMirror instance is initialized, use CodeMirror.defineInitHook.
+    Give it a function as its only argument, and from then on, that function will be called (with the instance as argument)
+    whenever a new CodeMirror instance is initialized. */
+    function defineInitHook(func: Function): void;
+
+
+
+    function on(element: any, eventName: string, handler: Function): void;
+    function off(element: any, eventName: string, handler: Function): void;
+
+    /** Fired whenever a change occurs to the document. changeObj has a similar type as the object passed to the editor's "change" event,
+    but it never has a next property, because document change events are not batched (whereas editor change events are). */
+    function on(doc: Doc, eventName: 'change', handler: (instance: Doc, change: EditorChange) => void ): void;
+    function off(doc: Doc, eventName: 'change', handler: (instance: Doc, change: EditorChange) => void ): void;
+
+    /** See the description of the same event on editor instances. */
+    function on(doc: Doc, eventName: 'beforeChange', handler: (instance: Doc, change: EditorChangeCancellable) => void ): void;
+    function off(doc: Doc, eventName: 'beforeChange', handler: (instance: Doc, change: EditorChangeCancellable) => void ): void;
+
+    /** Fired whenever the cursor or selection in this document changes. */
+    function on(doc: Doc, eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;
+    function off(doc: Doc, eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;
+
+    /** Equivalent to the event by the same name as fired on editor instances. */
+    function on(doc: Doc, eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: Position; anchor: Position; }) => void ): void;
+    function off(doc: Doc, eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: Position; anchor: Position; }) => void ): void;
+
+    /** Will be fired when the line object is deleted. A line object is associated with the start of the line.
+    Mostly useful when you need to find out when your gutter markers on a given line are removed. */
+    function on(line: LineHandle, eventName: 'delete', handler: () => void ): void;
+    function off(line: LineHandle, eventName: 'delete', handler: () => void ): void;
+
+    /** Fires when the line's text content is changed in any way (but the line is not deleted outright).
+    The change object is similar to the one passed to change event on the editor object. */
+    function on(line: LineHandle, eventName: 'change', handler: (line: LineHandle, change: EditorChange) => void ): void;
+    function off(line: LineHandle, eventName: 'change', handler: (line: LineHandle, change: EditorChange) => void ): void;
+
+    /** Fired when the cursor enters the marked range. From this event handler, the editor state may be inspected but not modified,
+    with the exception that the range on which the event fires may be cleared. */
+    function on(marker: TextMarker, eventName: 'beforeCursorEnter', handler: () => void ): void;
+    function off(marker: TextMarker, eventName: 'beforeCursorEnter', handler: () => void ): void;
+
+    /** Fired when the range is cleared, either through cursor movement in combination with clearOnEnter or through a call to its clear() method.
+    Will only be fired once per handle. Note that deleting the range through text editing does not fire this event,
+    because an undo action might bring the range back into existence. */
+    function on(marker: TextMarker, eventName: 'clear', handler: () => void ): void;
+    function off(marker: TextMarker, eventName: 'clear', handler: () => void ): void;
+
+    /** Fired when the last part of the marker is removed from the document by editing operations. */
+    function on(marker: TextMarker, eventName: 'hide', handler: () => void ): void;
+    function off(marker: TextMarker, eventName: 'hide', handler: () => void ): void;
+
+    /** Fired when, after the marker was removed by editing, a undo operation brought the marker back. */
+    function on(marker: TextMarker, eventName: 'unhide', handler: () => void ): void;
+    function off(marker: TextMarker, eventName: 'unhide', handler: () => void ): void;
+
+    /** Fired whenever the editor re-adds the widget to the DOM. This will happen once right after the widget is added (if it is scrolled into view),
+    and then again whenever it is scrolled out of view and back in again, or when changes to the editor options
+    or the line the widget is on require the widget to be redrawn. */
+    function on(line: LineWidget, eventName: 'redraw', handler: () => void ): void;
+    function off(line: LineWidget, eventName: 'redraw', handler: () => void ): void;
+
+    /** Various CodeMirror-related objects emit events, which allow client code to react to various situations.
+    Handlers for such events can be registered with the on and off methods on the objects that the event fires on.
+    To fire your own events, use CodeMirror.signal(target, name, args...), where target is a non-DOM-node object. */
+    function signal(target: any, name: string, ...args: any[]): void;
+
+    interface Editor {
+
+        /** Tells you whether the editor currently has focus. */
+        hasFocus(): boolean;
+
+        /** Used to find the target position for horizontal cursor motion.start is a { line , ch } object,
+        amount an integer(may be negative), and unit one of the string "char", "column", or "word".
+        Will return a position that is produced by moving amount times the distance specified by unit.
+        When visually is true , motion in right - to - left text will be visual rather than logical.
+        When the motion was clipped by hitting the end or start of the document, the returned value will have a hitSide property set to true. */
+        findPosH(start: CodeMirror.Position, amount: number, unit: string, visually: boolean): { line: number; ch: number; hitSide?: boolean; };
+
+        /** Similar to findPosH , but used for vertical motion.unit may be "line" or "page".
+        The other arguments and the returned value have the same interpretation as they have in findPosH. */
+        findPosV(start: CodeMirror.Position, amount: number, unit: string): { line: number; ch: number; hitSide?: boolean; };
+
+
+        /** Change the configuration of the editor. option should the name of an option, and value should be a valid value for that option. */
+        setOption(option: string, value: any): void;
+
+        /** Retrieves the current value of the given option for this editor instance. */
+        getOption(option: string): any;
+
+        /** Attach an additional keymap to the editor.
+        This is mostly useful for add - ons that need to register some key handlers without trampling on the extraKeys option.
+        Maps added in this way have a higher precedence than the extraKeys and keyMap options, and between them,
+        the maps added earlier have a lower precedence than those added later, unless the bottom argument was passed,
+        in which case they end up below other keymaps added with this method. */
+        addKeyMap(map: any, bottom?: boolean): void;
+
+        /** Disable a keymap added with addKeyMap.Either pass in the keymap object itself , or a string,
+        which will be compared against the name property of the active keymaps. */
+        removeKeyMap(map: any): void;
+
+        /** Enable a highlighting overlay.This is a stateless mini - mode that can be used to add extra highlighting.
+        For example, the search add - on uses it to highlight the term that's currently being searched.
+        mode can be a mode spec or a mode object (an object with a token method). The options parameter is optional. If given, it should be an object.
+        Currently, only the opaque option is recognized. This defaults to off, but can be given to allow the overlay styling, when not null,
+        to override the styling of the base mode entirely, instead of the two being applied together. */
+        addOverlay(mode: any, options?: any): void;
+
+        /** Pass this the exact argument passed for the mode parameter to addOverlay to remove an overlay again. */
+        removeOverlay(mode: any): void;
+
+
+        /** Retrieve the currently active document from an editor. */
+        getDoc(): CodeMirror.Doc;
+
+        /** Attach a new document to the editor. Returns the old document, which is now no longer associated with an editor. */
+        swapDoc(doc: CodeMirror.Doc): CodeMirror.Doc;
+
+
+
+        /** Sets the gutter marker for the given gutter (identified by its CSS class, see the gutters option) to the given value.
+        Value can be either null, to clear the marker, or a DOM element, to set it. The DOM element will be shown in the specified gutter next to the specified line. */
+        setGutterMarker(line: any, gutterID: string, value: HTMLElement): CodeMirror.LineHandle;
+
+        /** Remove all gutter markers in the gutter with the given ID. */
+        clearGutter(gutterID: string): void;
+
+        /** Set a CSS class name for the given line.line can be a number or a line handle.
+        where determines to which element this class should be applied, can can be one of "text" (the text element, which lies in front of the selection),
+        "background"(a background element that will be behind the selection),
+        or "wrap" (the wrapper node that wraps all of the line's elements, including gutter elements).
+        class should be the name of the class to apply. */
+        addLineClass(line: any, where: string, _class_: string): CodeMirror.LineHandle;
+
+        /** Remove a CSS class from a line.line can be a line handle or number.
+        where should be one of "text", "background", or "wrap"(see addLineClass).
+        class can be left off to remove all classes for the specified node, or be a string to remove only a specific class. */
+        removeLineClass(line: any, where: string, class_: string): CodeMirror.LineHandle;
+
+        /** Returns the line number, text content, and marker status of the given line, which can be either a number or a line handle. */
+        lineInfo(line: any): {
+            line: any;
+            handle: any;
+            text: string;
+            /** Object mapping gutter IDs to marker elements. */
+            gutterMarks: any;
+            textClass: string;
+            bgClass: string;
+            wrapClass: string;
+            /** Array of line widgets attached to this line. */
+            widgets: any;
+        };
+
+        /** Puts node, which should be an absolutely positioned DOM node, into the editor, positioned right below the given { line , ch } position.
+        When scrollIntoView is true, the editor will ensure that the entire node is visible (if possible).
+        To remove the widget again, simply use DOM methods (move it somewhere else, or call removeChild on its parent). */
+        addWidget(pos: CodeMirror.Position, node: HTMLElement, scrollIntoView: boolean): void;
+
+        /** Adds a line widget, an element shown below a line, spanning the whole of the editor's width, and moving the lines below it downwards.
+        line should be either an integer or a line handle, and node should be a DOM node, which will be displayed below the given line.
+        options, when given, should be an object that configures the behavior of the widget.
+        Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some cases affect it. */
+        addLineWidget(line: any, node: HTMLElement, options?: {
+            /** Whether the widget should cover the gutter. */
+            coverGutter: boolean;
+            /** Whether the widget should stay fixed in the face of horizontal scrolling. */
+            noHScroll: boolean;
+            /** Causes the widget to be placed above instead of below the text of the line. */
+            above: boolean;
+            /** When true, will cause the widget to be rendered even if the line it is associated with is hidden. */
+            showIfHidden: boolean;
+        }): CodeMirror.LineWidget;
+
+
+        /** Programatically set the size of the editor (overriding the applicable CSS rules).
+        width and height height can be either numbers(interpreted as pixels) or CSS units ("100%", for example).
+        You can pass null for either of them to indicate that that dimension should not be changed. */
+        setSize(width: any, height: any): void;
+
+        /** Scroll the editor to a given(pixel) position.Both arguments may be left as null or undefined to have no effect. */
+        scrollTo(x: number, y: number): void;
+
+        /** Get an { left , top , width , height , clientWidth , clientHeight } object that represents the current scroll position, the size of the scrollable area,
+        and the size of the visible area(minus scrollbars). */
+        getScrollInfo(): {
+            left: any;
+            top: any;
+            width: any;
+            height: any;
+            clientWidth: any;
+            clientHeight: any;
+        }
+
+        /** Scrolls the given element into view. pos is a { line , ch } position, referring to a given character, null, to refer to the cursor.
+        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
+        scrollIntoView(pos: CodeMirror.Position, margin?: number): void;
+
+        /** Scrolls the given element into view. pos is a { left , top , right , bottom } object, in editor-local coordinates.
+        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
+        scrollIntoView(pos: { left: number; top: number; right: number; bottom: number; }, margin: number): void;
+
+        /** Scrolls the given element into view. pos is a { line, ch } object, in editor-local coordinates.
+        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
+        scrollIntoView(pos: { line: number, ch: number }, margin?: number): void;
+
+        /** Scrolls the given element into view. pos is a { from, to } object, in editor-local coordinates.
+        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
+        scrollIntoView(pos: { from: CodeMirror.Position, to: CodeMirror.Position }, margin: number): void;
+
+        /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.
+        If mode is "local" , they will be relative to the top-left corner of the editable document.
+        If it is "page" or not given, they are relative to the top-left corner of the page.
+        where is a boolean indicating whether you want the start(true) or the end(false) of the selection. */
+        cursorCoords(where: boolean, mode: string): { left: number; top: number; bottom: number; };
+
+        /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.
+        If mode is "local" , they will be relative to the top-left corner of the editable document.
+        If it is "page" or not given, they are relative to the top-left corner of the page.
+        where specifies the precise position at which you want to measure. */
+        cursorCoords(where: CodeMirror.Position, mode: string): { left: number; top: number; bottom: number; };
+
+        /** Returns the position and dimensions of an arbitrary character.pos should be a { line , ch } object.
+        This differs from cursorCoords in that it'll give the size of the whole character,
+        rather than just the position that the cursor would have when it would sit at that position. */
+        charCoords(pos: CodeMirror.Position, mode: string): { left: number; right: number; top: number; bottom: number; };
+
+        /** Given an { left , top } object , returns the { line , ch } position that corresponds to it.
+        The optional mode parameter determines relative to what the coordinates are interpreted. It may be "window" , "page"(the default) , or "local". */
+        coordsChar(object: { left: number; top: number; }, mode?: string): CodeMirror.Position;
+
+        /** Returns the line height of the default font for the editor. */
+        defaultTextHeight(): number;
+
+        /** Returns the pixel width of an 'x' in the default font for the editor.
+        (Note that for non - monospace fonts , this is mostly useless, and even for monospace fonts, non - ascii characters might have a different width). */
+        defaultCharWidth(): number;
+
+        /** Returns a { from , to } object indicating the start (inclusive) and end (exclusive) of the currently rendered part of the document.
+        In big documents, when most content is scrolled out of view, CodeMirror will only render the visible part, and a margin around it.
+        See also the viewportChange event. */
+        getViewport(): { from: number; to: number };
+
+        /** If your code does something to change the size of the editor element (window resizes are already listened for), or unhides it,
+        you should probably follow up by calling this method to ensure CodeMirror is still looking as intended. */
+        refresh(): void;
+
+
+        /** Retrieves information about the token the current mode found before the given position (a {line, ch} object). */
+        getTokenAt(pos: CodeMirror.Position): {
+            /** The character(on the given line) at which the token starts. */
+            start: number;
+            /** The character at which the token ends. */
+            end: number;
+            /** The token's string. */
+            string: string;
+            /** The token type the mode assigned to the token, such as "keyword" or "comment" (may also be null). */
+            type: string;
+            /** The mode's state at the end of this token. */
+            state: any;
+        };
+
+        /** Returns the mode's parser state, if any, at the end of the given line number.
+        If no line number is given, the state at the end of the document is returned.
+        This can be useful for storing parsing errors in the state, or getting other kinds of contextual information for a line. */
+        getStateAfter(line?: number): any;
+
+        /** CodeMirror internally buffers changes and only updates its DOM structure after it has finished performing some operation.
+        If you need to perform a lot of operations on a CodeMirror instance, you can call this method with a function argument.
+        It will call the function, buffering up all changes, and only doing the expensive update after the function returns.
+        This can be a lot faster. The return value from this method will be the return value of your function. */
+        operation<T>(fn: ()=> T): T;
+
+        /** Adjust the indentation of the given line.
+        The second argument (which defaults to "smart") may be one of:
+        "prev" Base indentation on the indentation of the previous line.
+        "smart" Use the mode's smart indentation if available, behave like "prev" otherwise.
+        "add" Increase the indentation of the line by one indent unit.
+        "subtract" Reduce the indentation of the line. */
+        indentLine(line: number, dir?: string): void;
+
+
+        /** Give the editor focus. */
+        focus(): void;
+
+        /** Returns the hidden textarea used to read input. */
+        getInputField(): HTMLTextAreaElement;
+
+        /** Returns the DOM node that represents the editor, and controls its size. Remove this from your tree to delete an editor instance. */
+        getWrapperElement(): HTMLElement;
+
+        /** Returns the DOM node that is responsible for the scrolling of the editor. */
+        getScrollerElement(): HTMLElement;
+
+        /** Fetches the DOM node that contains the editor gutters. */
+        getGutterElement(): HTMLElement;
+
+
+
+        /** Events are registered with the on method (and removed with the off method).
+        These are the events that fire on the instance object. The name of the event is followed by the arguments that will be passed to the handler.
+        The instance argument always refers to the editor instance. */
+        on(eventName: string, handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: string, handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** Fires every time the content of the editor is changed. */
+        on(eventName: 'change', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChange) => void ): void;
+        off(eventName: 'change', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChange) => void ): void;
+
+        /** Like the "change" event, but batched per operation, passing an
+         * array containing all the changes that happened in the operation.
+         * This event is fired after the operation finished, and display
+         * changes it makes will trigger a new operation. */
+        on(eventName: 'changes', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChange[]) => void ): void;
+        off(eventName: 'changes', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChange[]) => void ): void;
+
+        /** This event is fired before a change is applied, and its handler may choose to modify or cancel the change.
+        The changeObj never has a next property, since this is fired for each individual change, and not batched per operation.
+        Note: you may not do anything from a "beforeChange" handler that would cause changes to the document or its visualization.
+        Doing so will, since this handler is called directly from the bowels of the CodeMirror implementation,
+        probably cause the editor to become corrupted. */
+        on(eventName: 'beforeChange', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeCancellable) => void ): void;
+        off(eventName: 'beforeChange', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeCancellable) => void ): void;
+
+        /** Will be fired when the cursor or selection moves, or any change is made to the editor content. */
+        on(eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** This event is fired before the selection is moved. Its handler may modify the resulting selection head and anchor.
+        Handlers for this event have the same restriction as "beforeChange" handlers � they should not do anything to directly update the state of the editor. */
+        on(eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: CodeMirror.Position; anchor: CodeMirror.Position; }) => void ): void;
+        off(eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: CodeMirror.Position; anchor: CodeMirror.Position; }) => void ): void;
+
+        /** Fires whenever the view port of the editor changes (due to scrolling, editing, or any other factor).
+        The from and to arguments give the new start and end of the viewport. */
+        on(eventName: 'viewportChange', handler: (instance: CodeMirror.Editor, from: number, to: number) => void ): void;
+        off(eventName: 'viewportChange', handler: (instance: CodeMirror.Editor, from: number, to: number) => void ): void;
+
+        /** Fires when the editor gutter (the line-number area) is clicked. Will pass the editor instance as first argument,
+        the (zero-based) number of the line that was clicked as second argument, the CSS class of the gutter that was clicked as third argument,
+        and the raw mousedown event object as fourth argument. */
+        on(eventName: 'gutterClick', handler: (instance: CodeMirror.Editor, line: number, gutter: string, clickEvent: Event) => void ): void;
+        off(eventName: 'gutterClick', handler: (instance: CodeMirror.Editor, line: number, gutter: string, clickEvent: Event) => void ): void;
+
+        /** Fires whenever the editor is focused. */
+        on(eventName: 'focus', handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: 'focus', handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** Fires whenever the editor is unfocused. */
+        on(eventName: 'blur', handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: 'blur', handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** Fires when the editor is scrolled. */
+        on(eventName: 'scroll', handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: 'scroll', handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** Will be fired whenever CodeMirror updates its DOM display. */
+        on(eventName: 'update', handler: (instance: CodeMirror.Editor) => void ): void;
+        off(eventName: 'update', handler: (instance: CodeMirror.Editor) => void ): void;
+
+        /** Fired whenever a line is (re-)rendered to the DOM. Fired right after the DOM element is built, before it is added to the document.
+        The handler may mess with the style of the resulting element, or add event handlers, but should not try to change the state of the editor. */
+        on(eventName: 'renderLine', handler: (instance: CodeMirror.Editor, line: number, element: HTMLElement) => void ): void;
+        off(eventName: 'renderLine', handler: (instance: CodeMirror.Editor, line: number, element: HTMLElement) => void ): void;
+    }
+
+    interface EditorFromTextArea extends Editor {
+
+        /** Copy the content of the editor into the textarea. */
+        save(): void;
+
+        /** Remove the editor, and restore the original textarea (with the editor's current content). */
+        toTextArea(): void;
+
+        /** Returns the textarea that the instance was based on. */
+        getTextArea(): HTMLTextAreaElement;
+    }
+
+    interface DocConstructor {
+        new (text: string, mode?: any, firstLineNumber?: number, lineSep?: string): Doc;
+        (text: string, mode?: any, firstLineNumber?: number, lineSep?: string): Doc;
+    }
+
+    interface Doc {
+        /** Get the current editor content. You can pass it an optional argument to specify the string to be used to separate lines (defaults to "\n"). */
+        getValue(seperator?: string): string;
+
+        /** Set the editor content. */
+        setValue(content: string): void;
+
+        /** Get the text between the given points in the editor, which should be {line, ch} objects.
+        An optional third argument can be given to indicate the line separator string to use (defaults to "\n"). */
+        getRange(from: Position, to: CodeMirror.Position, seperator?: string): string;
+
+        /** Replace the part of the document between from and to with the given string.
+        from and to must be {line, ch} objects. to can be left off to simply insert the string at position from. */
+        replaceRange(replacement: string, from: CodeMirror.Position, to: CodeMirror.Position): void;
+
+        /** Get the content of line n. */
+        getLine(n: number): string;
+
+        /** Set the content of line n. */
+        setLine(n: number, text: string): void;
+
+        /** Remove the given line from the document. */
+        removeLine(n: number): void;
+
+        /** Get the number of lines in the editor. */
+        lineCount(): number;
+
+        /** Get the first line of the editor. This will usually be zero but for linked sub-views,
+        or documents instantiated with a non-zero first line, it might return other values. */
+        firstLine(): number;
+
+        /** Get the last line of the editor. This will usually be lineCount() - 1, but for linked sub-views, it might return other values. */
+        lastLine(): number;
+
+        /** Fetches the line handle for the given line number. */
+        getLineHandle(num: number): CodeMirror.LineHandle;
+
+        /** Given a line handle, returns the current position of that line (or null when it is no longer in the document). */
+        getLineNumber(handle: CodeMirror.LineHandle): number;
+
+        /** Iterate over the whole document, and call f for each line, passing the line handle.
+        This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.
+        Note that line handles have a text property containing the line's content (as a string). */
+        eachLine(f: (line: CodeMirror.LineHandle) => void ): void;
+
+        /** Iterate over the range from start up to (not including) end, and call f for each line, passing the line handle.
+        This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.
+        Note that line handles have a text property containing the line's content (as a string). */
+        eachLine(start: number, end: number, f: (line: CodeMirror.LineHandle) => void ): void;
+
+        /** Set the editor content as 'clean', a flag that it will retain until it is edited, and which will be set again when such an edit is undone again.
+        Useful to track whether the content needs to be saved. */
+        markClean(): void;
+
+        /** Returns whether the document is currently clean (not modified since initialization or the last call to markClean). */
+        isClean(): boolean;
+
+
+
+        /** Get the currently selected code. */
+        getSelection(): string;
+
+        /** Replace the selection with the given string. By default, the new selection will span the inserted text.
+        The optional collapse argument can be used to change this � passing "start" or "end" will collapse the selection to the start or end of the inserted text. */
+        replaceSelection(replacement: string, collapse?: string): void;
+
+        /** start is a an optional string indicating which end of the selection to return.
+        It may be "start" , "end" , "head"(the side of the selection that moves when you press shift + arrow),
+        or "anchor"(the fixed side of the selection).Omitting the argument is the same as passing "head".A { line , ch } object will be returned. */
+        getCursor(start?: string): CodeMirror.Position;
+
+        /** Retrieves a list of all current selections. These will always be sorted, and never overlap (overlapping selections are merged).
+        Each object in the array contains anchor and head properties referring to {line, ch} objects. */
+        listSelections(): { anchor: CodeMirror.Position; head: CodeMirror.Position }[];
+
+        /** Return true if any text is selected. */
+        somethingSelected(): boolean;
+
+        /** Set the cursor position.You can either pass a single { line , ch } object , or the line and the character as two separate parameters. */
+        setCursor(pos: CodeMirror.Position): void;
+
+        /** Set the selection range.anchor and head should be { line , ch } objects.head defaults to anchor when not given. */
+        setSelection(anchor: CodeMirror.Position, head: CodeMirror.Position): void;
+
+        /** Similar to setSelection , but will, if shift is held or the extending flag is set,
+        move the head of the selection while leaving the anchor at its current place.
+        pos2 is optional , and can be passed to ensure a region (for example a word or paragraph) will end up selected
+        (in addition to whatever lies between that region and the current anchor). */
+        extendSelection(from: CodeMirror.Position, to?: CodeMirror.Position): void;
+
+        /** Sets or clears the 'extending' flag , which acts similar to the shift key,
+        in that it will cause cursor movement and calls to extendSelection to leave the selection anchor in place. */
+        setExtending(value: boolean): void;
+
+
+        /** Retrieve the editor associated with a document. May return null. */
+        getEditor(): CodeMirror.Editor;
+
+
+        /** Create an identical copy of the given doc. When copyHistory is true , the history will also be copied.Can not be called directly on an editor. */
+        copy(copyHistory: boolean): CodeMirror.Doc;
+
+        /** Create a new document that's linked to the target document. Linked documents will stay in sync (changes to one are also applied to the other) until unlinked. */
+        linkedDoc(options: {
+            /** When turned on, the linked copy will share an undo history with the original.
+            Thus, something done in one of the two can be undone in the other, and vice versa. */
+            sharedHist?: boolean;
+            from?: number;
+            /** Can be given to make the new document a subview of the original. Subviews only show a given range of lines.
+            Note that line coordinates inside the subview will be consistent with those of the parent,
+            so that for example a subview starting at line 10 will refer to its first line as line 10, not 0. */
+            to?: number;
+            /** By default, the new document inherits the mode of the parent. This option can be set to a mode spec to give it a different mode. */
+            mode: any;
+        }): CodeMirror.Doc;
+
+        /** Break the link between two documents. After calling this , changes will no longer propagate between the documents,
+        and, if they had a shared history, the history will become separate. */
+        unlinkDoc(doc: CodeMirror.Doc): void;
+
+        /** Will call the given function for all documents linked to the target document. It will be passed two arguments,
+        the linked document and a boolean indicating whether that document shares history with the target. */
+        iterLinkedDocs(fn: (doc: CodeMirror.Doc, sharedHist: boolean) => void ): void;
+
+        /** Undo one edit (if any undo events are stored). */
+        undo(): void;
+
+        /** Redo one undone edit. */
+        redo(): void;
+
+        /** Returns an object with {undo, redo } properties , both of which hold integers , indicating the amount of stored undo and redo operations. */
+        historySize(): { undo: number; redo: number; };
+
+        /** Clears the editor's undo history. */
+        clearHistory(): void;
+
+        /** Get a(JSON - serializeable) representation of the undo history. */
+        getHistory(): any;
+
+        /** Replace the editor's undo history with the one provided, which must be a value as returned by getHistory.
+        Note that this will have entirely undefined results if the editor content isn't also the same as it was when getHistory was called. */
+        setHistory(history: any): void;
+
+
+        /** Can be used to mark a range of text with a specific CSS class name. from and to should be { line , ch } objects. */
+        markText(from: CodeMirror.Position, to: CodeMirror.Position, options?: CodeMirror.TextMarkerOptions): TextMarker;
+
+        /** Inserts a bookmark, a handle that follows the text around it as it is being edited, at the given position.
+        A bookmark has two methods find() and clear(). The first returns the current position of the bookmark, if it is still in the document,
+        and the second explicitly removes the bookmark. */
+        setBookmark(pos: CodeMirror.Position, options?: {
+            /** Can be used to display a DOM node at the current location of the bookmark (analogous to the replacedWith option to markText). */
+            widget?: HTMLElement;
+
+            /** By default, text typed when the cursor is on top of the bookmark will end up to the right of the bookmark.
+            Set this option to true to make it go to the left instead. */
+            insertLeft?: boolean;
+        }): CodeMirror.TextMarker;
+
+        /** Returns an array of all the bookmarks and marked ranges found between the given positions. */
+        findMarks(from: CodeMirror.Position, to: CodeMirror.Position): TextMarker[];
+
+        /** Returns an array of all the bookmarks and marked ranges present at the given position. */
+        findMarksAt(pos: CodeMirror.Position): TextMarker[];
+
+        /** Returns an array containing all marked ranges in the document. */
+        getAllMarks(): CodeMirror.TextMarker[];
+
+
+        /** Gets the mode object for the editor. Note that this is distinct from getOption("mode"), which gives you the mode specification,
+        rather than the resolved, instantiated mode object. */
+        getMode(): any;
+
+        /** Calculates and returns a { line , ch } object for a zero-based index whose value is relative to the start of the editor's text.
+        If the index is out of range of the text then the returned object is clipped to start or end of the text respectively. */
+        posFromIndex(index: number): CodeMirror.Position;
+
+        /** The reverse of posFromIndex. */
+        indexFromPos(object: CodeMirror.Position): number;
+
+    }
+
+    interface LineHandle {
+        text: string;
+    }
+
+    interface TextMarker {
+        /** Remove the mark. */
+        clear(): void;
+
+        /** Returns a {from, to} object (both holding document positions), indicating the current position of the marked range,
+        or undefined if the marker is no longer in the document. */
+        find(): CodeMirror.Position;
+
+        /**  Returns an object representing the options for the marker. If copyWidget is given true, it will clone the value of the replacedWith option, if any. */
+        getOptions(copyWidget: boolean): CodeMirror.TextMarkerOptions;
+    }
+
+    interface LineWidget {
+        /** Removes the widget. */
+        clear(): void;
+
+        /** Call this if you made some change to the widget's DOM node that might affect its height.
+        It'll force CodeMirror to update the height of the line that contains the widget. */
+        changed(): void;
+    }
+
+    interface EditorChange {
+        /** Position (in the pre-change coordinate system) where the change started. */
+        from: CodeMirror.Position;
+        /** Position (in the pre-change coordinate system) where the change ended. */
+        to: CodeMirror.Position;
+        /** Array of strings representing the text that replaced the changed range (split by line). */
+        text: string[];
+        /**  Text that used to be between from and to, which is overwritten by this change. */
+        removed: string[];
+        /**  String representing the origin of the change event and wether it can be merged with history */
+        origin: string;
+    }
+
+    interface EditorChangeCancellable extends CodeMirror.EditorChange {
+        /** may be used to modify the change. All three arguments to update are optional, and can be left off to leave the existing value for that field intact. */
+        update(from?: CodeMirror.Position, to?: CodeMirror.Position, text?: string): void;
+
+        cancel(): void;
+    }
+
+    interface PositionConstructor {
+        new (line: number, ch: number): Position;
+        (line: number, ch: number): Position;
+    }
+
+    interface Position {
+        ch: number;
+        line: number;
+    }
+
+    interface EditorConfiguration {
+        /** string| The starting value of the editor. Can be a string, or a document object. */
+        value?: any;
+
+        /** string|object. The mode to use. When not given, this will default to the first mode that was loaded.
+        It may be a string, which either simply names the mode or is a MIME type associated with the mode.
+        Alternatively, it may be an object containing configuration options for the mode,
+        with a name property that names the mode (for example {name: "javascript", json: true}). */
+        mode?: any;
+
+        /** The theme to style the editor with. You must make sure the CSS file defining the corresponding .cm-s-[name] styles is loaded.
+        The default is "default". */
+        theme?: string;
+
+        /** How many spaces a block (whatever that means in the edited language) should be indented. The default is 2. */
+        indentUnit?: number;
+
+        /** Whether to use the context-sensitive indentation that the mode provides (or just indent the same as the line before). Defaults to true. */
+        smartIndent?: boolean;
+
+        /** The width of a tab character. Defaults to 4. */
+        tabSize?: number;
+
+        /** Whether, when indenting, the first N*tabSize spaces should be replaced by N tabs. Default is false. */
+        indentWithTabs?: boolean;
+
+        /** Configures whether the editor should re-indent the current line when a character is typed
+        that might change its proper indentation (only works if the mode supports indentation). Default is true. */
+        electricChars?: boolean;
+
+        /** Determines whether horizontal cursor movement through right-to-left (Arabic, Hebrew) text
+        is visual (pressing the left arrow moves the cursor left)
+        or logical (pressing the left arrow moves to the next lower index in the string, which is visually right in right-to-left text).
+        The default is false on Windows, and true on other platforms. */
+        rtlMoveVisually?: boolean;
+
+        /** Configures the keymap to use. The default is "default", which is the only keymap defined in codemirror.js itself.
+        Extra keymaps are found in the keymap directory. See the section on keymaps for more information. */
+        keyMap?: string;
+
+        /** Can be used to specify extra keybindings for the editor, alongside the ones defined by keyMap. Should be either null, or a valid keymap value. */
+        extraKeys?: any;
+
+        /** Whether CodeMirror should scroll or wrap for long lines. Defaults to false (scroll). */
+        lineWrapping?: boolean;
+
+        /** Whether to show line numbers to the left of the editor. */
+        lineNumbers?: boolean;
+
+        /** At which number to start counting lines. Default is 1. */
+        firstLineNumber?: number;
+
+        /** A function used to format line numbers. The function is passed the line number, and should return a string that will be shown in the gutter. */
+        lineNumberFormatter?: (line: number) => string;
+
+        /** Can be used to add extra gutters (beyond or instead of the line number gutter).
+        Should be an array of CSS class names, each of which defines a width (and optionally a background),
+        and which will be used to draw the background of the gutters.
+        May include the CodeMirror-linenumbers class, in order to explicitly set the position of the line number gutter
+        (it will default to be to the right of all other gutters). These class names are the keys passed to setGutterMarker. */
+        gutters?: string[];
+
+        /** Determines whether the gutter scrolls along with the content horizontally (false)
+        or whether it stays fixed during horizontal scrolling (true, the default). */
+        fixedGutter?: boolean;
+
+        /** boolean|string. This disables editing of the editor content by the user. If the special value "nocursor" is given (instead of simply true), focusing of the editor is also disallowed. */
+        readOnly?: any;
+
+        /**Whether the cursor should be drawn when a selection is active. Defaults to false. */
+        showCursorWhenSelecting?: boolean;
+
+        /** The maximum number of undo levels that the editor stores. Defaults to 40. */
+        undoDepth?: number;
+
+        /** The period of inactivity (in milliseconds) that will cause a new history event to be started when typing or deleting. Defaults to 500. */
+        historyEventDelay?: number;
+
+        /** The tab index to assign to the editor. If not given, no tab index will be assigned. */
+        tabindex?: number;
+
+        /** Can be used to make CodeMirror focus itself on initialization. Defaults to off.
+        When fromTextArea is used, and no explicit value is given for this option, it will be set to true when either the source textarea is focused,
+        or it has an autofocus attribute and no other element is focused. */
+        autofocus?: boolean;
+
+        /** Controls whether drag-and - drop is enabled. On by default. */
+        dragDrop?: boolean;
+
+        /** When given , this will be called when the editor is handling a dragenter , dragover , or drop event.
+        It will be passed the editor instance and the event object as arguments.
+        The callback can choose to handle the event itself , in which case it should return true to indicate that CodeMirror should not do anything further. */
+        onDragEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;
+
+        /** This provides a rather low - level hook into CodeMirror's key handling.
+        If provided, this function will be called on every keydown, keyup, and keypress event that CodeMirror captures.
+        It will be passed two arguments, the editor instance and the key event.
+        This key event is pretty much the raw key event, except that a stop() method is always added to it.
+        You could feed it to, for example, jQuery.Event to further normalize it.
+        This function can inspect the key event, and handle it if it wants to.
+        It may return true to tell CodeMirror to ignore the event.
+        Be wary that, on some browsers, stopping a keydown does not stop the keypress from firing, whereas on others it does.
+        If you respond to an event, you should probably inspect its type property and only do something when it is keydown
+        (or keypress for actions that need character data). */
+        onKeyEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;
+
+        /** Half - period in milliseconds used for cursor blinking. The default blink rate is 530ms. */
+        cursorBlinkRate?: number;
+
+        /** Determines the height of the cursor. Default is 1 , meaning it spans the whole height of the line.
+        For some fonts (and by some tastes) a smaller height (for example 0.85),
+        which causes the cursor to not reach all the way to the bottom of the line, looks better */
+        cursorHeight?: number;
+
+        /** Highlighting is done by a pseudo background - thread that will work for workTime milliseconds,
+        and then use timeout to sleep for workDelay milliseconds.
+        The defaults are 200 and 300, you can change these options to make the highlighting more or less aggressive. */
+        workTime?: number;
+
+        /** See workTime. */
+        workDelay?: number;
+
+        /** Indicates how quickly CodeMirror should poll its input textarea for changes(when focused).
+        Most input is captured by events, but some things, like IME input on some browsers, don't generate events that allow CodeMirror to properly detect it.
+        Thus, it polls. Default is 100 milliseconds. */
+        pollInterval?: number
+
+        /** By default, CodeMirror will combine adjacent tokens into a single span if they have the same class.
+        This will result in a simpler DOM tree, and thus perform better. With some kinds of styling(such as rounded corners),
+        this will change the way the document looks. You can set this option to false to disable this behavior. */
+        flattenSpans?: boolean;
+
+        /** When highlighting long lines, in order to stay responsive, the editor will give up and simply style
+        the rest of the line as plain text when it reaches a certain position. The default is 10000.
+        You can set this to Infinity to turn off this behavior. */
+        maxHighlightLength?: number;
+
+        /** Specifies the amount of lines that are rendered above and below the part of the document that's currently scrolled into view.
+        This affects the amount of updates needed when scrolling, and the amount of work that such an update does.
+        You should usually leave it at its default, 10. Can be set to Infinity to make sure the whole document is always rendered,
+        and thus the browser's text search works on it. This will have bad effects on performance of big documents. */
+        viewportMargin?: number;
+
+        /** Optional lint configuration to be used in conjunction with CodeMirror's linter addon. */
+        lint?: boolean | LintOptions;
+
+    /** Optional value to be used in conduction with CodeMirror’s placeholder add-on. */
+    placeholder?: string;
+    }
+
+    interface TextMarkerOptions {
+        /** Assigns a CSS class to the marked stretch of text. */
+        className?: string;
+
+        /** Determines whether text inserted on the left of the marker will end up inside or outside of it. */
+        inclusiveLeft?: boolean;
+
+        /** Like inclusiveLeft , but for the right side. */
+        inclusiveRight?: boolean;
+
+        /** Atomic ranges act as a single unit when cursor movement is concerned — i.e. it is impossible to place the cursor inside of them.
+        In atomic ranges, inclusiveLeft and inclusiveRight have a different meaning — they will prevent the cursor from being placed
+        respectively directly before and directly after the range. */
+        atomic?: boolean;
+
+        /** Collapsed ranges do not show up in the display.Setting a range to be collapsed will automatically make it atomic. */
+        collapsed?: boolean;
+
+        /** When enabled, will cause the mark to clear itself whenever the cursor enters its range.
+        This is mostly useful for text - replacement widgets that need to 'snap open' when the user tries to edit them.
+        The "clear" event fired on the range handle can be used to be notified when this happens. */
+        clearOnEnter?: boolean;
+
+        /** Determines whether the mark is automatically cleared when it becomes empty. Default is true. */
+        clearWhenEmpty?: boolean;
+
+        /** Use a given node to display this range.Implies both collapsed and atomic.
+        The given DOM node must be an inline element(as opposed to a block element). */
+        replacedWith?: HTMLElement;
+
+        /** When replacedWith is given, this determines whether the editor will
+         * capture mouse and drag events occurring in this widget. Default is
+         * false—the events will be left alone for the default browser handler,
+         * or specific handlers on the widget, to capture. */
+        handleMouseEvents?: boolean;
+
+        /** A read - only span can, as long as it is not cleared, not be modified except by calling setValue to reset the whole document.
+        Note: adding a read - only span currently clears the undo history of the editor,
+        because existing undo events being partially nullified by read - only spans would corrupt the history (in the current implementation). */
+        readOnly?: boolean;
+
+        /** When set to true (default is false), adding this marker will create an event in the undo history that can be individually undone(clearing the marker). */
+        addToHistory?: boolean;
+
+        /** Can be used to specify an extra CSS class to be applied to the leftmost span that is part of the marker. */
+        startStyle?: string;
+
+        /** Equivalent to startStyle, but for the rightmost span. */
+        endStyle?: string;
+
+        /** A string of CSS to be applied to the covered text. For example "color: #fe3". */
+        css?: string;
+
+        /** When given, will give the nodes created for this span a HTML title attribute with the given value. */
+        title?: string;
+
+        /** When the target document is linked to other documents, you can set shared to true to make the marker appear in all documents.
+        By default, a marker appears only in its target document. */
+        shared?: boolean;
+    }
+
+    interface StringStream {
+        lastColumnPos: number;
+        lastColumnValue: number;
+        lineStart: number;
+
+        /**
+         * Current position in the string.
+         */
+        pos: number;
+
+        /**
+         * Where the stream's position was when it was first passed to the token function.
+         */
+        start: number;
+
+        /**
+         * The current line's content.
+         */
+        string: string;
+
+        /**
+         * Number of spaces per tab character.
+         */
+        tabSize: number;
+
+        /**
+         * Returns true only if the stream is at the end of the line.
+         */
+        eol(): boolean;
+
+        /**
+         * Returns true only if the stream is at the start of the line.
+         */
+        sol(): boolean;
+
+        /**
+         * Returns the next character in the stream without advancing it. Will return an null at the end of the line.
+         */
+        peek(): string;
+
+        /**
+         * Returns the next character in the stream and advances it. Also returns null when no more characters are available.
+         */
+        next(): string;
+
+        /**
+         * match can be a character, a regular expression, or a function that takes a character and returns a boolean.
+         * If the next character in the stream 'matches' the given argument, it is consumed and returned.
+         * Otherwise, undefined is returned.
+         */
+        eat(match: string): string;
+        eat(match: RegExp): string;
+        eat(match: (char: string) => boolean): string;
+
+        /**
+         * Repeatedly calls eat with the given argument, until it fails. Returns true if any characters were eaten.
+         */
+        eatWhile(match: string): boolean;
+        eatWhile(match: RegExp): boolean;
+        eatWhile(match: (char: string) => boolean): boolean;
+
+        /**
+         * Shortcut for eatWhile when matching white-space.
+         */
+        eatSpace(): boolean;
+
+        /**
+         * Moves the position to the end of the line.
+         */
+        skipToEnd(): void;
+
+        /**
+         * Skips to the next occurrence of the given character, if found on the current line (doesn't advance the stream if
+         * the character does not occur on the line).
+         *
+         * Returns true if the character was found.
+         */
+        skipTo(ch: string): boolean;
+
+        /**
+         * Act like a multi-character eat - if consume is true or not given - or a look-ahead that doesn't update the stream
+         * position - if it is false. pattern can be either a string or a regular expression starting with ^. When it is a
+         * string, caseFold can be set to true to make the match case-insensitive. When successfully matching a regular
+         * expression, the returned value will be the array returned by match, in case you need to extract matched groups.
+         */
+        match(pattern: string, consume?: boolean, caseFold?: boolean): boolean;
+        match(pattern: RegExp, consume?: boolean): string[];
+
+        /**
+         * Backs up the stream n characters. Backing it up further than the start of the current token will cause things to
+         * break, so be careful.
+         */
+        backUp(n: number): void;
+
+        /**
+         * Returns the column (taking into account tabs) at which the current token starts.
+         */
+        column(): number;
+
+        /**
+         * Tells you how far the current line has been indented, in spaces. Corrects for tab characters.
+         */
+        indentation(): number;
+
+        /**
+         * Get the string between the start of the current token and the current stream position.
+         */
+        current(): string;
+    }
+
+    /**
+     * A Mode is, in the simplest case, a lexer (tokenizer) for your language — a function that takes a character stream as input,
+     * advances it past a token, and returns a style for that token. More advanced modes can also handle indentation for the language.
+     */
+    interface Mode<T> {
+        /**
+         * This function should read one token from the stream it is given as an argument, optionally update its state,
+         * and return a style string, or null for tokens that do not have to be styled. Multiple styles can be returned, separated by spaces.
+         */
+        token(stream: StringStream, state: T): string;
+
+        /**
+         * A function that produces a state object to be used at the start of a document.
+         */
+        startState?: () => T;
+        /**
+         * For languages that have significant blank lines, you can define a blankLine(state) method on your mode that will get called
+         * whenever a blank line is passed over, so that it can update the parser state.
+         */
+        blankLine?: (state: T) => void;
+        /**
+         * Given a state returns a safe copy of that state.
+         */
+        copyState?: (state: T) => T;
+
+        /**
+         * The indentation method should inspect the given state object, and optionally the textAfter string, which contains the text on
+         * the line that is being indented, and return an integer, the amount of spaces to indent.
+         */
+        indent?: (state: T, textAfter: string) => number;
+
+        /** The four below strings are used for working with the commenting addon. */
+        /**
+         * String that starts a line comment.
+         */
+        lineComment?: string;
+        /**
+         * String that starts a block comment.
+         */
+        blockCommentStart?: string;
+        /**
+         * String that ends a block comment.
+         */
+        blockCommentEnd?: string;
+        /**
+         * String to put at the start of continued lines in a block comment.
+         */
+        blockCommentLead?: string;
+
+        /**
+         * Trigger a reindent whenever one of the characters in the string is typed.
+         */
+        electricChars?: string
+        /**
+         * Trigger a reindent whenever the regex matches the part of the line before the cursor.
+         */
+        electricinput?: RegExp
+    }
+
+    /**
+     * A function that, given a CodeMirror configuration object and an optional mode configuration object, returns a mode object.
+     */
+    interface ModeFactory<T> {
+        (config: CodeMirror.EditorConfiguration, modeOptions?: any): Mode<T>
+    }
+
+    /**
+     * id will be the id for the defined mode. Typically, you should use this second argument to defineMode as your module scope function
+     * (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.
+     */
+    function defineMode(id: string, modefactory: ModeFactory<any>, baseMode?: string): void;
+
+    function defineMIME(mime: string, modeSpec: string | modespec): void;
+
+    /**
+     * The first argument is a configuration object as passed to the mode constructor function, and the second argument
+     * is a mode specification as in the EditorConfiguration mode option.
+     */
+    function getMode<T>(config: CodeMirror.EditorConfiguration, mode: any): Mode<T>;
+
+    /**
+     * Utility function from the overlay.js addon that allows modes to be combined. The mode given as the base argument takes care of
+     * most of the normal mode functionality, but a second (typically simple) mode is used, which can override the style of text.
+     * Both modes get to parse all of the text, but when both assign a non-null style to a piece of code, the overlay wins, unless
+     * the combine argument was true and not overridden, or state.overlay.combineTokens was true, in which case the styles are combined.
+     */
+    function overlayMode<T, S>(base: Mode<T>, overlay: Mode<S>, combine?: boolean): Mode<any>
+
+    /**
+     * async specifies that the lint process runs asynchronously. hasGutters specifies that lint errors should be displayed in the CodeMirror
+     * gutter, note that you must use this in conjunction with [ "CodeMirror-lint-markers" ] as an element in the gutters argument on
+     * initialization of the CodeMirror instance.
+     */
+    interface LintStateOptions {
+        async: boolean;
+        hasGutters: boolean;
+    }
+
+    /**
+     * Adds the getAnnotations callback to LintStateOptions which may be overridden by the user if they choose use their own
+     * linter.
+     */
+    interface LintOptions extends LintStateOptions {
+        getAnnotations: AnnotationsCallback;
+    }
+
+    /**
+     * A function that calls the updateLintingCallback with any errors found during the linting process.
+     */
+    interface AnnotationsCallback {
+        (content: string, updateLintingCallback: UpdateLintingCallback, options: LintStateOptions, codeMirror: Editor): void;
+    }
+
+    /**
+     * A function that, given an array of annotations, updates the CodeMirror linting GUI with those annotations
+     */
+    interface UpdateLintingCallback {
+        (codeMirror: Editor, annotations: Annotation[]): void;
+    }
+
+    /**
+     * An annotation contains a description of a lint error, detailing the location of the error within the code, the severity of the error,
+     * and an explaination as to why the error was thrown.
+     */
+    interface Annotation {
+        from: Position;
+        message?: string;
+        severity?: string;
+        to?: Position;
+    }
+}
+
+declare module "codemirror" {
+    export = CodeMirror;
+}

+ 238 - 0
src/typings/es6-promise.d.ts

@@ -0,0 +1,238 @@
+// ES6 Promises
+// From https://github.com/Microsoft/TypeScript/blob/911d07a81b9348dd576985f263955c8e0968277e/lib/lib.core.es6.d.ts
+
+/*! *****************************************************************************
+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 ON AN *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 Symbol {
+    /** Returns a string representation of an object. */
+    toString(): string;
+
+    /** Returns the primitive value of the specified object. */
+    valueOf(): Object;
+
+    //[Symbol.toStringTag]: "Symbol";
+}
+
+interface SymbolConstructor {
+    /** 
+      * A reference to the prototype. 
+      */
+    prototype: Symbol;
+
+    /**
+      * Returns a new unique Symbol value.
+      * @param  description Description of the new Symbol object.
+      */
+    (description?: string|number): symbol;
+
+    /**
+      * Returns a Symbol object from the global symbol registry matching the given key if found. 
+      * Otherwise, returns a new symbol with this key.
+      * @param key key to search for.
+      */
+    for(key: string): symbol;
+
+    /**
+      * Returns a key from the global symbol registry matching the given Symbol if found. 
+      * Otherwise, returns a undefined.
+      * @param sym Symbol to find the key for.
+      */
+    keyFor(sym: symbol): string;
+
+    // Well-known Symbols
+
+    /** 
+      * A method that determines if a constructor object recognizes an object as one of the 
+      * constructor’s instances. Called by the semantics of the instanceof operator. 
+      */
+    hasInstance: symbol;
+
+    /** 
+      * A Boolean value that if true indicates that an object should flatten to its array elements
+      * by Array.prototype.concat.
+      */
+    isConcatSpreadable: symbol;
+
+    /** 
+      * A method that returns the default iterator for an object. Called by the semantics of the 
+      * for-of statement.
+      */
+    iterator: symbol;
+
+    /**
+      * A regular expression method that matches the regular expression against a string. Called 
+      * by the String.prototype.match method. 
+      */
+    match: symbol;
+
+    /** 
+      * A regular expression method that replaces matched substrings of a string. Called by the 
+      * String.prototype.replace method.
+      */
+    replace: symbol;
+
+    /**
+      * A regular expression method that returns the index within a string that matches the 
+      * regular expression. Called by the String.prototype.search method.
+      */
+    search: symbol;
+
+    /** 
+      * A function valued property that is the constructor function that is used to create 
+      * derived objects.
+      */
+    species: symbol;
+
+    /**
+      * A regular expression method that splits a string at the indices that match the regular 
+      * expression. Called by the String.prototype.split method.
+      */
+    split: symbol;
+
+    /** 
+      * A method that converts an object to a corresponding primitive value.
+      * Called by the ToPrimitive abstract operation.
+      */
+    toPrimitive: symbol;
+
+    /** 
+      * A String value that is used in the creation of the default string description of an object.
+      * Called by the built-in method Object.prototype.toString.
+      */
+    toStringTag: symbol;
+
+    /**
+      * An Object whose own property names are property names that are excluded from the 'with'
+      * environment bindings of the associated objects.
+      */
+    unscopables: symbol;
+}
+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>;
+}
+
+
+/**
+ * Represents the completion of an asynchronous operation
+ */
+interface Promise<T> {
+    /**
+    * Attaches callbacks for the resolution and/or rejection of the Promise.
+    * @param onfulfilled The callback to execute when the Promise is resolved.
+    * @param onrejected The callback to execute when the Promise is rejected.
+    * @returns A Promise for the completion of which ever callback is executed.
+    */
+    then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<TResult>;
+    then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
+
+    /**
+     * Attaches a callback for only the rejection of the Promise.
+     * @param onrejected The callback to execute when the Promise is rejected.
+     * @returns A Promise for the completion of the callback.
+     */
+    catch(onrejected?: (reason: any) => T | PromiseLike<T>): Promise<T>;
+    catch(onrejected?: (reason: any) => void): Promise<T>;
+
+    //[Symbol.toStringTag]: "Promise";
+}
+
+interface PromiseConstructor {
+    /** 
+      * A reference to the prototype. 
+      */
+    prototype: Promise<any>;
+
+    /**
+     * Creates a new Promise.
+     * @param executor A callback used to initialize the promise. This callback is passed two arguments: 
+     * a resolve callback used resolve the promise with a value or the result of another promise, 
+     * and a reject callback used to reject the promise with a provided reason or error.
+     */
+    new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
+
+    /**
+     * Creates a Promise that is resolved with an array of results when all of the provided Promises 
+     * resolve, or rejected when any Promise is rejected.
+     * @param values An array of Promises.
+     * @returns A new Promise.
+     */
+    all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
+    all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
+    all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
+    all<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;
+    all<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
+    all<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
+    all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
+    all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
+    all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
+    all<TAll>(values: Iterable<TAll | PromiseLike<TAll>>): Promise<TAll[]>;
+
+    /**
+     * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved 
+     * or rejected.
+     * @param values An array of Promises.
+     * @returns A new Promise.
+     */
+    race<T>(values: Iterable<T | PromiseLike<T>>): Promise<T>;
+
+    /**
+     * Creates a new rejected promise for the provided reason.
+     * @param reason The reason the promise was rejected.
+     * @returns A new rejected Promise.
+     */
+    reject(reason: any): Promise<void>;
+
+    /**
+     * Creates a new rejected promise for the provided reason.
+     * @param reason The reason the promise was rejected.
+     * @returns A new rejected Promise.
+     */
+    reject<T>(reason: any): Promise<T>;
+
+    /**
+      * Creates a new resolved promise for the provided value.
+      * @param value A promise.
+      * @returns A promise whose internal state matches the provided promise.
+      */
+    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
+
+    /**
+     * Creates a new resolved promise .
+     * @returns A resolved promise.
+     */
+    resolve(): Promise<void>;
+
+    [Symbol.species]: Function;
+}
+
+declare var Promise: PromiseConstructor;
+
+
+// *just* for jupyter-js-services, which uses a different name.
+declare type Thenable<T> = PromiseLike<T>;

+ 227 - 0
src/typings/expect.js/expect.js.d.ts

@@ -0,0 +1,227 @@
+// Type definitions for expect.js 0.3.1
+// Project: https://github.com/Automattic/expect.js
+// Definitions by: Teppei Sato <https://github.com/teppeis>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare function expect(target?: any): Expect.Root;
+
+declare module Expect {
+    interface Assertion {
+        /**
+         * Check if the value is truthy
+         */
+        ok(): void;
+
+        /**
+         * Creates an anonymous function which calls fn with arguments.
+         */
+        withArgs(...args: any[]): Root;
+
+        /**
+         * Assert that the function throws.
+         *
+         * @param fn callback to match error string against
+         */
+        throwError(fn?: (exception: any) => void): void;
+
+        /**
+         * Assert that the function throws.
+         *
+         * @param fn callback to match error string against
+         */
+        throwException(fn?: (exception: any) => void): void;
+
+        /**
+         * Assert that the function throws.
+         *
+         * @param regexp regexp to match error string against
+         */
+        throwError(regexp: RegExp): void;
+
+        /**
+         * Assert that the function throws.
+         *
+         * @param fn callback to match error string against
+         */
+        throwException(regexp: RegExp): void;
+
+        /**
+         * Checks if the array is empty.
+         */
+        empty(): Assertion;
+
+        /**
+         * Checks if the obj exactly equals another.
+         */
+        equal(obj: any): Assertion;
+
+        /**
+         * Checks if the obj sortof equals another.
+         */
+        eql(obj: any): Assertion;
+
+        /**
+         * Assert within start to finish (inclusive).
+         *
+         * @param start
+         * @param finish
+         */
+        within(start: number, finish: number): Assertion;
+
+        /**
+         * Assert typeof.
+         */
+        a(type: string): Assertion;
+
+        /**
+         * Assert instanceof.
+         */
+        a(type: Function): Assertion;
+
+        /**
+         * Assert typeof / instanceof.
+         */
+        an: An;
+
+        /**
+         * Assert numeric value above n.
+         */
+        greaterThan(n: number): Assertion;
+
+        /**
+         * Assert numeric value above n.
+         */
+        above(n: number): Assertion;
+
+        /**
+         * Assert numeric value below n.
+         */
+        lessThan(n: number): Assertion;
+
+        /**
+         * Assert numeric value below n.
+         */
+        below(n: number): Assertion;
+
+        /**
+         * Assert string value matches regexp.
+         *
+         * @param regexp
+         */
+        match(regexp: RegExp): Assertion;
+
+        /**
+         * Assert property "length" exists and has value of n.
+         *
+         * @param n
+         */
+        length(n: number): Assertion;
+
+        /**
+         * Assert property name exists, with optional val.
+         *
+         * @param name
+         * @param val
+         */
+        property(name: string, val?: any): Assertion;
+
+        /**
+         * Assert that string contains str.
+         */
+        contain(str: string): Assertion;
+        string(str: string): Assertion;
+
+        /**
+         * Assert that the array contains obj.
+         */
+        contain(obj: any): Assertion;
+        string(obj: any): Assertion;
+
+        /**
+         * Assert exact keys or inclusion of keys by using the `.own` modifier.
+         */
+        key(keys: string[]): Assertion;
+        /**
+         * Assert exact keys or inclusion of keys by using the `.own` modifier.
+         */
+        key(...keys: string[]): Assertion;
+        /**
+         * Assert exact keys or inclusion of keys by using the `.own` modifier.
+         */
+        keys(keys: string[]): Assertion;
+        /**
+         * Assert exact keys or inclusion of keys by using the `.own` modifier.
+         */
+        keys(...keys: string[]): Assertion;
+
+        /**
+         * Assert a failure.
+         */
+        fail(message?: string): Assertion;
+    }
+
+    interface Root extends Assertion {
+        not: Not;
+        to: To;
+        only: Only;
+        have: Have;
+        be: Be;
+    }
+
+    interface Be extends Assertion {
+        /**
+         * Checks if the obj exactly equals another.
+         */
+        (obj: any): Assertion;
+
+        an: An;
+    }
+
+    interface An extends Assertion {
+        /**
+         * Assert typeof.
+         */
+        (type: string): Assertion;
+
+        /**
+         * Assert instanceof.
+         */
+        (type: Function): Assertion;
+    }
+
+    interface Not extends Expect.NotBase {
+        to: Expect.ToBase;
+    }
+
+    interface NotBase extends Assertion {
+        be: Be;
+        have: Have;
+        include: Assertion;
+        only: Only;
+    }
+
+    interface To extends Expect.ToBase {
+        not: Expect.NotBase;
+    }
+
+    interface ToBase extends Assertion {
+        be: Be;
+        have: Have;
+        include: Assertion;
+        only: Only;
+    }
+
+    interface Only extends Assertion {
+        have: Have;
+    }
+
+    interface Have extends Assertion {
+        own: Assertion;
+    }
+}
+
+declare module "expect.js" {
+
+    export = expect;
+
+}

+ 220 - 0
src/typings/mocha/mocha.d.ts

@@ -0,0 +1,220 @@
+// Type definitions for mocha 2.2.5
+// Project: http://mochajs.org/
+// Definitions by: Kazi Manzur Rashid <https://github.com/kazimanzurrashid/>, otiai10 <https://github.com/otiai10>, jt000 <https://github.com/jt000>, Vadim Macagon <https://github.com/enlight>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+interface MochaSetupOptions {
+    //milliseconds to wait before considering a test slow
+    slow?: number;
+
+    // timeout in milliseconds
+    timeout?: number;
+
+    // ui name "bdd", "tdd", "exports" etc
+    ui?: string;
+
+    //array of accepted globals
+    globals?: any[];
+
+    // reporter instance (function or string), defaults to `mocha.reporters.Spec`
+    reporter?: any;
+
+    // bail on the first test failure
+    bail?: boolean;
+
+    // ignore global leaks
+    ignoreLeaks?: boolean;
+
+    // grep string or regexp to filter tests with
+    grep?: any;
+}
+
+interface MochaDone {
+    (error?: Error): void;
+}
+
+declare var mocha: Mocha;
+declare var describe: Mocha.IContextDefinition;
+declare var xdescribe: Mocha.IContextDefinition;
+// alias for `describe`
+declare var context: Mocha.IContextDefinition;
+// alias for `describe`
+declare var suite: Mocha.IContextDefinition;
+declare var it: Mocha.ITestDefinition;
+declare var xit: Mocha.ITestDefinition;
+// alias for `it`
+declare var test: Mocha.ITestDefinition;
+
+declare function before(action: () => void): void;
+
+declare function before(action: (done: MochaDone) => void): void;
+
+declare function setup(action: () => void): void;
+
+declare function setup(action: (done: MochaDone) => void): void;
+
+declare function after(action: () => void): void;
+
+declare function after(action: (done: MochaDone) => void): void;
+
+declare function teardown(action: () => void): void;
+
+declare function teardown(action: (done: MochaDone) => void): void;
+
+declare function beforeEach(action: () => void): void;
+
+declare function beforeEach(action: (done: MochaDone) => void): void;
+
+declare function suiteSetup(action: () => void): void;
+
+declare function suiteSetup(action: (done: MochaDone) => void): void;
+
+declare function afterEach(action: () => void): void;
+
+declare function afterEach(action: (done: MochaDone) => void): void;
+
+declare function suiteTeardown(action: () => void): void;
+
+declare function suiteTeardown(action: (done: MochaDone) => void): void;
+
+declare class Mocha {
+    constructor(options?: {
+        grep?: RegExp;
+        ui?: string;
+        reporter?: string;
+        timeout?: number;
+        bail?: boolean;
+    });
+
+    /** Setup mocha with the given options. */
+    setup(options: MochaSetupOptions): Mocha;
+    bail(value?: boolean): Mocha;
+    addFile(file: string): Mocha;
+    /** Sets reporter by name, defaults to "spec". */
+    reporter(name: string): Mocha;
+    /** Sets reporter constructor, defaults to mocha.reporters.Spec. */
+    reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha;
+    ui(value: string): Mocha;
+    grep(value: string): Mocha;
+    grep(value: RegExp): Mocha;
+    invert(): Mocha;
+    ignoreLeaks(value: boolean): Mocha;
+    checkLeaks(): Mocha;
+    /**
+     * Function to allow assertion libraries to throw errors directly into mocha.
+     * This is useful when running tests in a browser because window.onerror will
+     * only receive the 'message' attribute of the Error.
+     */
+    throwError(error: Error): void;
+    /** Enables growl support. */
+    growl(): Mocha;
+    globals(value: string): Mocha;
+    globals(values: string[]): Mocha;
+    useColors(value: boolean): Mocha;
+    useInlineDiffs(value: boolean): Mocha;
+    timeout(value: number): Mocha;
+    slow(value: number): Mocha;
+    enableTimeouts(value: boolean): Mocha;
+    asyncOnly(value: boolean): Mocha;
+    noHighlighting(value: boolean): Mocha;
+    /** Runs tests and invokes `onComplete()` when finished. */
+    run(onComplete?: (failures: number) => void): Mocha.IRunner;
+}
+
+// merge the Mocha class declaration with a module
+declare module Mocha {
+    /** Partial interface for Mocha's `Runnable` class. */
+    interface IRunnable {
+        title: string;
+        fn: Function;
+        async: boolean;
+        sync: boolean;
+        timedOut: boolean;
+    }
+
+    /** Partial interface for Mocha's `Suite` class. */
+    interface ISuite {
+        parent: ISuite;
+        title: string;
+
+        fullTitle(): string;
+    }
+
+    /** Partial interface for Mocha's `Test` class. */
+    interface ITest extends IRunnable {
+        parent: ISuite;
+        pending: boolean;
+
+        fullTitle(): string;
+    }
+
+    /** Partial interface for Mocha's `Runner` class. */
+    interface IRunner {}
+
+    interface IContextDefinition {
+        (description: string, spec: () => void): ISuite;
+        only(description: string, spec: () => void): ISuite;
+        skip(description: string, spec: () => void): void;
+        timeout(ms: number): void;
+    }
+
+    interface ITestDefinition {
+        (expectation: string, assertion?: () => void): ITest;
+        (expectation: string, assertion?: (done: MochaDone) => void): ITest;
+        only(expectation: string, assertion?: () => void): ITest;
+        only(expectation: string, assertion?: (done: MochaDone) => void): ITest;
+        skip(expectation: string, assertion?: () => void): void;
+        skip(expectation: string, assertion?: (done: MochaDone) => void): void;
+        timeout(ms: number): void;
+    }
+
+    export module reporters {
+        export class Base {
+            stats: {
+                suites: number;
+                tests: number;
+                passes: number;
+                pending: number;
+                failures: number;
+            };
+
+            constructor(runner: IRunner);
+        }
+
+        export class Doc extends Base {}
+        export class Dot extends Base {}
+        export class HTML extends Base {}
+        export class HTMLCov extends Base {}
+        export class JSON extends Base {}
+        export class JSONCov extends Base {}
+        export class JSONStream extends Base {}
+        export class Landing extends Base {}
+        export class List extends Base {}
+        export class Markdown extends Base {}
+        export class Min extends Base {}
+        export class Nyan extends Base {}
+        export class Progress extends Base {
+            /**
+             * @param options.open String used to indicate the start of the progress bar.
+             * @param options.complete String used to indicate a complete test on the progress bar.
+             * @param options.incomplete String used to indicate an incomplete test on the progress bar.
+             * @param options.close String used to indicate the end of the progress bar.
+             */
+            constructor(runner: IRunner, options?: {
+                open?: string;
+                complete?: string;
+                incomplete?: string;
+                close?: string;
+            });
+        }
+        export class Spec extends Base {}
+        export class TAP extends Base {}
+        export class XUnit extends Base {
+            constructor(runner: IRunner, options?: any);
+        }
+    }
+}
+
+declare module "mocha" {
+    export = Mocha;
+}

+ 479 - 0
src/typings/moment/moment.d.ts

@@ -0,0 +1,479 @@
+// Type definitions for Moment.js 2.8.0
+// Project: https://github.com/timrwood/moment
+// Definitions by: Michael Lakerveld <https://github.com/Lakerfield>, Aaron King <https://github.com/kingdango>, Hiroki Horiuchi <https://github.com/horiuchi>, Dick van den Brink <https://github.com/DickvdBrink>, Adi Dahiya <https://github.com/adidahiya>, Matt Brooks <https://github.com/EnableSoftware>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare module moment {
+
+    interface MomentInput {
+        /** Year */
+        years?: number;
+        /** Year */
+        year?: number;
+        /** Year */
+        y?: number;
+
+        /** Month */
+        months?: number;
+        /** Month */
+        month?: number;
+        /** Month */
+        M?: number;
+
+        /** Week */
+        weeks?: number;
+        /** Week */
+        week?: number;
+        /** Week */
+        w?: number;
+
+        /** Day/Date */
+        days?: number;
+        /** Day/Date */
+        day?: number;
+        /** Day/Date */
+        date?: number;
+        /** Day/Date */
+        d?: number;
+
+        /** Hour */
+        hours?: number;
+        /** Hour */
+        hour?: number;
+        /** Hour */
+        h?: number;
+
+        /** Minute */
+        minutes?: number;
+        /** Minute */
+        minute?: number;
+        /** Minute */
+        m?: number;
+
+        /** Second */
+        seconds?: number;
+        /** Second */
+        second?: number;
+        /** Second */
+        s?: number;
+
+        /** Millisecond */
+        milliseconds?: number;
+        /** Millisecond */
+        millisecond?: number;
+        /** Millisecond */
+        ms?: number;
+    }
+
+    interface Duration {
+        humanize(withSuffix?: boolean): string;
+
+        as(units: string): number;
+
+        milliseconds(): number;
+        asMilliseconds(): number;
+
+        seconds(): number;
+        asSeconds(): number;
+
+        minutes(): number;
+        asMinutes(): number;
+
+        hours(): number;
+        asHours(): number;
+
+        days(): number;
+        asDays(): number;
+
+        months(): number;
+        asMonths(): number;
+
+        years(): number;
+        asYears(): number;
+
+        add(n: number, p: string): Duration;
+        add(n: number): Duration;
+        add(d: Duration): Duration;
+
+        subtract(n: number, p: string): Duration;
+        subtract(n: number): Duration;
+        subtract(d: Duration): Duration;
+
+        toISOString(): string;
+        toJSON(): string;
+    }
+
+    interface Moment {
+        format(format: string): string;
+        format(): string;
+
+        fromNow(withoutSuffix?: boolean): string;
+
+        startOf(unitOfTime: string): Moment;
+        endOf(unitOfTime: string): Moment;
+
+        /**
+         * Mutates the original moment by adding time. (deprecated in 2.8.0)
+         *
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         * @param amount the amount you want to add
+         */
+        add(unitOfTime: string, amount: number): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         */
+        add(amount: number, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by adding time. Note that the order of arguments can be flipped.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         */
+        add(amount: string, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param objectLiteral an object literal that describes multiple time units {days:7,months:1}
+         */
+        add(objectLiteral: MomentInput): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param duration a length of time
+         */
+        add(duration: Duration): Moment;
+
+        /**
+         * Mutates the original moment by subtracting time. (deprecated in 2.8.0)
+         *
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         * @param amount the amount you want to subtract
+         */
+        subtract(unitOfTime: string, amount: number): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         * @param amount the amount you want to subtract
+         */
+        subtract(amount: number, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by subtracting time. Note that the order of arguments can be flipped.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         */
+        subtract(amount: string, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param objectLiteral an object literal that describes multiple time units {days:7,months:1}
+         */
+        subtract(objectLiteral: MomentInput): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param duration a length of time
+         */
+        subtract(duration: Duration): Moment;
+
+        calendar(): string;
+        calendar(start: Moment): string;
+        calendar(start: Moment, formats: MomentCalendar): string;
+
+        clone(): Moment;
+
+        /**
+         * @return Unix timestamp, or milliseconds since the epoch.
+         */
+        valueOf(): number;
+
+        local(): Moment; // current date/time in local mode
+
+        utc(): Moment; // current date/time in UTC mode
+
+        isValid(): boolean;
+        invalidAt(): number;
+
+        year(y: number): Moment;
+        year(): number;
+        quarter(): number;
+        quarter(q: number): Moment;
+        month(M: number): Moment;
+        month(M: string): Moment;
+        month(): number;
+        day(d: number): Moment;
+        day(d: string): Moment;
+        day(): number;
+        date(d: number): Moment;
+        date(): number;
+        hour(h: number): Moment;
+        hour(): number;
+        hours(h: number): Moment;
+        hours(): number;
+        minute(m: number): Moment;
+        minute(): number;
+        minutes(m: number): Moment;
+        minutes(): number;
+        second(s: number): Moment;
+        second(): number;
+        seconds(s: number): Moment;
+        seconds(): number;
+        millisecond(ms: number): Moment;
+        millisecond(): number;
+        milliseconds(ms: number): Moment;
+        milliseconds(): number;
+        weekday(): number;
+        weekday(d: number): Moment;
+        isoWeekday(): number;
+        isoWeekday(d: number): Moment;
+        weekYear(): number;
+        weekYear(d: number): Moment;
+        isoWeekYear(): number;
+        isoWeekYear(d: number): Moment;
+        week(): number;
+        week(d: number): Moment;
+        weeks(): number;
+        weeks(d: number): Moment;
+        isoWeek(): number;
+        isoWeek(d: number): Moment;
+        isoWeeks(): number;
+        isoWeeks(d: number): Moment;
+        weeksInYear(): number;
+        isoWeeksInYear(): number;
+        dayOfYear(): number;
+        dayOfYear(d: number): Moment;
+
+        from(f: Moment|string|number|Date|number[], suffix?: boolean): string;
+        to(f: Moment|string|number|Date|number[], suffix?: boolean): string;
+        toNow(withoutPrefix?: boolean): string;
+
+        diff(b: Moment): number;
+        diff(b: Moment, unitOfTime: string): number;
+        diff(b: Moment, unitOfTime: string, round: boolean): number;
+
+        toArray(): number[];
+        toDate(): Date;
+        toISOString(): string;
+        toJSON(): string;
+        unix(): number;
+
+        isLeapYear(): boolean;
+        zone(): number;
+        zone(b: number): Moment;
+        zone(b: string): Moment;
+        utcOffset(): number;
+        utcOffset(b: number): Moment;
+        utcOffset(b: string): Moment;
+        daysInMonth(): number;
+        isDST(): boolean;
+
+        isBefore(): boolean;
+        isBefore(b: Moment|string|number|Date|number[], granularity?: string): boolean;
+
+        isAfter(): boolean;
+        isAfter(b: Moment|string|number|Date|number[], granularity?: string): boolean;
+
+        isSame(b: Moment|string|number|Date|number[], granularity?: string): boolean;
+        isBetween(a: Moment|string|number|Date|number[], b: Moment|string|number|Date|number[], granularity?: string): boolean;
+
+        // Deprecated as of 2.8.0.
+        lang(language: string): Moment;
+        lang(reset: boolean): Moment;
+        lang(): MomentLanguage;
+
+        locale(language: string): Moment;
+        locale(reset: boolean): Moment;
+        locale(): string;
+
+        localeData(language: string): Moment;
+        localeData(reset: boolean): Moment;
+        localeData(): MomentLanguage;
+
+        // Deprecated as of 2.7.0.
+        max(date: Moment|string|number|Date|any[]): Moment;
+        max(date: string, format: string): Moment;
+
+        // Deprecated as of 2.7.0.
+        min(date: Moment|string|number|Date|any[]): Moment;
+        min(date: string, format: string): Moment;
+
+        get(unit: string): number;
+        set(unit: string, value: number): Moment;
+        set(objectLiteral: MomentInput): Moment;
+    }
+
+    type formatFunction = () => string;
+
+    interface MomentCalendar {
+      lastDay?: string | formatFunction;
+      sameDay?: string | formatFunction;
+      nextDay?: string | formatFunction;
+      lastWeek?: string | formatFunction;
+      nextWeek?: string | formatFunction;
+      sameElse?: string | formatFunction;
+    }
+
+    interface BaseMomentLanguage {
+        months ?: any;
+        monthsShort ?: any;
+        weekdays ?: any;
+        weekdaysShort ?: any;
+        weekdaysMin ?: any;
+        relativeTime ?: MomentRelativeTime;
+        meridiem ?: (hour: number, minute: number, isLowercase: boolean) => string;
+        calendar ?: MomentCalendar;
+        ordinal ?: (num: number) => string;
+    }
+
+    interface MomentLanguage extends BaseMomentLanguage {
+      longDateFormat?: MomentLongDateFormat;
+    }
+
+    interface MomentLanguageData extends BaseMomentLanguage {
+        /**
+         * @param formatType should be L, LL, LLL, LLLL.
+         */
+        longDateFormat(formatType: string): string;
+    }
+
+    interface MomentLongDateFormat {
+      L: string;
+      LL: string;
+      LLL: string;
+      LLLL: string;
+      LT: string;
+      LTS: string;
+      l?: string;
+      ll?: string;
+      lll?: string;
+      llll?: string;
+      lt?: string;
+      lts?: string;
+    }
+
+    interface MomentRelativeTime {
+      future: any;
+      past: any;
+      s: any;
+      m: any;
+      mm: any;
+      h: any;
+      hh: any;
+      d: any;
+      dd: any;
+      M: any;
+      MM: any;
+      y: any;
+      yy: any;
+    }
+
+    interface MomentStatic {
+        version: string;
+        fn: Moment;
+
+        (): Moment;
+        (date: number): Moment;
+        (date: number[]): Moment;
+        (date: string, format?: string, strict?: boolean): Moment;
+        (date: string, format?: string, language?: string, strict?: boolean): Moment;
+        (date: string, formats: string[], strict?: boolean): Moment;
+        (date: string, formats: string[], language?: string, strict?: boolean): Moment;
+        (date: string, specialFormat: () => void, strict?: boolean): Moment;
+        (date: string, specialFormat: () => void, language?: string, strict?: boolean): Moment;
+        (date: string, formatsIncludingSpecial: any[], strict?: boolean): Moment;
+        (date: string, formatsIncludingSpecial: any[], language?: string, strict?: boolean): Moment;
+        (date: Date): Moment;
+        (date: Moment): Moment;
+        (date: Object): Moment;
+
+        utc(): Moment;
+        utc(date: number): Moment;
+        utc(date: number[]): Moment;
+        utc(date: string, format?: string, strict?: boolean): Moment;
+        utc(date: string, format?: string, language?: string, strict?: boolean): Moment;
+        utc(date: string, formats: string[], strict?: boolean): Moment;
+        utc(date: string, formats: string[], language?: string, strict?: boolean): Moment;
+        utc(date: Date): Moment;
+        utc(date: Moment): Moment;
+        utc(date: Object): Moment;
+
+        unix(timestamp: number): Moment;
+
+        invalid(parsingFlags?: Object): Moment;
+        isMoment(): boolean;
+        isMoment(m: any): boolean;
+        isDate(m: any): boolean;
+        isDuration(): boolean;
+        isDuration(d: any): boolean;
+
+        // Deprecated in 2.8.0.
+        lang(language?: string): string;
+        lang(language?: string, definition?: MomentLanguage): string;
+
+        locale(language?: string): string;
+        locale(language?: string[]): string;
+        locale(language?: string, definition?: MomentLanguage): string;
+
+        localeData(language?: string): MomentLanguageData;
+
+        longDateFormat: any;
+        relativeTime: any;
+        meridiem: (hour: number, minute: number, isLowercase: boolean) => string;
+        calendar: any;
+        ordinal: (num: number) => string;
+
+        duration(milliseconds: Number): Duration;
+        duration(num: Number, unitOfTime: string): Duration;
+        duration(input: MomentInput): Duration;
+        duration(object: any): Duration;
+        duration(): Duration;
+
+        parseZone(date: string): Moment;
+
+        months(): string[];
+        months(index: number): string;
+        months(format: string): string[];
+        months(format: string, index: number): string;
+        monthsShort(): string[];
+        monthsShort(index: number): string;
+        monthsShort(format: string): string[];
+        monthsShort(format: string, index: number): string;
+
+        weekdays(): string[];
+        weekdays(index: number): string;
+        weekdays(format: string): string[];
+        weekdays(format: string, index: number): string;
+        weekdaysShort(): string[];
+        weekdaysShort(index: number): string;
+        weekdaysShort(format: string): string[];
+        weekdaysShort(format: string, index: number): string;
+        weekdaysMin(): string[];
+        weekdaysMin(index: number): string;
+        weekdaysMin(format: string): string[];
+        weekdaysMin(format: string, index: number): string;
+
+        min(...moments: Moment[]): Moment;
+        max(...moments: Moment[]): Moment;
+
+        normalizeUnits(unit: string): string;
+        relativeTimeThreshold(threshold: string): number|boolean;
+        relativeTimeThreshold(threshold: string, limit:number): boolean;
+
+        /**
+         * Constant used to enable explicit ISO_8601 format parsing.
+         */
+        ISO_8601(): void;
+
+        defaultFormat: string;
+    }
+
+}
+
+declare module 'moment' {
+    var moment: moment.MomentStatic;
+    export = moment;
+}

+ 397 - 0
src/typings/requirejs/requirejs.d.ts

@@ -0,0 +1,397 @@
+// Type definitions for RequireJS 2.1.20
+// Project: http://requirejs.org/
+// Definitions by: Josh Baldwin <https://github.com/jbaldwin/>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+/*
+require-2.1.8.d.ts may be freely distributed under the MIT license.
+
+Copyright (c) 2013 Josh Baldwin https://github.com/jbaldwin/require.d.ts
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+declare module 'module' {
+    var mod: {
+        config: () => any;
+        id: string;
+        uri: string;
+    }
+    export = mod;
+}
+
+interface RequireError extends Error {
+
+    /**
+    * The error ID that maps to an ID on a web page.
+    **/
+    requireType: string;
+
+    /**
+    * Required modules.
+    **/
+    requireModules: string[];
+
+    /**
+    * The original error, if there is one (might be null).
+    **/
+    originalError: Error;
+}
+
+interface RequireShim {
+
+    /**
+    * List of dependencies.
+    **/
+    deps?: string[];
+
+    /**
+    * Name the module will be exported as.
+    **/
+    exports?: string;
+
+    /**
+    * Initialize function with all dependcies passed in,
+    * if the function returns a value then that value is used
+    * as the module export value instead of the object
+    * found via the 'exports' string.
+    * @param dependencies
+    * @return
+    **/
+    init?: (...dependencies: any[]) => any;
+}
+
+interface RequireConfig {
+
+    // The root path to use for all module lookups.
+    baseUrl?: string;
+
+    // Path mappings for module names not found directly under
+    // baseUrl.
+    paths?: { [key: string]: any; };
+
+
+    // Dictionary of Shim's.
+    // does not cover case of key->string[]
+    shim?: { [key: string]: RequireShim; };
+
+    /**
+    * For the given module prefix, instead of loading the
+    * module with the given ID, substitude a different
+    * module ID.
+    *
+    * @example
+    * requirejs.config({
+    *    map: {
+    *        'some/newmodule': {
+    *            'foo': 'foo1.2'
+    *        },
+    *        'some/oldmodule': {
+    *            'foo': 'foo1.0'
+    *        }
+    *    }
+    * });
+    **/
+    map?: {
+        [id: string]: {
+            [id: string]: string;
+        };
+    };
+
+    /**
+    * Allows pointing multiple module IDs to a module ID that contains a bundle of modules.
+    *
+    * @example
+    * requirejs.config({
+    *    bundles: {
+    *        'primary': ['main', 'util', 'text', 'text!template.html'],
+    *        'secondary': ['text!secondary.html']
+    *    }
+    * });
+    **/
+    bundles?: { [key: string]: string[]; };
+
+    /**
+    * AMD configurations, use module.config() to access in
+    * define() functions
+    **/
+    config?: { [id: string]: {}; };
+
+    /**
+    * Configures loading modules from CommonJS packages.
+    **/
+    packages?: {};
+
+    /**
+    * The number of seconds to wait before giving up on loading
+    * a script.  The default is 7 seconds.
+    **/
+    waitSeconds?: number;
+
+    /**
+    * A name to give to a loading context.  This allows require.js
+    * to load multiple versions of modules in a page, as long as
+    * each top-level require call specifies a unique context string.
+    **/
+    context?: string;
+
+    /**
+    * An array of dependencies to load.
+    **/
+    deps?: string[];
+
+    /**
+    * A function to pass to require that should be require after
+    * deps have been loaded.
+    * @param modules
+    **/
+    callback?: (...modules: any[]) => void;
+
+    /**
+    * If set to true, an error will be thrown if a script loads
+    * that does not call define() or have shim exports string
+    * value that can be checked.
+    **/
+    enforceDefine?: boolean;
+
+    /**
+    * If set to true, document.createElementNS() will be used
+    * to create script elements.
+    **/
+    xhtml?: boolean;
+
+    /**
+    * Extra query string arguments appended to URLs that RequireJS
+    * uses to fetch resources.  Most useful to cache bust when
+    * the browser or server is not configured correctly.
+    *
+    * @example
+    * urlArgs: "bust= + (new Date()).getTime()
+    **/
+    urlArgs?: string;
+
+    /**
+    * Specify the value for the type="" attribute used for script
+    * tags inserted into the document by RequireJS.  Default is
+    * "text/javascript".  To use Firefox's JavasScript 1.8
+    * features, use "text/javascript;version=1.8".
+    **/
+    scriptType?: string;
+
+    /**
+    * If set to true, skips the data-main attribute scanning done
+    * to start module loading. Useful if RequireJS is embedded in
+    * a utility library that may interact with other RequireJS
+    * library on the page, and the embedded version should not do
+    * data-main loading.
+    **/
+    skipDataMain?: boolean;
+
+    /**
+    * Allow extending requirejs to support Subresource Integrity
+    * (SRI).
+    **/
+    onNodeCreated?: (node: HTMLScriptElement, config: RequireConfig, moduleName: string, url: string) => void;
+}
+
+// todo: not sure what to do with this guy
+interface RequireModule {
+
+    /**
+    *
+    **/
+    config(): {};
+
+}
+
+/**
+*
+**/
+interface RequireMap {
+
+    /**
+    *
+    **/
+    prefix: string;
+
+    /**
+    *
+    **/
+    name: string;
+
+    /**
+    *
+    **/
+    parentMap: RequireMap;
+
+    /**
+    *
+    **/
+    url: string;
+
+    /**
+    *
+    **/
+    originalName: string;
+
+    /**
+    *
+    **/
+    fullName: string;
+}
+
+interface Require {
+
+    /**
+    * Configure require.js
+    **/
+    config(config: RequireConfig): Require;
+
+    /**
+    * CommonJS require call
+    * @param module Module to load
+    * @return The loaded module
+    */
+    (module: string): any;
+
+    /**
+    * Start the main app logic.
+    * Callback is optional.
+    * Can alternatively use deps and callback.
+    * @param modules Required modules to load.
+    **/
+    (modules: string[]): void;
+
+    /**
+    * @see Require()
+    * @param ready Called when required modules are ready.
+    **/
+    (modules: string[], ready: Function): void;
+
+    /**
+    * @see http://requirejs.org/docs/api.html#errbacks
+    * @param ready Called when required modules are ready.
+    **/
+    (modules: string[], ready: Function, errback: Function): void;
+
+    /**
+    * Generate URLs from require module
+    * @param module Module to URL
+    * @return URL string
+    **/
+    toUrl(module: string): string;
+
+    /**
+    * Returns true if the module has already been loaded and defined.
+    * @param module Module to check
+    **/
+    defined(module: string): boolean;
+
+    /**
+    * Returns true if the module has already been requested or is in the process of loading and should be available at some point.
+    * @param module Module to check
+    **/
+    specified(module: string): boolean;
+
+    /**
+    * On Error override
+    * @param err
+    **/
+    onError(err: RequireError, errback?: (err: RequireError) => void): void;
+
+    /**
+    * Undefine a module
+    * @param module Module to undefine.
+    **/
+    undef(module: string): void;
+
+    /**
+    * Semi-private function, overload in special instance of undef()
+    **/
+    onResourceLoad(context: Object, map: RequireMap, depArray: RequireMap[]): void;
+}
+
+interface RequireDefine {
+
+    /**
+    * Define Simple Name/Value Pairs
+    * @param config Dictionary of Named/Value pairs for the config.
+    **/
+    (config: { [key: string]: any; }): void;
+
+    /**
+    * Define function.
+    * @param func: The function module.
+    **/
+    (func: () => any): void;
+
+    /**
+    * Define function with dependencies.
+    * @param deps List of dependencies module IDs.
+    * @param ready Callback function when the dependencies are loaded.
+    *    callback param deps module dependencies
+    *    callback return module definition
+    **/
+        (deps: string[], ready: Function): void;
+
+    /**
+    *  Define module with simplified CommonJS wrapper.
+    * @param ready
+    *    callback require requirejs instance
+    *    callback exports exports object
+    *    callback module module
+    *    callback return module definition
+    **/
+    (ready: (require: Require, exports: { [key: string]: any; }, module: RequireModule) => any): void;
+
+    /**
+    * Define a module with a name and dependencies.
+    * @param name The name of the module.
+    * @param deps List of dependencies module IDs.
+    * @param ready Callback function when the dependencies are loaded.
+    *    callback deps module dependencies
+    *    callback return module definition
+    **/
+    (name: string, deps: string[], ready: Function): void;
+
+    /**
+    * Define a module with a name.
+    * @param name The name of the module.
+    * @param ready Callback function when the dependencies are loaded.
+    *    callback return module definition
+    **/
+    (name: string, ready: Function): void;
+
+    /**
+    * Used to allow a clear indicator that a global define function (as needed for script src browser loading) conforms
+    * to the AMD API, any global define function SHOULD have a property called "amd" whose value is an object.
+    * This helps avoid conflict with any other existing JavaScript code that could have defined a define() function
+    * that does not conform to the AMD API.
+    * define.amd.jQuery is specific to jQuery and indicates that the loader is able to account for multiple version
+    * of jQuery being loaded simultaneously.
+    */
+    amd: Object;
+}
+
+// Ambient declarations for 'require' and 'define'
+declare var requirejs: Require;
+declare var require: Require;
+declare var define: RequireDefine;

+ 21 - 0
src/typings/widgets.d.ts

@@ -0,0 +1,21 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+declare module Widgets {
+    export class ManagerBase {
+        display_view(msg: any, view: any, options: any): Promise<any>;
+        handle_comm_open(comm: shims.services.Comm, msg: any): Promise<any>;
+    }
+    
+    export module shims {
+        export module services {
+            export class Comm {
+                constructor(comm: any);
+            }            
+        }
+    }
+}
+
+declare module "jupyter-js-widgets" {
+    export = Widgets;
+}