Browse Source

Revamp editor settings (#3441)

* Make file editor settings look more like editor config.

* Add editor configuration to the three types of notebook cells.

* File editor command cleanup.

* Update tests.

* Remove matchBrackets from notebook IEditorViewer, respect IEditorConfig
for toggling line numbers.
Ian Rose 7 years ago
parent
commit
b9c1fb8f51

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

@@ -203,6 +203,14 @@ class Cell extends Widget {
     let footer = this._footer = this.contentFactory.createCellFooter();
     footer.addClass(CELL_FOOTER_CLASS);
     (this.layout as PanelLayout).addWidget(footer);
+
+    // Editor settings
+    if (options.editorConfig) {
+      Object.keys(options.editorConfig)
+      .forEach((key: keyof CodeEditor.IConfig) => {
+        this.editor.setOption(key, options.editorConfig[key]);
+      });
+    }
   }
 
   /**
@@ -398,6 +406,11 @@ namespace Cell {
      * The factory object for customizable cell children.
      */
     contentFactory?: IContentFactory;
+
+    /**
+     * The configuration options for the text editor widget.
+     */
+    editorConfig?: Partial<CodeEditor.IConfig>;
   }
 
   /**
@@ -530,9 +543,6 @@ class CodeCell extends Cell {
     let contentFactory = this.contentFactory;
     let model = this.model;
 
-    // Code cells should not wrap lines.
-    this.editor.setOption('lineWrap', false);
-
     // Insert the output before the cell footer.
     let outputWrapper = this._outputWrapper = new Panel();
     outputWrapper.addClass(CELL_OUTPUT_WRAPPER_CLASS);

+ 0 - 2
packages/codemirror-extension/package.json

@@ -31,13 +31,11 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^0.13.1",
-    "@jupyterlab/apputils": "^0.13.1",
     "@jupyterlab/codeeditor": "^0.13.0",
     "@jupyterlab/codemirror": "^0.13.0",
     "@jupyterlab/coreutils": "^0.13.0",
     "@jupyterlab/fileeditor": "^0.13.0",
     "@jupyterlab/mainmenu": "^0.2.0",
-    "@phosphor/coreutils": "^1.3.0",
     "@phosphor/widgets": "^1.5.0"
   },
   "devDependencies": {

+ 2 - 29
packages/codemirror-extension/src/index.ts

@@ -1,10 +1,6 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import {
-  JSONObject
-} from '@phosphor/coreutils';
-
 import {
   Menu
 } from '@phosphor/widgets';
@@ -13,10 +9,6 @@ import {
   JupyterLab, JupyterLabPlugin
 } from '@jupyterlab/application';
 
-import {
-  ICommandPalette
-} from '@jupyterlab/apputils';
-
 import {
   IMainMenu, IEditMenu
 } from '@jupyterlab/mainmenu';
@@ -77,7 +69,6 @@ const commands: JupyterLabPlugin<void> = {
   requires: [
     IEditorTracker,
     IMainMenu,
-    ICommandPalette,
     IStateDB,
     ISettingRegistry
   ],
@@ -101,7 +92,7 @@ const id = commands.id;
 /**
  * Set up the editor widget menu and commands.
  */
-function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMenu: IMainMenu, palette: ICommandPalette, state: IStateDB, settingRegistry: ISettingRegistry): void {
+function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMenu: IMainMenu, state: IStateDB, settingRegistry: ISettingRegistry): void {
   const { commands, restored } = app;
   let { theme, keyMap } = CodeMirrorEditor.defaultConfig;
 
@@ -164,12 +155,10 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
   const themeMenu = new Menu({ commands });
   const keyMapMenu = new Menu({ commands });
   const modeMenu = new Menu({ commands });
-  const tabMenu = new Menu({ commands });
 
   themeMenu.title.label = 'Text Editor Theme';
   keyMapMenu.title.label = 'Text Editor Key Map';
   modeMenu.title.label = 'Text Editor Syntax Highlighting';
-  tabMenu.title.label = 'Text Editor Indentation';
 
   commands.addCommand(CommandIDs.changeTheme, {
     label: args => args['theme'] as string,
@@ -284,29 +273,13 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
     });
   });
 
-  let args: JSONObject = {
-    insertSpaces: false, size: 4, name: 'Indent with Tab'
-  };
-  let command = 'fileeditor:change-tabs';
-  tabMenu.addItem({ command, args });
-  palette.addItem({ command, args, category: 'Text 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: 'Text Editor' });
-  }
-
   // Add some of the editor settings to the settings menu.
   mainMenu.settingsMenu.addGroup([
     { type: 'submenu' as Menu.ItemType, submenu: keyMapMenu },
     { type: 'submenu' as Menu.ItemType, submenu: themeMenu }
   ], 10);
 
-  // Add indentation settings to the edit menu.
-  mainMenu.editMenu.addGroup([{ type: 'submenu', submenu: tabMenu }], 20);
+  // Add the syntax highlighting submenu to the `View` menu.
   mainMenu.viewMenu.addGroup([{ type: 'submenu', submenu: modeMenu }], 40);
 
   // Add find-replace capabilities to the edit menu.

+ 1 - 3
packages/codemirror/src/editor.ts

@@ -538,10 +538,8 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     Mode.ensure(mime).then(spec => {
       editor.setOption('mode', spec.mime);
     });
-    let isCode = (mime !== 'text/plain') && (mime !== 'text/x-ipythongfm');
-    editor.setOption('matchBrackets', isCode);
-    editor.setOption('autoCloseBrackets', isCode);
     let extraKeys = editor.getOption('extraKeys') || {};
+    const isCode = (mime !== 'text/plain') && (mime !== 'text/x-ipythongfm');
     if (isCode) {
       extraKeys['Backspace'] = 'delSpaceToPrevTabStop';
     } else {

+ 3 - 1
packages/fileeditor-extension/package.json

@@ -37,7 +37,9 @@
     "@jupyterlab/filebrowser": "^0.13.2",
     "@jupyterlab/fileeditor": "^0.13.0",
     "@jupyterlab/launcher": "^0.13.2",
-    "@jupyterlab/mainmenu": "^0.2.0"
+    "@jupyterlab/mainmenu": "^0.2.0",
+    "@phosphor/coreutils": "^1.3.0",
+    "@phosphor/widgets": "^1.5.0"
   },
   "devDependencies": {
     "rimraf": "~2.6.2",

+ 44 - 13
packages/fileeditor-extension/schema/plugin.json

@@ -2,21 +2,52 @@
   "jupyter.lab.setting-icon-class": "jp-TextEditorIcon",
   "jupyter.lab.setting-icon-label": "Editor",
   "title": "Text Editor",
-  "description": "Text editor settings for all editors.",
-  "additionalProperties": false,
+  "description": "Text editor settings.",
+  "definitions": {
+    "editorConfig": {
+      "properties": {
+        "autoClosingBrackets": {
+          "type": "boolean"
+        },
+        "lineNumbers": {
+          "type": "boolean"
+        },
+        "lineWrap": {
+          "type": "boolean"
+        },
+        "matchBrackets": {
+          "type": "boolean"
+        },
+        "readOnly": {
+          "type": "boolean"
+        },
+        "insertSpaces": {
+          "type": "boolean"
+        },
+        "tabSize": {
+          "type": "number"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  },
   "properties": {
-    "autoClosingBrackets": {
-      "type": "boolean", "title": "Autoclosing Brackets", "default": true
-    },
-    "lineNumbers": {
-      "type": "boolean", "title": "Line Numbers", "default": true
-    },
-    "lineWrap": {
-      "type": "boolean", "title": "Line Wrap", "default": false
-    },
-    "matchBrackets": {
-      "type": "boolean", "title": "Match Brackets", "default": true
+    "editorConfig": {
+      "title": "Editor Configuration",
+      "description": "The configuration for all text editors.",
+      "$ref": "#/definitions/editorConfig",
+      "default": {
+        "autoClosingBrackets": true,
+        "lineNumbers": true,
+        "lineWrap": true,
+        "matchBrackets": true,
+        "readOnly": false,
+        "insertSpaces": true,
+        "tabSize": 4
+      }
     }
   },
+  "additionalProperties": false,
   "type": "object"
 }

+ 85 - 64
packages/fileeditor-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  InstanceTracker
+  ICommandPalette, InstanceTracker
 } from '@jupyterlab/apputils';
 
 import {
@@ -33,6 +33,13 @@ import {
   IEditMenu, IFileMenu, IMainMenu, IViewMenu
 } from '@jupyterlab/mainmenu';
 
+import {
+  JSONObject
+} from '@phosphor/coreutils';
+
+import {
+  Menu
+} from '@phosphor/widgets';
 
 /**
  * The class name for the text editor icon from the default theme.
@@ -85,7 +92,7 @@ const plugin: JupyterLabPlugin<IEditorTracker> = {
   activate,
   id: '@jupyterlab/fileeditor-extension:plugin',
   requires: [IEditorServices, IFileBrowserFactory, ILayoutRestorer, ISettingRegistry],
-  optional: [ILauncher, IMainMenu],
+  optional: [ICommandPalette, ILauncher, IMainMenu],
   provides: IEditorTracker,
   autoStart: true
 };
@@ -100,7 +107,7 @@ export default plugin;
 /**
  * Activate the editor tracker plugin.
  */
-function activate(app: JupyterLab, editorServices: IEditorServices, browserFactory: IFileBrowserFactory, restorer: ILayoutRestorer, settingRegistry: ISettingRegistry, launcher: ILauncher | null, menu: IMainMenu | null): IEditorTracker {
+function activate(app: JupyterLab, editorServices: IEditorServices, browserFactory: IFileBrowserFactory, restorer: ILayoutRestorer, settingRegistry: ISettingRegistry, palette: ICommandPalette, launcher: ILauncher | null, menu: IMainMenu | null): IEditorTracker {
   const id = plugin.id;
   const namespace = 'editor';
   const factory = new FileEditorFactory({
@@ -112,9 +119,7 @@ function activate(app: JupyterLab, editorServices: IEditorServices, browserFacto
   const isEnabled = () => tracker.currentWidget !== null &&
                           tracker.currentWidget === app.shell.currentWidget;
 
-  let {
-    lineNumbers, lineWrap, matchBrackets, autoClosingBrackets
-  } = CodeEditor.defaultConfig;
+  let config = { ...CodeEditor.defaultConfig };
 
   // Handle state restoration.
   restorer.restore(tracker, {
@@ -127,14 +132,12 @@ function activate(app: JupyterLab, editorServices: IEditorServices, browserFacto
    * Update the setting values.
    */
   function updateSettings(settings: ISettingRegistry.ISettings): void {
-    let cached = settings.get('lineNumbers').composite as boolean | null;
-    lineNumbers = cached === null ? lineNumbers : !!cached;
-    cached = settings.get('matchBrackets').composite as boolean | null;
-    matchBrackets = cached === null ? matchBrackets : !!cached;
-    cached = settings.get('autoClosingBrackets').composite as boolean | null;
-    autoClosingBrackets = cached === null ? autoClosingBrackets : !!cached;
-    cached = settings.get('lineWrap').composite as boolean | null;
-    lineWrap = cached === null ? lineWrap : !!cached;
+    let cached =
+      settings.get('editorConfig').composite as Partial<CodeEditor.IConfig>;
+    Object.keys(config).forEach((key: keyof CodeEditor.IConfig) => {
+      config[key] = cached[key] === null ?
+        CodeEditor.defaultConfig[key] : cached[key];
+    });
   }
 
   /**
@@ -149,10 +152,9 @@ function activate(app: JupyterLab, editorServices: IEditorServices, browserFacto
    */
   function updateWidget(widget: FileEditor): void {
     const editor = widget.editor;
-    editor.setOption('lineNumbers', lineNumbers);
-    editor.setOption('lineWrap', lineWrap);
-    editor.setOption('matchBrackets', matchBrackets);
-    editor.setOption('autoClosingBrackets', autoClosingBrackets);
+    Object.keys(config).forEach((key: keyof CodeEditor.IConfig) => {
+      editor.setOption(key, config[key]);
+    });
   }
 
   // Fetch the initial state of the settings.
@@ -185,88 +187,72 @@ function activate(app: JupyterLab, editorServices: IEditorServices, browserFacto
 
   commands.addCommand(CommandIDs.lineNumbers, {
     execute: () => {
-      const key = 'lineNumbers';
-      const value = lineNumbers = !lineNumbers;
-
-      updateTracker();
-      return settingRegistry.set(id, key, value).catch((reason: Error) => {
-        console.error(`Failed to set ${id}:${key} - ${reason.message}`);
+      config.lineNumbers = !config.lineNumbers;
+      return settingRegistry.set(id, 'editorConfig', config)
+      .catch((reason: Error) => {
+        console.error(`Failed to set ${id}: ${reason.message}`);
       });
     },
     isEnabled,
-    isToggled: () => lineNumbers,
+    isToggled: () => config.lineNumbers,
     label: 'Line Numbers'
   });
 
   commands.addCommand(CommandIDs.lineWrap, {
     execute: () => {
-      const key = 'lineWrap';
-      const value = lineWrap = !lineWrap;
-
-      updateTracker();
-      return settingRegistry.set(id, key, value).catch((reason: Error) => {
-        console.error(`Failed to set ${id}:${key} - ${reason.message}`);
+      config.lineWrap = !config.lineWrap;
+      return settingRegistry.set(id, 'editorConfig', config)
+      .catch((reason: Error) => {
+        console.error(`Failed to set ${id}: ${reason.message}`);
       });
     },
     isEnabled,
-    isToggled: () => lineWrap,
+    isToggled: () => config.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);
+      config.tabSize = args['size'] as number || 4;
+      config.insertSpaces = !!args['insertSpaces'];
+      return settingRegistry.set(id, 'editorConfig', config)
+      .catch((reason: Error) => {
+        console.error(`Failed to set ${id}: ${reason.message}`);
+      });
     },
     isEnabled,
     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;
+      const insertSpaces = !!args['insertSpaces'];
+      const size = args['size'] as number || 4;
+      return config.insertSpaces === insertSpaces && config.tabSize === size;
     }
   });
 
   commands.addCommand(CommandIDs.matchBrackets, {
     execute: () => {
-      matchBrackets = !matchBrackets;
-      tracker.forEach(widget => {
-        widget.editor.setOption('matchBrackets', matchBrackets);
+      config.matchBrackets = !config.matchBrackets;
+      return settingRegistry.set(id, 'editorConfig', config)
+      .catch((reason: Error) => {
+        console.error(`Failed to set ${id}: ${reason.message}`);
       });
-      return settingRegistry.set(id, 'matchBrackets', matchBrackets);
     },
     label: 'Match Brackets',
     isEnabled,
-    isToggled: () => matchBrackets
+    isToggled: () => config.matchBrackets
   });
 
   commands.addCommand(CommandIDs.autoClosingBrackets, {
     execute: () => {
-      autoClosingBrackets = !autoClosingBrackets;
-      tracker.forEach(widget => {
-        widget.editor.setOption('autoClosingBrackets', autoClosingBrackets);
+      config.autoClosingBrackets = !config.autoClosingBrackets;
+      return settingRegistry.set(id, 'editorConfig', config)
+      .catch((reason: Error) => {
+        console.error(`Failed to set ${id}: ${reason.message}`);
       });
-      return settingRegistry
-        .set(id, 'autoClosingBrackets', autoClosingBrackets);
     },
-    label: 'Auto Close Brackets',
+    label: 'Auto Close Brackets for Text Editor',
     isEnabled,
-    isToggled: () => autoClosingBrackets
+    isToggled: () => config.autoClosingBrackets
   });
 
   commands.addCommand(CommandIDs.createConsole, {
@@ -396,7 +382,42 @@ function activate(app: JupyterLab, editorServices: IEditorServices, browserFacto
     });
   }
 
+  if (palette) {
+    let args: JSONObject = {
+      insertSpaces: false, size: 4, name: 'Indent with Tab'
+    };
+    let command = 'fileeditor:change-tabs';
+    palette.addItem({ command, args, category: 'Text Editor' });
+
+    for (let size of [1, 2, 4, 8]) {
+      let args: JSONObject = {
+        insertSpaces: true, size, name: `Spaces: ${size} `
+      };
+      palette.addItem({ command, args, category: 'Text Editor' });
+    }
+  }
+
   if (menu) {
+    // Add the editing commands to the settings menu.
+    const tabMenu = new Menu({ commands });
+    tabMenu.title.label = 'Text Editor Indentation';
+    let args: JSONObject = {
+      insertSpaces: false, size: 4, name: 'Indent with Tab'
+    };
+    let command = 'fileeditor:change-tabs';
+    tabMenu.addItem({ command, args });
+
+    for (let size of [1, 2, 4, 8]) {
+      let args: JSONObject = {
+        insertSpaces: true, size, name: `Spaces: ${size} `
+      };
+      tabMenu.addItem({ command, args });
+    }
+    menu.settingsMenu.addGroup([
+      { type: 'submenu', submenu: tabMenu },
+      { command: CommandIDs.autoClosingBrackets }
+    ], 30);
+
     // Add new text file creation to the file menu.
     menu.fileMenu.newMenu.addGroup([{ command: CommandIDs.createNew }], 30);
 

+ 4 - 2
packages/notebook-extension/package.json

@@ -11,7 +11,8 @@
   "files": [
     "lib/*.d.ts",
     "lib/*.js.map",
-    "lib/*.js"
+    "lib/*.js",
+    "schema/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -49,6 +50,7 @@
     "typescript": "~2.6.2"
   },
   "jupyterlab": {
-    "extension": true
+    "extension": true,
+    "schemaDir": "schema"
   }
 }

+ 81 - 0
packages/notebook-extension/schema/tracker.json

@@ -0,0 +1,81 @@
+{
+  "jupyter.lab.setting-icon-class": "jp-NotebookIcon",
+  "jupyter.lab.setting-icon-label": "Notebook",
+  "title": "Notebook",
+  "description": "Notebook settings.",
+  "definitions": {
+    "editorConfig": {
+      "properties": {
+        "autoClosingBrackets": {
+          "type": "boolean"
+        },
+        "lineNumbers": {
+          "type": "boolean"
+        },
+        "lineWrap": {
+          "type": "boolean"
+        },
+        "matchBrackets": {
+          "type": "boolean"
+        },
+        "readOnly": {
+          "type": "boolean"
+        },
+        "insertSpaces": {
+          "type": "boolean"
+        },
+        "tabSize": {
+          "type": "number"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  },
+  "properties": {
+    "codeCellConfig": {
+      "title": "Code Cell Configuration",
+      "description": "The configuration for all code cells.",
+      "$ref": "#/definitions/editorConfig",
+      "default": {
+        "autoClosingBrackets": true,
+        "lineNumbers": false,
+        "lineWrap": false,
+        "matchBrackets": true,
+        "readOnly": false,
+        "insertSpaces": true,
+        "tabSize": 4
+      }
+    },
+    "markdownCellConfig": {
+      "title": "Markdown Cell Configuration",
+      "description": "The configuration for all markdown cells.",
+      "$ref": "#/definitions/editorConfig",
+      "default": {
+        "autoClosingBrackets": false,
+        "lineNumbers": false,
+        "lineWrap": true,
+        "matchBrackets": false,
+        "readOnly": false,
+        "insertSpaces": true,
+        "tabSize": 4
+      }
+    },
+    "rawCellConfig": {
+      "title": "Raw Cell Configuration",
+      "description": "The configuration for all raw cells.",
+      "$ref": "#/definitions/editorConfig",
+      "default": {
+        "autoClosingBrackets": false,
+        "lineNumbers": false,
+        "lineWrap": true,
+        "matchBrackets": false,
+        "readOnly": false,
+        "insertSpaces": true,
+        "tabSize": 4
+      }
+    }
+  },
+  "additionalProperties": false,
+  "type": "object"
+}

+ 63 - 15
packages/notebook-extension/src/index.ts

@@ -14,11 +14,11 @@ import {
 } from '@jupyterlab/cells';
 
 import {
-  IEditorServices
+  CodeEditor, IEditorServices
 } from '@jupyterlab/codeeditor';
 
 import {
-  IStateDB, PageConfig, URLExt, uuid
+  ISettingRegistry, IStateDB, PageConfig, URLExt, uuid
 } from '@jupyterlab/coreutils';
 
 import  {
@@ -35,7 +35,8 @@ import {
 
 import {
   CellTools, ICellTools, INotebookTracker, NotebookActions,
-  NotebookModelFactory,  NotebookPanel, NotebookTracker, NotebookWidgetFactory
+  NotebookModelFactory,  NotebookPanel, NotebookTracker, NotebookWidgetFactory,
+  StaticNotebook
 } from '@jupyterlab/notebook';
 
 import {
@@ -274,7 +275,7 @@ const EXPORT_TO_FORMATS = [
 /**
  * The notebook widget tracker provider.
  */
-const tracker: JupyterLabPlugin<INotebookTracker> = {
+const trackerPlugin: JupyterLabPlugin<INotebookTracker> = {
   id: '@jupyterlab/notebook-extension:tracker',
   provides: INotebookTracker,
   requires: [
@@ -283,7 +284,8 @@ const tracker: JupyterLabPlugin<INotebookTracker> = {
     NotebookPanel.IContentFactory,
     IEditorServices,
     ILayoutRestorer,
-    IRenderMimeRegistry
+    IRenderMimeRegistry,
+    ISettingRegistry
   ],
   optional: [IFileBrowserFactory, ILauncher],
   activate: activateNotebookHandler,
@@ -320,7 +322,7 @@ const tools: JupyterLabPlugin<ICellTools> = {
 /**
  * Export the plugins as default.
  */
-const plugins: JupyterLabPlugin<any>[] = [factory, tracker, tools];
+const plugins: JupyterLabPlugin<any>[] = [factory, trackerPlugin, tools];
 export default plugins;
 
 
@@ -394,8 +396,10 @@ function activateCellTools(app: JupyterLab, tracker: INotebookTracker, editorSer
 /**
  * Activate the notebook handler extension.
  */
-function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, contentFactory: NotebookPanel.IContentFactory, editorServices: IEditorServices, restorer: ILayoutRestorer, rendermime: IRenderMimeRegistry, browserFactory: IFileBrowserFactory | null, launcher: ILauncher | null): INotebookTracker {
+function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, contentFactory: NotebookPanel.IContentFactory, editorServices: IEditorServices, restorer: ILayoutRestorer, rendermime: IRenderMimeRegistry, settingRegistry: ISettingRegistry, browserFactory: IFileBrowserFactory | null, launcher: ILauncher | null): INotebookTracker {
   const services = app.serviceManager;
+  // An object for tracking the current notebook settings.
+  let editorConfig = StaticNotebook.defaultEditorConfig;
   const factory = new NotebookWidgetFactory({
     name: FACTORY,
     fileTypes: ['notebook'],
@@ -405,9 +409,10 @@ function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette:
     canStartKernel: true,
     rendermime: rendermime,
     contentFactory,
+    editorConfig,
     mimeTypeService: editorServices.mimeTypeService
   });
-  const { commands } = app;
+  const { commands, restored } = app;
   const tracker = new NotebookTracker({ namespace: 'notebook' });
 
   // Handle state restoration.
@@ -437,6 +442,51 @@ function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette:
     tracker.add(widget);
   });
 
+  /**
+   * Update the setting values.
+   */
+  function updateConfig(settings: ISettingRegistry.ISettings): void {
+    let cached =
+      settings.get('codeCellConfig').composite as Partial<CodeEditor.IConfig>;
+    let code = { ...StaticNotebook.defaultEditorConfig.code };
+    Object.keys(code).forEach((key: keyof CodeEditor.IConfig) => {
+      code[key] = cached[key] === null ? code[key] : cached[key];
+    });
+    cached =
+      settings.get('markdownCellConfig').composite as Partial<CodeEditor.IConfig>;
+    let markdown = { ...StaticNotebook.defaultEditorConfig.markdown };
+    Object.keys(markdown).forEach((key: keyof CodeEditor.IConfig) => {
+      markdown[key] = cached[key] === null ? markdown[key] : cached[key];
+    });
+    cached =
+      settings.get('rawCellConfig').composite as Partial<CodeEditor.IConfig>;
+    let raw = { ...StaticNotebook.defaultEditorConfig.raw };
+    Object.keys(raw).forEach((key: keyof CodeEditor.IConfig) => {
+      raw[key] = cached[key] === null ? raw[key] : cached[key];
+    });
+    factory.editorConfig = editorConfig = { code, markdown, raw };
+  }
+
+  /**
+   * Update the settings of the current tracker instances.
+   */
+  function updateTracker(): void {
+    tracker.forEach(widget => { widget.notebook.editorConfig = editorConfig; });
+  }
+
+  // Fetch the initial state of the settings.
+  Promise.all([settingRegistry.load(trackerPlugin.id), restored]).then(([settings]) => {
+    updateConfig(settings);
+    updateTracker();
+    settings.changed.connect(() => {
+      updateConfig(settings);
+      updateTracker();
+    });
+  }).catch((reason: Error) => {
+    console.error(reason.message);
+    updateTracker();
+  });
+
   // Add main menu notebook menu.
   populateMenus(app, mainMenu, tracker);
 
@@ -1541,13 +1591,11 @@ function populateMenus(app: JupyterLab, mainMenu: IMainMenu, tracker: INotebookT
     toggleLineNumbers: widget => {
       NotebookActions.toggleAllLineNumbers(widget.notebook);
     },
-    toggleMatchBrackets: widget => {
-      NotebookActions.toggleAllMatchBrackets(widget.notebook);
-    },
-    lineNumbersToggled: widget =>
-      widget.notebook.activeCell.editor.getOption('lineNumbers'),
-    matchBracketsToggled: widget =>
-      widget.notebook.activeCell.editor.getOption('matchBrackets'),
+    lineNumbersToggled: widget => {
+      const config = widget.notebook.editorConfig;
+      return !!(config.code.lineNumbers && config.markdown.lineNumbers &&
+        config.raw.lineNumbers);
+    }
   } as IViewMenu.IEditorViewer<NotebookPanel>);
 
   // Add an ICodeRunner to the application run menu

+ 9 - 26
packages/notebook/src/actions.ts

@@ -827,28 +827,6 @@ namespace NotebookActions {
     Private.handleState(widget, state);
   }
 
-  /**
-   * Toggle match-brackets of all cells.
-   *
-   * @param widget - The target notebook widget.
-   *
-   * #### Notes
-   * The original state is based on the state of the active cell.
-   * The `mode` of the widget will be preserved.
-   */
-  export
-  function toggleAllMatchBrackets(widget: Notebook): void {
-    if (!widget.model || !widget.activeCell) {
-      return;
-    }
-    let state = Private.getState(widget);
-    let matchBrackets = widget.activeCell.editor.getOption('matchBrackets');
-    each(widget.widgets, child => {
-      child.editor.setOption('matchBrackets', !matchBrackets);
-    });
-    Private.handleState(widget, state);
-  }
-
   /**
    * Toggle the line number of all cells.
    *
@@ -864,10 +842,15 @@ namespace NotebookActions {
       return;
     }
     let state = Private.getState(widget);
-    let lineNumbers = widget.activeCell.editor.getOption('lineNumbers');
-    each(widget.widgets, child => {
-      child.editor.setOption('lineNumbers', !lineNumbers);
-    });
+    const config = widget.editorConfig;
+    const lineNumbers = !(config.code.lineNumbers &&
+      config.markdown.lineNumbers && config.raw.lineNumbers);
+    const newConfig = {
+      code: { ...config.code, lineNumbers },
+      markdown: { ...config.markdown, lineNumbers },
+      raw: { ...config.raw, lineNumbers }
+    };
+    widget.editorConfig = newConfig;
     Private.handleState(widget, state);
   }
 

+ 9 - 3
packages/notebook/src/panel.ts

@@ -51,7 +51,7 @@ import {
 } from './model';
 
 import {
-  Notebook
+  Notebook, StaticNotebook
 } from './widget';
 
 
@@ -97,11 +97,12 @@ class NotebookPanel extends Widget implements DocumentRegistry.IReadyWidget {
     toolbar.addClass(NOTEBOOK_PANEL_TOOLBAR_CLASS);
 
     // Notebook
-    let nbOptions = {
+    let nbOptions: Notebook.IOptions = {
       rendermime: this.rendermime,
       languagePreference: options.languagePreference,
       contentFactory: contentFactory,
-      mimeTypeService: options.mimeTypeService
+      mimeTypeService: options.mimeTypeService,
+      editorConfig: options.editorConfig,
     };
     let notebook = this.notebook = contentFactory.createNotebook(nbOptions);
     notebook.addClass(NOTEBOOK_PANEL_NOTEBOOK_CLASS);
@@ -417,6 +418,11 @@ export namespace NotebookPanel {
      * The mimeType service.
      */
     mimeTypeService: IEditorMimeTypeService;
+
+    /**
+     * The notebook cell editor configuration.
+     */
+    editorConfig?: StaticNotebook.IEditorConfig;
   }
 
   /**

+ 92 - 3
packages/notebook/src/widget.ts

@@ -196,6 +196,8 @@ class StaticNotebook extends Widget {
     this.contentFactory = (
       options.contentFactory || StaticNotebook.defaultContentFactory
     );
+    this.editorConfig = options.editorConfig ||
+                        StaticNotebook.defaultEditorConfig;
     this._mimetypeService = options.mimeTypeService;
   }
 
@@ -273,6 +275,17 @@ class StaticNotebook extends Widget {
     return (this.layout as PanelLayout).widgets as ReadonlyArray<Cell>;
   }
 
+  /**
+   * A configuration object for cell editor settings.
+   */
+  get editorConfig(): StaticNotebook.IEditorConfig {
+    return this._editorConfig;
+  }
+  set editorConfig(value: StaticNotebook.IEditorConfig) {
+    this._editorConfig = value;
+    this._updateEditorConfig();
+  }
+
   /**
    * Dispose of the resources held by the widget.
    */
@@ -444,7 +457,8 @@ class StaticNotebook extends Widget {
   private _createCodeCell(model: ICodeCellModel): CodeCell {
     let rendermime = this.rendermime;
     let contentFactory = this.contentFactory;
-    let options = { model, rendermime, contentFactory };
+    const editorConfig = this.editorConfig.code;
+    let options = { editorConfig, model, rendermime, contentFactory };
     return this.contentFactory.createCodeCell(options, this);
   }
 
@@ -454,7 +468,8 @@ class StaticNotebook extends Widget {
   private _createMarkdownCell(model: IMarkdownCellModel): MarkdownCell {
     let rendermime = this.rendermime;
     let contentFactory = this.contentFactory;
-    let options = { model, rendermime, contentFactory };
+    const editorConfig = this.editorConfig.markdown;
+    let options = { editorConfig, model, rendermime, contentFactory };
     return this.contentFactory.createMarkdownCell(options, this);
   }
 
@@ -463,7 +478,8 @@ class StaticNotebook extends Widget {
    */
   private _createRawCell(model: IRawCellModel): RawCell {
     let contentFactory = this.contentFactory;
-    let options = { model, contentFactory };
+    const editorConfig = this.editorConfig.raw;
+    let options = { editorConfig, model, contentFactory };
     return this.contentFactory.createRawCell(options, this);
   }
 
@@ -519,7 +535,31 @@ class StaticNotebook extends Widget {
     }
   }
 
+  /**
+   * Update editor settings for notebook cells.
+   */
+  private _updateEditorConfig() {
+    for (let i = 0; i < this.widgets.length; i++) {
+      const cell = this.widgets[i];
+      let config: Partial<CodeEditor.IConfig>;
+      switch (cell.model.type) {
+        case 'code':
+          config = this._editorConfig.code;
+          break;
+        case 'markdown':
+          config = this._editorConfig.markdown;
+          break;
+        default:
+          config = this._editorConfig.raw;
+          break;
+      }
+      Object.keys(config).forEach((key: keyof CodeEditor.IConfig) => {
+        cell.editor.setOption(key, config[key]);
+      });
+    }
+  }
 
+  private _editorConfig = StaticNotebook.defaultEditorConfig;
   private _mimetype = 'text/plain';
   private _model: INotebookModel = null;
   private _mimetypeService: IEditorMimeTypeService;
@@ -553,6 +593,11 @@ namespace StaticNotebook {
      */
     contentFactory?: IContentFactory;
 
+    /**
+     * A configuration object for the cell editor settings.
+     */
+    editorConfig?: IEditorConfig;
+
     /**
      * The service used to look up mime types.
      */
@@ -586,6 +631,50 @@ namespace StaticNotebook {
     createRawCell(options: RawCell.IOptions, parent: StaticNotebook): RawCell;
   }
 
+  /**
+   * A config object for the cell editors.
+   */
+  export
+  interface IEditorConfig {
+    /**
+     * Config options for code cells.
+     */
+    readonly code: Partial<CodeEditor.IConfig>;
+    /**
+     * Config options for markdown cells.
+     */
+    readonly markdown: Partial<CodeEditor.IConfig>;
+    /**
+     * Config options for raw cells.
+     */
+    readonly raw: Partial<CodeEditor.IConfig>;
+  }
+
+  /**
+   * Default configuration options for cell editors.
+   */
+  export
+  const defaultEditorConfig: IEditorConfig = {
+    code: {
+      ...CodeEditor.defaultConfig,
+      lineWrap: false,
+      matchBrackets: true,
+      autoClosingBrackets: true
+    },
+    markdown: {
+      ...CodeEditor.defaultConfig,
+      lineWrap: true,
+      matchBrackets: false,
+      autoClosingBrackets: false
+    },
+    raw: {
+      ...CodeEditor.defaultConfig,
+      lineWrap: true,
+      matchBrackets: false,
+      autoClosingBrackets: false
+    }
+  };
+
   /**
    * The default implementation of an `IContentFactory`.
    */

+ 25 - 1
packages/notebook/src/widgetfactory.ts

@@ -25,6 +25,10 @@ import {
   NotebookPanel
 } from './panel';
 
+import {
+  StaticNotebook
+} from './widget';
+
 
 /**
  * A widget factory for notebook panels.
@@ -41,6 +45,8 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
     this.rendermime = options.rendermime;
     this.contentFactory = options.contentFactory;
     this.mimeTypeService = options.mimeTypeService;
+    this._editorConfig = options.editorConfig
+                         || StaticNotebook.defaultEditorConfig;
   }
 
   /*
@@ -58,6 +64,16 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
    */
   readonly mimeTypeService: IEditorMimeTypeService;
 
+  /**
+   * A configuration object for cell editor settings.
+   */
+  get editorConfig(): StaticNotebook.IEditorConfig {
+    return this._editorConfig;
+  }
+  set editorConfig(value: StaticNotebook.IEditorConfig) {
+    this._editorConfig = value;
+  }
+
   /**
    * Create a new widget.
    *
@@ -70,12 +86,15 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
     let panel = new NotebookPanel({
       rendermime,
       contentFactory: this.contentFactory,
-      mimeTypeService: this.mimeTypeService
+      mimeTypeService: this.mimeTypeService,
+      editorConfig: this._editorConfig
     });
     panel.context = context;
     ToolbarItems.populateDefaults(panel);
     return panel;
   }
+
+  private _editorConfig: StaticNotebook.IEditorConfig;
 }
 
 
@@ -103,5 +122,10 @@ namespace NotebookWidgetFactory {
      * The service used to look up mime types.
      */
     mimeTypeService: IEditorMimeTypeService;
+
+    /**
+     * The notebook cell editor configuration.
+     */
+    editorConfig?: StaticNotebook.IEditorConfig;
   }
 }

+ 13 - 1
test/src/cells/widget.spec.ts

@@ -16,7 +16,7 @@ import {
 } from '@jupyterlab/apputils';
 
 import {
-  CodeEditorWrapper
+  CodeEditor, CodeEditorWrapper
 } from '@jupyterlab/codeeditor';
 
 import {
@@ -121,6 +121,18 @@ describe('cells/widget', () => {
         expect(widget).to.be.a(Cell);
       });
 
+      it('shoule accept a custom editorConfig', () => {
+        let editorConfig: Partial<CodeEditor.IConfig> = {
+          insertSpaces: false,
+          matchBrackets: false
+        };
+        let widget = new Cell({ editorConfig, model, contentFactory });
+        expect(widget.editor.getOption('insertSpaces')).to.be(false);
+        expect(widget.editor.getOption('matchBrackets')).to.be(false);
+        expect(widget.editor.getOption('lineNumbers'))
+        .to.be(CodeEditor.defaultConfig.lineNumbers);
+      });
+
     });
 
     describe('#model', () => {

+ 3 - 1
test/src/notebook/utils.ts

@@ -18,7 +18,7 @@ import {
 } from '@jupyterlab/coreutils';
 
 import {
-  NotebookPanel, Notebook, NotebookModel
+  NotebookPanel, Notebook, NotebookModel, StaticNotebook
 } from '@jupyterlab/notebook';
 
 import {
@@ -36,6 +36,8 @@ export
 // tslint:disable-next-line
 const DEFAULT_CONTENT: nbformat.INotebookContent = require('../../../examples/notebook/test.ipynb') as nbformat.INotebookContent;
 
+export
+const defaultEditorConfig = { ...StaticNotebook.defaultEditorConfig };
 
 export
 const editorFactory = editorServices.factoryService.newInlineEditor.bind(

+ 32 - 2
test/src/notebook/widget.spec.ts

@@ -30,13 +30,13 @@ import {
 
 import {
   DEFAULT_CONTENT, createNotebookFactory, rendermime, mimeTypeService,
-  editorFactory
+  editorFactory, defaultEditorConfig
 } from './utils';
 
 
 const contentFactory = createNotebookFactory();
 const options: Notebook.IOptions = {
-  rendermime, contentFactory, mimeTypeService
+  rendermime, contentFactory, mimeTypeService, editorConfig: defaultEditorConfig
 };
 
 
@@ -161,6 +161,10 @@ describe('notebook/widget', () => {
         expect(widget.contentFactory).to.be(contentFactory);
       });
 
+      it('should accept an optional editor config', () => {
+        let widget = new StaticNotebook(options);
+        expect(widget.editorConfig).to.be(defaultEditorConfig);
+      });
     });
 
     describe('#modelChanged', () => {
@@ -329,6 +333,32 @@ describe('notebook/widget', () => {
 
     });
 
+    describe('#editorConfig', () => {
+
+      it('should be the cell widget contentFactory used by the widget', () => {
+        let widget = new StaticNotebook(options);
+        expect(widget.editorConfig).to.be(options.editorConfig);
+      });
+
+      it('should be settable', () => {
+        let widget = createWidget();
+        expect(widget.widgets[0].editor.getOption('autoClosingBrackets'))
+        .to.be(true);
+        let newConfig = {
+          raw: defaultEditorConfig.raw,
+          markdown: defaultEditorConfig.markdown,
+          code: {
+            ...defaultEditorConfig.code,
+            autoClosingBrackets: false
+          }
+        };
+        widget.editorConfig = newConfig;
+        expect(widget.widgets[0].editor.getOption('autoClosingBrackets'))
+        .to.be(false);
+      });
+
+    });
+
     describe('#codeMimetype', () => {
 
       it('should get the mime type for code cells', () => {

+ 25 - 2
test/src/notebook/widgetfactory.spec.ts

@@ -28,7 +28,7 @@ import {
 } from '../utils';
 
 import {
-  createNotebookPanelFactory, rendermime, mimeTypeService
+  createNotebookPanelFactory, defaultEditorConfig, rendermime, mimeTypeService
 } from './utils';
 
 
@@ -41,7 +41,8 @@ function createFactory(): NotebookWidgetFactory {
     fileTypes: ['notebook'],
     rendermime,
     contentFactory,
-    mimeTypeService
+    mimeTypeService,
+    editorConfig: defaultEditorConfig
   });
 }
 
@@ -101,6 +102,22 @@ describe('notebook/notebook/widgetfactory', () => {
 
     });
 
+    describe('#editorConfig', () => {
+
+      it('should be the editor config passed into the constructor', () => {
+        let factory = createFactory();
+        expect(factory.editorConfig).to.be(defaultEditorConfig);
+      });
+
+      it('should be settable', () => {
+        let factory = createFactory();
+        let newConfig = { ...defaultEditorConfig };
+        factory.editorConfig = newConfig;
+        expect(factory.editorConfig).to.be(newConfig);
+      });
+
+    });
+
     describe('#createNew()', () => {
 
       it('should create a new `NotebookPanel` widget', () => {
@@ -115,6 +132,12 @@ describe('notebook/notebook/widgetfactory', () => {
         expect(panel.rendermime).to.not.be(rendermime);
       });
 
+      it('should pass the editor config to the notebook', () => {
+        let factory = createFactory();
+        let panel = factory.createNew(context);
+        expect(panel.notebook.editorConfig).to.be(defaultEditorConfig);
+      });
+
       it('should populate the default toolbar items', () => {
         let factory = createFactory();
         let panel = factory.createNew(context);