Browse Source

Merge pull request #2419 from blink1073/codemirror-options

Clean up CodeMirror Option handling
Brian E. Granger 7 years ago
parent
commit
5aca53a3c2

+ 2 - 3
packages/cells/src/widget.ts

@@ -342,7 +342,7 @@ class Cell extends Widget {
       return;
     }
     // Handle read only state.
-    this.editor.readOnly = this._readOnly;
+    this.editor.setOption('readOnly', this._readOnly);
     this.toggleClass(READONLY_CLASS, this._readOnly);
   }
 
@@ -511,7 +511,7 @@ class CodeCell extends Cell {
     let model = this.model;
 
     // Code cells should not wrap lines.
-    this.editor.wordWrap = false;
+    this.editor.setOption('lineWrap', false);
 
     // Insert the output before the cell footer.
     let outputWrapper = this._outputWrapper = new Panel();
@@ -739,7 +739,6 @@ class MarkdownCell extends Cell {
     super(options);
     this.addClass(MARKDOWN_CELL_CLASS);
     this._rendermime = options.rendermime;
-    this.editor.wordWrap = true;
 
     // Throttle the rendering rate of the widget.
     this._monitor = new ActivityMonitor({

+ 70 - 30
packages/codeeditor/src/editor.ts

@@ -366,21 +366,6 @@ namespace CodeEditor {
     */
     selectionStyle: CodeEditor.ISelectionStyle;
 
-    /**
-     * Whether line numbers should be displayed. Defaults to false.
-     */
-    lineNumbers: boolean;
-
-    /**
-     * Set to false for horizontal scrolling. Defaults to true.
-     */
-    wordWrap: boolean;
-
-    /**
-     * Whether the editor is read-only.  Defaults to false.
-     */
-    readOnly: boolean;
-
     /**
      * The DOM node that hosts the editor.
      */
@@ -407,6 +392,16 @@ namespace CodeEditor {
      */
     readonly lineCount: number;
 
+    /**
+     * Get a config option for the editor.
+     */
+    getOption<K extends keyof IConfig>(option: K): IConfig[K];
+
+    /**
+     * Set a config option for the editor.
+     */
+    setOption<K extends keyof IConfig>(option: K, value: IConfig[K]): void;
+
     /**
      * Returns the content for the given line number.
      *
@@ -472,7 +467,7 @@ namespace CodeEditor {
 
     /**
      * Repaint the editor.
-     * 
+     *
      * #### Notes
      * A repainted editor should fit to its host node.
      */
@@ -542,6 +537,61 @@ namespace CodeEditor {
   export
   type Factory = (options: IOptions) => CodeEditor.IEditor;
 
+  /**
+   * The configuration options for an editor.
+   */
+  export
+  interface IConfig {
+    /**
+     * Whether line numbers should be displayed.
+     */
+    lineNumbers: boolean;
+
+    /**
+     * Set to false for horizontal scrolling.
+     */
+    lineWrap: boolean;
+
+    /**
+     * Whether the editor is read-only.
+     */
+    readOnly: boolean;
+
+    /**
+     * The number of spaces a tab is equal to.
+     */
+    tabSize: number;
+
+    /**
+     * Whether to insert spaces when pressing Tab.
+     */
+    insertSpaces: boolean;
+
+    /**
+     * Whether to highlight matching brackets when one of them is selected.
+     */
+    matchBrackets: boolean;
+
+    /**
+     * Whether to automatically close brackets after opening them.
+     */
+    autoClosingBrackets: boolean;
+  }
+
+  /**
+   * The default configuration options for an editor.
+   */
+  export
+  let defaultConfig: IConfig = {
+    lineNumbers: false,
+    lineWrap: true,
+    readOnly: false,
+    tabSize: 4,
+    insertSpaces: true,
+    matchBrackets: true,
+    autoClosingBrackets: true
+  };
+
   /**
    * The options used to initialize an editor.
    */
@@ -563,24 +613,14 @@ namespace CodeEditor {
     uuid?: string;
 
     /**
-     * Whether line numbers should be displayed. Defaults to `false`.
-     */
-    lineNumbers?: boolean;
-
-    /**
-     * Set to false for horizontal scrolling. Defaults to `true`.
+     * The default selection style for the editor.
      */
-    wordWrap?: boolean;
+    selectionStyle?: CodeEditor.ISelectionStyle;
 
     /**
-     * Whether the editor is read-only. Defaults to `false`.
+     * The configuration options for the editor.
      */
-    readOnly?: boolean;
-
-   /**
-    * The default selection style for the editor.
-    */
-    selectionStyle?: CodeEditor.ISelectionStyle;
+    config?: Partial<IConfig>;
   }
 
   export

+ 2 - 2
packages/codeeditor/src/jsoneditor.ts

@@ -94,7 +94,7 @@ class JSONEditor extends Widget {
     model.value.changed.connect(this._onValueChanged, this);
     this.model = model;
     this.editor = options.editorFactory({ host, model });
-    this.editor.readOnly = true;
+    this.editor.setOption('readOnly', true);
     this.collapsible = !!options.collapsible;
   }
 
@@ -179,7 +179,7 @@ class JSONEditor extends Widget {
       this._source.changed.disconnect(this._onSourceChanged, this);
     }
     this._source = value;
-    this.editor.readOnly = !value;
+    this.editor.setOption('readOnly', !this.editor.getOption('readOnly'));
     if (value) {
       value.changed.connect(this._onSourceChanged, this);
     }

+ 3 - 14
packages/codeeditor/src/widget.ts

@@ -34,8 +34,7 @@ class CodeEditorWrapper extends Widget {
       host: this.node,
       model: options.model,
       uuid: options.uuid,
-      wordWrap: options.wordWrap,
-      readOnly: options.readOnly,
+      config: options.config,
       selectionStyle: options.selectionStyle
     });
     editor.model.selections.changed.connect(this._onSelectionsChanged, this);
@@ -149,19 +148,9 @@ namespace CodeEditorWrapper {
     uuid?: string;
 
     /**
-     * Whether line numbers should be displayed. Defaults to `false`.
+     * The configuration options for the editor.
      */
-    lineNumbers?: boolean;
-
-    /**
-     * Set to false for horizontal scrolling. Defaults to `true`.
-     */
-    wordWrap?: boolean;
-
-    /**
-     * Whether the editor is read-only. Defaults to `false`.
-     */
-    readOnly?: boolean;
+    config?: Partial<CodeEditor.IConfig>;
 
    /**
     * The default selection style for the editor.

+ 24 - 91
packages/codemirror-extension/src/index.ts

@@ -38,9 +38,6 @@ import {
  * The command IDs used by the codemirror plugin.
  */
 namespace CommandIDs {
-  export
-  const matchBrackets = 'codemirror:match-brackets';
-
   export
   const changeKeyMap = 'codemirror:change-keymap';
 
@@ -49,9 +46,6 @@ namespace CommandIDs {
 
   export
   const changeMode = 'codemirror:change-mode';
-
-  export
-  const changeTabs = 'codemirror:change-tabs';
 };
 
 
@@ -91,26 +85,12 @@ export default plugins;
 function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMenu: IMainMenu, palette: ICommandPalette, state: IStateDB, settingRegistry: ISettingRegistry): void {
   const { commands, restored } = app;
   const { id } = commandsPlugin;
-  let theme: string = CodeMirrorEditor.DEFAULT_THEME;
-  let keyMap: string = 'default';
-  let matchBrackets = false;
-
-  // Annotate the plugin settings.
-  settingRegistry.annotate(id, '', {
-    iconClass: 'jp-ImageTextEditor',
-    iconLabel: 'CodeMirror',
-    label: 'CodeMirror'
-  });
-  settingRegistry.annotate(id, 'keyMap', { label: 'Key Map' });
-  settingRegistry.annotate(id, 'matchBrackets', { label: 'Match Brackets' });
-  settingRegistry.annotate(id, 'theme', { label: 'Theme' });
+  let { theme, keyMap } = CodeMirrorEditor.defaultConfig;
 
   /**
    * Update the setting values.
    */
   function updateSettings(settings: ISettingRegistry.ISettings): void {
-    const cached = settings.get('matchBrackets') as boolean | null;
-    matchBrackets = cached === null ? false : !!cached;
     keyMap = settings.get('keyMap') as string | null || keyMap;
     theme = settings.get('theme') as string | null || theme;
   }
@@ -124,7 +104,6 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
         let cm = widget.editor.editor;
         cm.setOption('keyMap', keyMap);
         cm.setOption('theme', theme);
-        cm.setOption('matchBrackets', matchBrackets);
       }
     });
   }
@@ -147,14 +126,13 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
       let cm = widget.editor.editor;
       cm.setOption('keyMap', keyMap);
       cm.setOption('theme', theme);
-      cm.setOption('matchBrackets', matchBrackets);
     }
   });
 
   // Update the command registry when the codemirror state changes.
   tracker.currentChanged.connect(() => {
     if (tracker.size <= 1) {
-      commands.notifyCommandChanged(CommandIDs.matchBrackets);
+      commands.notifyCommandChanged(CommandIDs.changeKeyMap);
     }
   });
 
@@ -184,7 +162,7 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
     commands.addCommand(CommandIDs.changeTheme, {
       label: args => args['theme'] as string,
       execute: args => {
-        theme = args['theme'] as string || CodeMirrorEditor.DEFAULT_THEME;
+        theme = args['theme'] as string || theme;
         tracker.forEach(widget => {
           if (widget.editor instanceof CodeMirrorEditor) {
             let cm = widget.editor.editor;
@@ -203,7 +181,7 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
         return title === 'sublime' ? 'Sublime Text' : title;
       },
       execute: args => {
-        keyMap = args['keyMap'] as string || 'default';
+        keyMap = args['keyMap'] as string || keyMap;
         tracker.forEach(widget => {
           if (widget.editor instanceof CodeMirrorEditor) {
             let cm = widget.editor.editor;
@@ -241,51 +219,6 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
       }
     });
 
-    commands.addCommand(CommandIDs.changeTabs, {
-      label: args => args['name'] as string,
-      execute: args => {
-        let widget = tracker.currentWidget;
-        if (!widget) {
-          return;
-        }
-        let editor = widget.editor as CodeMirrorEditor;
-        let size = args['size'] as number || 4;
-        let tabs = !!args['tabs'];
-        editor.editor.setOption('indentWithTabs', tabs);
-        editor.editor.setOption('indentUnit', size);
-      },
-      isEnabled: hasWidget,
-      isToggled: args => {
-        let widget = tracker.currentWidget;
-        if (!widget) {
-          return false;
-        }
-        let tabs = !!args['tabs'];
-        let size = args['size'] as number || 4;
-        let editor = widget.editor as CodeMirrorEditor;
-        if (editor.editor.getOption('indentWithTabs') !== tabs) {
-          return false;
-        }
-        return editor.editor.getOption('indentUnit') === size;
-      }
-    });
-
-    let args: JSONObject = { tabs: true, size: 4, name: 'Indent with Tab' };
-    tabMenu.addItem({ command: CommandIDs.changeTabs, args });
-    palette.addItem({
-      command: CommandIDs.changeTabs, args, category: 'Editor'
-    });
-
-    for (let size of [1, 2, 4, 8]) {
-      let args: JSONObject = {
-        tabs: false, size, name: `Spaces: ${size} `
-      };
-      tabMenu.addItem({ command: CommandIDs.changeTabs, args });
-      palette.addItem({
-        command: CommandIDs.changeTabs, args, category: 'Editor'
-      });
-    }
-
     Mode.getModeInfo().sort((a, b) => {
       return a.name.localeCompare(b.name);
     }).forEach(spec => {
@@ -311,12 +244,28 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
       });
     });
 
+    let args: JSONObject = {
+      insertSpaces: false, size: 4, name: 'Indent with Tab'
+    };
+    let command = 'editor:change-tabs';
+    tabMenu.addItem({ command, args });
+    palette.addItem({ command, args, category: 'Editor' });
+
+    for (let size of [1, 2, 4, 8]) {
+      let args: JSONObject = {
+        insertSpaces: true, size, name: `Spaces: ${size} `
+      };
+      tabMenu.addItem({ command, args });
+      palette.addItem({ command, args, category: 'Editor' });
+    }
+
     menu.addItem({ type: 'submenu', submenu: modeMenu });
     menu.addItem({ type: 'submenu', submenu: tabMenu });
     menu.addItem({ type: 'separator' });
     menu.addItem({ command: 'editor:line-numbers' });
-    menu.addItem({ command: 'editor:word-wrap' });
-    menu.addItem({ command: CommandIDs.matchBrackets });
+    menu.addItem({ command: 'editor:line-wrap' });
+    menu.addItem({ command: 'editor:match-brackets' });
+    menu.addItem({ command: 'editor:autoclosing-brackets' });
     menu.addItem({ type: 'submenu', submenu: keyMapMenu });
     menu.addItem({ type: 'submenu', submenu: themeMenu });
 
@@ -325,27 +274,11 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
 
   mainMenu.addMenu(createMenu(), { rank: 30 });
 
-  commands.addCommand(CommandIDs.matchBrackets, {
-    execute: () => {
-      matchBrackets = !matchBrackets;
-      tracker.forEach(widget => {
-        const editor = widget.editor;
-        if (editor instanceof CodeMirrorEditor) {
-          const cm = editor.editor;
-          cm.setOption('matchBrackets', matchBrackets);
-        }
-      });
-      return settingRegistry.set(id, 'matchBrackets', matchBrackets);
-    },
-    label: 'Match Brackets',
-    isEnabled: hasWidget,
-    isToggled: () => matchBrackets
-  });
-
   [
     'editor:line-numbers',
     'editor:line-wrap',
-    CommandIDs.matchBrackets,
+    'editor:match-brackets',
+    'editor-autoclosing-brackets',
     'editor:create-console',
     'editor:run-code'
   ].forEach(command => palette.addItem({ command, category: 'Editor' }));

+ 211 - 60
packages/codemirror/src/editor.ts

@@ -84,7 +84,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Construct a CodeMirror editor.
    */
-  constructor(options: CodeEditor.IOptions, config: CodeMirror.EditorConfiguration={}) {
+  constructor(options: CodeMirrorEditor.IOptions) {
     let host = this.host = options.host;
     host.classList.add(EDITOR_CLASS);
     host.addEventListener('focus', this, true);
@@ -93,10 +93,10 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     this._uuid = options.uuid || uuid();
     this._selectionStyle = options.selectionStyle || {};
 
-    Private.updateConfig(options, config);
-
     let model = this._model = options.model;
-    let editor = this._editor = CodeMirror(host, config);
+    let editor = this._editor = CodeMirror(host, {});
+    Private.handleConfig(editor, options.config || {});
+
     let doc = editor.getDoc();
 
     // Handle initial values for text, mimetype, and selections.
@@ -196,42 +196,6 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     return this.doc.lineCount();
   }
 
-  /**
-   * Control the rendering of line numbers.
-   */
-  get lineNumbers(): boolean {
-    return this._editor.getOption('lineNumbers');
-  }
-  set lineNumbers(value: boolean) {
-    this._editor.setOption('lineNumbers', value);
-  }
-
-  /**
-   * Set to false for horizontal scrolling. Defaults to true.
-   */
-  get wordWrap(): boolean {
-    return this._editor.getOption('lineWrapping');
-  }
-  set wordWrap(value: boolean) {
-    this._editor.setOption('lineWrapping', value);
-  }
-
-  /**
-   * Should the editor be read only.
-   */
-  get readOnly(): boolean {
-    return this._editor.getOption('readOnly') !== false;
-  }
-  set readOnly(readOnly: boolean) {
-    this._editor.setOption('readOnly', readOnly);
-    if (readOnly) {
-      this.host.classList.add(READ_ONLY_CLASS);
-    } else {
-      this.host.classList.remove(READ_ONLY_CLASS);
-      this.blur();
-    }
-  }
-
   /**
    * Returns a model for this editor.
    */
@@ -275,6 +239,20 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     Signal.clearData(this);
   }
 
+  /**
+   * Get a config option for the editor.
+   */
+  getOption<K extends keyof CodeMirrorEditor.IConfig>(option: K): CodeMirrorEditor.IConfig[K] {
+    return Private.getOption(this.editor, option);
+  }
+
+  /**
+   * Set a config option for the editor.
+   */
+  setOption<K extends keyof CodeMirrorEditor.IConfig>(option: K, value: CodeMirrorEditor.IConfig[K]): void {
+    Private.setOption(this.editor, option, value);
+  }
+
   /**
    * Returns the content for the given line number.
    */
@@ -871,10 +849,139 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
 export
 namespace CodeMirrorEditor {
   /**
-   * The name of the default CodeMirror theme
+   * The options used to initialize a code mirror editor.
    */
   export
-  const DEFAULT_THEME: string = 'jupyter';
+  interface IOptions extends CodeEditor.IOptions {
+    /**
+     * The configuration options for the editor.
+     */
+    config?: Partial<IConfig>;
+  }
+
+  /**
+   * The configuration options for a codemirror editor.
+   */
+  export
+  interface IConfig extends CodeEditor.IConfig {
+    /**
+     * The mode to use.
+     */
+    mode?: string | Mode.ISpec;
+
+    /**
+     * The theme to style the editor with.
+     * You must make sure the CSS file defining the corresponding
+     * .cm-s-[name] styles is loaded.
+     */
+    theme?: string;
+
+    /**
+     * Whether to use the context-sensitive indentation that the mode provides
+     * (or just indent the same as the line before).
+     */
+    smartIndent?: boolean;
+
+    /**
+     * Configures whether the editor should re-indent the current line when a
+     * character is typed that might change its proper indentation
+     * (only works if the mode supports indentation).
+     */
+    electricChars?: boolean;
+
+    /**
+     * Configures the keymap to use. The default is "default", which is the
+     * only keymap defined in codemirror.js itself.
+     * Extra keymaps are found in the CodeMirror keymap directory.
+     */
+    keyMap?: string;
+
+    /**
+     * Can be used to specify extra keybindings for the editor, alongside the
+     * ones defined by keyMap. Should be either null, or a valid keymap value.
+     */
+    extraKeys?: any;
+
+    /**
+     * Can be used to add extra gutters (beyond or instead of the line number
+     * gutter).
+     * Should be an array of CSS class names, each of which defines a width
+     * (and optionally a background),
+     * and which will be used to draw the background of the gutters.
+     * May include the CodeMirror-linenumbers class, in order to explicitly
+     * set the position of the line number gutter
+     * (it will default to be to the right of all other gutters).
+     * These class names are the keys passed to setGutterMarker.
+     */
+    gutters?: ReadonlyArray<string>;
+
+    /**
+     * Determines whether the gutter scrolls along with the content
+     * horizontally (false)
+     * or whether it stays fixed during horizontal scrolling (true,
+     * the default).
+     */
+    fixedGutter?: boolean;
+
+    /**
+     * Whether the cursor should be drawn when a selection is active.
+     */
+    showCursorWhenSelecting?: boolean;
+
+    /**
+     * When fixedGutter is on, and there is a horizontal scrollbar, by default
+     * the gutter will be visible to the left of this scrollbar. If this
+     * option is set to true, it will be covered by an element with class
+     * CodeMirror-gutter-filler.
+     */
+    coverGutterNextToScrollbar?: boolean;
+
+    /**
+     * Explicitly set the line separator for the editor.
+     * By default (value null), the document will be split on CRLFs as well as
+     * lone CRs and LFs, and a single LF will be used as line separator in all
+     * output (such as getValue). When a specific string is given, lines will
+     * only be split on that string, and output will, by default, use that
+     * same separator.
+     */
+    lineSeparator?: string;
+
+    /**
+     * Chooses a scrollbar implementation. The default is "native", showing
+     * native scrollbars. The core library also provides the "null" style,
+     * which completely hides the scrollbars. Addons can implement additional
+     * scrollbar models.
+     */
+    scrollbarStyle?: string;
+
+    /**
+     * When enabled, which is the default, doing copy or cut when there is no
+     * selection will copy or cut the whole lines that have cursors on them.
+     */
+    lineWiseCopyCut?: boolean;
+  }
+
+  /**
+   * The default configuration options for an editor.
+   */
+  export
+  let defaultConfig: IConfig = {
+    ...CodeEditor.defaultConfig,
+    mode: 'null',
+    theme: 'jupyter',
+    smartIndent: false,
+    electricChars: false,
+    keyMap: 'default',
+    extraKeys: null,
+    gutters: Object.freeze([]),
+    fixedGutter: true,
+    showCursorWhenSelecting: false,
+    coverGutterNextToScrollbar: false,
+    dragDrop: true,
+    lineSeparator: null,
+    scrollbarStyle: 'native',
+    lineWiseCopyCut: true,
+  };
 
   /**
    * Add a command to CodeMirror.
@@ -895,27 +1002,18 @@ namespace CodeMirrorEditor {
  */
 namespace Private {
   /**
-   * Handle extra codemirror config from codeeditor options.
+   * Handle the codemirror configuration options.
    */
   export
-  function updateConfig(options: CodeEditor.IOptions, config: CodeMirror.EditorConfiguration): void {
-    if (options.readOnly !== undefined) {
-      config.readOnly = options.readOnly;
-    } else {
-      config.readOnly = false;
-    }
-    if (options.lineNumbers !== undefined) {
-      config.lineNumbers = options.lineNumbers;
-    } else {
-      config.lineNumbers = false;
-    }
-    if (options.wordWrap !== undefined) {
-      config.lineWrapping = options.wordWrap;
-    } else {
-      config.lineWrapping = true;
+  function handleConfig(editor: CodeMirror.Editor, config: Partial<CodeMirrorEditor.IConfig>): void {
+    config = {
+      ...CodeMirrorEditor.defaultConfig,
+      ...config
+    };
+    for (let key of Object.keys(config)) {
+      let option = key as keyof CodeMirrorEditor.IConfig;
+      Private.setOption(editor, option, config[option]);
     }
-    config.theme = (config.theme || CodeMirrorEditor.DEFAULT_THEME);
-    config.indentUnit = config.indentUnit || 4;
   }
 
   /**
@@ -955,6 +1053,59 @@ namespace Private {
   function posEq(a: CodeMirror.Position, b: CodeMirror.Position): boolean {
     return a.line === b.line && a.ch === b.ch;
   };
+
+  /**
+   * Get a config option for the editor.
+   */
+  export
+  function getOption<K extends keyof CodeMirrorEditor.IConfig>(editor: CodeMirror.Editor, option: K): CodeMirrorEditor.IConfig[K] {
+    switch (option) {
+    case 'lineWrap':
+      return editor.getOption('lineWrapping');
+    case 'insertSpaces':
+      return !editor.getOption('indentWithTabs');
+    case 'tabSize':
+      return editor.getOption('indentUnit');
+    case 'autoClosingBrackets':
+      return editor.getOption('autoCloseBrackets');
+    default:
+      return editor.getOption(option);
+    }
+  }
+
+  /**
+   * Set a config option for the editor.
+   */
+  export
+  function setOption<K extends keyof CodeMirrorEditor.IConfig>(editor: CodeMirror.Editor, option: K, value: CodeMirrorEditor.IConfig[K]): void {
+    switch (option) {
+    case 'lineWrap':
+      editor.setOption('lineWrapping', value);
+      break;
+    case 'tabSize':
+      editor.setOption('indentUnit', value);
+      break;
+    case 'insertSpaces':
+      editor.setOption('indentWithTabs', !value);
+      break;
+    case 'autoClosingBrackets':
+      editor.setOption('autoCloseBrackets', value);
+      break;
+    case 'readOnly':
+      let el = editor.getWrapperElement();
+      if (value) {
+        el.classList.add(READ_ONLY_CLASS);
+      } else {
+        el.classList.remove(READ_ONLY_CLASS);
+        editor.getInputField().blur();
+      }
+      editor.setOption(option, value);
+      break;
+    default:
+      editor.setOption(option, value);
+      break;
+    }
+  }
 }
 
 

+ 18 - 36
packages/codemirror/src/factory.ts

@@ -1,9 +1,6 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import * as CodeMirror
-  from 'codemirror';
-
 import {
   CodeEditor, IEditorFactoryService
 } from '@jupyterlab/codeeditor';
@@ -18,12 +15,12 @@ import {
  */
 export
 class CodeMirrorEditorFactory implements IEditorFactoryService {
-
   /**
    * Construct an IEditorFactoryService for CodeMirrorEditors.
    */
-  constructor(codeMirrorOptions?: CodeMirror.EditorConfiguration) {
-    this.inlineCodeMirrorOptions = {
+  constructor(defaults: Partial<CodeMirrorEditor.IConfig> = {}) {
+    this.inlineCodeMirrorConfig = {
+      ...CodeMirrorEditor.defaultConfig,
       extraKeys: {
         'Cmd-Right': 'goLineRight',
         'End': 'goLineRight',
@@ -34,56 +31,41 @@ class CodeMirrorEditorFactory implements IEditorFactoryService {
         'Ctrl-Alt-[': 'indentAuto',
         'Cmd-/': 'toggleComment',
         'Ctrl-/': 'toggleComment',
-      }
+      },
+      ...defaults
     };
-    this.documentCodeMirrorOptions = {
+    this.documentCodeMirrorConfig = {
+      ...CodeMirrorEditor.defaultConfig,
       extraKeys: {
         'Tab': 'indentMore',
         'Shift-Enter': () => { /* no-op */ }
       },
       lineNumbers: true,
-      lineWrapping: true
+      ...defaults
     };
-    if (codeMirrorOptions !== undefined) {
-      // Note: If codeMirrorOptions include `extraKeys`,
-      // existing option will be overwritten.
-      Private.assign(this.inlineCodeMirrorOptions, codeMirrorOptions);
-      Private.assign(this.documentCodeMirrorOptions, codeMirrorOptions);
-    }
   }
 
   /**
    * Create a new editor for inline code.
    */
   newInlineEditor(options: CodeEditor.IOptions): CodeEditor.IEditor {
-    return new CodeMirrorEditor(options, this.inlineCodeMirrorOptions);
+    return new CodeMirrorEditor({
+      ...options,
+      config: { ...this.inlineCodeMirrorConfig, ...options.config || {} }
+    });
   }
 
   /**
    * Create a new editor for a full document.
    */
   newDocumentEditor(options: CodeEditor.IOptions): CodeEditor.IEditor {
-    return new CodeMirrorEditor(options, this.documentCodeMirrorOptions);
+    return new CodeMirrorEditor({
+      ...options,
+      config: { ...this.documentCodeMirrorConfig, ...options.config || {} }
+    });
   }
 
-  protected inlineCodeMirrorOptions: CodeMirror.EditorConfiguration;
-  protected documentCodeMirrorOptions: CodeMirror.EditorConfiguration;
-
-}
-
+  protected inlineCodeMirrorConfig: Partial<CodeMirrorEditor.IConfig>;
+  protected documentCodeMirrorConfig: Partial<CodeMirrorEditor.IConfig>;
 
-namespace Private {
-  // Replace with Object.assign when available.
-  export
-  function assign<T>(target: T, ...configs: any[]): T {
-    for (const source of configs) {
-      if (source) {
-        Object.keys(source).forEach(key => {
-          (target as any)[key] = (source as any)[key];
-        });
-      }
-    }
-
-    return target;
-  }
 }

+ 1 - 1
packages/codemirror/style/index.css

@@ -57,7 +57,7 @@
 }
 
 
-.jp-CodeMirrorEditor.jp-mod-readOnly .CodeMirror-cursor {
+.CodeMirror.jp-mod-readOnly .CodeMirror-cursor {
   display: none;
 }
 

+ 100 - 20
packages/fileeditor-extension/src/index.ts

@@ -14,7 +14,7 @@ import {
 } from '@jupyterlab/coreutils';
 
 import {
-  IEditorServices
+  CodeEditor, IEditorServices
 } from '@jupyterlab/codeeditor';
 
 import {
@@ -53,7 +53,16 @@ namespace CommandIDs {
   const lineNumbers = 'editor:line-numbers';
 
   export
-  const wordWrap = 'editor:word-wrap';
+  const lineWrap = 'editor:line-wrap';
+
+  export
+  const changeTabs = 'editor:change-tabs';
+
+  export
+  const matchBrackets = 'editor:match-brackets';
+
+  export
+  const autoClosingBrackets = 'editor:autoclosing-brackets';
 
   export
   const createConsole = 'editor:create-console';
@@ -98,8 +107,9 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
   const tracker = new InstanceTracker<FileEditor>({ namespace });
   const hasWidget = () => tracker.currentWidget !== null;
 
-  let lineNumbers = true;
-  let wordWrap = true;
+  let {
+    lineNumbers, lineWrap, matchBrackets, autoClosingBrackets
+  } = CodeEditor.defaultConfig;
 
   // Handle state restoration.
   restorer.restore(tracker, {
@@ -113,9 +123,13 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
    */
   function updateSettings(settings: ISettingRegistry.ISettings): void {
     let cached = settings.get('lineNumbers') as boolean | null;
-    lineNumbers = cached === null ? false : !!cached;
-    cached = settings.get('wordWrap') as boolean | null;
-    wordWrap = cached === null ? false : !!cached;
+    lineNumbers = cached === null ? lineNumbers : !!cached;
+    cached = settings.get('matchBrackets') as boolean | null;
+    matchBrackets = cached === null ? matchBrackets : !!cached;
+    cached = settings.get('autoClosingBrackets') as boolean | null;
+    autoClosingBrackets = cached === null ? autoClosingBrackets : !!cached;
+    cached = settings.get('lineWrap') as boolean | null;
+    lineWrap = cached === null ? lineWrap : !!cached;
   }
 
   /**
@@ -123,11 +137,21 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
    */
   function updateTracker(): void {
     tracker.forEach(widget => {
-      widget.editor.lineNumbers = lineNumbers;
-      widget.editor.wordWrap = wordWrap;
+      updateWidget(widget);
     });
   }
 
+  /**
+   * Update the settings of a widget.
+   */
+  function updateWidget(widget: FileEditor): void {
+    let editor = widget.editor;
+    editor.setOption('lineNumbers', lineNumbers);
+    editor.setOption('lineWrap', lineWrap);
+    editor.setOption('matchBrackets', matchBrackets);
+    editor.setOption('autoClosingBrackets', autoClosingBrackets);
+  }
+
   // Fetch the initial state of the settings.
   Promise.all([settingRegistry.load(id), restored]).then(([settings]) => {
     updateSettings(settings);
@@ -144,22 +168,21 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
     // Notify the instance tracker if restore data needs to update.
     widget.context.pathChanged.connect(() => { tracker.save(widget); });
     tracker.add(widget);
-    widget.editor.lineNumbers = lineNumbers;
-    widget.editor.wordWrap = wordWrap;
+    updateWidget(widget);
   });
   registry.addWidgetFactory(factory);
 
   // Handle the settings of new widgets.
   tracker.widgetAdded.connect((sender, widget) => {
-    const editor = widget.editor;
-    editor.lineNumbers = lineNumbers;
-    editor.wordWrap = wordWrap;
+    updateWidget(widget);
   });
 
   commands.addCommand(CommandIDs.lineNumbers, {
     execute: () => {
       lineNumbers = !lineNumbers;
-      tracker.forEach(widget => { widget.editor.lineNumbers = lineNumbers; });
+      tracker.forEach(widget => {
+        widget.editor.setOption('lineNumbers', lineNumbers);
+      });
       return settingRegistry.set(id, 'lineNumbers', lineNumbers);
     },
     isEnabled: hasWidget,
@@ -167,17 +190,74 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
     label: 'Line Numbers'
   });
 
-  commands.addCommand(CommandIDs.wordWrap, {
+  commands.addCommand(CommandIDs.lineWrap, {
     execute: () => {
-      wordWrap = !wordWrap;
-      tracker.forEach(widget => { widget.editor.wordWrap = wordWrap; });
-      return settingRegistry.set(id, 'wordWrap', wordWrap);
+      lineWrap = !lineWrap;
+      tracker.forEach(widget => {
+        widget.editor.setOption('lineWrap', lineWrap);
+      });
+      return settingRegistry.set(id, 'lineWrap', lineWrap);
     },
     isEnabled: hasWidget,
-    isToggled: () => wordWrap,
+    isToggled: () => lineWrap,
     label: 'Word Wrap'
   });
 
+  commands.addCommand(CommandIDs.changeTabs, {
+    label: args => args['name'] as string,
+    execute: args => {
+      let widget = tracker.currentWidget;
+      if (!widget) {
+        return;
+      }
+      let editor = widget.editor;
+      let size = args['size'] as number || 4;
+      let insertSpaces = !!args['insertSpaces'];
+      editor.setOption('insertSpaces', insertSpaces);
+      editor.setOption('tabSize', size);
+    },
+    isEnabled: hasWidget,
+    isToggled: args => {
+      let widget = tracker.currentWidget;
+      if (!widget) {
+        return false;
+      }
+      let insertSpaces = !!args['insertSpaces'];
+      let size = args['size'] as number || 4;
+      let editor = widget.editor;
+      if (editor.getOption('insertSpaces') !== insertSpaces) {
+        return false;
+      }
+      return editor.getOption('tabSize') === size;
+    }
+  });
+
+  commands.addCommand(CommandIDs.matchBrackets, {
+    execute: () => {
+      matchBrackets = !matchBrackets;
+      tracker.forEach(widget => {
+        widget.editor.setOption('matchBrackets', matchBrackets);
+      });
+      return settingRegistry.set(id, 'matchBrackets', matchBrackets);
+    },
+    label: 'Match Brackets',
+    isEnabled: hasWidget,
+    isToggled: () => matchBrackets
+  });
+
+  commands.addCommand(CommandIDs.autoClosingBrackets, {
+    execute: () => {
+      autoClosingBrackets = !autoClosingBrackets;
+      tracker.forEach(widget => {
+        widget.editor.setOption('autoClosingBrackets', autoClosingBrackets);
+      });
+      return settingRegistry.set(id, 'autoClosingBrackets', autoClosingBrackets);
+    },
+    label: 'Auto-Closing Brackets',
+    isEnabled: hasWidget,
+    isToggled: () => autoClosingBrackets
+  });
+
   commands.addCommand(CommandIDs.createConsole, {
     execute: args => {
       const widget = tracker.currentWidget;

+ 0 - 3
packages/fileeditor/src/widget.ts

@@ -207,9 +207,6 @@ class FileEditorFactory extends ABCWidgetFactory<FileEditor, DocumentRegistry.IC
     let func = this._services.factoryService.newDocumentEditor.bind(
       this._services.factoryService);
     let factory: CodeEditor.Factory = options => {
-      options.lineNumbers = true;
-      options.readOnly = false;
-      options.wordWrap = true;
       return func(options);
     };
     return new FileEditor({

+ 4 - 4
packages/notebook/src/actions.ts

@@ -702,10 +702,10 @@ namespace NotebookActions {
       return;
     }
     let state = Private.getState(widget);
-    let lineNumbers = widget.activeCell.editor.lineNumbers;
+    let lineNumbers = widget.activeCell.editor.getOption('lineNumbers');
     each(widget.widgets, child => {
       if (widget.isSelected(child)) {
-        child.editor.lineNumbers = !lineNumbers;
+        child.editor.setOption('lineNumbers', !lineNumbers);
       }
     });
     Private.handleState(widget, state);
@@ -726,9 +726,9 @@ namespace NotebookActions {
       return;
     }
     let state = Private.getState(widget);
-    let lineNumbers = widget.activeCell.editor.lineNumbers;
+    let lineNumbers = widget.activeCell.editor.getOption('lineNumbers');
     each(widget.widgets, child => {
-      child.editor.lineNumbers = !lineNumbers;
+      child.editor.setOption('lineNumbers', !lineNumbers);
     });
     Private.handleState(widget, state);
   }

+ 1 - 1
packages/notebook/src/celltools.ts

@@ -363,7 +363,7 @@ namespace CellTools {
       let editorWidget = new CodeEditorWrapper({ model, factory });
       editorWidget.addClass('jp-InputArea-editor');
       editorWidget.addClass('jp-InputArea-editor');
-      editorWidget.editor.readOnly = true;
+      editorWidget.editor.setOption('readOnly', true);
       layout.addWidget(prompt);
       layout.addWidget(editorWidget);
     }

+ 1 - 1
packages/rendermime/src/widgets.ts

@@ -489,7 +489,7 @@ namespace Private {
       sanitize: false,
       tables: true,
       // breaks: true; We can't use GFM breaks as it causes problems with tables
-      langPrefix: `cm-s-${CodeMirrorEditor.DEFAULT_THEME} language-`,
+      langPrefix: `cm-s-${CodeMirrorEditor.defaultConfig.theme} language-`,
       highlight: (code, lang, callback) => {
         if (!lang) {
             // no language, no highlight

+ 2 - 2
test/src/codeeditor/widget.spec.ts

@@ -82,7 +82,7 @@ describe('CodeEditorWrapper', () => {
   let widget: LogWidget;
   let editorFactory = (options: CodeEditor.IOptions) => {
     options.uuid = 'foo';
-    return new LogEditor(options, {});
+    return new LogEditor(options);
   };
 
   beforeEach(() => {
@@ -112,7 +112,7 @@ describe('CodeEditorWrapper', () => {
   describe('#editor', () => {
 
     it('should be a a code editor', () => {
-      expect(widget.editor.lineNumbers).to.be(false);
+      expect(widget.editor.getOption('lineNumbers')).to.be(false);
     });
 
   });

+ 17 - 21
test/src/codemirror/editor.spec.ts

@@ -47,7 +47,7 @@ describe('CodeMirrorEditor', () => {
     host.style.height = '200px';
     document.body.appendChild(host);
     model = new CodeEditor.Model();
-    editor = new LogFileEditor({ host, model }, {});
+    editor = new LogFileEditor({ host, model });
   });
 
   afterEach(() => {
@@ -143,41 +143,37 @@ describe('CodeMirrorEditor', () => {
 
   });
 
-  describe('#lineNumbers', () => {
+  describe('#getOption()', () => {
 
     it('should get whether line numbers should be shown', () => {
-      expect(editor.lineNumbers).to.be(false);
+      expect(editor.getOption('lineNumbers')).to.be(false);
     });
 
-    it('should set whether line numbers should be shown', () => {
-      editor.lineNumbers = true;
-      expect(editor.lineNumbers).to.be(true);
-    });
-
-  });
-
-  describe('#wordWrap', () => {
-
     it('should get whether horizontally scrolling should be used', () => {
-      expect(editor.wordWrap).to.be(true);
+      expect(editor.getOption('lineWrap')).to.be(true);
     });
 
-    it('should set whether horizontally scrolling should be used', () => {
-      editor.wordWrap = false;
-      expect(editor.wordWrap).to.be(false);
+    it('should get whether the editor is readonly', () => {
+      expect(editor.getOption('readOnly')).to.be(false);
     });
 
   });
 
-  describe('#readOnly', () => {
+  describe('#setOption()', () => {
 
-    it('should get whether the editor is readonly', () => {
-      expect(editor.readOnly).to.be(false);
+    it('should set whether line numbers should be shown', () => {
+      editor.setOption('lineNumbers', true);
+      expect(editor.getOption('lineNumbers')).to.be(true);
+    });
+
+    it('should set whether horizontally scrolling should be used', () => {
+      editor.setOption('lineWrap', false);
+      expect(editor.getOption('lineWrap')).to.be(false);
     });
 
     it('should set whether the editor is readonly', () => {
-      editor.readOnly = true;
-      expect(editor.readOnly).to.be(true);
+      editor.setOption('readOnly', true);
+      expect(editor.getOption('readOnly')).to.be(true);
     });
 
   });

+ 15 - 16
test/src/codemirror/factory.spec.ts

@@ -13,8 +13,8 @@ import {
 
 
 class ExposeCodeMirrorEditorFactory extends CodeMirrorEditorFactory {
-  public inlineCodeMirrorOptions: CodeMirror.EditorConfiguration;
-  public documentCodeMirrorOptions: CodeMirror.EditorConfiguration;
+  public inlineCodeMirrorConfig: CodeMirrorEditor.IConfig;
+  public documentCodeMirrorConfig: CodeMirrorEditor.IConfig;
 }
 
 
@@ -22,13 +22,12 @@ describe('CodeMirrorEditorFactory', () => {
   let host: HTMLElement;
   let model: CodeEditor.IModel;
 
-  const options: CodeMirror.EditorConfiguration = {
+  const options: Partial<CodeMirrorEditor.IConfig> = {
     lineNumbers: false,
-    lineWrapping: true,
+    lineWrap: true,
     extraKeys: {
       'Ctrl-Tab': 'indentAuto',
-    },
-    undoDepth: 5,
+    }
   };
 
   beforeEach(() => {
@@ -52,8 +51,8 @@ describe('CodeMirrorEditorFactory', () => {
 
       let factory = new ExposeCodeMirrorEditorFactory(options);
       expect(factory).to.be.a(CodeMirrorEditorFactory);
-      expect(factory.inlineCodeMirrorOptions).to.eql(options);
-      expect(factory.documentCodeMirrorOptions).to.eql(options);
+      expect(factory.inlineCodeMirrorConfig.extraKeys).to.eql(options.extraKeys);
+      expect(factory.documentCodeMirrorConfig.extraKeys).to.eql(options.extraKeys);
     });
 
   });
@@ -69,11 +68,11 @@ describe('CodeMirrorEditorFactory', () => {
 
     it('should create a new editor with given options', () => {
       let factory = new CodeMirrorEditorFactory(options);
-      let editor = factory.newInlineEditor({host, model});
+      let editor = factory.newInlineEditor({host, model}) as CodeMirrorEditor;
       expect(editor).to.be.a(CodeMirrorEditor);
-      let inner = (editor as CodeMirrorEditor).editor;
-      for (let key of Object.keys(options)) {
-        expect(inner.getOption(key)).to.equal((options as any)[key]);
+      for (let key in Object.keys(options)) {
+        let option = key as keyof CodeMirrorEditor.IConfig;
+        expect(editor.getOption(option)).to.equal(options[option]);
       }
       editor.dispose();
     });
@@ -91,11 +90,11 @@ describe('CodeMirrorEditorFactory', () => {
 
     it('should create a new editor with given options', () => {
       let factory = new CodeMirrorEditorFactory(options);
-      let editor = factory.newDocumentEditor({host, model});
+      let editor = factory.newDocumentEditor({host, model}) as CodeMirrorEditor;
       expect(editor).to.be.a(CodeMirrorEditor);
-      let inner = (editor as CodeMirrorEditor).editor;
-      for (let key of Object.keys(options)) {
-        expect(inner.getOption(key)).to.equal((options as any)[key]);
+      for (let key in Object.keys(options)) {
+        let option = key as keyof CodeMirrorEditor.IConfig;
+        expect(editor.getOption(option)).to.equal(options[option]);
       }
       editor.dispose();
     });

+ 2 - 2
test/src/console/history.spec.ts

@@ -199,7 +199,7 @@ describe('console/history', () => {
         expect(history.methods).to.not.contain('onTextChange');
         let model = new CodeEditor.Model();
         let host = document.createElement('div');
-        let editor = new CodeMirrorEditor({ model, host }, {});
+        let editor = new CodeMirrorEditor({ model, host });
         history.editor = editor;
         model.value.text = 'foo';
         expect(history.methods).to.contain('onTextChange');
@@ -214,7 +214,7 @@ describe('console/history', () => {
         expect(history.methods).to.not.contain('onEdgeRequest');
         let host = document.createElement('div');
         let model = new CodeEditor.Model();
-        let editor = new CodeMirrorEditor({ model, host }, {});
+        let editor = new CodeMirrorEditor({ model, host });
         history.editor = editor;
         history.push('foo');
         editor.model.value.changed.connect(() => {

+ 108 - 119
test/src/notebook/actions.spec.ts

@@ -52,6 +52,7 @@ describe('@jupyterlab/notebook', () => {
     before(() => {
       return createClientSession().then(s => {
         session = s;
+        return session.initialize();
       });
     });
 
@@ -468,66 +469,62 @@ describe('@jupyterlab/notebook', () => {
 
     describe('#run()', () => {
 
-      beforeEach(() => {
-        return session.initialize();
-      });
-
-      it('should run the selected cells', function (done) {
+      it('should run the selected cells', function () {
         let next = widget.widgets[1] as MarkdownCell;
         widget.select(next);
         let cell = widget.activeCell as CodeCell;
         cell.model.outputs.clear();
         next.rendered = false;
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(true);
           expect(cell.model.outputs.length).to.be.above(0);
           expect(next.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
-      it('should be a no-op if there is no model', (done) => {
+      it('should be a no-op if there is no model', () => {
         widget.model = null;
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should activate the last selected cell', (done) => {
+      it('should activate the last selected cell', () => {
         let other = widget.widgets[2];
         widget.select(other);
         other.model.value.text = 'a = 1';
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.activeCell).to.be(other);
-        }).then(done, done);
+        });
       });
 
-      it('should clear the selection', (done) => {
+      it('should clear the selection', () => {
         let next = widget.widgets[1];
         widget.select(next);
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.isSelected(widget.widgets[0])).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should change to command mode', (done) => {
+      it('should change to command mode', () => {
         widget.mode = 'edit';
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.mode).to.be('command');
-        }).then(done, done);
+        });
       });
 
-      it('should handle no session', (done) => {
-        NotebookActions.run(widget, null).then(result => {
+      it('should handle no session', () => {
+        return NotebookActions.run(widget, null).then(result => {
           expect(result).to.be(true);
           let cell = widget.activeCell as CodeCell;
           expect(cell.model.executionCount).to.be(null);
-        }).then(done, done);
+        });
       });
 
-      it('should stop executing code cells on an error', (done) => {
+      it('should stop executing code cells on an error', () => {
         let cell = widget.model.contentFactory.createCodeCell({});
         cell.value.text = ERROR_INPUT;
         widget.model.cells.insert(2, cell);
@@ -535,206 +532,198 @@ describe('@jupyterlab/notebook', () => {
         cell = widget.model.contentFactory.createCodeCell({});
         widget.model.cells.push(cell);
         widget.select(widget.widgets[widget.widgets.length - 1]);
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.executionCount).to.be(null);
-        }).then(done, done);
+        });
       });
 
-      it('should render all markdown cells on an error', (done) => {
+      it('should render all markdown cells on an error', () => {
         let cell = widget.model.contentFactory.createMarkdownCell({});
         widget.model.cells.push(cell);
         let child = widget.widgets[widget.widgets.length - 1] as MarkdownCell;
         child.rendered = false;
         widget.select(child);
         widget.activeCell.model.value.text = ERROR_INPUT;
-        NotebookActions.run(widget, session).then(result => {
+        return NotebookActions.run(widget, session).then(result => {
           expect(result).to.be(false);
           expect(child.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
     });
 
     describe('#runAndAdvance()', () => {
 
-      beforeEach(() => {
-        return session;
-      });
-
-      it('should run the selected cells ', (done) => {
+      it('should run the selected cells ', () => {
         let next = widget.widgets[1] as MarkdownCell;
         widget.select(next);
         let cell = widget.activeCell as CodeCell;
         cell.model.outputs.clear();
         next.rendered = false;
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(true);
           expect(cell.model.outputs.length).to.be.above(0);
           expect(next.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
-      it('should be a no-op if there is no model', (done) => {
+      it('should be a no-op if there is no model', () => {
         widget.model = null;
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should clear the existing selection', (done) => {
+      it('should clear the existing selection', () => {
         let next = widget.widgets[2];
         widget.select(next);
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(false);
           expect(widget.isSelected(widget.widgets[0])).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should change to command mode', (done) => {
+      it('should change to command mode', () => {
         widget.mode = 'edit';
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.mode).to.be('command');
-        }).then(done, done);
+        });
       });
 
-      it('should activate the cell after the last selected cell', (done) => {
+      it('should activate the cell after the last selected cell', () => {
         let next = widget.widgets[3] as MarkdownCell;
         widget.select(next);
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.activeCellIndex).to.be(4);
-        }).then(done, done);
+        });
       });
 
-      it('should create a new code cell in edit mode if necessary', (done) => {
+      it('should create a new code cell in edit mode if necessary', () => {
         let count = widget.widgets.length;
         widget.activeCellIndex = count - 1;
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.widgets.length).to.be(count + 1);
           expect(widget.activeCell).to.be.a(CodeCell);
           expect(widget.mode).to.be('edit');
-        }).then(done, done);
+        });
       });
 
-      it('should allow an undo of the new cell', (done) => {
+      it('should allow an undo of the new cell', () => {
         let count = widget.widgets.length;
         widget.activeCellIndex = count - 1;
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(true);
           NotebookActions.undo(widget);
           expect(widget.widgets.length).to.be(count);
-        }).then(done, done);
+        });
       });
 
-      it('should stop executing code cells on an error', (done) => {
+      it('should stop executing code cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.model.contentFactory.createCodeCell({});
         widget.model.cells.push(cell);
         widget.select(widget.widgets[widget.widgets.length - 1]);
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.executionCount).to.be(null);
-        }).then(done, done);
+        });
       });
 
-      it('should render all markdown cells on an error', (done) => {
+      it('should render all markdown cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.widgets[1] as MarkdownCell;
         cell.rendered = false;
         widget.select(cell);
-        NotebookActions.runAndAdvance(widget, session).then(result => {
+        return NotebookActions.runAndAdvance(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.rendered).to.be(true);
           expect(widget.activeCellIndex).to.be(2);
-        }).then(done, done);
+        });
       });
 
     });
 
     describe('#runAndInsert()', () => {
 
-      beforeEach(() => {
-        return session.initialize();
-      });
-
-      it('should run the selected cells ', (done) => {
+      it('should run the selected cells ', () => {
         let next = widget.widgets[1] as MarkdownCell;
         widget.select(next);
         let cell = widget.activeCell as CodeCell;
         cell.model.outputs.clear();
         next.rendered = false;
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(true);
           expect(cell.model.outputs.length).to.be.above(0);
           expect(next.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
-      it('should be a no-op if there is no model', (done) => {
+      it('should be a no-op if there is no model', () => {
         widget.model = null;
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should clear the existing selection', (done) => {
+      it('should clear the existing selection', () => {
         let next = widget.widgets[1];
         widget.select(next);
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.isSelected(widget.widgets[0])).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should insert a new code cell in edit mode after the last selected cell', (done) => {
+      it('should insert a new code cell in edit mode after the last selected cell', () => {
         let next = widget.widgets[2];
         widget.select(next);
         next.model.value.text = 'a = 1';
         let count = widget.widgets.length;
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.activeCell).to.be.a(CodeCell);
           expect(widget.mode).to.be('edit');
           expect(widget.widgets.length).to.be(count + 1);
-        }).then(done, done);
+        });
       });
 
-      it('should allow an undo of the cell insert', (done) => {
+      it('should allow an undo of the cell insert', () => {
         let next = widget.widgets[2];
         widget.select(next);
         next.model.value.text = 'a = 1';
         let count = widget.widgets.length;
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(true);
           NotebookActions.undo(widget);
           expect(widget.widgets.length).to.be(count);
-        }).then(done, done);
+        });
       });
 
-      it('should stop executing code cells on an error', (done) => {
+      it('should stop executing code cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.model.contentFactory.createCodeCell({});
         widget.model.cells.push(cell);
         widget.select(widget.widgets[widget.widgets.length - 1]);
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.executionCount).to.be(null);
-        }).then(done, done);
+        });
       });
 
-      it('should render all markdown cells on an error', (done) => {
+      it('should render all markdown cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.widgets[1] as MarkdownCell;
         cell.rendered = false;
         widget.select(cell);
-        NotebookActions.runAndInsert(widget, session).then(result => {
+        return NotebookActions.runAndInsert(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.rendered).to.be(true);
           expect(widget.activeCellIndex).to.be(2);
-        }).then(done, done);
+        });
       });
 
     });
@@ -744,76 +733,74 @@ describe('@jupyterlab/notebook', () => {
       beforeEach(() => {
         // Make sure all cells have valid code.
         widget.widgets[2].model.value.text = 'a = 1';
-
-        return session.initialize();
       });
 
-      it('should run all of the cells in the notebok', (done) => {
+      it('should run all of the cells in the notebok', () => {
         let next = widget.widgets[1] as MarkdownCell;
         let cell = widget.activeCell as CodeCell;
         cell.model.outputs.clear();
         next.rendered = false;
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(true);
           expect(cell.model.outputs.length).to.be.above(0);
           expect(next.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
-      it('should be a no-op if there is no model', (done) => {
+      it('should be a no-op if there is no model', () => {
         widget.model = null;
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should change to command mode', (done) => {
+      it('should change to command mode', () => {
         widget.mode = 'edit';
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.mode).to.be('command');
-        }).then(done, done);
+        });
       });
 
-      it('should clear the existing selection', (done) => {
+      it('should clear the existing selection', () => {
         let next = widget.widgets[2];
         widget.select(next);
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(true);
           expect(widget.isSelected(widget.widgets[2])).to.be(false);
-        }).then(done, done);
+        });
       });
 
-      it('should activate the last cell', (done) => {
-        NotebookActions.runAll(widget, session).then(result => {
+      it('should activate the last cell', () => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(widget.activeCellIndex).to.be(widget.widgets.length - 1);
-        }).then(done, done);
+        });
       });
 
-      it('should stop executing code cells on an error', (done) => {
+      it('should stop executing code cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.model.contentFactory.createCodeCell({});
         widget.model.cells.push(cell);
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.executionCount).to.be(null);
           expect(widget.activeCellIndex).to.be(widget.widgets.length - 1);
-        }).then(done, done);
+        });
       });
 
-      it('should render all markdown cells on an error', (done) => {
+      it('should render all markdown cells on an error', () => {
         widget.activeCell.model.value.text = ERROR_INPUT;
         let cell = widget.widgets[1] as MarkdownCell;
         cell.rendered = false;
-        NotebookActions.runAll(widget, session).then(result => {
+        return NotebookActions.runAll(widget, session).then(result => {
           expect(result).to.be(false);
           expect(cell.rendered).to.be(true);
-        }).then(done, done);
+        });
       });
 
     });
 
-    describe('#selectAbove()', () => {
+    describe('#selectAbove(`)', () => {
 
       it('should select the cell above the active cell', () => {
         widget.activeCellIndex = 1;
@@ -1234,19 +1221,19 @@ describe('@jupyterlab/notebook', () => {
     describe('#toggleLineNumbers()', () => {
 
       it('should toggle line numbers on the selected cells', () => {
-        let state = widget.activeCell.editor.lineNumbers;
+        let state = widget.activeCell.editor.getOption('lineNumbers');
         NotebookActions.toggleLineNumbers(widget);
-        expect(widget.activeCell.editor.lineNumbers).to.be(!state);
+        expect(widget.activeCell.editor.getOption('lineNumbers')).to.be(!state);
       });
 
       it('should be based on the state of the active cell', () => {
-        let state = widget.activeCell.editor.lineNumbers;
+        let state = widget.activeCell.editor.getOption('lineNumbers');
         let next = widget.widgets[1];
-        next.editor.lineNumbers = !state;
+        next.editor.setOption('lineNumbers', !state);
         widget.select(next);
         NotebookActions.toggleLineNumbers(widget);
-        expect(widget.widgets[0].editor.lineNumbers).to.be(!state);
-        expect(widget.widgets[1].editor.lineNumbers).to.be(!state);
+        expect(widget.widgets[0].editor.getOption('lineNumbers')).to.be(!state);
+        expect(widget.widgets[1].editor.getOption('lineNumbers')).to.be(!state);
       });
 
       it('should preserve the widget mode', () => {
@@ -1268,21 +1255,23 @@ describe('@jupyterlab/notebook', () => {
     describe('#toggleAllLineNumbers()', () => {
 
       it('should toggle line numbers on all cells', () => {
-        let state = widget.activeCell.editor.lineNumbers;
+        let state = widget.activeCell.editor.getOption('lineNumbers');
         NotebookActions.toggleAllLineNumbers(widget);
         for (let i = 0; i < widget.widgets.length; i++) {
-          expect(widget.widgets[i].editor.lineNumbers).to.be(!state);
+          let lineNumbers = widget.widgets[i].editor.getOption('lineNumbers');
+          expect(lineNumbers).to.be(!state);
         }
       });
 
       it('should be based on the state of the active cell', () => {
-        let state = widget.activeCell.editor.lineNumbers;
+        let state = widget.activeCell.editor.getOption('lineNumbers');
         for (let i = 1; i < widget.widgets.length; i++) {
-          widget.widgets[i].editor.lineNumbers = !state;
+          widget.widgets[i].editor.setOption('lineNumbers', !state);
         }
         NotebookActions.toggleAllLineNumbers(widget);
         for (let i = 0; i < widget.widgets.length; i++) {
-          expect(widget.widgets[i].editor.lineNumbers).to.be(!state);
+          let lineNumbers = widget.widgets[i].editor.getOption('lineNumbers');
+          expect(lineNumbers).to.be(!state);
         }
       });