Sfoglia il codice sorgente

Merge pull request #2134 from blink1073/text-editor-toolbar

Clean up handling of editor settings
Afshin Darian 8 anni fa
parent
commit
d6a26121d0

+ 101 - 37
packages/codemirror-extension/src/index.ts

@@ -10,7 +10,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette, IMainMenu
+  ICommandPalette, IMainMenu, IStateDB
 } from '@jupyterlab/apputils';
 
 import {
@@ -34,7 +34,7 @@ namespace CommandIDs {
   const matchBrackets = 'codemirror:match-brackets';
 
   export
-  const vimMode = 'codemirror:vim-mode';
+  const changeKeyMap = 'codemirror:change-keyMap';
 
   export
   const changeTheme = 'codemirror:change-theme';
@@ -58,7 +58,7 @@ const servicesPlugin: JupyterLabPlugin<IEditorServices> = {
 export
 const commandsPlugin: JupyterLabPlugin<void> = {
   id: 'jupyter.services.codemirror-commands',
-  requires: [IEditorTracker, IMainMenu, ICommandPalette],
+  requires: [IEditorTracker, IMainMenu, ICommandPalette, IStateDB],
   activate: activateEditorCommands,
   autoStart: true
 };
@@ -74,74 +74,142 @@ export default plugins;
 /**
  * Set up the editor widget menu and commands.
  */
-function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMenu: IMainMenu, palette: ICommandPalette): void {
+function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMenu: IMainMenu, palette: ICommandPalette, state: IStateDB): void {
   let { commands } = app;
+  let theme: string = CodeMirrorEditor.DEFAULT_THEME;
+  let keyMap: string = 'default';
+  let matchBrackets = false;
+  let id = 'codemirror:settings';
+
+  // Fetch the initial state of the settings.
+  state.fetch(id).then(settings => {
+    if (!settings) {
+      return;
+    }
+    if (typeof settings['theme'] === 'string') {
+      commands.execute(CommandIDs.changeTheme, settings);
+    }
+    if (typeof settings['keyMap'] === 'string') {
+      commands.execute(CommandIDs.changeKeyMap, settings);
+    }
+    if (typeof settings['matchBrackets'] === 'boolean') {
+      if (settings['matchBrackets'] !== matchBrackets) {
+        commands.execute(CommandIDs.matchBrackets);
+      }
+    }
+  });
 
   /**
-   * Toggle editor matching brackets
+   * Save the codemirror settings state.
    */
-  function toggleMatchBrackets(): void {
-    if (tracker.currentWidget) {
-      let editor = tracker.currentWidget.editor;
-      if (editor instanceof CodeMirrorEditor) {
-        let cm = editor.editor;
-        cm.setOption('matchBrackets', !cm.getOption('matchBrackets'));
-      }
+  function saveState(): Promise<void> {
+    return state.save(id, { theme, keyMap, matchBrackets });
+  }
+
+  /**
+   * Handle the settings of new widgets.
+   */
+  tracker.widgetAdded.connect((sender, widget) => {
+    if (widget.editor instanceof CodeMirrorEditor) {
+      let cm = widget.editor.editor;
+      cm.setOption('keyMap', keyMap);
+      cm.setOption('theme', theme);
+      cm.setOption('matchBrackets', matchBrackets);
     }
+  });
+
+  /**
+   * A test for whether the tracker has an active widget.
+   */
+  function hasWidget(): boolean {
+    return tracker.currentWidget !== null;
   }
 
   /**
-   * Toggle the editor's vim mode
+   * Toggle editor matching brackets
    */
-  function toggleVim(): void {
+  function toggleMatchBrackets(): Promise<void> {
+    matchBrackets = !matchBrackets;
     tracker.forEach(widget => {
-      if (widget.editor instanceof CodeMirrorEditor) {
-        let cm = widget.editor.editor;
-        let keymap = cm.getOption('keyMap') === 'vim' ? 'default'
-        : 'vim';
-        cm.setOption('keyMap', keymap);
+      let editor = widget.editor;
+      if (editor instanceof CodeMirrorEditor) {
+        let cm = editor.editor;
+        cm.setOption('matchBrackets', matchBrackets);
       }
     });
+    return saveState();
   }
 
   /**
    * Create a menu for the editor.
    */
   function createMenu(): Menu {
-    let theme = new Menu({ commands });
     let menu = new Menu({ commands });
+    let themeMenu = new Menu({ commands });
+    let keyMapMenu = new Menu({ commands });
 
     menu.title.label = 'Editor';
-    theme.title.label = 'Theme';
+    themeMenu.title.label = 'Theme';
+    keyMapMenu.title.label = 'Key Map';
 
     commands.addCommand(CommandIDs.changeTheme, {
       label: args => args['theme'] as string,
       execute: args => {
-        let name = args['theme'] as string || CodeMirrorEditor.DEFAULT_THEME;
+        theme = args['theme'] as string || CodeMirrorEditor.DEFAULT_THEME;
         tracker.forEach(widget => {
           if (widget.editor instanceof CodeMirrorEditor) {
             let cm = widget.editor.editor;
-            cm.setOption('theme', name);
+            cm.setOption('theme', theme);
           }
         });
-      }
+        return saveState();
+      },
+      isEnabled: hasWidget,
+      isToggled: args => { return args['theme'] === theme; }
+    });
+
+
+    commands.addCommand(CommandIDs.changeKeyMap, {
+      label: args => {
+        let title = args['keyMap'] as string;
+        return title === 'sublime' ? 'Sublime Text' : title;
+      },
+      execute: args => {
+        keyMap = args['keyMap'] as string || 'default';
+        tracker.forEach(widget => {
+          if (widget.editor instanceof CodeMirrorEditor) {
+            let cm = widget.editor.editor;
+            cm.setOption('keyMap', keyMap);
+          }
+        });
+        return saveState();
+      },
+      isEnabled: hasWidget,
+      isToggled: args => { return args['keyMap'] === keyMap; }
     });
 
     [
      'jupyter', 'default', 'abcdef', 'base16-dark', 'base16-light',
      'hopscotch', 'material', 'mbo', 'mdn-like', 'seti', 'the-matrix',
      'xq-light', 'zenburn'
-    ].forEach(name => theme.addItem({
-      command: 'codemirror:change-theme',
+    ].forEach(name => themeMenu.addItem({
+      command: CommandIDs.changeTheme,
       args: { theme: name }
     }));
 
+    [
+     'default', 'sublime', 'vim', 'emacs'
+    ].forEach(name => keyMapMenu.addItem({
+      command: CommandIDs.changeKeyMap,
+      args: { keyMap: name }
+    }));
+
     menu.addItem({ command: 'editor:line-numbers' });
-    menu.addItem({ command: 'editor:line-wrap' });
+    menu.addItem({ command: 'editor:word-wrap' });
     menu.addItem({ command: CommandIDs.matchBrackets });
-    menu.addItem({ command: CommandIDs.vimMode });
     menu.addItem({ type: 'separator' });
-    menu.addItem({ type: 'submenu', submenu: theme });
+    menu.addItem({ type: 'submenu', submenu: keyMapMenu });
+    menu.addItem({ type: 'submenu', submenu: themeMenu });
 
     return menu;
   }
@@ -149,20 +217,16 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
   mainMenu.addMenu(createMenu(), { rank: 30 });
 
   commands.addCommand(CommandIDs.matchBrackets, {
-    execute: () => { toggleMatchBrackets(); },
-    label: 'Toggle Match Brackets',
-  });
-
-  commands.addCommand(CommandIDs.vimMode, {
-    execute: () => { toggleVim(); },
-    label: 'Toggle Vim Mode'
+    execute: toggleMatchBrackets,
+    label: 'Match Brackets',
+    isEnabled: hasWidget,
+    isToggled: () => { return !matchBrackets; }
   });
 
   [
     'editor:line-numbers',
     'editor:line-wrap',
     CommandIDs.matchBrackets,
-    CommandIDs.vimMode,
     'editor:create-console',
     'editor:run-code'
   ].forEach(command => palette.addItem({ command, category: 'Editor' }));

+ 2 - 0
packages/codemirror/src/editor.ts

@@ -39,6 +39,8 @@ import {
 import 'codemirror/addon/edit/matchbrackets.js';
 import 'codemirror/addon/edit/closebrackets.js';
 import 'codemirror/addon/comment/comment.js';
+import 'codemirror/keymap/emacs.js';
+import 'codemirror/keymap/sublime.js';
 import 'codemirror/keymap/vim.js';
 
 

+ 143 - 6
packages/editorwidget-extension/src/index.ts

@@ -1,12 +1,16 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  JSONObject
+} from '@phosphor/coreutils';
+
 import {
   JupyterLab, JupyterLabPlugin
 } from '@jupyterlab/application';
 
 import {
-  ILayoutRestorer, InstanceTracker
+  ILayoutRestorer, InstanceTracker, IStateDB
 } from '@jupyterlab/apputils';
 
 import {
@@ -18,7 +22,7 @@ import {
 } from '@jupyterlab/docregistry';
 
 import {
-  IEditorTracker, EditorWidget, EditorWidgetFactory, addDefaultCommands
+  IEditorTracker, EditorWidget, EditorWidgetFactory
 } from '@jupyterlab/editorwidget';
 
 import {
@@ -37,13 +41,31 @@ const EDITOR_ICON_CLASS = 'jp-ImageTextEditor';
 const FACTORY = 'Editor';
 
 
+/**
+ * The command IDs used by the editorwidget plugin.
+ */
+namespace CommandIDs {
+  export
+  const lineNumbers = 'editor:line-numbers';
+
+  export
+  const wordWrap = 'editor:word-wrap';
+
+  export
+  const createConsole = 'editor:create-console';
+
+  export
+  const runCode = 'editor:run-code';
+};
+
+
 /**
  * The editor tracker extension.
  */
 const plugin: JupyterLabPlugin<IEditorTracker> = {
   activate,
   id: 'jupyter.services.editor-tracker',
-  requires: [IDocumentRegistry, ILayoutRestorer, IEditorServices],
+  requires: [IDocumentRegistry, ILayoutRestorer, IEditorServices, IStateDB],
   optional: [ILauncher],
   provides: IEditorTracker,
   autoStart: true
@@ -58,7 +80,7 @@ export default plugin;
 /**
  * Activate the editor tracker plugin.
  */
-function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayoutRestorer, editorServices: IEditorServices, launcher: ILauncher | null): IEditorTracker {
+function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayoutRestorer, editorServices: IEditorServices, state: IStateDB, launcher: ILauncher | null): IEditorTracker {
   const factory = new EditorWidgetFactory({
     editorServices,
     factoryOptions: {
@@ -67,12 +89,16 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
       defaultFor: ['*']
     }
   });
-  const shell = app.shell;
+  const { commands, shell } = app;
+  const id = 'editor:settings';
   const tracker = new InstanceTracker<EditorWidget>({
     namespace: 'editor',
     shell
   });
 
+  let lineNumbers = true;
+  let wordWrap = true;
+
   // Handle state restoration.
   restorer.restore(tracker, {
     command: 'file-operations:open',
@@ -80,15 +106,126 @@ function activate(app: JupyterLab, registry: IDocumentRegistry, restorer: ILayou
     name: widget => widget.context.path
   });
 
+  // Fetch the initial state of the settings.
+  state.fetch(id).then(settings => {
+    if (!settings) {
+      return;
+    }
+    if (typeof settings['wordWrap'] === 'string') {
+      commands.execute(CommandIDs.wordWrap, settings);
+    }
+    if (typeof settings['lineNumbers'] === 'string') {
+      commands.execute(CommandIDs.lineNumbers, settings);
+    }
+  });
+
+  /**
+   * Save the editor widget settings state.
+   */
+  function saveState(): Promise<void> {
+    return state.save(id, { lineNumbers, wordWrap });
+  }
+
   factory.widgetCreated.connect((sender, widget) => {
     widget.title.icon = EDITOR_ICON_CLASS;
     // 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;
   });
   registry.addWidgetFactory(factory);
 
-  addDefaultCommands(tracker, app.commands);
+  /**
+   * Handle the settings of new widgets.
+   */
+  tracker.widgetAdded.connect((sender, widget) => {
+    let editor = widget.editor;
+    editor.lineNumbers = lineNumbers;
+    editor.wordWrap = wordWrap;
+  });
+
+  /**
+   * Toggle editor line numbers
+   */
+  function toggleLineNums(args: JSONObject): Promise<void> {
+    lineNumbers = !lineNumbers;
+    tracker.forEach(widget => {
+      widget.editor.lineNumbers = lineNumbers;
+    });
+    return saveState();
+  }
+
+  /**
+   * Toggle editor line wrap
+   */
+  function toggleLineWrap(args: JSONObject): Promise<void> {
+    wordWrap = !wordWrap;
+    tracker.forEach(widget => {
+      widget.editor.wordWrap = wordWrap;
+    });
+    return saveState();
+  }
+
+  /**
+   * A test for whether the tracker has an active widget.
+   */
+  function hasWidget(): boolean {
+    return tracker.currentWidget !== null;
+  }
+
+  commands.addCommand(CommandIDs.lineNumbers, {
+    execute: toggleLineNums,
+    isEnabled: hasWidget,
+    isToggled: () => { return lineNumbers; },
+    label: 'Line Numbers'
+  });
+
+  commands.addCommand(CommandIDs.wordWrap, {
+    execute: toggleLineWrap,
+    isEnabled: hasWidget,
+    isToggled: () => { return wordWrap; },
+    label: 'Word Wrap'
+  });
+
+  commands.addCommand(CommandIDs.createConsole, {
+    execute: args => {
+      let widget = tracker.currentWidget;
+      if (!widget) {
+        return;
+      }
+      let options: JSONObject = {
+        path: widget.context.path,
+        preferredLanguage: widget.context.model.defaultKernelLanguage,
+        activate: args['activate']
+      };
+      return commands.execute('console:create', options);
+    },
+    isEnabled: hasWidget,
+    label: 'Create Console for Editor'
+  });
+
+  commands.addCommand(CommandIDs.runCode, {
+    execute: args => {
+      let widget = tracker.currentWidget;
+      if (!widget) {
+        return;
+      }
+      // Get the selected code from the editor.
+      const editor = widget.editor;
+      const selection = editor.getSelection();
+      const start = editor.getOffsetAt(selection.start);
+      const end = editor.getOffsetAt(selection.end);
+      const options: JSONObject = {
+        path: widget.context.path,
+        code: editor.model.value.text.substring(start, end),
+        activate: args['activate']
+      };
+      return commands.execute('console:inject', options);
+    },
+    isEnabled: hasWidget,
+    label: 'Run Code'
+  });
 
   // Add a launcher item if the launcher is available.
   if (launcher) {

+ 1 - 87
packages/editorwidget/src/index.ts

@@ -2,11 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  CommandRegistry
-} from '@phosphor/commands';
-
-import {
-  JSONObject, Token
+ Token
 } from '@phosphor/coreutils';
 
 import {
@@ -34,85 +30,3 @@ interface IEditorTracker extends IInstanceTracker<EditorWidget> {}
 export
 const IEditorTracker = new Token<IEditorTracker>('jupyter.services.editor-tracker');
 /* tslint:enable */
-
-
-/**
- * Add the default commands for the editor.
- */
-export
-function addDefaultCommands(tracker: IEditorTracker, commands: CommandRegistry) {
-  /**
-   * Toggle editor line numbers
-   */
-  function toggleLineNums(args: JSONObject) {
-    let widget = tracker.currentWidget;
-    if (!widget) {
-      return;
-    }
-    widget.editor.lineNumbers = !widget.editor.lineNumbers;
-    if (args['activate'] !== false) {
-      widget.activate();
-    }
-  }
-
-  /**
-   * Toggle editor line wrap
-   */
-  function toggleLineWrap(args: JSONObject) {
-    let widget = tracker.currentWidget;
-    if (!widget) {
-      return;
-    }
-    widget.editor.wordWrap = !widget.editor.wordWrap;
-    if (args['activate'] !== false) {
-      widget.activate();
-    }
-  }
-
-  commands.addCommand('editor:line-numbers', {
-    execute: args => { toggleLineNums(args); },
-    label: 'Toggle Line Numbers'
-  });
-
-  commands.addCommand('editor:line-wrap', {
-    execute: args => { toggleLineWrap(args); },
-    label: 'Toggle Line Wrap'
-  });
-
-  commands.addCommand('editor:create-console', {
-    execute: args => {
-      let widget = tracker.currentWidget;
-      if (!widget) {
-        return;
-      }
-      let options: JSONObject = {
-        path: widget.context.path,
-        preferredLanguage: widget.context.model.defaultKernelLanguage,
-        activate: args['activate']
-      };
-      return commands.execute('console:create', options);
-    },
-    label: 'Create Console for Editor'
-  });
-
-  commands.addCommand('editor:run-code', {
-    execute: args => {
-      let widget = tracker.currentWidget;
-      if (!widget) {
-        return;
-      }
-      // Get the selected code from the editor.
-      const editor = widget.editor;
-      const selection = editor.getSelection();
-      const start = editor.getOffsetAt(selection.start);
-      const end = editor.getOffsetAt(selection.end);
-      const options: JSONObject = {
-        path: widget.context.path,
-        code: editor.model.value.text.substring(start, end),
-        activate: args['activate']
-      };
-      return commands.execute('console:inject', options);
-    },
-    label: 'Run Code'
-  });
-}