Sfoglia il codice sorgente

Merge pull request #609 from TypeFox/ak/abstract_over_code_mirror

Decouple Notebook and Console from CodeMirror
Afshin Darian 8 anni fa
parent
commit
8dd61bb1eb

+ 6 - 0
.vscode/settings.json

@@ -0,0 +1,6 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+  "editor.tabSize": 2,
+  "editor.insertSpaces": true,
+  "editor.detectIndentation": false
+}

+ 6 - 1
examples/notebook/src/index.ts

@@ -6,6 +6,10 @@ import {
   NotebookModelFactory, NotebookActions
 } from 'jupyterlab/lib/notebook';
 
+import {
+  CodeMirrorNotebookPanelRenderer
+} from 'jupyterlab/lib/notebook/codemirror/notebook/panel';
+
 import {
   IServiceManager, createServiceManager
 } from 'jupyter-js-services';
@@ -136,7 +140,8 @@ function createApp(manager: IServiceManager): void {
   });
   let mFactory = new NotebookModelFactory();
   let clipboard = new MimeData();
-  let wFactory = new NotebookWidgetFactory(rendermime, clipboard);
+  let renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
+  let wFactory = new NotebookWidgetFactory(rendermime, clipboard, renderer);
   docRegistry.addModelFactory(mFactory);
   docRegistry.addWidgetFactory(wFactory, {
     displayName: 'Notebook',

+ 3 - 1
jupyterlab/index.js

@@ -35,7 +35,9 @@ lab.registerPlugins([
   require('jupyterlab/lib/running/plugin').runningSessionsExtension,
   require('jupyterlab/lib/services/plugin').servicesProvider,
   require('jupyterlab/lib/shortcuts/plugin').shortcutsExtension,
-  require('jupyterlab/lib/terminal/plugin').terminalExtension
+  require('jupyterlab/lib/terminal/plugin').terminalExtension,
+  require('jupyterlab/lib/notebook/codemirror/plugin').rendererProvider,
+  require('jupyterlab/lib/console/codemirror/plugin').rendererProvider
 
 
   // require('jupyter-js-widgets-labextension/lib/plugin').widgetManagerExtension,

+ 25 - 0
src/console/codemirror/plugin.ts

@@ -0,0 +1,25 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  JupyterLabPlugin
+} from '../../application';
+
+import {
+  ConsoleWidget
+} from '../widget';
+
+import {
+  CodeMirrorConsoleRenderer
+} from './widget';
+
+
+/**
+ * The provider for a console's code mirror renderer.
+ */
+export
+const rendererProvider: JupyterLabPlugin<ConsoleWidget.IRenderer> = {
+  id: 'jupyter.services.console.codemirror.renderer',
+  provides: ConsoleWidget.IRenderer,
+  activate: () => CodeMirrorConsoleRenderer.defaultRenderer
+};

+ 65 - 0
src/console/codemirror/widget.ts

@@ -0,0 +1,65 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  CodeCellModel, RawCellModel
+} from '../../notebook/cells/model';
+
+import {
+  CodeCellWidget, RawCellWidget
+} from '../../notebook/cells/widget';
+
+import {
+  CodeMirrorNotebookRenderer
+} from '../../notebook/codemirror/notebook/widget';
+
+import {
+  RenderMime
+} from '../../rendermime';
+
+import {
+  ConsoleWidget
+} from '../widget';
+
+
+/**
+ * A code mirror renderer for a console.
+ */
+export
+class CodeMirrorConsoleRenderer implements ConsoleWidget.IRenderer {
+  /**
+   * Create a new banner widget.
+   */
+  createBanner(): RawCellWidget {
+    let widget = new RawCellWidget({
+      renderer: CodeMirrorNotebookRenderer.defaultRawCellRenderer
+    });
+    widget.model = new RawCellModel();
+    return widget;
+  }
+
+  /**
+   * Create a new prompt widget.
+   */
+  createPrompt(rendermime: RenderMime): CodeCellWidget {
+    let widget = new CodeCellWidget({
+      rendermime,
+      renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer
+    });
+    widget.model = new CodeCellModel();
+    return widget;
+  }
+}
+
+
+/**
+ * A namespace for `CodeMirrorConsoleRenderer` statics.
+ */
+export
+namespace CodeMirrorConsoleRenderer {
+  /**
+   * A default code mirror renderer for a console.
+   */
+  export
+  const defaultRenderer = new CodeMirrorConsoleRenderer();
+}

+ 1 - 0
src/console/index.ts

@@ -1,4 +1,5 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+export * from './widget';
 export * from './panel';

+ 7 - 1
src/console/panel.ts

@@ -47,7 +47,8 @@ class ConsolePanel extends Panel {
     // Create console widget.
     this._console = options.console || new ConsoleWidget({
       session: options.session,
-      rendermime: options.rendermime
+      rendermime: options.rendermime,
+      renderer: options.renderer
     });
     this.addWidget(this._console);
   }
@@ -130,6 +131,11 @@ namespace ConsolePanel {
      */
     rendermime: IRenderMime;
 
+    /**
+     * The renderer for a console widget.
+     */
+    renderer?: ConsoleWidget.IRenderer;
+
     /**
      * The session for the console panel.
      */

+ 10 - 4
src/console/plugin.ts

@@ -42,7 +42,7 @@ import {
 } from '../widgettracker';
 
 import {
-  ConsolePanel
+  ConsolePanel, ConsoleWidget
 } from './';
 
 
@@ -57,7 +57,8 @@ const consoleExtension: JupyterLabPlugin<void> = {
     IRenderMime,
     IMainMenu,
     IInspector,
-    ICommandPalette
+    ICommandPalette,
+    ConsoleWidget.IRenderer
   ],
   activate: activateConsole,
   autoStart: true
@@ -78,12 +79,15 @@ const CONSOLE_ICON_CLASS = 'jp-ImageConsole';
 /**
  * Activate the console extension.
  */
-function activateConsole(app: JupyterLab, services: IServiceManager, rendermime: IRenderMime, mainMenu: IMainMenu, inspector: IInspector, palette: ICommandPalette): void {
+function activateConsole(app: JupyterLab, services: IServiceManager, rendermime: IRenderMime, mainMenu: IMainMenu, inspector: IInspector, palette: ICommandPalette, renderer: ConsoleWidget.IRenderer): void {
   let tracker = new WidgetTracker<ConsolePanel>();
   let manager = services.sessions;
   let { commands, keymap } = app;
   let category = 'Console';
+
   let menu = new Menu({ commands, keymap });
+  menu.title.label = 'Console';
+
   let submenu: Menu = null;
   let command: string;
 
@@ -123,7 +127,9 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
         let kernelName = `${displayNameMap[displayName]}`;
         manager.startNew({ path, kernelName }).then(session => {
           let panel = new ConsolePanel({
-            session, rendermime: rendermime.clone()
+            session,
+            rendermime: rendermime.clone(),
+            renderer: renderer
           });
           panel.id = `console-${count}`;
           panel.title.label = `${displayName} (${count})`;

+ 13 - 32
src/console/widget.ts

@@ -9,6 +9,10 @@ import {
   clearSignalData
 } from 'phosphor/lib/core/signaling';
 
+import {
+  Token
+} from 'phosphor/lib/core/token';
+
 import {
   Message
 } from 'phosphor/lib/core/messaging';
@@ -30,11 +34,11 @@ import {
 } from '../notebook/notebook/nbformat';
 
 import {
-  CodeCellWidget, CodeCellModel, RawCellModel, RawCellWidget
+  CodeCellWidget, RawCellWidget
 } from '../notebook/cells';
 
 import {
-  EdgeLocation, CellEditorWidget
+  EdgeLocation, ICellEditorWidget
 } from '../notebook/cells/editor';
 
 import {
@@ -85,7 +89,7 @@ class ConsoleWidget extends Widget {
     let layout = new PanelLayout();
 
     this.layout = layout;
-    this._renderer = options.renderer || ConsoleWidget.defaultRenderer;
+    this._renderer = options.renderer;
     this._rendermime = options.rendermime;
     this._session = options.session;
 
@@ -294,7 +298,7 @@ class ConsoleWidget extends Widget {
   /**
    * Handle an edge requested signal.
    */
-  protected onEdgeRequest(editor: CellEditorWidget, location: EdgeLocation): void {
+  protected onEdgeRequest(editor: ICellEditorWidget, location: EdgeLocation): void {
     let prompt = this.prompt;
     if (location === 'top') {
       this._history.back().then(value => {
@@ -412,7 +416,7 @@ namespace ConsoleWidget {
     /**
      * The renderer for a console widget.
      */
-    renderer?: IRenderer;
+    renderer: IRenderer;
 
     /**
      * The session for the console widget.
@@ -436,36 +440,13 @@ namespace ConsoleWidget {
     createPrompt(rendermime: IRenderMime): CodeCellWidget;
   }
 
+  /* tslint:disable */
   /**
-   * The default implementation of an `IRenderer`.
-   */
-  export
-  class Renderer implements IRenderer {
-    /**
-     * Create a new banner widget.
-     */
-    createBanner(): RawCellWidget {
-      let widget = new RawCellWidget();
-      widget.model = new RawCellModel();
-      return widget;
-    }
-
-    /**
-     * Create a new prompt widget.
-     */
-    createPrompt(rendermime: IRenderMime): CodeCellWidget {
-      let widget = new CodeCellWidget({ rendermime });
-      widget.model = new CodeCellModel();
-      return widget;
-    }
-  }
-
-
-  /**
-   * The default `IRenderer` instance.
+   * The console renderer token.
    */
   export
-  const defaultRenderer = new Renderer();
+  const IRenderer = new Token<IRenderer>('jupyter.services.console.renderer');
+  /* tslint:enable */
 }
 
 

+ 6 - 4
src/inspector/handler.ts

@@ -14,7 +14,7 @@ import {
 } from 'phosphor/lib/core/signaling';
 
 import {
-  CellEditorWidget, ITextChange
+  ICellEditorWidget, ITextChange
 } from '../notebook/cells/editor';
 
 import {
@@ -69,13 +69,15 @@ class InspectionHandler implements IDisposable, Inspector.IInspectable {
     }
 
     if (this._activeCell && !this._activeCell.isDisposed) {
-      this._activeCell.editor.textChanged.disconnect(this.onTextChanged, this);
+      const editor = this._activeCell.editor;
+      editor.textChanged.disconnect(this.onTextChanged, this);
     }
     this._activeCell = newValue;
     if (this._activeCell) {
       // Clear ephemeral inspectors in preparation for a new editor.
       this.ephemeralCleared.emit(void 0);
-      this._activeCell.editor.textChanged.connect(this.onTextChanged, this);
+      const editor = this._activeCell.editor;
+      editor.textChanged.connect(this.onTextChanged, this);
     }
   }
 
@@ -149,7 +151,7 @@ class InspectionHandler implements IDisposable, Inspector.IInspectable {
    * #### Notes
    * Update the hints inspector based on a text change.
    */
-  protected onTextChanged(editor: CellEditorWidget, change: ITextChange): void {
+  protected onTextChanged(editor: ICellEditorWidget, change: ITextChange): void {
     let update: Inspector.IInspectorUpdate = {
       content: null,
       type: 'hints'

+ 38 - 207
src/notebook/cells/editor.ts

@@ -1,55 +1,27 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import * as CodeMirror
-  from 'codemirror';
-
 import {
   JSONObject
 } from 'phosphor/lib/algorithm/json';
 
 import {
-  defineSignal, ISignal
+  ISignal
 } from 'phosphor/lib/core/signaling';
 
 import {
-  CodeMirrorWidget
-} from '../../codemirror/widget';
-
-import {
-  IChangedArgs
-} from '../../common/interfaces';
+  Widget
+} from 'phosphor/lib/ui/widget';
 
 import {
   ICellModel,
 } from './model';
 
 
-/**
- * The key code for the up arrow key.
- */
-const UP_ARROW = 38;
-
-/**
- * The key code for the down arrow key.
- */
-const DOWN_ARROW = 40;
-
-/**
- * The key code for the tab key.
- */
-const TAB = 9;
-
 /**
  * The location of requested edges.
  */
-export
-type EdgeLocation = 'top' | 'bottom';
-
-/**
- * The class name added to cell editor widget nodes.
- */
-const CELL_EDITOR_CLASS = 'jp-CellEditor';
+export type EdgeLocation = 'top' | 'bottom';
 
 
 /**
@@ -78,6 +50,7 @@ interface ICoords extends JSONObject {
   bottom: number;
 }
 
+
 /**
  * An interface describing the state of the editor in an event.
  */
@@ -148,217 +121,75 @@ interface ICompletionRequest extends IEditorState {
  * A widget for a cell editor.
  */
 export
-class CellEditorWidget extends CodeMirrorWidget {
-  /**
-   * Construct a new cell editor widget.
-   */
-  constructor(options: CodeMirror.EditorConfiguration = {}) {
-    super(options);
-    this.addClass(CELL_EDITOR_CLASS);
-
-    CodeMirror.on(this.editor.getDoc(), 'change', (instance, change) => {
-      this.onDocChange(instance, change);
-    });
-    CodeMirror.on(this.editor, 'keydown', (instance, evt) => {
-      this.onEditorKeydown(instance, evt);
-    });
-  }
-
+interface ICellEditorWidget extends Widget {
   /**
-   * A signal emitted when a tab (text) completion is requested.
+   * The cell model used by the editor.
    */
-  completionRequested: ISignal<CellEditorWidget, ICompletionRequest>;
+  model: ICellModel;
 
   /**
    * A signal emitted when either the top or bottom edge is requested.
    */
-  edgeRequested: ISignal<CellEditorWidget, EdgeLocation>;
+  edgeRequested: ISignal<ICellEditorWidget, EdgeLocation>;
 
   /**
    * A signal emitted when a text change is completed.
    */
-  textChanged: ISignal<CellEditorWidget, ITextChange>;
+  textChanged: ISignal<ICellEditorWidget, ITextChange>;
 
   /**
-   * The cell model used by the editor.
+   * A signal emitted when a completion is requested.
    */
-  get model(): ICellModel {
-    return this._model;
-  }
-  set model(model: ICellModel) {
-    if (!model && !this._model || model === this._model) {
-      return;
-    }
-
-    let doc = this.editor.getDoc();
-
-    // If the model is being replaced, disconnect the old signal handler.
-    if (this._model) {
-      this._model.stateChanged.disconnect(this.onModelStateChanged, this);
-    }
-
-    if (!model) {
-      doc.setValue('');
-      this._model = null;
-      return;
-    }
-
-    this._model = model;
-    doc.setValue(this._model.source || '');
-    this._model.stateChanged.connect(this.onModelStateChanged, this);
-  }
+  completionRequested: ISignal<ICellEditorWidget, ICompletionRequest>;
 
   /**
    * The line numbers state of the editor.
    */
-  get lineNumbers(): boolean {
-    return this.editor.getOption('lineNumbers');
-  }
-  set lineNumbers(value: boolean) {
-    this.editor.setOption('lineNumbers', value);
-  }
+  lineNumbers: boolean;
 
   /**
-   * Dispose of the resources held by the editor.
+   * Change the mime type for an editor.
    */
-  dispose(): void {
-    this._model = null;
-    super.dispose();
-  }
+  setMimeType(mimeType: string): void;
 
   /**
-   * Get the current cursor position of the editor.
+   * Set whether the editor is read only.
    */
-  getCursorPosition(): number {
-    let doc = this.editor.getDoc();
-    let position = doc.getCursor();
-    return doc.indexFromPos(position);
-  }
+  setReadOnly(readOnly: boolean): void;
 
   /**
-   * Set the current cursor position of the editor.
+   * Give keyboard focus to the cell editor.
    */
-  setCursorPosition(position: number): void {
-    let doc = this.editor.getDoc();
-    doc.setCursor(doc.posFromIndex(position));
-  }
+  focus(): void;
 
   /**
-   * Handle changes in the model state.
+   * Test whether the editor has keyboard focus.
    */
-  protected onModelStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
-    switch (args.name) {
-    case 'source':
-      let doc = this.editor.getDoc();
-      if (doc.getValue() !== args.newValue) {
-        doc.setValue(args.newValue);
-      }
-      break;
-    default:
-      break;
-    }
-  }
+  hasFocus(): boolean;
 
   /**
-   * Handle change events from the document.
+   * Returns a zero-based last line number.
    */
-  protected onDocChange(doc: CodeMirror.Doc, change: CodeMirror.EditorChange): void {
-    if (change.origin === 'setValue') {
-      return;
-    }
-    let model = this.model;
-    let editor = this.editor;
-    let oldValue = model.source;
-    let newValue = doc.getValue();
-    if (oldValue === newValue) {
-      return;
-    }
-    model.source = newValue;
-
-    let cursor = doc.getCursor();
-    let line = cursor.line;
-    let ch = cursor.ch;
-    let chHeight = editor.defaultTextHeight();
-    let chWidth = editor.defaultCharWidth();
-    let coords = editor.charCoords({ line, ch }, 'page') as ICoords;
-    let position = editor.getDoc().indexFromPos({ line, ch });
-    this.textChanged.emit({
-      line, ch, chHeight, chWidth, coords, position, oldValue, newValue
-    });
-  }
+  getLastLine(): number;
 
   /**
-   * Handle keydown events from the editor.
+   * Returns the position of the cursor.
    */
-  protected onEditorKeydown(editor: CodeMirror.Editor, event: KeyboardEvent): void {
-    let doc = editor.getDoc();
-    let cursor = doc.getCursor();
-    let line = cursor.line;
-    let ch = cursor.ch;
-
-    if (event.keyCode === TAB) {
-      // If the tab is modified, ignore it.
-      if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
-        return;
-      }
-      return this.onTabEvent(event, ch, line);
-    }
-
-    if (line === 0 && ch === 0 && event.keyCode === UP_ARROW) {
-      this.edgeRequested.emit('top');
-      return;
-    }
-
-    let lastLine = doc.lastLine();
-    let lastCh = doc.getLineHandle(lastLine).text.length;
-    if (line === lastLine && ch === lastCh && event.keyCode === DOWN_ARROW) {
-      this.edgeRequested.emit('bottom');
-      return;
-    }
-  }
+  getCursorPosition(): number;
 
   /**
-   * Handle a tab key press.
+   * Set the position of the cursor.
+   *
+   * @param position - A new cursor's position.
    */
-  protected onTabEvent(event: KeyboardEvent, ch: number, line: number): void {
-    let editor = this.editor;
-    let doc = editor.getDoc();
-
-    // If there is a text selection, no completion requests should be emitted.
-    if (doc.getSelection()) {
-      return;
-    }
-
-    let currentValue = doc.getValue();
-    let currentLine = currentValue.split('\n')[line];
-    let chHeight = editor.defaultTextHeight();
-    let chWidth = editor.defaultCharWidth();
-    let coords = editor.charCoords({ line, ch }, 'page') as ICoords;
-    let position = editor.getDoc().indexFromPos({ line, ch });
-
-    // A completion request signal should only be emitted if the current
-    // character or a preceding character is not whitespace.
-    //
-    // Otherwise, the default tab action of creating a tab character should be
-    // allowed to propagate.
-    if (!currentLine.substring(0, ch).match(/\S/)) {
-      return;
-    }
-
-    event.preventDefault();
-    event.stopPropagation();
-    event.stopImmediatePropagation();
-
-    let data = {
-      line, ch, chHeight, chWidth, coords, position, currentValue
-    };
-    this.completionRequested.emit(data as ICompletionRequest);
-  }
-
-  private _model: ICellModel = null;
-}
+  setCursorPosition(cursorPosition: number): void;
 
-// Define the signals for the `CellEditorWidget` class.
-defineSignal(CellEditorWidget.prototype, 'completionRequested');
-defineSignal(CellEditorWidget.prototype, 'edgeRequested');
-defineSignal(CellEditorWidget.prototype, 'textChanged');
+  /**
+   * Set the position of the cursor.
+   *
+   * @param line - A zero-based line number.
+   *
+   * @param character - A zero-based character number.
+   */
+  setCursor(line: number, character: number): void;
+}

+ 23 - 66
src/notebook/cells/widget.ts

@@ -21,10 +21,6 @@ import {
   Widget
 } from 'phosphor/lib/ui/widget';
 
-import {
-  loadModeByMIME
-} from '../../codemirror';
-
 import {
   IChangedArgs
 } from '../../common/interfaces';
@@ -42,7 +38,7 @@ import {
 } from '../output-area';
 
 import {
-  CellEditorWidget
+  ICellEditorWidget
 } from './editor';
 
 import {
@@ -120,28 +116,13 @@ class BaseCellWidget extends Widget {
   /**
    * Construct a new base cell widget.
    */
-  constructor(options: BaseCellWidget.IOptions = {}) {
+  constructor(options: BaseCellWidget.IOptions) {
     super();
     this.addClass(CELL_CLASS);
     this.layout = new PanelLayout();
 
-    let renderer = options.renderer || BaseCellWidget.defaultRenderer;
-    this._editor = renderer.createCellEditor({
-      indentUnit: 4,
-      readOnly: false,
-      theme: 'default',
-      extraKeys: {
-        'Cmd-Right': 'goLineRight',
-        'End': 'goLineRight',
-        'Cmd-Left': 'goLineLeft',
-        'Tab': 'indentMore',
-        'Shift-Tab' : 'indentLess',
-        'Cmd-Alt-[' : 'indentAuto',
-        'Ctrl-Alt-[' : 'indentAuto',
-        'Cmd-/' : 'toggleComment',
-        'Ctrl-/' : 'toggleComment',
-      }
-    });
+    let renderer = options.renderer;
+    this._editor = renderer.createCellEditor();
     this._input = renderer.createInputArea(this._editor);
 
     (this.layout as PanelLayout).addWidget(this._input);
@@ -177,7 +158,7 @@ class BaseCellWidget extends Widget {
    * #### Notes
    * This is a ready-only property.
    */
-  get editor(): CellEditorWidget {
+  get editor(): ICellEditorWidget {
     return this._editor;
   }
 
@@ -195,7 +176,7 @@ class BaseCellWidget extends Widget {
       return;
     }
     this._mimetype = value;
-    loadModeByMIME(this.editor.editor, value);
+    this.editor.setMimeType(value);
   }
 
   /**
@@ -230,7 +211,7 @@ class BaseCellWidget extends Widget {
    * Focus the widget.
    */
   focus(): void {
-    this.editor.editor.focus();
+    this.editor.focus();
   }
 
   /**
@@ -282,8 +263,7 @@ class BaseCellWidget extends Widget {
       return;
     }
     // Handle read only state.
-    let option = this._readOnly ? 'nocursor' : false;
-    this.editor.editor.setOption('readOnly', option);
+    this._editor.setReadOnly(this._readOnly);
     this.toggleClass(READONLY_CLASS, this._readOnly);
   }
 
@@ -332,7 +312,7 @@ class BaseCellWidget extends Widget {
 
     // Reset the editor model and set its mode to be the default MIME type.
     this._editor.model = this._model;
-    loadModeByMIME(this._editor.editor, this._mimetype);
+    this._editor.setMimeType(this._mimetype);
 
     // Handle trusted cursor.
     this._trustedCursor = this._model.getMetadata('trusted');
@@ -344,7 +324,7 @@ class BaseCellWidget extends Widget {
   }
 
   private _input: InputAreaWidget = null;
-  private _editor: CellEditorWidget = null;
+  private _editor: ICellEditorWidget = null;
   private _model: ICellModel = null;
   private _mimetype = 'text/plain';
   private _readOnly = false;
@@ -372,7 +352,7 @@ namespace BaseCellWidget {
      *
      * The default is a shared renderer instance.
      */
-    renderer?: IRenderer;
+    renderer: IRenderer;
   }
 
   /**
@@ -383,43 +363,34 @@ namespace BaseCellWidget {
     /**
      * Create a new cell editor for the widget.
      */
-    createCellEditor(options?: CodeMirror.EditorConfiguration): CellEditorWidget;
+    createCellEditor(): ICellEditorWidget;
 
     /**
      * Create a new input area for the widget.
      */
-    createInputArea(editor: CellEditorWidget): InputAreaWidget;
+    createInputArea(editor: ICellEditorWidget): InputAreaWidget;
   }
 
-
   /**
    * The default implementation of an `IRenderer`.
    */
   export
-  class Renderer implements IRenderer {
+  abstract class Renderer implements IRenderer {
     /**
      * Create a new cell editor for the widget.
      */
-    createCellEditor(options?: CodeMirror.EditorConfiguration): CellEditorWidget {
-      return new CellEditorWidget(options);
-    }
+    abstract createCellEditor(): ICellEditorWidget;
 
     /**
      * Create a new input area for the widget.
      */
-    createInputArea(editor: CellEditorWidget): InputAreaWidget {
+    createInputArea(editor: ICellEditorWidget): InputAreaWidget {
       return new InputAreaWidget(editor);
     }
   }
-
-
-  /**
-   * The default `IRenderer` instance.
-   */
-  export
-  const defaultRenderer = new Renderer();
 }
 
+
 /**
  * A widget for a code cell.
  */
@@ -430,11 +401,9 @@ class CodeCellWidget extends BaseCellWidget {
    */
   constructor(options: CodeCellWidget.IOptions) {
     super(options);
-    this.editor.editor.setOption('matchBrackets', true);
-    this.editor.editor.setOption('autoCloseBrackets', true);
     this.addClass(CODE_CELL_CLASS);
     this._rendermime = options.rendermime;
-    this._renderer = options.renderer || CodeCellWidget.defaultRenderer;
+    this._renderer = options.renderer;
   }
 
   /**
@@ -565,7 +534,7 @@ namespace CodeCellWidget {
      *
      * The default is a shared renderer instance.
      */
-    renderer?: IRenderer;
+    renderer: IRenderer;
 
     /**
      * The mime renderer for the cell widget.
@@ -573,7 +542,6 @@ namespace CodeCellWidget {
     rendermime: RenderMime;
   }
 
-
   /**
    * A renderer for creating code cell widgets.
    */
@@ -585,12 +553,11 @@ namespace CodeCellWidget {
     createOutputArea(rendermime: RenderMime): OutputAreaWidget;
   }
 
-
   /**
    * The default implementation of an `IRenderer`.
    */
   export
-  class Renderer extends BaseCellWidget.Renderer implements IRenderer {
+  abstract class Renderer extends BaseCellWidget.Renderer implements IRenderer {
     /**
      * Create an output area widget.
      */
@@ -598,12 +565,6 @@ namespace CodeCellWidget {
       return new OutputAreaWidget({ rendermime });
     }
   }
-
-  /**
-   * The default `IRenderer` instance.
-   */
-  export
-  const defaultRenderer = new Renderer();
 }
 
 
@@ -630,8 +591,6 @@ class MarkdownCellWidget extends BaseCellWidget {
     this._markdownWidget = new Widget();
     this._markdownWidget.addClass(MARKDOWN_CONTENT_CLASS);
     (this.layout as PanelLayout).addWidget(this._markdownWidget);
-    // Turn on line wrapping for markdown cells.
-    this.editor.editor.setOption('lineWrapping', true);
   }
 
   /**
@@ -715,7 +674,7 @@ namespace MarkdownCellWidget {
      *
      * The default is a shared renderer instance.
      */
-    renderer?: BaseCellWidget.IRenderer;
+    renderer: BaseCellWidget.IRenderer;
 
     /**
      * The mime renderer for the cell widget.
@@ -733,11 +692,9 @@ class RawCellWidget extends BaseCellWidget {
   /**
    * Construct a raw cell widget.
    */
-  constructor(options: BaseCellWidget.IOptions = {}) {
+  constructor(options: BaseCellWidget.IOptions) {
     super(options);
     this.addClass(RAW_CELL_CLASS);
-    // Turn on line wrapping for raw cells.
-    this.editor.editor.setOption('lineWrapping', true);
   }
 
   /**
@@ -755,7 +712,7 @@ class InputAreaWidget extends Widget {
   /**
    * Construct an input area widget.
    */
-  constructor(editor: CellEditorWidget) {
+  constructor(editor: ICellEditorWidget) {
     super();
     this.addClass(INPUT_CLASS);
     editor.addClass(EDITOR_CLASS);

+ 324 - 0
src/notebook/codemirror/cells/editor.ts

@@ -0,0 +1,324 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import{
+  defineSignal, ISignal
+} from 'phosphor/lib/core/signaling';
+
+import * as CodeMirror
+  from 'codemirror';
+
+import {
+  loadModeByMIME
+} from '../../../codemirror';
+
+import {
+  CodeMirrorWidget
+} from '../../../codemirror/widget';
+
+import {
+  IChangedArgs
+} from '../../../common/interfaces';
+
+import {
+  ICellModel
+} from '../../cells/model';
+
+import {
+  ICellEditorWidget, EdgeLocation, ITextChange, ICompletionRequest, ICoords
+} from '../../cells/editor';
+
+
+/**
+ * The key code for the up arrow key.
+ */
+const UP_ARROW = 38;
+
+/**
+ * The key code for the down arrow key.
+ */
+const DOWN_ARROW = 40;
+
+/**
+ * The key code for the tab key.
+ */
+const TAB = 9;
+
+/**
+ * The class name added to cell editor widget nodes.
+ */
+const CELL_EDITOR_CLASS = 'jp-CellEditor';
+
+
+/**
+ * A code mirror widget for a cell editor.
+ */
+export
+class CodeMirrorCellEditorWidget extends CodeMirrorWidget implements ICellEditorWidget {
+  /**
+   * Construct a new cell editor widget.
+   */
+  constructor(options: CodeMirror.EditorConfiguration = {}) {
+    super(options);
+    this.addClass(CELL_EDITOR_CLASS);
+
+    CodeMirror.on(this.editor.getDoc(), 'change', (instance, change) => {
+      this.onDocChange(instance, change);
+    });
+    CodeMirror.on(this.editor, 'keydown', (instance, evt) => {
+      this.onEditorKeydown(instance, evt);
+    });
+  }
+
+  /**
+   * A signal emitted when a tab (text) completion is requested.
+   */
+  completionRequested: ISignal<ICellEditorWidget, ICompletionRequest>;
+
+  /**
+   * A signal emitted when either the top or bottom edge is requested.
+   */
+  edgeRequested: ISignal<ICellEditorWidget, EdgeLocation>;
+
+  /**
+   * A signal emitted when a text change is completed.
+   */
+  textChanged: ISignal<ICellEditorWidget, ITextChange>;
+
+  /**
+   * The cell model used by the editor.
+   */
+  get model(): ICellModel {
+    return this._model;
+  }
+  set model(model: ICellModel) {
+    if (!model && !this._model || model === this._model) {
+      return;
+    }
+
+    let doc = this.editor.getDoc();
+
+    // If the model is being replaced, disconnect the old signal handler.
+    if (this._model) {
+      this._model.stateChanged.disconnect(this.onModelStateChanged, this);
+    }
+
+    if (!model) {
+      doc.setValue('');
+      this._model = null;
+      return;
+    }
+
+    this._model = model;
+    doc.setValue(this._model.source || '');
+    this._model.stateChanged.connect(this.onModelStateChanged, this);
+  }
+
+  /**
+   * The line numbers state of the editor.
+   */
+  get lineNumbers(): boolean {
+    return this.editor.getOption('lineNumbers');
+  }
+  set lineNumbers(value: boolean) {
+    this.editor.setOption('lineNumbers', value);
+  }
+
+  /**
+   * Dispose of the resources held by the editor.
+   */
+  dispose(): void {
+    this._model = null;
+    super.dispose();
+  }
+
+  /**
+   * Change the mode for an editor based on the given mime type.
+   */
+  setMimeType(mimeType: string): void {
+    loadModeByMIME(this.editor, mimeType);
+  }
+
+  /**
+   * Set whether the editor is read only.
+   */
+  setReadOnly(readOnly: boolean): void {
+    let option = readOnly ? 'nocursor' : false;
+    this.editor.setOption('readOnly', option);
+  }
+
+  /**
+   * Give keyboard focus to the cell editor.
+   */
+  focus(): void {
+    this.editor.focus();
+  }
+
+  /**
+   * Test whether the editor has keyboard focus.
+   */
+  hasFocus(): boolean {
+    return this.editor.hasFocus();
+  }
+
+  /**
+   * Returns a zero-based last line number.
+   */
+  getLastLine(): number {
+    return this.editor.getDoc().lastLine();
+  }
+
+  /**
+   * Get the current cursor position of the editor.
+   */
+  getCursorPosition(): number {
+    let doc = this.editor.getDoc();
+    let position = doc.getCursor();
+    return doc.indexFromPos(position);
+  }
+
+  /**
+   * Set the position of the cursor.
+   *
+   * @param position - A new cursor's position.
+   */
+  setCursorPosition(position: number): void {
+    let doc = this.editor.getDoc();
+    doc.setCursor(doc.posFromIndex(position));
+  }
+
+  /**
+   * Set the position of the cursor.
+   *
+   * @param line - A zero-based line number.
+   *
+   * @param character - A zero-based character number.
+   */
+  setCursor(line: number, character: number): void {
+    let doc = this.editor.getDoc();
+    doc.setCursor({
+      line: line,
+      ch: character
+    });
+  }
+
+  /**
+   * Handle changes in the model state.
+   */
+  protected onModelStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
+    switch (args.name) {
+    case 'source':
+      let doc = this.editor.getDoc();
+      if (doc.getValue() !== args.newValue) {
+        doc.setValue(args.newValue);
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  /**
+   * Handle change events from the document.
+   */
+  protected onDocChange(doc: CodeMirror.Doc, change: CodeMirror.EditorChange): void {
+    if (change.origin === 'setValue') {
+      return;
+    }
+    let model = this.model;
+    let editor = this.editor;
+    let oldValue = model.source;
+    let newValue = doc.getValue();
+    if (oldValue === newValue) {
+      return;
+    }
+    model.source = newValue;
+
+    let cursor = doc.getCursor();
+    let line = cursor.line;
+    let ch = cursor.ch;
+    let chHeight = editor.defaultTextHeight();
+    let chWidth = editor.defaultCharWidth();
+    let coords = editor.charCoords({ line, ch }, 'page') as ICoords;
+    let position = editor.getDoc().indexFromPos({ line, ch });
+    this.textChanged.emit({
+      line, ch, chHeight, chWidth, coords, position, oldValue, newValue
+    });
+  }
+
+  /**
+   * Handle keydown events from the editor.
+   */
+  protected onEditorKeydown(editor: CodeMirror.Editor, event: KeyboardEvent): void {
+    let doc = editor.getDoc();
+    let cursor = doc.getCursor();
+    let line = cursor.line;
+    let ch = cursor.ch;
+
+    if (event.keyCode === TAB) {
+      // If the tab is modified, ignore it.
+      if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
+        return;
+      }
+      return this.onTabEvent(event, ch, line);
+    }
+
+    if (line === 0 && ch === 0 && event.keyCode === UP_ARROW) {
+      this.edgeRequested.emit('top');
+      return;
+    }
+
+    let lastLine = doc.lastLine();
+    let lastCh = doc.getLineHandle(lastLine).text.length;
+    if (line === lastLine && ch === lastCh && event.keyCode === DOWN_ARROW) {
+      this.edgeRequested.emit('bottom');
+      return;
+    }
+  }
+
+  /**
+   * Handle a tab key press.
+   */
+  protected onTabEvent(event: KeyboardEvent, ch: number, line: number): void {
+    let editor = this.editor;
+    let doc = editor.getDoc();
+
+    // If there is a text selection, no completion requests should be emitted.
+    if (doc.getSelection()) {
+      return;
+    }
+
+    let currentValue = doc.getValue();
+    let currentLine = currentValue.split('\n')[line];
+    let chHeight = editor.defaultTextHeight();
+    let chWidth = editor.defaultCharWidth();
+    let coords = editor.charCoords({ line, ch }, 'page') as ICoords;
+    let position = editor.getDoc().indexFromPos({ line, ch });
+
+    // A completion request signal should only be emitted if the current
+    // character or a preceding character is not whitespace.
+    //
+    // Otherwise, the default tab action of creating a tab character should be
+    // allowed to propagate.
+    if (!currentLine.substring(0, ch).match(/\S/)) {
+      return;
+    }
+
+    event.preventDefault();
+    event.stopPropagation();
+    event.stopImmediatePropagation();
+
+    let data = {
+      line, ch, chHeight, chWidth, coords, position, currentValue
+    };
+    this.completionRequested.emit(data as ICompletionRequest);
+  }
+
+  private _model: ICellModel = null;
+}
+
+
+// Define the signals for the `CodeMirrorCellEditorWidget` class.
+defineSignal(CodeMirrorCellEditorWidget.prototype, 'completionRequested');
+defineSignal(CodeMirrorCellEditorWidget.prototype, 'edgeRequested');
+defineSignal(CodeMirrorCellEditorWidget.prototype, 'textChanged');

+ 96 - 0
src/notebook/codemirror/cells/widget.ts

@@ -0,0 +1,96 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  ICellEditorWidget
+} from '../../cells/editor';
+
+import {
+  CodeCellWidget
+} from '../../cells/widget';
+
+import {
+  CodeMirrorCellEditorWidget
+} from './editor';
+
+
+/**
+ * A code mirror renderer for a code cell widget.
+ */
+export
+class CodeMirrorCodeCellWidgetRenderer extends CodeCellWidget.Renderer {
+  /**
+   * Construct a code mirror renderer for a code cell widget.
+   * @param editorConfiguration a code mirror editor configuration
+   * @param editorInitializer a code cell widget initializer
+   */
+  constructor(options: CodeMirrorCodeCellWidgetRenderer.IOptions = {}) {
+    super();
+    this._editorConfiguration = (options.editorConfiguration ||
+      CodeMirrorCodeCellWidgetRenderer.defaultEditorConfiguration);
+    this._editorInitializer = (options.editorInitializer ||
+      (editor => { /* no-op */ }));
+  }
+
+  /**
+   * Construct a code cell widget.
+   */
+  createCellEditor(): ICellEditorWidget {
+    const widget = new CodeMirrorCellEditorWidget(this._editorConfiguration);
+    this._editorInitializer(widget);
+    return widget;
+  }
+
+  private _editorConfiguration: CodeMirror.EditorConfiguration = null;
+  private _editorInitializer: (editor: CodeMirrorCellEditorWidget) => void = null;
+}
+
+
+/**
+ * A namespace for `CodeMirrorCodeCellWidgetRenderer` statics.
+ */
+export
+namespace CodeMirrorCodeCellWidgetRenderer {
+  /**
+   * The options used to construct a code mirror code cell widget renderer.
+   */
+  export
+  interface IOptions {
+    /**
+     * A code mirror editor configuration.
+     */
+    editorConfiguration?: CodeMirror.EditorConfiguration;
+
+    /**
+     * A code cell widget initializer function.
+     */
+    editorInitializer?: (editor: CodeMirrorCellEditorWidget) => void;
+  }
+
+  /**
+   * A default code mirror configuration for a cell editor.
+   */
+  export
+  const defaultEditorConfiguration: CodeMirror.EditorConfiguration = {
+    indentUnit: 4,
+    readOnly: false,
+    theme: 'default',
+    extraKeys: {
+      'Cmd-Right': 'goLineRight',
+      'End': 'goLineRight',
+      'Cmd-Left': 'goLineLeft',
+      'Tab': 'indentMore',
+      'Shift-Tab': 'indentLess',
+      'Cmd-Alt-[': 'indentAuto',
+      'Ctrl-Alt-[': 'indentAuto',
+      'Cmd-/': 'toggleComment',
+      'Ctrl-/': 'toggleComment',
+    }
+  };
+
+  /**
+   * A default code mirror renderer for a code cell widget.
+   */
+  export
+  const defaultRenderer = new CodeMirrorCodeCellWidgetRenderer();
+}

+ 47 - 0
src/notebook/codemirror/notebook/panel.ts

@@ -0,0 +1,47 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  RenderMime
+} from '../../../rendermime';
+
+import {
+  Notebook
+} from '../../notebook/widget';
+
+import {
+  NotebookPanel
+} from '../../notebook/panel';
+
+import {
+  CodeMirrorNotebookRenderer
+} from './widget';
+
+
+/**
+ * A code mirror renderer for a notebook panel.
+ */
+export
+class CodeMirrorNotebookPanelRenderer extends NotebookPanel.Renderer {
+  /**
+   * Create a notebook.
+   */
+  createContent(rendermime: RenderMime): Notebook {
+    return new Notebook({
+      rendermime,
+      renderer: CodeMirrorNotebookRenderer.defaultRenderer
+    });
+  }
+}
+
+
+/**
+ * A namespace for `CodeMirrorNotebookPanelRenderer` statics.
+ */
+export namespace CodeMirrorNotebookPanelRenderer {
+  /**
+   * A default code mirror renderer for a notebook panel.
+   */
+  export
+  const defaultRenderer = new CodeMirrorNotebookPanelRenderer();
+}

+ 110 - 0
src/notebook/codemirror/notebook/widget.ts

@@ -0,0 +1,110 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  RenderMime
+} from '../../../rendermime';
+
+import {
+  ICodeCellModel, IMarkdownCellModel, IRawCellModel
+} from '../../cells/model';
+
+import {
+  CodeCellWidget, MarkdownCellWidget, RawCellWidget
+} from '../../cells/widget';
+
+import {
+  Notebook
+} from '../../notebook/widget';
+
+import {
+  CodeMirrorCodeCellWidgetRenderer
+} from '../cells/widget';
+
+
+/**
+ * A code mirror renderer for a notebook.
+ */
+export
+class CodeMirrorNotebookRenderer extends Notebook.Renderer {
+  /**
+   * Create a code cell editor.
+   */
+  createCodeCell(model: ICodeCellModel, rendermime: RenderMime): CodeCellWidget {
+    const widget = new CodeCellWidget({
+      rendermime,
+      renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer
+    });
+    widget.model = model;
+    return widget;
+  }
+
+  /**
+   * Create a markdown cell editor.
+   */
+  createMarkdownCell(model: IMarkdownCellModel, rendermime: RenderMime): MarkdownCellWidget {
+    const widget = new MarkdownCellWidget({
+      rendermime,
+      renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer
+    });
+    widget.model = model;
+    return widget;
+  }
+
+  /**
+   * Create a raw cell editor.
+   */
+  createRawCell(model: IRawCellModel): RawCellWidget {
+    const widget = new RawCellWidget({
+      renderer: CodeMirrorNotebookRenderer.defaultRawCellRenderer
+    });
+    widget.model = model;
+    return widget;
+  }
+}
+
+
+/**
+ * A namespace for `CodeMirrorNotebookRenderer` statics.
+ */
+export
+namespace CodeMirrorNotebookRenderer {
+  /**
+   * A default code mirror renderer for a code cell editor.
+   */
+  export
+  const defaultCodeCellRenderer = new CodeMirrorCodeCellWidgetRenderer({
+    editorInitializer: (editor) => {
+      editor.editor.setOption('matchBrackets', true);
+      editor.editor.setOption('autoCloseBrackets', true);
+    }
+  });
+
+  /**
+   * A default code mirror renderer for a markdown cell editor.
+   */
+  export
+  const defaultMarkdownCellRenderer = new CodeMirrorCodeCellWidgetRenderer({
+    editorInitializer: (editor) => {
+      // Turn on line wrapping for markdown cells.
+      editor.editor.setOption('lineWrapping', true);
+    }
+  });
+
+  /**
+   * A default code mirror renderer for a raw cell editor.
+   */
+  export
+  const defaultRawCellRenderer = new CodeMirrorCodeCellWidgetRenderer({
+    editorInitializer: (editor) => {
+      // Turn on line wrapping for markdown cells.
+      editor.editor.setOption('lineWrapping', true);
+    }
+  });
+
+  /**
+   * A default code mirror renderer for a notebook.
+   */
+  export
+  const defaultRenderer = new CodeMirrorNotebookRenderer();
+}

+ 24 - 0
src/notebook/codemirror/plugin.ts

@@ -0,0 +1,24 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  NotebookPanel
+} from '../notebook/panel';
+
+import {
+  CodeMirrorNotebookPanelRenderer
+} from './notebook/panel';
+
+import {
+  JupyterLabPlugin
+} from '../../application';
+
+/**
+ * The provider for a notebook's code mirror renderer.
+ */
+export
+const rendererProvider: JupyterLabPlugin<NotebookPanel.IRenderer> = {
+  id: 'jupyter.services.notebook.codemirror.renderer',
+  provides: NotebookPanel.IRenderer,
+  activate: () => CodeMirrorNotebookPanelRenderer.defaultRenderer
+};

+ 5 - 5
src/notebook/completion/handler.ts

@@ -10,7 +10,7 @@ import {
 } from 'phosphor/lib/core/disposable';
 
 import {
-  CellEditorWidget, ITextChange, ICompletionRequest
+  ICellEditorWidget, ITextChange, ICompletionRequest
 } from '../cells/editor';
 
 import {
@@ -57,13 +57,13 @@ class CellCompletionHandler implements IDisposable {
     }
 
     if (this._activeCell && !this._activeCell.isDisposed) {
-      let editor = this._activeCell.editor;
+      const editor = this._activeCell.editor;
       editor.textChanged.disconnect(this.onTextChanged, this);
       editor.completionRequested.disconnect(this.onCompletionRequested, this);
     }
     this._activeCell = newValue;
     if (this._activeCell) {
-      let editor = this._activeCell.editor;
+      const editor = this._activeCell.editor as ICellEditorWidget;
       editor.textChanged.connect(this.onTextChanged, this);
       editor.completionRequested.connect(this.onCompletionRequested, this);
     }
@@ -140,7 +140,7 @@ class CellCompletionHandler implements IDisposable {
   /**
    * Handle a text changed signal from an editor.
    */
-  protected onTextChanged(editor: CellEditorWidget, change: ITextChange): void {
+  protected onTextChanged(editor: ICellEditorWidget, change: ITextChange): void {
     if (!this._completion.model) {
       return;
     }
@@ -150,7 +150,7 @@ class CellCompletionHandler implements IDisposable {
   /**
    * Handle a completion requested signal from an editor.
    */
-  protected onCompletionRequested(editor: CellEditorWidget, request: ICompletionRequest): void {
+  protected onCompletionRequested(editor: ICellEditorWidget, request: ICompletionRequest): void {
     if (!this.kernel || !this._completion.model) {
       return;
     }

+ 15 - 11
src/notebook/notebook/panel.ts

@@ -13,6 +13,10 @@ import {
   defineSignal, ISignal
 } from 'phosphor/lib/core/signaling';
 
+import {
+  Token
+} from 'phosphor/lib/core/token';
+
 import {
   Panel, PanelLayout
 } from 'phosphor/lib/ui/panel';
@@ -83,11 +87,11 @@ class NotebookPanel extends Widget {
     this.addClass(NB_PANEL);
     this._rendermime = options.rendermime;
     this._clipboard = options.clipboard;
-    this._renderer = options.renderer || NotebookPanel.defaultRenderer;
+    this._renderer = options.renderer;
 
     this.layout = new PanelLayout();
     let rendermime = this._rendermime;
-    this._content = this._renderer.createContent({ rendermime });
+    this._content = this._renderer.createContent(rendermime);
     let toolbar = this._renderer.createToolbar();
 
     let container = new Panel();
@@ -412,7 +416,7 @@ export namespace NotebookPanel {
      *
      * The default is a shared `IRenderer` instance.
      */
-    renderer?: IRenderer;
+    renderer: IRenderer;
   }
 
   /**
@@ -423,7 +427,7 @@ export namespace NotebookPanel {
     /**
      * Create a new content area for the panel.
      */
-    createContent(options: Notebook.IOptions): Notebook;
+    createContent(rendermime: RenderMime): Notebook;
 
     /**
      * Create a new toolbar for the panel.
@@ -440,13 +444,11 @@ export namespace NotebookPanel {
    * The default implementation of an `IRenderer`.
    */
   export
-  class Renderer implements IRenderer {
+  abstract class Renderer implements IRenderer {
     /**
      * Create a new content area for the panel.
      */
-    createContent(options: Notebook.IOptions): Notebook {
-      return new Notebook(options);
-    }
+    abstract createContent(rendermime: RenderMime): Notebook;
 
     /**
      * Create a new toolbar for the panel.
@@ -464,9 +466,11 @@ export namespace NotebookPanel {
     }
   }
 
+  /* tslint:disable */
   /**
-   * The shared default instance of a `Renderer`.
+   * The notebook renderer token.
    */
-   export
-   const defaultRenderer = new Renderer();
+  export
+  const IRenderer = new Token<IRenderer>('jupyter.services.notebook.renderer');
+  /* tslint:enable */
 }

+ 9 - 35
src/notebook/notebook/widget.ts

@@ -131,7 +131,7 @@ class StaticNotebook extends Widget {
     this.addClass(NB_CLASS);
     this._rendermime = options.rendermime;
     this.layout = new Private.NotebookPanelLayout();
-    this._renderer = options.renderer || StaticNotebook.defaultRenderer;
+    this._renderer = options.renderer;
   }
 
   /**
@@ -459,7 +459,7 @@ namespace StaticNotebook {
      *
      * The default is a shared renderer instance.
      */
-    renderer?: IRenderer;
+    renderer: IRenderer;
   }
 
   /**
@@ -500,33 +500,21 @@ namespace StaticNotebook {
    * The default implementation of an `IRenderer`.
    */
   export
-  class Renderer implements IRenderer {
+  abstract class Renderer implements IRenderer {
     /**
      * Create a new code cell widget.
      */
-    createCodeCell(model: ICodeCellModel, rendermime: RenderMime): CodeCellWidget {
-      let widget = new CodeCellWidget({ rendermime });
-      widget.model = model;
-      return widget;
-    }
+    abstract createCodeCell(model: ICodeCellModel, rendermime: RenderMime): CodeCellWidget;
 
     /**
      * Create a new markdown cell widget.
      */
-    createMarkdownCell(model: IMarkdownCellModel, rendermime: RenderMime): MarkdownCellWidget {
-      let widget = new MarkdownCellWidget({ rendermime });
-      widget.model = model;
-      return widget;
-    }
+    abstract createMarkdownCell(model: IMarkdownCellModel, rendermime: RenderMime): MarkdownCellWidget;
 
     /**
      * Create a new raw cell widget.
      */
-    createRawCell(model: IRawCellModel): RawCellWidget {
-      let widget = new RawCellWidget();
-      widget.model = model;
-      return widget;
-    }
+    abstract createRawCell(model: IRawCellModel): RawCellWidget;
 
     /**
      * Update a cell widget.
@@ -550,12 +538,6 @@ namespace StaticNotebook {
       return mimetypeForLanguage(info as KernelMessage.ILanguageInfo);
     }
   }
-
-  /**
-   * The default `IRenderer` instance.
-   */
-  export
-  const defaultRenderer = new Renderer();
 }
 
 
@@ -878,11 +860,8 @@ class Notebook extends StaticNotebook {
       this.activeCellIndex--;
       // Move the cursor to the first position on the last line.
       if (this.activeCellIndex < prev) {
-        let doc = this.activeCell.editor.editor.getDoc();
-        doc.setCursor({
-          ch: 0,
-          line: doc.lastLine()
-        });
+        let lastLine = this.activeCell.editor.getLastLine();
+        this.activeCell.editor.setCursor(lastLine, 0);
       }
     } else {
       this.activeCellIndex++;
@@ -997,13 +976,8 @@ namespace Notebook {
    * The default implementation of an `IRenderer`.
    */
   export
-  class Renderer extends StaticNotebook.Renderer { }
+  abstract class Renderer extends StaticNotebook.Renderer { }
 
-  /**
-   * The default `IRenderer` instance.
-   */
-  export
-  const defaultRenderer = new Renderer();
 }
 
 

+ 10 - 2
src/notebook/notebook/widgetfactory.ts

@@ -45,11 +45,14 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
    * @param rendermime - The rendermime instance.
    *
    * @param clipboard - The application clipboard.
+   *
+   * @param renderer - The notebook panel renderer.
    */
-  constructor(rendermime: RenderMime, clipboard: IClipboard) {
+  constructor(rendermime: RenderMime, clipboard: IClipboard, renderer: NotebookPanel.IRenderer) {
     super();
     this._rendermime = rendermime;
     this._clipboard = clipboard;
+    this._renderer = renderer;
   }
 
   /**
@@ -76,7 +79,11 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
     if (kernel) {
       context.changeKernel(kernel);
     }
-    let panel = new NotebookPanel({ rendermime, clipboard: this._clipboard });
+    let panel = new NotebookPanel({
+      rendermime,
+      clipboard: this._clipboard,
+      renderer: this._renderer
+    });
     panel.context = context;
     ToolbarItems.populateDefaults(panel);
     this.widgetCreated.emit(panel);
@@ -85,4 +92,5 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
 
   private _rendermime: RenderMime = null;
   private _clipboard: IClipboard = null;
+  private _renderer: NotebookPanel.IRenderer = null;
 }

+ 4 - 3
src/notebook/plugin.ts

@@ -117,7 +117,8 @@ const notebookTrackerProvider: JupyterLabPlugin<INotebookTracker> = {
     IClipboard,
     IMainMenu,
     ICommandPalette,
-    IInspector
+    IInspector,
+    NotebookPanel.IRenderer
   ],
   activate: activateNotebookHandler,
   autoStart: true
@@ -127,8 +128,8 @@ const notebookTrackerProvider: JupyterLabPlugin<INotebookTracker> = {
 /**
  * Activate the notebook handler extension.
  */
-function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, services: IServiceManager, rendermime: IRenderMime, clipboard: IClipboard, mainMenu: IMainMenu, palette: ICommandPalette, inspector: IInspector): INotebookTracker {
-  let widgetFactory = new NotebookWidgetFactory(rendermime, clipboard);
+function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, services: IServiceManager, rendermime: IRenderMime, clipboard: IClipboard, mainMenu: IMainMenu, palette: ICommandPalette, inspector: IInspector, renderer: NotebookPanel.IRenderer): INotebookTracker {
+  let widgetFactory = new NotebookWidgetFactory(rendermime, clipboard, renderer);
   let options: IWidgetFactoryOptions = {
     fileExtensions: ['.ipynb'],
     displayName: 'Notebook',

+ 24 - 20
test/src/notebook/cells/editor.spec.ts

@@ -15,9 +15,13 @@ import {
 } from '../../../../lib/notebook/cells';
 
 import {
-  CellEditorWidget, ITextChange, ICompletionRequest, EdgeLocation
+  ITextChange, ICompletionRequest, EdgeLocation
 } from '../../../../lib/notebook/cells/editor';
 
+import {
+  CodeMirrorCellEditorWidget
+} from '../../../../lib/notebook/codemirror/cells/editor';
+
 
 const UP_ARROW = 38;
 
@@ -26,7 +30,7 @@ const DOWN_ARROW = 40;
 const TAB = 9;
 
 
-class LogEditorWidget extends CellEditorWidget {
+class LogEditorWidget extends CodeMirrorCellEditorWidget {
   methods: string[] = [];
 
   protected onModelStateChanged(model: ICellModel, args: any): void {
@@ -53,17 +57,17 @@ class LogEditorWidget extends CellEditorWidget {
 
 describe('notebook/cells/editor', () => {
 
-  describe('CellEditorWidget', () => {
+  describe('CodeMirrorCellEditorWidget', () => {
 
     describe('#constructor()', () => {
 
       it('should create a cell editor widget', () => {
-        let widget = new CellEditorWidget();
-        expect(widget).to.be.a(CellEditorWidget);
+        let widget = new CodeMirrorCellEditorWidget();
+        expect(widget).to.be.a(CodeMirrorCellEditorWidget);
       });
 
       it('should accept editor configuration options', () => {
-        let widget = new CellEditorWidget({
+        let widget = new CodeMirrorCellEditorWidget({
           value: 'foo',
           mode: 'bar'
         });
@@ -76,12 +80,12 @@ describe('notebook/cells/editor', () => {
     describe('#lineNumbers', () => {
 
       it('should get the line numbers state of the editor', () => {
-        let widget = new CellEditorWidget(new CellModel());
+        let widget = new CodeMirrorCellEditorWidget(new CellModel());
         expect(widget.lineNumbers).to.be(widget.editor.getOption('lineNumbers'));
       });
 
       it('should set the line numbers state of the editor', () => {
-        let widget = new CellEditorWidget(new CellModel());
+        let widget = new CodeMirrorCellEditorWidget(new CellModel());
         widget.lineNumbers = !widget.lineNumbers;
         expect(widget.lineNumbers).to.be(widget.editor.getOption('lineNumbers'));
       });
@@ -91,7 +95,7 @@ describe('notebook/cells/editor', () => {
     describe('#edgeRequested', () => {
 
       it('should emit a signal when the top edge is requested', () => {
-        let widget = new CellEditorWidget(new CellModel());
+        let widget = new CodeMirrorCellEditorWidget(new CellModel());
         let edge: EdgeLocation = null;
         let event = generate('keydown', { keyCode: UP_ARROW });
         let listener = (sender: any, args: EdgeLocation) => { edge = args; }
@@ -102,7 +106,7 @@ describe('notebook/cells/editor', () => {
       });
 
       it('should emit a signal when the bottom edge is requested', () => {
-        let widget = new CellEditorWidget(new CellModel());
+        let widget = new CodeMirrorCellEditorWidget(new CellModel());
         let edge: EdgeLocation = null;
         let event = generate('keydown', { keyCode: DOWN_ARROW });
         let listener = (sender: any, args: EdgeLocation) => { edge = args; }
@@ -117,7 +121,7 @@ describe('notebook/cells/editor', () => {
     describe('#textChanged', () => {
 
       it('should emit a signal when editor text is changed', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         let doc = widget.editor.getDoc();
         let want = { oldValue: '', newValue: 'foo' };
@@ -129,7 +133,7 @@ describe('notebook/cells/editor', () => {
         };
         widget.textChanged.connect(listener);
 
-        // CellEditorWidget suppresses signals when the code mirror instance's
+        // CodeMirrorCellEditorWidget suppresses signals when the code mirror instance's
         // content is changed programmatically via the `setValue` method, so
         // for this test, the `replaceRange` method is being used to generate
         // the text change.
@@ -141,7 +145,7 @@ describe('notebook/cells/editor', () => {
       });
 
       it('should not emit a signal if editor text already matches model', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         let doc = widget.editor.getDoc();
         let fromPos = { line: 0, ch: 0 };
@@ -161,7 +165,7 @@ describe('notebook/cells/editor', () => {
     describe('#completionRequested', () => {
 
       it('should emit a signal when the user requests a tab completion', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         let doc = widget.editor.getDoc();
         let want = { currentValue: 'foo', line: 0, ch: 3 };
@@ -189,7 +193,7 @@ describe('notebook/cells/editor', () => {
 
       it('should be settable', () => {
         let model = new CellModel();
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         expect(widget.model).to.be(null);
         widget.model = model;
         expect(widget.model).to.be(model);
@@ -197,14 +201,14 @@ describe('notebook/cells/editor', () => {
 
       it('should be safe to set multiple times', () => {
         let model = new CellModel();
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         widget.model = model;
         expect(widget.model).to.be(model);
       });
 
       it('should empty the code mirror if set to null', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         widget.model.source = 'foo';
         expect(widget.editor.getDoc().getValue()).to.be('foo');
@@ -217,7 +221,7 @@ describe('notebook/cells/editor', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the widget', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         expect(widget.model).to.be.ok();
         widget.dispose();
@@ -230,7 +234,7 @@ describe('notebook/cells/editor', () => {
     describe('#getCursorPosition()', () => {
 
       it('should return the cursor position of the editor', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         let doc = widget.editor.getDoc();
         let fromPos = { line: 0, ch: 0 };
@@ -246,7 +250,7 @@ describe('notebook/cells/editor', () => {
     describe('#setCursorPosition()', () => {
 
       it('should set the cursor position of the editor', () => {
-        let widget = new CellEditorWidget();
+        let widget = new CodeMirrorCellEditorWidget();
         widget.model = new CellModel();
         expect(widget.getCursorPosition()).to.be(0);
         widget.model.source = 'foo';

+ 120 - 62
test/src/notebook/cells/widget.spec.ts

@@ -25,12 +25,24 @@ import {
   RawCellWidget
 } from '../../../../lib/notebook/cells';
 
+import {
+  CodeMirrorCellEditorWidget
+} from '../../../../lib/notebook/codemirror/cells/editor';
+
+import {
+  CodeMirrorCodeCellWidgetRenderer
+} from '../../../../lib/notebook/codemirror/cells/widget';
+
+import {
+  CodeMirrorNotebookRenderer
+} from '../../../../lib/notebook/codemirror/notebook/widget';
+
 import {
   OutputAreaWidget
 } from '../../../../lib/notebook/output-area';
 
 import {
-  CellEditorWidget
+  ICellEditorWidget
 } from '../../../../lib/notebook/cells/editor';
 
 
@@ -52,6 +64,12 @@ class LogBaseCell extends BaseCellWidget {
 
   methods: string[] = [];
 
+  constructor() {
+    super({
+      renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+    });
+  }
+
   protected onAfterAttach(msg: Message): void {
     super.onAfterAttach(msg);
     this.methods.push('onAfterAttach');
@@ -116,15 +134,15 @@ class LogMarkdownCell extends MarkdownCellWidget {
 }
 
 
-class LogRenderer extends CodeCellWidget.Renderer {
+class LogRenderer extends CodeMirrorCodeCellWidgetRenderer {
   methods: string[] = [];
 
-  createCellEditor(model: ICellModel): CellEditorWidget {
+  createCellEditor(): ICellEditorWidget {
     this.methods.push('createCellEditor');
-    return super.createCellEditor(model);
+    return super.createCellEditor();
   }
 
-  createInputArea(editor: CellEditorWidget): InputAreaWidget {
+  createInputArea(editor: ICellEditorWidget): InputAreaWidget {
     this.methods.push('createInputArea');
     return super.createInputArea(editor);
   }
@@ -143,7 +161,9 @@ describe('notebook/cells/widget', () => {
     describe('#constructor()', () => {
 
       it('should create a base cell widget', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget).to.be.a(BaseCellWidget);
       });
 
@@ -166,7 +186,9 @@ describe('notebook/cells/widget', () => {
 
       it('should be settable', () => {
         let model = new CellModel();
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget.model).to.be(null);
         widget.model = model;
         expect(widget.model).to.be(model);
@@ -179,7 +201,9 @@ describe('notebook/cells/widget', () => {
     describe('#modelChanged', () => {
 
       it('should emit a signal when the model changes', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         let called = false;
         widget.modelChanged.connect(() => { called = true; });
         expect(called).to.be(false);
@@ -188,7 +212,9 @@ describe('notebook/cells/widget', () => {
       });
 
       it('should not emit a signal when the model has not changed', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         let model = new CellModel();
         let called = 0;
         widget.modelChanged.connect(() => { called++; });
@@ -204,12 +230,16 @@ describe('notebook/cells/widget', () => {
     describe('#editor', () => {
 
       it('should be a cell editor widget', () => {
-        let widget = new BaseCellWidget();
-        expect(widget.editor).to.be.a(CellEditorWidget);
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
+        expect(widget.editor).to.be.a(CodeMirrorCellEditorWidget);
       });
 
       it('should be read-only', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(() => { widget.editor = null; }).to.throwError();
       });
 
@@ -218,30 +248,40 @@ describe('notebook/cells/widget', () => {
     describe('#mimetype', () => {
 
       it('should be a string', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(typeof widget.mimetype).to.be('string');
       });
 
       it('should default to text/plain', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget.mimetype).to.be('text/plain');
       });
 
       it('should supporting being set to other types', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         widget.mimetype = 'test/test';
         expect(widget.mimetype).to.be('test/test');
       });
 
       it('should be safe to set multiple times', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         widget.mimetype = 'test/test';
         widget.mimetype = 'test/test';
         expect(widget.mimetype).to.be('test/test');
       });
 
       it('should not allow being set to empty or null strings', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         widget.mimetype = null;
         expect(widget.mimetype).to.be('text/plain');
         widget.mimetype = '';
@@ -253,17 +293,23 @@ describe('notebook/cells/widget', () => {
     describe('#readOnly', () => {
 
       it('should be a boolean', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(typeof widget.readOnly).to.be('boolean');
       });
 
       it('should default to false', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget.readOnly).to.be(false);
       });
 
       it('should be settable', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         widget.readOnly = true;
         expect(widget.readOnly).to.be(true);
       });
@@ -283,24 +329,32 @@ describe('notebook/cells/widget', () => {
     describe('#trusted', () => {
 
       it('should be a boolean', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(typeof widget.trusted).to.be('boolean');
       });
 
       it('should default to false', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget.trusted).to.be(false);
       });
 
       it('should be settable', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         widget.model = new CellModel();
         widget.trusted = true;
         expect(widget.trusted).to.be(true);
       });
 
       it('should do nothing if there is no model', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(widget.trusted).to.be(false);
         widget.trusted = true;
         expect(widget.trusted).to.be(false);
@@ -311,11 +365,13 @@ describe('notebook/cells/widget', () => {
     describe('#focus()', () => {
 
       it('should focus the cell editor', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         Widget.attach(widget, document.body);
-        expect(widget.editor.editor.hasFocus()).to.be(false);
+        expect(widget.editor.hasFocus()).to.be(false);
         widget.focus();
-        expect(widget.editor.editor.hasFocus()).to.be(true);
+        expect(widget.editor.hasFocus()).to.be(true);
         widget.dispose();
       });
 
@@ -324,7 +380,9 @@ describe('notebook/cells/widget', () => {
     describe('#setPrompt()', () => {
 
       it('should not throw an error (full test in input area)', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(() => { widget.setPrompt(void 0); }).to.not.throwError();
         expect(() => { widget.setPrompt(null); }).to.not.throwError();
         expect(() => { widget.setPrompt(''); }).to.not.throwError();
@@ -337,7 +395,7 @@ describe('notebook/cells/widget', () => {
     describe('#toggleInput()', () => {
 
       it('should toggle whether the input is shown', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         let input = widget.node.getElementsByClassName(INPUT_CLASS)[0];
         Widget.attach(widget, document.body);
         expect(window.getComputedStyle(input).display).to.not.be('none');
@@ -352,13 +410,13 @@ describe('notebook/cells/widget', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the widget', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
       });
 
       it('should be safe to call multiple times', () => {
-        let widget = new BaseCellWidget();
+        let widget = new BaseCellWidget({renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         widget.dispose();
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
@@ -436,7 +494,7 @@ describe('notebook/cells/widget', () => {
       describe('#constructor()', () => {
 
         it('should create a renderer', () => {
-          let renderer = new BaseCellWidget.Renderer();
+          let renderer = new CodeMirrorCodeCellWidgetRenderer();
           expect(renderer).to.be.a(BaseCellWidget.Renderer);
         });
 
@@ -445,9 +503,9 @@ describe('notebook/cells/widget', () => {
       describe('#createCellEditor()', () => {
 
         it('should create a cell editor widget', () => {
-          let renderer = new BaseCellWidget.Renderer();
-          let editor = renderer.createCellEditor(new CellModel());
-          expect(editor).to.be.a(CellEditorWidget);
+          let renderer = new CodeMirrorCodeCellWidgetRenderer();
+          let editor = renderer.createCellEditor();
+          expect(editor).to.be.a(CodeMirrorCellEditorWidget);
         });
 
       });
@@ -455,8 +513,8 @@ describe('notebook/cells/widget', () => {
       describe('#createInputArea()', () => {
 
         it('should create an input area widget', () => {
-          let renderer = new BaseCellWidget.Renderer();
-          let editor = renderer.createCellEditor(new CellModel());
+          let renderer = new CodeMirrorCodeCellWidgetRenderer();
+          let editor = renderer.createCellEditor();
           let input = renderer.createInputArea(editor);
           expect(input).to.be.an(InputAreaWidget);
         });
@@ -466,7 +524,7 @@ describe('notebook/cells/widget', () => {
       describe('#defaultRenderer', () => {
 
         it('should be a renderer', () => {
-          let defaultRenderer = BaseCellWidget.defaultRenderer;
+          let defaultRenderer = CodeMirrorCodeCellWidgetRenderer.defaultRenderer;
           expect(defaultRenderer).to.be.a(BaseCellWidget.Renderer);
         });
 
@@ -481,7 +539,7 @@ describe('notebook/cells/widget', () => {
     describe('#constructor()', () => {
 
       it('should create a code cell widget', () => {
-        let widget = new CodeCellWidget({ rendermime });
+        let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         expect(widget).to.be.a(CodeCellWidget);
       });
 
@@ -506,13 +564,13 @@ describe('notebook/cells/widget', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the widget', () => {
-        let widget = new CodeCellWidget({ rendermime });
+        let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
       });
 
       it('should be safe to call multiple times', () => {
-        let widget = new CodeCellWidget({ rendermime });
+        let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         widget.dispose();
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
@@ -523,14 +581,14 @@ describe('notebook/cells/widget', () => {
     describe('#execute()', () => {
 
       it('should fulfill a promise if there is no code to execute', (done) => {
-        let widget = new CodeCellWidget({ rendermime });
+        let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         let kernel = new MockKernel();
         widget.model = new CodeCellModel();
         widget.execute(kernel).then(() => { done(); });
       });
 
       it('should fulfill a promise if there is code to execute', (done) => {
-        let widget = new CodeCellWidget({ rendermime });
+        let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         let kernel = new MockKernel();
         widget.model = new CodeCellModel();
         widget.model.source = 'foo';
@@ -548,7 +606,7 @@ describe('notebook/cells/widget', () => {
     describe('#onUpdateRequest()', () => {
 
       it('should update the widget', () => {
-        let widget = new LogCodeCell({ rendermime });
+        let widget = new LogCodeCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         expect(widget.methods).to.not.contain('onUpdateRequest');
         sendMessage(widget, WidgetMessage.UpdateRequest);
         expect(widget.methods).to.contain('onUpdateRequest');
@@ -560,7 +618,7 @@ describe('notebook/cells/widget', () => {
 
       it('should fire when the model changes', () => {
         let method = 'onModelChanged';
-        let widget = new LogCodeCell({ rendermime });
+        let widget = new LogCodeCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         expect(widget.methods).to.not.contain(method);
         widget.model = new CodeCellModel();
         expect(widget.methods).to.contain(method);
@@ -572,7 +630,7 @@ describe('notebook/cells/widget', () => {
 
       it('should fire when model state changes', () => {
         let method = 'onModelStateChanged';
-        let widget = new LogCodeCell({ rendermime });
+        let widget = new LogCodeCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         widget.model = new CodeCellModel();
         expect(widget.methods).to.not.contain(method);
         widget.model.source = 'foo';
@@ -585,7 +643,7 @@ describe('notebook/cells/widget', () => {
 
       it('should fire when model metadata changes', () => {
         let method = 'onMetadataChanged';
-        let widget = new LogCodeCell({ rendermime });
+        let widget = new LogCodeCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
         widget.model = new CodeCellModel();
         expect(widget.methods).to.not.contain(method);
         widget.model.metadataChanged.emit({
@@ -603,7 +661,7 @@ describe('notebook/cells/widget', () => {
       describe('#constructor()', () => {
 
         it('should create a renderer', () => {
-          let renderer = new CodeCellWidget.Renderer();
+          let renderer = new CodeMirrorCodeCellWidgetRenderer();
           expect(renderer).to.be.a(CodeCellWidget.Renderer);
         });
 
@@ -612,7 +670,7 @@ describe('notebook/cells/widget', () => {
       describe('#createOutputArea()', () => {
 
         it('should create an output area widget', () => {
-          let renderer = new CodeCellWidget.Renderer();
+          let renderer = new CodeMirrorCodeCellWidgetRenderer();
           let output = renderer.createOutputArea(rendermime);
           expect(output).to.be.an(OutputAreaWidget);
         });
@@ -622,7 +680,7 @@ describe('notebook/cells/widget', () => {
       describe('#defaultRenderer', () => {
 
         it('should be a renderer', () => {
-          let defaultRenderer = CodeCellWidget.defaultRenderer;
+          let defaultRenderer = CodeMirrorNotebookRenderer.defaultCodeCellRenderer;
           expect(defaultRenderer).to.be.a(CodeCellWidget.Renderer);
         });
 
@@ -637,7 +695,7 @@ describe('notebook/cells/widget', () => {
     describe('#constructor()', () => {
 
       it('should create a markdown cell widget', () => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         expect(widget).to.be.a(MarkdownCellWidget);
       });
 
@@ -655,7 +713,7 @@ describe('notebook/cells/widget', () => {
       });
 
       it('should set the default mimetype to text/x-ipythongfm', () => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         expect(widget.mimetype).to.be('text/x-ipythongfm');
       });
 
@@ -664,7 +722,7 @@ describe('notebook/cells/widget', () => {
     describe('#rendered', () => {
 
       it('should default to true', (done) => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         Widget.attach(widget, document.body);
         expect(widget.rendered).to.be(true);
         requestAnimationFrame(() => {
@@ -675,7 +733,7 @@ describe('notebook/cells/widget', () => {
       });
 
       it('should unrender the widget', (done) => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         Widget.attach(widget, document.body);
         widget.rendered = false;
         requestAnimationFrame(() => {
@@ -686,7 +744,7 @@ describe('notebook/cells/widget', () => {
       });
 
       it('should ignore being set to the same value', (done) => {
-        let widget = new LogMarkdownCell({ rendermime });
+        let widget = new LogMarkdownCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         Widget.attach(widget, document.body);
         widget.rendered = false;
         widget.rendered = false;
@@ -704,13 +762,13 @@ describe('notebook/cells/widget', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the widget', () => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
       });
 
       it('should be safe to call multiple times', () => {
-        let widget = new MarkdownCellWidget({ rendermime });
+        let widget = new MarkdownCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         widget.dispose();
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
@@ -721,7 +779,7 @@ describe('notebook/cells/widget', () => {
     describe('#onUpdateRequest()', () => {
 
       it('should update the widget', () => {
-        let widget = new LogMarkdownCell({ rendermime });
+        let widget = new LogMarkdownCell({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultMarkdownCellRenderer });
         expect(widget.methods).to.not.contain('onUpdateRequest');
         sendMessage(widget, WidgetMessage.UpdateRequest);
         expect(widget.methods).to.contain('onUpdateRequest');
@@ -736,7 +794,7 @@ describe('notebook/cells/widget', () => {
     describe('#constructor()', () => {
 
       it('should create a raw cell widget', () => {
-        let widget = new RawCellWidget();
+        let widget = new RawCellWidget({renderer:CodeMirrorNotebookRenderer.defaultRawCellRenderer});
         expect(widget).to.be.a(RawCellWidget);
       });
 
@@ -749,7 +807,7 @@ describe('notebook/cells/widget', () => {
     describe('#constructor()', () => {
 
       it('should create an input area widget', () => {
-        let editor = new CellEditorWidget(new CellModel());
+        let editor = new CodeMirrorCellEditorWidget(new CellModel());
         let widget = new InputAreaWidget(editor);
         expect(widget).to.be.an(InputAreaWidget);
       });
@@ -759,7 +817,7 @@ describe('notebook/cells/widget', () => {
     describe('#setPrompt()', () => {
 
       it('should change the value of the input prompt', () => {
-        let editor = new CellEditorWidget(new CellModel());
+        let editor = new CodeMirrorCellEditorWidget(new CellModel());
         let widget = new InputAreaWidget(editor);
         let prompt = widget.node.querySelector(`.${PROMPT_CLASS}`);
         expect(prompt.textContent).to.be.empty();
@@ -768,7 +826,7 @@ describe('notebook/cells/widget', () => {
       });
 
       it('should treat the string value "null" as special', () => {
-        let editor = new CellEditorWidget(new CellModel());
+        let editor = new CodeMirrorCellEditorWidget(new CellModel());
         let widget = new InputAreaWidget(editor);
         let prompt = widget.node.querySelector(`.${PROMPT_CLASS}`);
         expect(prompt.textContent).to.be.empty();

+ 16 - 12
test/src/notebook/completion/handler.spec.ts

@@ -16,13 +16,17 @@ import {
 } from '../../../../lib/notebook/cells';
 
 import {
-  ICompletionRequest, CellEditorWidget, ITextChange
+  ICompletionRequest, ICellEditorWidget, ITextChange
 } from '../../../../lib/notebook/cells/editor';
 
 import {
   CompletionWidget, CellCompletionHandler, CompletionModel, ICompletionPatch
 } from '../../../../lib/notebook/completion';
 
+import {
+  CodeMirrorCodeCellWidgetRenderer
+} from '../../../../lib/notebook/codemirror/cells/widget';
+
 
 class TestCompletionModel extends CompletionModel {
   methods: string[] = [];
@@ -53,12 +57,12 @@ class TestCompletionHandler extends CellCompletionHandler {
     this.methods.push('onReply');
   }
 
-  onTextChanged(editor: CellEditorWidget, change: ITextChange): void {
+  onTextChanged(editor: ICellEditorWidget, change: ITextChange): void {
     super.onTextChanged(editor, change);
     this.methods.push('onTextChanged');
   }
 
-  onCompletionRequested(editor: CellEditorWidget, request: ICompletionRequest): void {
+  onCompletionRequested(editor: ICellEditorWidget, request: ICompletionRequest): void {
     super.onCompletionRequested(editor, request);
     this.methods.push('onCompletionRequested');
   }
@@ -111,7 +115,7 @@ describe('notebook/completion/handler', () => {
 
       it('should be settable', () => {
         let handler = new CellCompletionHandler(new CompletionWidget());
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         expect(handler.activeCell).to.be(null);
         handler.activeCell = cell;
         expect(handler.activeCell).to.be.a(BaseCellWidget);
@@ -120,8 +124,8 @@ describe('notebook/completion/handler', () => {
 
       it('should be resettable', () => {
         let handler = new CellCompletionHandler(new CompletionWidget());
-        let one = new BaseCellWidget();
-        let two = new BaseCellWidget();
+        let one = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
+        let two = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         expect(handler.activeCell).to.be(null);
         handler.activeCell = one;
         expect(handler.activeCell).to.be.a(BaseCellWidget);
@@ -311,7 +315,7 @@ describe('notebook/completion/handler', () => {
           oldValue: 'fo',
           newValue: 'foo'
         };
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
 
         handler.activeCell = cell;
         expect(handler.methods).to.not.contain('onTextChanged');
@@ -334,7 +338,7 @@ describe('notebook/completion/handler', () => {
           oldValue: 'fo',
           newValue: 'foo'
         };
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         let model = completion.model as TestCompletionModel;
 
         handler.activeCell = cell;
@@ -358,7 +362,7 @@ describe('notebook/completion/handler', () => {
           position: 0,
           currentValue: 'foo'
         };
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
 
         handler.activeCell = cell;
         expect(handler.methods).to.not.contain('onCompletionRequested');
@@ -380,7 +384,7 @@ describe('notebook/completion/handler', () => {
           position: 0,
           currentValue: 'foo'
         };
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         let model = completion.model as TestCompletionModel;
 
         handler.kernel = new MockKernel();
@@ -410,7 +414,7 @@ describe('notebook/completion/handler', () => {
         let handler = new TestCompletionHandler(completion);
         let model = completion.model as TestCompletionModel;
 
-        handler.activeCell = new BaseCellWidget();
+        handler.activeCell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         expect(model.methods).to.not.contain('createPatch');
         completion.selected.emit('foo');
         expect(model.methods).to.contain('createPatch');
@@ -421,7 +425,7 @@ describe('notebook/completion/handler', () => {
         let patch = 'foobar';
         let completion = new CompletionWidget({ model });
         let handler = new TestCompletionHandler(completion);
-        let cell = new BaseCellWidget();
+        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,

+ 8 - 1
test/src/notebook/notebook/actions.spec.ts

@@ -39,6 +39,10 @@ import {
   DEFAULT_CONTENT
 } from '../utils';
 
+import {
+  CodeMirrorNotebookRenderer
+} from '../../../../lib/notebook/codemirror/notebook/widget';
+
 
 const clipboard = new MimeData();
 
@@ -51,7 +55,10 @@ describe('notebook/notebook/actions', () => {
     let kernel: IKernel;
 
     beforeEach(() => {
-      widget = new Notebook({ rendermime: defaultRenderMime() });
+      widget = new Notebook({
+        rendermime: defaultRenderMime(),
+        renderer: CodeMirrorNotebookRenderer.defaultRenderer
+      });
       let model = new NotebookModel();
       model.fromJSON(DEFAULT_CONTENT);
       widget.model = model;

+ 6 - 1
test/src/notebook/notebook/default-toolbar.spec.ts

@@ -43,6 +43,10 @@ import {
   DEFAULT_CONTENT
 } from '../utils';
 
+import {
+  CodeMirrorNotebookPanelRenderer
+} from '../../../../lib/notebook/codemirror/notebook/panel';
+
 
 /**
  * Default data.
@@ -57,9 +61,10 @@ describe('notebook/notebook/default-toolbar', () => {
 
     let panel: NotebookPanel;
     let context: MockContext<NotebookModel>;
+    const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
 
     beforeEach((done) => {
-      panel = new NotebookPanel({ rendermime, clipboard });
+      panel = new NotebookPanel({ rendermime, clipboard, renderer });
       let model = new NotebookModel();
       model.fromJSON(DEFAULT_CONTENT);
       context = new MockContext<NotebookModel>(model);

+ 42 - 33
test/src/notebook/notebook/panel.spec.ts

@@ -19,6 +19,10 @@ import {
   IDocumentContext
 } from '../../../../lib/docregistry';
 
+import {
+  ICellEditorWidget, ITextChange, ICompletionRequest
+} from '../../../../lib/notebook/cells/editor';
+
 import {
   CompletionWidget
 } from '../../../../lib/notebook/completion';
@@ -51,12 +55,17 @@ import {
   DEFAULT_CONTENT
 } from '../utils';
 
+import {
+  CodeMirrorNotebookPanelRenderer
+} from '../../../../lib/notebook/codemirror/notebook/panel';
+
 
 /**
  * Default data.
  */
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
+const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
 
 
 class LogNotebookPanel extends NotebookPanel {
@@ -86,7 +95,7 @@ class LogNotebookPanel extends NotebookPanel {
 
 
 function createPanel(): LogNotebookPanel {
-  let panel = new LogNotebookPanel({ rendermime, clipboard });
+  let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
   let model = new NotebookModel();
   model.fromJSON(DEFAULT_CONTENT);
   let context = new MockContext<NotebookModel>(model);
@@ -102,15 +111,15 @@ describe('notebook/notebook/panel', () => {
     describe('#constructor()', () => {
 
       it('should create a notebook panel', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer});
         expect(panel).to.be.a(NotebookPanel);
       });
 
 
       it('should accept an optional render', () => {
-        let renderer = new NotebookPanel.Renderer();
-        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
-        expect(panel.renderer).to.be(renderer);
+        let newRenderer = new CodeMirrorNotebookPanelRenderer();
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer: newRenderer});
+        expect(panel.renderer).to.be(newRenderer);
       });
 
     });
@@ -118,7 +127,7 @@ describe('notebook/notebook/panel', () => {
     describe('#contextChanged', () => {
 
       it('should be emitted when the context on the panel changes', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
         let model = new NotebookModel();
         let context = new MockContext<INotebookModel>(model);
@@ -132,7 +141,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should not be emitted if the context does not change', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
         let model = new NotebookModel();
         let context = new MockContext<INotebookModel>(model);
@@ -163,12 +172,12 @@ describe('notebook/notebook/panel', () => {
     describe('#toolbar', () => {
 
       it('should be the toolbar used by the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.toolbar).to.be.a(NotebookToolbar);
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.toolbar = null; }).to.throwError();
       });
 
@@ -177,12 +186,12 @@ describe('notebook/notebook/panel', () => {
     describe('#content', () => {
 
       it('should be the content area used by the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.content).to.be.a(Notebook);
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.content = null; }).to.throwError();
       });
 
@@ -198,7 +207,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.kernel = null; }).to.throwError();
       });
 
@@ -207,12 +216,12 @@ describe('notebook/notebook/panel', () => {
     describe('#rendermime', () => {
 
       it('should be the rendermime instance used by the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.rendermime).to.be(rendermime);
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.rendermime = null; }).to.throwError();
       });
 
@@ -221,13 +230,13 @@ describe('notebook/notebook/panel', () => {
     describe('#renderer', () => {
 
       it('should be the renderer used by the widget', () => {
-        let renderer = new NotebookPanel.Renderer();
+        let renderer = new CodeMirrorNotebookPanelRenderer();
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.renderer).to.be(renderer);
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.renderer = null; });
       });
 
@@ -236,12 +245,12 @@ describe('notebook/notebook/panel', () => {
     describe('#clipboard', () => {
 
       it('should be the clipboard instance used by the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.clipboard).to.be(clipboard);
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.clipboard = null; }).to.throwError();
       });
 
@@ -250,7 +259,7 @@ describe('notebook/notebook/panel', () => {
     describe('#model', () => {
 
       it('should be the model for the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.model).to.be(null);
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
@@ -260,7 +269,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should be read-only', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(() => { panel.model = null; }).to.throwError();
       });
 
@@ -269,12 +278,12 @@ describe('notebook/notebook/panel', () => {
     describe('#context', () => {
 
       it('should get the document context for the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.context).to.be(null);
       });
 
       it('should set the document context for the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
         panel.context = context;
@@ -282,7 +291,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should emit the `contextChanged` signal', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
@@ -296,7 +305,7 @@ describe('notebook/notebook/panel', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources used by the widget', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
         panel.context = context;
@@ -305,7 +314,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should be safe to call more than once', () => {
-        let panel = new NotebookPanel({ rendermime, clipboard });
+        let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         panel.dispose();
         panel.dispose();
         expect(panel.isDisposed).to.be(true);
@@ -316,7 +325,7 @@ describe('notebook/notebook/panel', () => {
     describe('#onContextChanged()', () => {
 
       it('should be called when the context changes', () => {
-        let panel = new LogNotebookPanel({ rendermime, clipboard });
+        let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
         panel.methods = [];
@@ -329,7 +338,7 @@ describe('notebook/notebook/panel', () => {
     describe('#onPopulated()', () => {
 
       it('should initialize the model state', () => {
-        let panel = new LogNotebookPanel({ rendermime, clipboard });
+        let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
         let model = new NotebookModel();
         model.fromJSON(DEFAULT_CONTENT);
         expect(model.cells.canUndo).to.be(true);
@@ -370,7 +379,7 @@ describe('notebook/notebook/panel', () => {
       });
 
       it('should be called when the context changes', () => {
-        let panel = new LogNotebookPanel({ rendermime, clipboard });
+        let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
         panel.methods = [];
@@ -393,8 +402,8 @@ describe('notebook/notebook/panel', () => {
       describe('#createContent()', () => {
 
         it('should create a notebook widget', () => {
-          let renderer = new NotebookPanel.Renderer();
-          expect(renderer.createContent({ rendermime })).to.be.a(Notebook);
+          let renderer = new CodeMirrorNotebookPanelRenderer();
+          expect(renderer.createContent(rendermime)).to.be.a(Notebook);
         });
 
       });
@@ -402,7 +411,7 @@ describe('notebook/notebook/panel', () => {
       describe('#createToolbar()', () => {
 
         it('should create a notebook toolbar', () => {
-          let renderer = new NotebookPanel.Renderer();
+          let renderer = new CodeMirrorNotebookPanelRenderer();
           expect(renderer.createToolbar()).to.be.a(NotebookToolbar);
         });
 
@@ -411,7 +420,7 @@ describe('notebook/notebook/panel', () => {
       describe('#createCompletion()', () => {
 
         it('should create a completion widget', () => {
-          let renderer = new NotebookPanel.Renderer();
+          let renderer = new CodeMirrorNotebookPanelRenderer();
           expect(renderer.createCompletion()).to.be.a(CompletionWidget);
         });
 
@@ -422,7 +431,7 @@ describe('notebook/notebook/panel', () => {
     describe('.defaultRenderer', () => {
 
       it('should be an instance of a `Renderer`', () => {
-        expect(NotebookPanel.defaultRenderer).to.be.a(NotebookPanel.Renderer);
+        expect(CodeMirrorNotebookPanelRenderer.defaultRenderer).to.be.a(NotebookPanel.Renderer);
       });
 
     });

+ 31 - 26
test/src/notebook/notebook/widget.spec.ts

@@ -40,13 +40,18 @@ import {
   DEFAULT_CONTENT
 } from '../utils';
 
+import {
+  CodeMirrorNotebookRenderer
+} from '../../../../lib/notebook/codemirror/notebook/widget';
+
 
 const rendermime = defaultRenderMime();
+const renderer = CodeMirrorNotebookRenderer.defaultRenderer;
 
 
 function createWidget(): LogStaticNotebook {
   let model = new NotebookModel();
-  let widget = new LogStaticNotebook({ rendermime });
+  let widget = new LogStaticNotebook({ rendermime, renderer });
   widget.model = model;
   return widget;
 }
@@ -133,7 +138,7 @@ class LogNotebook extends Notebook {
 
 function createActiveWidget(): LogNotebook {
   let model = new NotebookModel();
-  let widget = new LogNotebook({ rendermime });
+  let widget = new LogNotebook({ rendermime, renderer });
   widget.model = model;
   return widget;
 }
@@ -146,17 +151,17 @@ describe('notebook/notebook/widget', () => {
     describe('#constructor()', () => {
 
       it('should create a notebook widget', () => {
-        let widget = new StaticNotebook({ rendermime });
+        let widget = new StaticNotebook({ rendermime, renderer });
         expect(widget).to.be.a(StaticNotebook);
       });
 
       it('should add the `jp-Notebook` class', () => {
-        let widget = new StaticNotebook({ rendermime });
+        let widget = new StaticNotebook({ rendermime, renderer });
         expect(widget.hasClass('jp-Notebook')).to.be(true);
       });
 
       it('should accept an optional render', () => {
-        let renderer = new StaticNotebook.Renderer();
+        let renderer = new CodeMirrorNotebookRenderer();
         let widget = new StaticNotebook({ rendermime, renderer });
         expect(widget.renderer).to.be(renderer);
       });
@@ -166,7 +171,7 @@ describe('notebook/notebook/widget', () => {
     describe('#modelChanged', () => {
 
       it('should be emitted when the model changes', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         let called = false;
         widget.modelChanged.connect((sender, args) => {
@@ -183,7 +188,7 @@ describe('notebook/notebook/widget', () => {
     describe('#modelContentChanged', () => {
 
       it('should be emitted when a cell is added', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         widget.model = new NotebookModel();
         let called = false;
         widget.modelContentChanged.connect(() => { called = true; });
@@ -193,7 +198,7 @@ describe('notebook/notebook/widget', () => {
       });
 
       it('should be emitted when metadata is set', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         widget.model = new NotebookModel();
         let called = false;
         widget.modelContentChanged.connect(() => { called = true; });
@@ -207,19 +212,19 @@ describe('notebook/notebook/widget', () => {
     describe('#model', () => {
 
       it('should get the model for the widget', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         expect(widget.model).to.be(null);
       });
 
       it('should set the model for the widget', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         widget.model = model;
         expect(widget.model).to.be(model);
       });
 
       it('should emit the `modelChanged` signal', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         widget.model = model;
         let called = false;
@@ -229,7 +234,7 @@ describe('notebook/notebook/widget', () => {
       });
 
       it('should be a no-op if the value does not change', () => {
-        let widget = new StaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new StaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         widget.model = model;
         let called = false;
@@ -239,7 +244,7 @@ describe('notebook/notebook/widget', () => {
       });
 
       it('should add the model cells to the layout', () => {
-        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         model.fromJSON(DEFAULT_CONTENT);
         widget.model = model;
@@ -247,7 +252,7 @@ describe('notebook/notebook/widget', () => {
       });
 
       it('should set the mime types of the cell widgets', () => {
-        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         let cursor = model.getMetadata('language_info');
         cursor.setValue({ name: 'python', codemirror_mode: 'python' });
@@ -311,7 +316,7 @@ describe('notebook/notebook/widget', () => {
     describe('#rendermime', () => {
 
       it('should be the rendermime instance used by the widget', () => {
-        let widget = new StaticNotebook({ rendermime });
+        let widget = new StaticNotebook({ rendermime, renderer });
         expect(widget.rendermime).to.be(rendermime);
       });
 
@@ -325,8 +330,8 @@ describe('notebook/notebook/widget', () => {
     describe('#renderer', () => {
 
       it('should be the cell widget renderer used by the widget', () => {
-        let widget = new StaticNotebook({ rendermime });
-        expect(widget.renderer).to.be(StaticNotebook.defaultRenderer);
+        let widget = new StaticNotebook({ rendermime, renderer });
+        expect(widget.renderer).to.be(CodeMirrorNotebookRenderer.defaultRenderer);
       });
 
       it('should be read-only', () => {
@@ -339,12 +344,12 @@ describe('notebook/notebook/widget', () => {
     describe('#codeMimetype', () => {
 
       it('should get the mime type for code cells', () => {
-        let widget = new StaticNotebook({ rendermime });
+        let widget = new StaticNotebook({ rendermime, renderer });
         expect(widget.codeMimetype).to.be('text/plain');
       });
 
       it('should be set from language metadata', () => {
-        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime() });
+        let widget = new LogStaticNotebook({ rendermime: defaultRenderMime(), renderer });
         let model = new NotebookModel();
         let cursor = model.getMetadata('language_info');
         cursor.setValue({ name: 'python', codemirror_mode: 'python' });
@@ -404,7 +409,7 @@ describe('notebook/notebook/widget', () => {
     describe('#onModelChanged()', () => {
 
       it('should be called when the model changes', () => {
-        let widget = new LogStaticNotebook({ rendermime });
+        let widget = new LogStaticNotebook({ rendermime, renderer });
         widget.model = new NotebookModel();
         expect(widget.methods).to.contain('onModelChanged');
       });
@@ -483,7 +488,7 @@ describe('notebook/notebook/widget', () => {
       describe('#createCodeCell()', () => {
 
         it('should create a `CodeCellWidget`', () => {
-          let renderer = new StaticNotebook.Renderer();
+          let renderer = new CodeMirrorNotebookRenderer();
           let model = new CodeCellModel();
           let widget = renderer.createCodeCell(model, rendermime);
           expect(widget).to.be.a(CodeCellWidget);
@@ -494,7 +499,7 @@ describe('notebook/notebook/widget', () => {
       describe('#createMarkdownCell()', () => {
 
         it('should create a `MarkdownCellWidget`', () => {
-          let renderer = new StaticNotebook.Renderer();
+          let renderer = new CodeMirrorNotebookRenderer();
           let model = new MarkdownCellModel();
           let widget = renderer.createMarkdownCell(model, rendermime);
           expect(widget).to.be.a(MarkdownCellWidget);
@@ -505,7 +510,7 @@ describe('notebook/notebook/widget', () => {
       describe('#createRawCell()', () => {
 
         it('should create a `RawCellWidget`', () => {
-          let renderer = new StaticNotebook.Renderer();
+          let renderer = new CodeMirrorNotebookRenderer();
           let model = new RawCellModel();
           let widget = renderer.createRawCell(model);
           expect(widget).to.be.a(RawCellWidget);
@@ -516,7 +521,7 @@ describe('notebook/notebook/widget', () => {
       describe('#updateCell()', () => {
 
         it('should be a no-op', () => {
-          let renderer = new StaticNotebook.Renderer();
+          let renderer = new CodeMirrorNotebookRenderer();
           let model = new CodeCellModel();
           let widget = renderer.createCodeCell(model, rendermime);
           renderer.updateCell(widget);
@@ -528,7 +533,7 @@ describe('notebook/notebook/widget', () => {
       describe('#getCodeMimetype()', () => {
 
         it('should get the preferred mime for code cells in the notebook', () => {
-          let renderer = new StaticNotebook.Renderer();
+          let renderer = new CodeMirrorNotebookRenderer();
           let model = new NotebookModel();
           let cursor = model.getMetadata('language_info');
           cursor.setValue({ name: 'python', mimetype: 'text/x-python' });
@@ -542,7 +547,7 @@ describe('notebook/notebook/widget', () => {
     describe('.defaultRenderer', () => {
 
       it('should be an instance of `StaticNotebook.Renderer', () => {
-        expect(StaticNotebook.defaultRenderer).to.be.a(StaticNotebook.Renderer);
+        expect(CodeMirrorNotebookRenderer.defaultRenderer).to.be.a(StaticNotebook.Renderer);
       });
 
     });

+ 16 - 11
test/src/notebook/notebook/widgetfactory.spec.ts

@@ -27,9 +27,14 @@ import {
   defaultRenderMime
 } from '../../rendermime/rendermime.spec';
 
+import {
+  CodeMirrorNotebookPanelRenderer
+} from '../../../../lib/notebook/codemirror/notebook/panel';
+
 
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
+const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
 
 
 describe('notebook/notebook/widgetfactory', () => {
@@ -39,7 +44,7 @@ describe('notebook/notebook/widgetfactory', () => {
     describe('#constructor()', () => {
 
       it('should create a notebook widget factory', () => {
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer);
         expect(factory).to.be.a(NotebookWidgetFactory);
       });
 
@@ -48,14 +53,14 @@ describe('notebook/notebook/widgetfactory', () => {
     describe('#isDisposed', () => {
 
       it('should get whether the factory has been disposed', () => {
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer);
         expect(factory.isDisposed).to.be(false);
         factory.dispose();
         expect(factory.isDisposed).to.be(true);
       });
 
       it('should be read-only', () => {
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer);
         expect(() => { factory.isDisposed = false; }).to.throwError();
       });
 
@@ -64,13 +69,13 @@ describe('notebook/notebook/widgetfactory', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the factory', () => {
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         factory.dispose();
         expect(factory.isDisposed).to.be(true);
       });
 
       it('should be safe to call multiple times', () => {
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         factory.dispose();
         factory.dispose();
         expect(factory.isDisposed).to.be(true);
@@ -83,7 +88,7 @@ describe('notebook/notebook/widgetfactory', () => {
       it('should create a new `NotebookPanel` widget', () => {
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel).to.be.a(NotebookPanel);
       });
@@ -91,7 +96,7 @@ describe('notebook/notebook/widgetfactory', () => {
       it('should create a clone of the rendermime', () => {
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel.rendermime).to.not.be(rendermime);
       });
@@ -99,7 +104,7 @@ describe('notebook/notebook/widgetfactory', () => {
       it('should start a kernel if one is given', () => {
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context, { name: 'shell' });
         expect(panel.context.kernel.name).to.be('shell');
       });
@@ -107,7 +112,7 @@ describe('notebook/notebook/widgetfactory', () => {
       it('should start a kernel given the default kernel language', () => {
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel.context.kernel.name).to.be('python');
       });
@@ -117,7 +122,7 @@ describe('notebook/notebook/widgetfactory', () => {
         let cursor = model.getMetadata('language_info');
         cursor.setValue({ name: 'shell' });
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel.context.kernel.name).to.be('shell');
       });
@@ -125,7 +130,7 @@ describe('notebook/notebook/widgetfactory', () => {
       it('should populate the default toolbar items', () => {
         let model = new NotebookModel();
         let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard);
+        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         let items = panel.toolbar.list();
         expect(items).to.contain('save');