Forráskód Böngészése

add Mode.addSpecLoader

Nicholas Bollweg 6 éve
szülő
commit
39b8144755

+ 65 - 12
packages/codemirror/src/mode.ts

@@ -1,6 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import { ArrayExt } from '@phosphor/algorithm';
 import { JSONValue } from '@phosphor/coreutils';
 
 import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
@@ -50,6 +51,37 @@ export namespace Mode {
     [key: string]: JSONValue;
   }
 
+  /**
+   * The interface for a codemirror spec resolver.
+   */
+  export interface ISpecLoader {
+    /**
+     * A function which returns whether it was successfully loaded
+     */
+    (spec: ISpec): Promise<boolean>;
+  }
+
+  let specLoaders: Private.IRankItem[] = [
+    {
+      // Simplest, cheapest check by mode name.
+      loader: async spec => CodeMirror.modes.hasOwnProperty(spec.mode),
+      rank: 0
+    },
+    {
+      // Fetch the mode asynchronously.
+      loader: function(spec) {
+        return new Promise<boolean>((resolve, reject) => {
+          // An arrow function below seems to miscompile in our current webpack to
+          // invalid js.
+          require([`codemirror/mode/${spec.mode}/${spec.mode}.js`], function() {
+            resolve(true);
+          });
+        });
+      },
+      rank: 99
+    }
+  ];
+
   /**
    * Get the raw list of available modes specs.
    */
@@ -76,22 +108,22 @@ export namespace Mode {
    *
    * @returns A promise that resolves when the mode is available.
    */
-  export function ensure(mode: string | ISpec): Promise<ISpec> {
+  export async function ensure(mode: string | ISpec): Promise<ISpec> {
     let spec = findBest(mode);
 
-    // Simplest, cheapest check by mode name.
-    if (CodeMirror.modes.hasOwnProperty(spec.mode)) {
-      return Promise.resolve(spec);
+    for (let specLoader of specLoaders) {
+      if (await specLoader.loader(spec)) {
+        return spec;
+      }
     }
 
-    // Fetch the mode asynchronously.
-    return new Promise<ISpec>((resolve, reject) => {
-      // An arrow function below seems to miscompile in our current webpack to
-      // invalid js.
-      require([`codemirror/mode/${spec.mode}/${spec.mode}.js`], function() {
-        resolve(spec);
-      });
-    });
+    return null;
+  }
+
+  export function addSpecLoader(loader: ISpecLoader, rank: number) {
+    let item = { loader, rank };
+    let index = ArrayExt.upperBound(specLoaders, item, Private.itemCmp);
+    ArrayExt.insert(specLoaders, index, item);
   }
 
   /**
@@ -148,3 +180,24 @@ export namespace Mode {
     }
   }
 }
+
+namespace Private {
+  export interface IRankItem {
+    /**
+     * The loader for the item
+     */
+    loader: Mode.ISpecLoader;
+
+    /**
+     * The sort rank of the widget.
+     */
+    rank: number;
+  }
+
+  /**
+   * A less-than comparison function for the loader rank
+   */
+  export function itemCmp(first: IRankItem, second: IRankItem): number {
+    return first.rank - second.rank;
+  }
+}

+ 1 - 0
tests/test-codemirror/package.json

@@ -16,6 +16,7 @@
     "@jupyterlab/codemirror": "^0.19.1",
     "@jupyterlab/testutils": "^0.3.1",
     "chai": "~4.1.2",
+    "codemirror": "~5.42.0",
     "jest": "^23.5.0",
     "jest-junit": "^5.2.0",
     "simulate-event": "~1.4.0",

+ 71 - 0
tests/test-codemirror/src/mode.spec.ts

@@ -0,0 +1,71 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { expect } from 'chai';
+
+import CodeMirror from 'codemirror';
+
+import { Mode } from '@jupyterlab/codemirror/src/mode';
+
+function fakeMode(name: string) {
+  return {
+    mode: name,
+    ext: [name],
+    mime: `text/${name}`,
+    name: name.toUpperCase()
+  };
+}
+
+describe('Mode', () => {
+  describe('#ensure', () => {
+    it('should load a defined spec', async () => {
+      CodeMirror.modeInfo.push(fakeMode('foo'));
+      CodeMirror.defineMode('foo', () => null);
+      let spec = await Mode.ensure('text/foo');
+      expect(spec.name).to.equal('FOO');
+    });
+
+    it('should load a bundled spec', async () => {
+      let spec = await Mode.ensure('application/json');
+      expect(spec.name).to.equal('JSON');
+    });
+
+    it('should add a spec loader', async () => {
+      let called = 0;
+      let loaded = 0;
+
+      Mode.addSpecLoader(async spec => {
+        called++;
+        if (spec.mode !== 'bar') {
+          return false;
+        }
+        loaded++;
+        return true;
+      }, 42);
+
+      CodeMirror.modeInfo.push(fakeMode('bar'));
+
+      let spec = await Mode.ensure('bar');
+      expect(called).to.equal(1);
+      expect(loaded).to.equal(1);
+      expect(spec.name).to.equal('BAR');
+
+      spec = await Mode.ensure('python');
+      expect(called).to.equal(1);
+      expect(loaded).to.equal(1);
+
+      try {
+        spec = await Mode.ensure('APL');
+      } catch (err) {
+        // apparently one cannot use webpack `require` in jest
+      }
+      expect(called).to.equal(2);
+      expect(loaded).to.equal(1);
+    });
+
+    it('should default to plain text', async () => {
+      let spec = await Mode.ensure('this is not a mode');
+      expect(spec.name).to.equal('Plain Text');
+    });
+  });
+});