diff --git a/dev_mode/package.json b/dev_mode/package.json
index cf677543909d..d5033f5863e0 100644
--- a/dev_mode/package.json
+++ b/dev_mode/package.json
@@ -31,6 +31,7 @@
"@jupyterlab/docmanager": "^0.19.1",
"@jupyterlab/docmanager-extension": "^0.19.1",
"@jupyterlab/docregistry": "^0.19.1",
+ "@jupyterlab/documentsearch-extension": "^0.19.1",
"@jupyterlab/extensionmanager": "^0.19.1",
"@jupyterlab/extensionmanager-extension": "^0.19.1",
"@jupyterlab/faq-extension": "^0.19.1",
@@ -147,6 +148,7 @@
"@jupyterlab/console-extension": "",
"@jupyterlab/csvviewer-extension": "",
"@jupyterlab/docmanager-extension": "",
+ "@jupyterlab/documentsearch-extension": "",
"@jupyterlab/extensionmanager-extension": "",
"@jupyterlab/faq-extension": "",
"@jupyterlab/filebrowser-extension": "",
@@ -251,6 +253,7 @@
"@jupyterlab/docmanager": "../packages/docmanager",
"@jupyterlab/docmanager-extension": "../packages/docmanager-extension",
"@jupyterlab/docregistry": "../packages/docregistry",
+ "@jupyterlab/documentsearch-extension": "../packages/documentsearch-extension",
"@jupyterlab/extensionmanager": "../packages/extensionmanager",
"@jupyterlab/extensionmanager-extension": "../packages/extensionmanager-extension",
"@jupyterlab/faq-extension": "../packages/faq-extension",
diff --git a/examples/console/package.json b/examples/console/package.json
index cdd3c027a41c..5c02cf759993 100644
--- a/examples/console/package.json
+++ b/examples/console/package.json
@@ -18,7 +18,7 @@
"es6-promise": "~4.1.1"
},
"devDependencies": {
- "@types/codemirror": "~0.0.46",
+ "@types/codemirror": "~0.0.70",
"css-loader": "~0.28.7",
"file-loader": "~1.1.11",
"mini-css-extract-plugin": "~0.4.4",
diff --git a/examples/filebrowser/package.json b/examples/filebrowser/package.json
index 2b107c6da10b..246690a3f694 100644
--- a/examples/filebrowser/package.json
+++ b/examples/filebrowser/package.json
@@ -22,7 +22,7 @@
"es6-promise": "~4.1.1"
},
"devDependencies": {
- "@types/codemirror": "~0.0.46",
+ "@types/codemirror": "~0.0.70",
"css-loader": "~0.28.7",
"file-loader": "~1.1.11",
"mini-css-extract-plugin": "~0.4.4",
diff --git a/examples/notebook/package.json b/examples/notebook/package.json
index 6e1229e52e10..11dd51b1f165 100644
--- a/examples/notebook/package.json
+++ b/examples/notebook/package.json
@@ -22,7 +22,7 @@
"es6-promise": "~4.1.1"
},
"devDependencies": {
- "@types/codemirror": "~0.0.46",
+ "@types/codemirror": "~0.0.70",
"css-loader": "~0.28.7",
"file-loader": "~1.1.11",
"mini-css-extract-plugin": "~0.4.4",
diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json
index 68b671741047..e15894056657 100644
--- a/packages/codemirror/package.json
+++ b/packages/codemirror/package.json
@@ -46,7 +46,7 @@
"react": "~16.4.2"
},
"devDependencies": {
- "@types/codemirror": "~0.0.46",
+ "@types/codemirror": "~0.0.70",
"rimraf": "~2.6.2",
"typedoc": "~0.12.0",
"typescript": "~3.1.1"
diff --git a/packages/codemirror/src/editor.ts b/packages/codemirror/src/editor.ts
index f12909c44b14..9af2c8daea82 100644
--- a/packages/codemirror/src/editor.ts
+++ b/packages/codemirror/src/editor.ts
@@ -1,5 +1,7 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
+///
+///
import CodeMirror from 'codemirror';
@@ -377,6 +379,65 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
this._clearHover();
}
+ // todo: docs, maybe define overlay options as a type?
+ addOverlay(mode: string | object, options?: object): void {
+ this._editor.addOverlay(mode, options);
+ }
+
+ removeOverlay(mode: string | object): void {
+ this._editor.removeOverlay(mode);
+ }
+
+ getSearchCursor(
+ query: string | RegExp,
+ start?: CodeMirror.Position,
+ caseFold?: boolean
+ ): CodeMirror.SearchCursor {
+ return this._editor.getDoc().getSearchCursor(query, start, caseFold);
+ }
+
+ getCursor(start?: string): CodeMirror.Position {
+ return this._editor.getDoc().getCursor(start);
+ }
+
+ get state(): any {
+ return this._editor.state;
+ }
+
+ operation(fn: () => T): T {
+ return this._editor.operation(fn);
+ }
+
+ firstLine(): number {
+ return this._editor.getDoc().firstLine();
+ }
+
+ lastLine(): number {
+ return this._editor.getDoc().lastLine();
+ }
+
+ scrollIntoView(
+ pos: { from: CodeMirror.Position; to: CodeMirror.Position },
+ margin: number
+ ): void {
+ this._editor.scrollIntoView(pos, margin);
+ }
+
+ cursorCoords(
+ where: boolean,
+ mode?: 'window' | 'page' | 'local'
+ ): { left: number; top: number; bottom: number } {
+ return this._editor.cursorCoords(where, mode);
+ }
+
+ getRange(
+ from: CodeMirror.Position,
+ to: CodeMirror.Position,
+ seperator?: string
+ ): string {
+ return this._editor.getDoc().getRange(from, to, seperator);
+ }
+
/**
* Add a keydown handler to the editor.
*
@@ -415,7 +476,10 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
* Reveal the given selection in the editor.
*/
revealSelection(selection: CodeEditor.IRange): void {
- const range = this._toCodeMirrorRange(selection);
+ const range = {
+ from: this._toCodeMirrorPosition(selection.start),
+ to: this._toCodeMirrorPosition(selection.end)
+ };
this._editor.scrollIntoView(range);
}
@@ -763,16 +827,6 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
};
}
- /**
- * Converts an editor selection to a code mirror selection.
- */
- private _toCodeMirrorRange(range: CodeEditor.IRange): CodeMirror.Range {
- return {
- from: this._toCodeMirrorPosition(range.start),
- to: this._toCodeMirrorPosition(range.end)
- };
- }
-
/**
* Convert a code mirror position to an editor position.
*/
diff --git a/packages/codemirror/src/typings.d.ts b/packages/codemirror/src/typings.d.ts
index b3dc4f3ed251..17387f9d7d8b 100644
--- a/packages/codemirror/src/typings.d.ts
+++ b/packages/codemirror/src/typings.d.ts
@@ -2,3 +2,4 @@
// Distributed under the terms of the Modified BSD License.
///
+///
diff --git a/packages/codemirror/typings/codemirror/codemirror.d.ts b/packages/codemirror/typings/codemirror/codemirror.d.ts
index f10e3b87e540..c41d8a926c62 100644
--- a/packages/codemirror/typings/codemirror/codemirror.d.ts
+++ b/packages/codemirror/typings/codemirror/codemirror.d.ts
@@ -3,48 +3,27 @@
// Definitions by: mihailik
// Definitions: https://github.com/borisyankov/DefinitelyTyped
-declare function CodeMirror(
- host: HTMLElement,
- options?: CodeMirror.EditorConfiguration
-): CodeMirror.Editor;
-declare function CodeMirror(
- callback: (host: HTMLElement) => void,
- options?: CodeMirror.EditorConfiguration
-): CodeMirror.Editor;
+import * as CodeMirror from 'codemirror';
-declare namespace CodeMirror {
- export var Doc: CodeMirror.DocConstructor;
- export var Pos: CodeMirror.PositionConstructor;
- export var Pass: any;
-
- function fromTextArea(
- host: HTMLTextAreaElement,
- options?: EditorConfiguration
- ): CodeMirror.EditorFromTextArea;
-
- // findMode* functions are from loading the codemirror/mode/meta module
- interface modespec {
- ext?: string[];
- name?: string;
- mode: string;
- mime: string;
- }
- function findModeByName(name: string): modespec;
- function findModeByExtension(name: string): modespec;
- function findModeByFileName(name: string): modespec;
- function findModeByMIME(mime: string): modespec;
-
- var modes: {
- [key: string]: any;
- };
-
- var mimeModes: {
- [key: string]: any;
- };
+/**
+ * Define extra codemirror types that do not exist in the DefinitelyTyped
+ * type resources
+ */
+declare module 'codemirror' {
+ /**
+ * id will be the id for the defined mode. Typically, you should use this second argument to defineMode as your module scope function
+ * (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.
+ */
+ function defineMode(
+ id: string,
+ modefactory: ModeFactory,
+ base: any
+ ): void;
- var commands: {
- [key: string]: any;
- };
+ /**
+ * Define a mimetype.
+ */
+ function defineMIME(mimetype: string, mode: any): void;
interface modeinfo {
ext: string[];
@@ -54,37 +33,10 @@ declare namespace CodeMirror {
}
var modeInfo: modeinfo[];
- function runMode(
- code: string,
- mode: modespec | string,
- el: HTMLElement
- ): void;
-
- var version: string;
-
- /** If you want to define extra methods in terms of the CodeMirror API, it is possible to use defineExtension.
- This will cause the given value(usually a method) to be added to all CodeMirror instances created from then on. */
- function defineExtension(name: string, value: any): void;
-
- /** Like defineExtension, but the method will be added to the interface for Doc objects instead. */
- function defineDocExtension(name: string, value: any): void;
-
- /** Similarly, defineOption can be used to define new options for CodeMirror.
- The updateFunc will be called with the editor instance and the new value when an editor is initialized,
- and whenever the option is modified through setOption. */
- function defineOption(
- name: string,
- default_: any,
- updateFunc: Function
- ): void;
-
- /** If your extension just needs to run some code whenever a CodeMirror instance is initialized, use CodeMirror.defineInitHook.
- Give it a function as its only argument, and from then on, that function will be called (with the instance as argument)
- whenever a new CodeMirror instance is initialized. */
- function defineInitHook(func: Function): void;
-
- function on(element: any, eventName: string, handler: Function): void;
- function off(element: any, eventName: string, handler: Function): void;
+ /**
+ * A mode that encompasses many mode types.
+ */
+ function multiplexingMode(...modes: any[]): Mode;
/**
* Fired on a keydown event on the editor.
@@ -100,891 +52,17 @@ declare namespace CodeMirror {
handler: (instance: Editor, event: KeyboardEvent) => void
): void;
- /** Fired whenever a change occurs to the document. changeObj has a similar type as the object passed to the editor's "change" event,
- but it never has a next property, because document change events are not batched (whereas editor change events are). */
- function on(
- doc: Doc,
- eventName: 'change',
- handler: (instance: Doc, change: EditorChange) => void
- ): void;
- function off(
- doc: Doc,
- eventName: 'change',
- handler: (instance: Doc, change: EditorChange) => void
- ): void;
-
- /** See the description of the same event on editor instances. */
- function on(
- doc: Doc,
- eventName: 'beforeChange',
- handler: (instance: Doc, change: EditorChangeCancellable) => void
- ): void;
- function off(
- doc: Doc,
- eventName: 'beforeChange',
- handler: (instance: Doc, change: EditorChangeCancellable) => void
- ): void;
-
- /** Fired whenever the cursor or selection in this document changes. */
- function on(
- doc: Doc,
- eventName: 'cursorActivity',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
- function off(
- doc: Doc,
- eventName: 'cursorActivity',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Equivalent to the event by the same name as fired on editor instances. */
- function on(
- doc: Doc,
- eventName: 'beforeSelectionChange',
- handler: (instance: CodeMirror.Editor, selection: Selection) => void
- ): void;
- function off(
- doc: Doc,
- eventName: 'beforeSelectionChange',
- handler: (instance: CodeMirror.Editor, selection: Selection) => void
- ): void;
-
- /** Will be fired when the line object is deleted. A line object is associated with the start of the line.
- Mostly useful when you need to find out when your gutter markers on a given line are removed. */
- function on(line: LineHandle, eventName: 'delete', handler: () => void): void;
- function off(
- line: LineHandle,
- eventName: 'delete',
- handler: () => void
- ): void;
-
- /** Fires when the line's text content is changed in any way (but the line is not deleted outright).
- The change object is similar to the one passed to change event on the editor object. */
- function on(
- line: LineHandle,
- eventName: 'change',
- handler: (line: LineHandle, change: EditorChange) => void
- ): void;
- function off(
- line: LineHandle,
- eventName: 'change',
- handler: (line: LineHandle, change: EditorChange) => void
- ): void;
-
- /** Fired when the cursor enters the marked range. From this event handler, the editor state may be inspected but not modified,
- with the exception that the range on which the event fires may be cleared. */
- function on(
- marker: TextMarker,
- eventName: 'beforeCursorEnter',
- handler: () => void
- ): void;
- function off(
- marker: TextMarker,
- eventName: 'beforeCursorEnter',
- handler: () => void
- ): void;
-
- /** Fired when the range is cleared, either through cursor movement in combination with clearOnEnter or through a call to its clear() method.
- Will only be fired once per handle. Note that deleting the range through text editing does not fire this event,
- because an undo action might bring the range back into existence. */
- function on(
- marker: TextMarker,
- eventName: 'clear',
- handler: () => void
- ): void;
- function off(
- marker: TextMarker,
- eventName: 'clear',
- handler: () => void
- ): void;
-
- /** Fired when the last part of the marker is removed from the document by editing operations. */
- function on(marker: TextMarker, eventName: 'hide', handler: () => void): void;
- function off(
- marker: TextMarker,
- eventName: 'hide',
- handler: () => void
- ): void;
-
- /** Fired when, after the marker was removed by editing, a undo operation brought the marker back. */
- function on(
- marker: TextMarker,
- eventName: 'unhide',
- handler: () => void
- ): void;
- function off(
- marker: TextMarker,
- eventName: 'unhide',
- handler: () => void
- ): void;
-
- /** Fired whenever the editor re-adds the widget to the DOM. This will happen once right after the widget is added (if it is scrolled into view),
- and then again whenever it is scrolled out of view and back in again, or when changes to the editor options
- or the line the widget is on require the widget to be redrawn. */
- function on(line: LineWidget, eventName: 'redraw', handler: () => void): void;
- function off(
- line: LineWidget,
- eventName: 'redraw',
- handler: () => void
- ): void;
-
- /** Various CodeMirror-related objects emit events, which allow client code to react to various situations.
- Handlers for such events can be registered with the on and off methods on the objects that the event fires on.
- To fire your own events, use CodeMirror.signal(target, name, args...), where target is a non-DOM-node object. */
- function signal(target: any, name: string, ...args: any[]): void;
-
interface Editor {
- /** Tells you whether the editor currently has focus. */
- hasFocus(): boolean;
-
- /** Used to find the target position for horizontal cursor motion.start is a { line , ch } object,
- amount an integer(may be negative), and unit one of the string "char", "column", or "word".
- Will return a position that is produced by moving amount times the distance specified by unit.
- When visually is true , motion in right - to - left text will be visual rather than logical.
- When the motion was clipped by hitting the end or start of the document, the returned value will have a hitSide property set to true. */
- findPosH(
- start: CodeMirror.Position,
- amount: number,
- unit: string,
- visually: boolean
- ): { line: number; ch: number; hitSide?: boolean };
-
- /** Similar to findPosH , but used for vertical motion.unit may be "line" or "page".
- The other arguments and the returned value have the same interpretation as they have in findPosH. */
- findPosV(
- start: CodeMirror.Position,
- amount: number,
- unit: string
- ): { line: number; ch: number; hitSide?: boolean };
-
- /** Change the configuration of the editor. option should the name of an option, and value should be a valid value for that option. */
- setOption(option: string, value: any): void;
-
- /** Retrieves the current value of the given option for this editor instance. */
- getOption(option: string): any;
-
- /** Attach an additional keymap to the editor.
- This is mostly useful for add - ons that need to register some key handlers without trampling on the extraKeys option.
- Maps added in this way have a higher precedence than the extraKeys and keyMap options, and between them,
- the maps added earlier have a lower precedence than those added later, unless the bottom argument was passed,
- in which case they end up below other keymaps added with this method. */
- addKeyMap(map: any, bottom?: boolean): void;
-
- /** Disable a keymap added with addKeyMap.Either pass in the keymap object itself , or a string,
- which will be compared against the name property of the active keymaps. */
- removeKeyMap(map: any): void;
-
- /** Enable a highlighting overlay.This is a stateless mini - mode that can be used to add extra highlighting.
- For example, the search add - on uses it to highlight the term that's currently being searched.
- mode can be a mode spec or a mode object (an object with a token method). The options parameter is optional. If given, it should be an object.
- Currently, only the opaque option is recognized. This defaults to off, but can be given to allow the overlay styling, when not null,
- to override the styling of the base mode entirely, instead of the two being applied together. */
- addOverlay(mode: any, options?: any): void;
-
- /** Pass this the exact argument passed for the mode parameter to addOverlay to remove an overlay again. */
- removeOverlay(mode: any): void;
-
- /** Retrieve the currently active document from an editor. */
- getDoc(): CodeMirror.Doc;
-
- /** Attach a new document to the editor. Returns the old document, which is now no longer associated with an editor. */
- swapDoc(doc: CodeMirror.Doc): CodeMirror.Doc;
-
- /** Sets the gutter marker for the given gutter (identified by its CSS class, see the gutters option) to the given value.
- Value can be either null, to clear the marker, or a DOM element, to set it. The DOM element will be shown in the specified gutter next to the specified line. */
- setGutterMarker(
- line: any,
- gutterID: string,
- value: HTMLElement
- ): CodeMirror.LineHandle;
-
- /** Remove all gutter markers in the gutter with the given ID. */
- clearGutter(gutterID: string): void;
-
- /** Set a CSS class name for the given line.line can be a number or a line handle.
- where determines to which element this class should be applied, can can be one of "text" (the text element, which lies in front of the selection),
- "background"(a background element that will be behind the selection),
- or "wrap" (the wrapper node that wraps all of the line's elements, including gutter elements).
- class should be the name of the class to apply. */
- addLineClass(
- line: any,
- where: string,
- _class_: string
- ): CodeMirror.LineHandle;
-
- /** Remove a CSS class from a line.line can be a line handle or number.
- where should be one of "text", "background", or "wrap"(see addLineClass).
- class can be left off to remove all classes for the specified node, or be a string to remove only a specific class. */
- removeLineClass(
- line: any,
- where: string,
- class_: string
- ): CodeMirror.LineHandle;
-
- /** Returns the line number, text content, and marker status of the given line, which can be either a number or a line handle. */
- lineInfo(
- line: any
- ): {
- line: any;
- handle: any;
- text: string;
- /** Object mapping gutter IDs to marker elements. */
- gutterMarks: any;
- textClass: string;
- bgClass: string;
- wrapClass: string;
- /** Array of line widgets attached to this line. */
- widgets: any;
- };
-
- /** Puts node, which should be an absolutely positioned DOM node, into the editor, positioned right below the given { line , ch } position.
- When scrollIntoView is true, the editor will ensure that the entire node is visible (if possible).
- To remove the widget again, simply use DOM methods (move it somewhere else, or call removeChild on its parent). */
- addWidget(
- pos: CodeMirror.Position,
- node: HTMLElement,
- scrollIntoView: boolean
- ): void;
-
- /** Adds a line widget, an element shown below a line, spanning the whole of the editor's width, and moving the lines below it downwards.
- line should be either an integer or a line handle, and node should be a DOM node, which will be displayed below the given line.
- options, when given, should be an object that configures the behavior of the widget.
- Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some cases affect it. */
- addLineWidget(
- line: any,
- node: HTMLElement,
- options?: {
- /** Whether the widget should cover the gutter. */
- coverGutter: boolean;
- /** Whether the widget should stay fixed in the face of horizontal scrolling. */
- noHScroll: boolean;
- /** Causes the widget to be placed above instead of below the text of the line. */
- above: boolean;
- /** When true, will cause the widget to be rendered even if the line it is associated with is hidden. */
- showIfHidden: boolean;
- }
- ): CodeMirror.LineWidget;
-
- /** Programmatically set the size of the editor (overriding the applicable CSS rules).
- width and height height can be either numbers(interpreted as pixels) or CSS units ("100%", for example).
- You can pass null for either of them to indicate that that dimension should not be changed. */
- setSize(width: any, height: any): void;
-
- /** Scroll the editor to a given(pixel) position.Both arguments may be left as null or undefined to have no effect. */
- scrollTo(x: number, y: number): void;
-
- /** Get an { left , top , width , height , clientWidth , clientHeight } object that represents the current scroll position, the size of the scrollable area,
- and the size of the visible area(minus scrollbars). */
- getScrollInfo(): {
- left: any;
- top: any;
- width: any;
- height: any;
- clientWidth: any;
- clientHeight: any;
- };
-
- /** Scrolls the given element into view. pos is a { line , ch } position, referring to a given character, null, to refer to the cursor.
- The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
- scrollIntoView(pos: CodeMirror.Position, margin?: number): void;
-
- /** Scrolls the given element into view. pos is a { left , top , right , bottom } object, in editor-local coordinates.
- The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
- scrollIntoView(
- pos: { left: number; top: number; right: number; bottom: number },
- margin: number
- ): void;
-
- /** Scrolls the given element into view. pos is a { line, ch } object, in editor-local coordinates.
- The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
- scrollIntoView(pos: Position, margin?: number): void;
-
/** Scrolls the given element into view. pos is a { from, to } object, in editor-local coordinates.
The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */
- scrollIntoView(pos: Range, margin?: number): void;
-
- /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.
- If mode is "local" , they will be relative to the top-left corner of the editable document.
- If it is "page" or not given, they are relative to the top-left corner of the page.
- where is a boolean indicating whether you want the start(true) or the end(false) of the selection. */
- cursorCoords(
- where: boolean,
- mode: string
- ): { left: number; top: number; bottom: number };
-
- /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.
- If mode is "local" , they will be relative to the top-left corner of the editable document.
- If it is "page" or not given, they are relative to the top-left corner of the page.
- where specifies the precise position at which you want to measure. */
- cursorCoords(
- where: CodeMirror.Position,
- mode: string
- ): { left: number; top: number; bottom: number };
-
- /** Returns the position and dimensions of an arbitrary character.pos should be a { line , ch } object.
- This differs from cursorCoords in that it'll give the size of the whole character,
- rather than just the position that the cursor would have when it would sit at that position. */
- charCoords(
- pos: CodeMirror.Position,
- mode: string
- ): { left: number; right: number; top: number; bottom: number };
-
- /** Given an { left , top } object , returns the { line , ch } position that corresponds to it.
- The optional mode parameter determines relative to what the coordinates are interpreted. It may be "window" , "page"(the default) , or "local". */
- coordsChar(
- object: { left: number; top: number },
- mode?: string
- ): CodeMirror.Position;
-
- /** Returns the line height of the default font for the editor. */
- defaultTextHeight(): number;
-
- /** Returns the pixel width of an 'x' in the default font for the editor.
- (Note that for non - monospace fonts , this is mostly useless, and even for monospace fonts, non - ascii characters might have a different width). */
- defaultCharWidth(): number;
-
- /** Returns a { from , to } object indicating the start (inclusive) and end (exclusive) of the currently rendered part of the document.
- In big documents, when most content is scrolled out of view, CodeMirror will only render the visible part, and a margin around it.
- See also the viewportChange event. */
- getViewport(): Range;
-
- /** If your code does something to change the size of the editor element (window resizes are already listened for), or unhides it,
- you should probably follow up by calling this method to ensure CodeMirror is still looking as intended. */
- refresh(): void;
-
- /** Retrieves information about the token the current mode found before the given position (a {line, ch} object). */
- getTokenAt(pos: CodeMirror.Position): Token;
-
- /** This is a (much) cheaper version of getTokenAt useful for when you just need the type of the token at a given position,
- and no other information. Will return null for unstyled tokens, and a string, potentially containing multiple
- space-separated style names, otherwise. */
- getTokenTypeAt(pos: CodeMirror.Position): string;
-
- /** This is similar to getTokenAt, but collects all tokens for a given line into an array. */
- getLineTokens(line: number, precise?: boolean): Token[];
-
- /** Returns the mode's parser state, if any, at the end of the given line number.
- If no line number is given, the state at the end of the document is returned.
- This can be useful for storing parsing errors in the state, or getting other kinds of contextual information for a line. */
- getStateAfter(line?: number): any;
-
- /** CodeMirror internally buffers changes and only updates its DOM structure after it has finished performing some operation.
- If you need to perform a lot of operations on a CodeMirror instance, you can call this method with a function argument.
- It will call the function, buffering up all changes, and only doing the expensive update after the function returns.
- This can be a lot faster. The return value from this method will be the return value of your function. */
- operation(fn: () => T): T;
-
- /** Adjust the indentation of the given line.
- The second argument (which defaults to "smart") may be one of:
- "prev" Base indentation on the indentation of the previous line.
- "smart" Use the mode's smart indentation if available, behave like "prev" otherwise.
- "add" Increase the indentation of the line by one indent unit.
- "subtract" Reduce the indentation of the line. */
- indentLine(line: number, dir?: string): void;
-
- /** Give the editor focus. */
- focus(): void;
-
- /** Returns the hidden textarea used to read input. */
- getInputField(): HTMLTextAreaElement;
-
- /** Returns the DOM node that represents the editor, and controls its size. Remove this from your tree to delete an editor instance. */
- getWrapperElement(): HTMLElement;
-
- /** Returns the DOM node that is responsible for the scrolling of the editor. */
- getScrollerElement(): HTMLElement;
-
- /** Fetches the DOM node that contains the editor gutters. */
- getGutterElement(): HTMLElement;
-
- /** Events are registered with the on method (and removed with the off method).
- These are the events that fire on the instance object. The name of the event is followed by the arguments that will be passed to the handler.
- The instance argument always refers to the editor instance. */
- on(eventName: string, handler: (instance: CodeMirror.Editor) => void): void;
- off(
- eventName: string,
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Fires every time the content of the editor is changed. */
- on(
- eventName: 'change',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChange
- ) => void
- ): void;
- off(
- eventName: 'change',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChange
- ) => void
- ): void;
-
- /** Like the "change" event, but batched per operation, passing an
- * array containing all the changes that happened in the operation.
- * This event is fired after the operation finished, and display
- * changes it makes will trigger a new operation. */
- on(
- eventName: 'changes',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChange[]
- ) => void
- ): void;
- off(
- eventName: 'changes',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChange[]
- ) => void
- ): void;
-
- /** This event is fired before a change is applied, and its handler may choose to modify or cancel the change.
- The changeObj never has a next property, since this is fired for each individual change, and not batched per operation.
- Note: you may not do anything from a "beforeChange" handler that would cause changes to the document or its visualization.
- Doing so will, since this handler is called directly from the bowels of the CodeMirror implementation,
- probably cause the editor to become corrupted. */
- on(
- eventName: 'beforeChange',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChangeCancellable
- ) => void
- ): void;
- off(
- eventName: 'beforeChange',
- handler: (
- instance: CodeMirror.Editor,
- change: CodeMirror.EditorChangeCancellable
- ) => void
- ): void;
-
- /** Will be fired when the cursor or selection moves, or any change is made to the editor content. */
- on(
- eventName: 'cursorActivity',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
- off(
- eventName: 'cursorActivity',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** This event is fired before the selection is moved. Its handler may modify the resulting selection head and anchor.
- Handlers for this event have the same restriction as "beforeChange" handlers � they should not do anything to directly update the state of the editor. */
- on(
- eventName: 'beforeSelectionChange',
- handler: (
- instance: CodeMirror.Editor,
- selection: CodeMirror.Selection
- ) => void
- ): void;
- off(
- eventName: 'beforeSelectionChange',
- handler: (
- instance: CodeMirror.Editor,
- selection: CodeMirror.Selection
- ) => void
- ): void;
-
- /** Fires whenever the view port of the editor changes (due to scrolling, editing, or any other factor).
- The from and to arguments give the new start and end of the viewport. */
- on(
- eventName: 'viewportChange',
- handler: (instance: CodeMirror.Editor, from: number, to: number) => void
- ): void;
- off(
- eventName: 'viewportChange',
- handler: (instance: CodeMirror.Editor, from: number, to: number) => void
- ): void;
-
- /** Fires when the editor gutter (the line-number area) is clicked. Will pass the editor instance as first argument,
- the (zero-based) number of the line that was clicked as second argument, the CSS class of the gutter that was clicked as third argument,
- and the raw mousedown event object as fourth argument. */
- on(
- eventName: 'gutterClick',
- handler: (
- instance: CodeMirror.Editor,
- line: number,
- gutter: string,
- clickEvent: Event
- ) => void
- ): void;
- off(
- eventName: 'gutterClick',
- handler: (
- instance: CodeMirror.Editor,
- line: number,
- gutter: string,
- clickEvent: Event
- ) => void
- ): void;
-
- /** Fires whenever the editor is focused. */
- on(
- eventName: 'focus',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
- off(
- eventName: 'focus',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Fires whenever the editor is unfocused. */
- on(eventName: 'blur', handler: (instance: CodeMirror.Editor) => void): void;
- off(
- eventName: 'blur',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Fires when the editor is scrolled. */
- on(
- eventName: 'scroll',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
- off(
- eventName: 'scroll',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Will be fired whenever CodeMirror updates its DOM display. */
- on(
- eventName: 'update',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
- off(
- eventName: 'update',
- handler: (instance: CodeMirror.Editor) => void
- ): void;
-
- /** Fired whenever a line is (re-)rendered to the DOM. Fired right after the DOM element is built, before it is added to the document.
- The handler may mess with the style of the resulting element, or add event handlers, but should not try to change the state of the editor. */
- on(
- eventName: 'renderLine',
- handler: (
- instance: CodeMirror.Editor,
- line: number,
- element: HTMLElement
- ) => void
- ): void;
- off(
- eventName: 'renderLine',
- handler: (
- instance: CodeMirror.Editor,
- line: number,
- element: HTMLElement
- ) => void
+ scrollIntoView(
+ pos: { from: CodeMirror.Position; to: CodeMirror.Position },
+ margin?: number
): void;
/** Trigger key events onto the editor instance. Not for production use, only for testing.
See this comment: https://github.com/codemirror/CodeMirror/issues/1935#issuecomment-28178991 */
triggerOnKeyDown(event: Event): void;
- triggerOnKeyPress(event: Event): void;
- triggerOnKeyUp(event: Event): void;
-
- /**
- * Execute a command on the editor.
- */
- execCommand(command: string): any;
- }
-
- interface EditorFromTextArea extends Editor {
- /** Copy the content of the editor into the textarea. */
- save(): void;
-
- /** Remove the editor, and restore the original textarea (with the editor's current content). */
- toTextArea(): void;
-
- /** Returns the textarea that the instance was based on. */
- getTextArea(): HTMLTextAreaElement;
- }
-
- interface DocConstructor {
- new (
- text: string,
- mode?: any,
- firstLineNumber?: number,
- lineSep?: string
- ): Doc;
- (text: string, mode?: any, firstLineNumber?: number, lineSep?: string): Doc;
- }
-
- interface Doc {
- /** Get the current editor content. You can pass it an optional argument to specify the string to be used to separate lines (defaults to "\n"). */
- getValue(separator?: string): string;
-
- /** Set the editor content. */
- setValue(content: string): void;
-
- /** Get the text between the given points in the editor, which should be {line, ch} objects.
- An optional third argument can be given to indicate the line separator string to use (defaults to "\n"). */
- getRange(
- from: Position,
- to: CodeMirror.Position,
- separator?: string
- ): string;
-
- /** Replace the part of the document between from and to with the given string.
- from and to must be {line, ch} objects. to can be left off to simply insert the string at position from. */
- replaceRange(
- replacement: string,
- from: CodeMirror.Position,
- to: CodeMirror.Position
- ): void;
-
- /** Get the content of line n. */
- getLine(n: number): string;
-
- /** Set the content of line n. */
- setLine(n: number, text: string): void;
-
- /** Remove the given line from the document. */
- removeLine(n: number): void;
-
- /** Get the number of lines in the editor. */
- lineCount(): number;
-
- /** Get the first line of the editor. This will usually be zero but for linked sub-views,
- or documents instantiated with a non-zero first line, it might return other values. */
- firstLine(): number;
-
- /** Get the last line of the editor. This will usually be lineCount() - 1, but for linked sub-views, it might return other values. */
- lastLine(): number;
-
- /** Fetches the line handle for the given line number. */
- getLineHandle(num: number): CodeMirror.LineHandle;
-
- /** Given a line handle, returns the current position of that line (or null when it is no longer in the document). */
- getLineNumber(handle: CodeMirror.LineHandle): number;
-
- /** Iterate over the whole document, and call f for each line, passing the line handle.
- This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.
- Note that line handles have a text property containing the line's content (as a string). */
- eachLine(f: (line: CodeMirror.LineHandle) => void): void;
-
- /** Iterate over the range from start up to (not including) end, and call f for each line, passing the line handle.
- This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.
- Note that line handles have a text property containing the line's content (as a string). */
- eachLine(
- start: number,
- end: number,
- f: (line: CodeMirror.LineHandle) => void
- ): void;
-
- /** Set the editor content as 'clean', a flag that it will retain until it is edited, and which will be set again when such an edit is undone again.
- Useful to track whether the content needs to be saved. */
- markClean(): void;
-
- /** Returns whether the document is currently clean (not modified since initialization or the last call to markClean). */
- isClean(): boolean;
-
- /** Get the currently selected code. */
- getSelection(): string;
-
- /** Replace the selection with the given string. By default, the new selection will span the inserted text.
- The optional collapse argument can be used to change this � passing "start" or "end" will collapse the selection to the start or end of the inserted text. */
- replaceSelection(replacement: string, collapse?: string): void;
-
- /** start is a an optional string indicating which end of the selection to return.
- It may be "start" , "end" , "head"(the side of the selection that moves when you press shift + arrow),
- or "anchor"(the fixed side of the selection).Omitting the argument is the same as passing "head".A { line , ch } object will be returned. */
- getCursor(start?: string): CodeMirror.Position;
-
- /** Retrieves a list of all current selections. These will always be sorted, and never overlap (overlapping selections are merged).
- Each object in the array contains anchor and head properties referring to {line, ch} objects. */
- listSelections(): CodeMirror.Selection[];
-
- /** Return true if any text is selected. */
- somethingSelected(): boolean;
-
- /** Set the cursor position.You can either pass a single { line , ch } object , or the line and the character as two separate parameters. */
- setCursor(pos: CodeMirror.Position): void;
-
- /** Set the selection range.anchor and head should be { line , ch } objects.head defaults to anchor when not given. */
- setSelection(
- anchor: CodeMirror.Position,
- head?: CodeMirror.Position,
- options?: any
- ): void;
-
- /**
- * Sets a new set of selections. There must be at least one selection in the given array.
- * When primary is a number, it determines which selection is the primary one.
- * When it is not given, the primary index is taken from the previous selection,
- * or set to the last range if the previous selection had less ranges than the new one.
- * Supports the same options as setSelection.
- */
- setSelections(
- ranges: CodeMirror.Selection[],
- primary?: number,
- options?: any
- ): void;
-
- /** Similar to setSelection , but will, if shift is held or the extending flag is set,
- move the head of the selection while leaving the anchor at its current place.
- pos2 is optional , and can be passed to ensure a region (for example a word or paragraph) will end up selected
- (in addition to whatever lies between that region and the current anchor). */
- extendSelection(from: CodeMirror.Position, to?: CodeMirror.Position): void;
-
- /** Sets or clears the 'extending' flag , which acts similar to the shift key,
- in that it will cause cursor movement and calls to extendSelection to leave the selection anchor in place. */
- setExtending(value: boolean): void;
-
- /** Retrieve the editor associated with a document. May return null. */
- getEditor(): CodeMirror.Editor;
-
- /** Create an identical copy of the given doc. When copyHistory is true , the history will also be copied.Can not be called directly on an editor. */
- copy(copyHistory: boolean): CodeMirror.Doc;
-
- /** Create a new document that's linked to the target document. Linked documents will stay in sync (changes to one are also applied to the other) until unlinked. */
- linkedDoc(options: {
- /** When turned on, the linked copy will share an undo history with the original.
- Thus, something done in one of the two can be undone in the other, and vice versa. */
- sharedHist?: boolean;
- from?: number;
- /** Can be given to make the new document a subview of the original. Subviews only show a given range of lines.
- Note that line coordinates inside the subview will be consistent with those of the parent,
- so that for example a subview starting at line 10 will refer to its first line as line 10, not 0. */
- to?: number;
- /** By default, the new document inherits the mode of the parent. This option can be set to a mode spec to give it a different mode. */
- mode: any;
- }): CodeMirror.Doc;
-
- /** Break the link between two documents. After calling this , changes will no longer propagate between the documents,
- and, if they had a shared history, the history will become separate. */
- unlinkDoc(doc: CodeMirror.Doc): void;
-
- /** Will call the given function for all documents linked to the target document. It will be passed two arguments,
- the linked document and a boolean indicating whether that document shares history with the target. */
- iterLinkedDocs(
- fn: (doc: CodeMirror.Doc, sharedHist: boolean) => void
- ): void;
-
- /** Undo one edit (if any undo events are stored). */
- undo(): void;
-
- /** Redo one undone edit. */
- redo(): void;
-
- /** Returns an object with {undo, redo } properties , both of which hold integers , indicating the amount of stored undo and redo operations. */
- historySize(): { undo: number; redo: number };
-
- /** Clears the editor's undo history. */
- clearHistory(): void;
-
- /** Get a(JSON - serializeable) representation of the undo history. */
- getHistory(): any;
-
- /** Replace the editor's undo history with the one provided, which must be a value as returned by getHistory.
- Note that this will have entirely undefined results if the editor content isn't also the same as it was when getHistory was called. */
- setHistory(history: any): void;
-
- /** Can be used to mark a range of text with a specific CSS class name. from and to should be { line , ch } objects. */
- markText(
- from: CodeMirror.Position,
- to: CodeMirror.Position,
- options?: CodeMirror.TextMarkerOptions
- ): TextMarker;
-
- /** Inserts a bookmark, a handle that follows the text around it as it is being edited, at the given position.
- A bookmark has two methods find() and clear(). The first returns the current position of the bookmark, if it is still in the document,
- and the second explicitly removes the bookmark. */
- setBookmark(
- pos: CodeMirror.Position,
- options?: {
- /** Can be used to display a DOM node at the current location of the bookmark (analogous to the replacedWith option to markText). */
- widget?: HTMLElement;
-
- /** By default, text typed when the cursor is on top of the bookmark will end up to the right of the bookmark.
- Set this option to true to make it go to the left instead. */
- insertLeft?: boolean;
- }
- ): CodeMirror.TextMarker;
-
- /** Returns an array of all the bookmarks and marked ranges found between the given positions. */
- findMarks(from: CodeMirror.Position, to: CodeMirror.Position): TextMarker[];
-
- /** Returns an array of all the bookmarks and marked ranges present at the given position. */
- findMarksAt(pos: CodeMirror.Position): TextMarker[];
-
- /** Returns an array containing all marked ranges in the document. */
- getAllMarks(): CodeMirror.TextMarker[];
-
- /** Gets the mode object for the editor. Note that this is distinct from getOption("mode"), which gives you the mode specification,
- rather than the resolved, instantiated mode object. */
- getMode(): any;
-
- /** Calculates and returns a { line , ch } object for a zero-based index whose value is relative to the start of the editor's text.
- If the index is out of range of the text then the returned object is clipped to start or end of the text respectively. */
- posFromIndex(index: number): CodeMirror.Position;
-
- /** The reverse of posFromIndex. */
- indexFromPos(object: CodeMirror.Position): number;
- }
-
- interface LineHandle {
- text: string;
- }
-
- interface TextMarker {
- /** Remove the mark. */
- clear(): void;
-
- /** Returns a {from, to} object (both holding document positions), indicating the current position of the marked range,
- or undefined if the marker is no longer in the document. */
- find(): CodeMirror.Position;
-
- /** Returns an object representing the options for the marker. If copyWidget is given true, it will clone the value of the replacedWith option, if any. */
- getOptions(copyWidget: boolean): CodeMirror.TextMarkerOptions;
- }
-
- interface LineWidget {
- /** Removes the widget. */
- clear(): void;
-
- /** Call this if you made some change to the widget's DOM node that might affect its height.
- It'll force CodeMirror to update the height of the line that contains the widget. */
- changed(): void;
- }
-
- interface EditorChange {
- /** Position (in the pre-change coordinate system) where the change started. */
- from: CodeMirror.Position;
- /** Position (in the pre-change coordinate system) where the change ended. */
- to: CodeMirror.Position;
- /** Array of strings representing the text that replaced the changed range (split by line). */
- text: string[];
- /** Text that used to be between from and to, which is overwritten by this change. */
- removed: string[];
- /** String representing the origin of the change event and whether it can be merged with history */
- origin: string;
- }
-
- interface EditorChangeCancellable extends CodeMirror.EditorChange {
- /** may be used to modify the change. All three arguments to update are optional, and can be left off to leave the existing value for that field intact. */
- update(
- from?: CodeMirror.Position,
- to?: CodeMirror.Position,
- text?: string
- ): void;
-
- cancel(): void;
- }
-
- interface PositionConstructor {
- new (line: number, ch: number): Position;
- (line: number, ch: number): Position;
- }
-
- interface Position {
- ch: number;
- line: number;
- }
-
- interface Range {
- from: Position;
- to: Position;
}
interface Selection {
@@ -998,511 +76,48 @@ declare namespace CodeMirror {
head: Position;
}
- interface Token {
- /**
- * The character(on the given line) at which the token starts.
- */
- start: number;
-
- /**
- * The character at which the token ends.
- */
- end: number;
-
- /**
- * The token's string.
- */
- string: string;
- /**
- * The token type the mode assigned to the token,
- * such as "keyword" or "comment" (may also be null).
- */
- type: string | null;
-
- /**
- * The mode's state at the end of this token.
- */
- state: any;
- }
-
- interface EditorConfiguration {
- /** string| The starting value of the editor. Can be a string, or a document object. */
- value?: any;
-
- /** string|object. The mode to use. When not given, this will default to the first mode that was loaded.
- It may be a string, which either simply names the mode or is a MIME type associated with the mode.
- Alternatively, it may be an object containing configuration options for the mode,
- with a name property that names the mode (for example {name: "javascript", json: true}). */
- mode?: any;
-
- /** The theme to style the editor with. You must make sure the CSS file defining the corresponding .cm-s-[name] styles is loaded.
- The default is "default". */
- theme?: string;
-
- /** How many spaces a block (whatever that means in the edited language) should be indented. The default is 2. */
- indentUnit?: number;
-
- /** Whether to use the context-sensitive indentation that the mode provides (or just indent the same as the line before). Defaults to true. */
- smartIndent?: boolean;
-
- /** The width of a tab character. Defaults to 4. */
- tabSize?: number;
-
- /** Whether, when indenting, the first N*tabSize spaces should be replaced by N tabs. Default is false. */
- indentWithTabs?: 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). Default is true. */
- electricChars?: boolean;
-
- /** Determines whether horizontal cursor movement through right-to-left (Arabic, Hebrew) text
- is visual (pressing the left arrow moves the cursor left)
- or logical (pressing the left arrow moves to the next lower index in the string, which is visually right in right-to-left text).
- The default is false on Windows, and true on other platforms. */
- rtlMoveVisually?: 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 keymap directory. See the section on keymaps for more information. */
- 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;
-
- /** Whether CodeMirror should scroll or wrap for long lines. Defaults to false (scroll). */
- lineWrapping?: boolean;
-
- /** Whether to show line numbers to the left of the editor. */
- lineNumbers?: boolean;
-
- /** At which number to start counting lines. Default is 1. */
- firstLineNumber?: number;
-
- /** A function used to format line numbers. The function is passed the line number, and should return a string that will be shown in the gutter. */
- lineNumberFormatter?: (line: number) => string;
-
- /** 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?: 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;
-
- /** boolean|string. This disables editing of the editor content by the user. If the special value "nocursor" is given (instead of simply true), focusing of the editor is also disallowed. */
- readOnly?: any;
-
- /**Whether the cursor should be drawn when a selection is active. Defaults to false. */
- showCursorWhenSelecting?: boolean;
-
- /** The maximum number of undo levels that the editor stores. Defaults to 40. */
- undoDepth?: number;
-
- /** The period of inactivity (in milliseconds) that will cause a new history event to be started when typing or deleting. Defaults to 500. */
- historyEventDelay?: number;
-
- /** The tab index to assign to the editor. If not given, no tab index will be assigned. */
- tabindex?: number;
-
- /** Can be used to make CodeMirror focus itself on initialization. Defaults to off.
- When fromTextArea is used, and no explicit value is given for this option, it will be set to true when either the source textarea is focused,
- or it has an autofocus attribute and no other element is focused. */
- autofocus?: boolean;
-
- /** Controls whether drag-and - drop is enabled. On by default. */
- dragDrop?: boolean;
-
- /** When given , this will be called when the editor is handling a dragenter , dragover , or drop event.
- It will be passed the editor instance and the event object as arguments.
- The callback can choose to handle the event itself , in which case it should return true to indicate that CodeMirror should not do anything further. */
- onDragEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;
-
- /** This provides a rather low - level hook into CodeMirror's key handling.
- If provided, this function will be called on every keydown, keyup, and keypress event that CodeMirror captures.
- It will be passed two arguments, the editor instance and the key event.
- This key event is pretty much the raw key event, except that a stop() method is always added to it.
- You could feed it to, for example, jQuery.Event to further normalize it.
- This function can inspect the key event, and handle it if it wants to.
- It may return true to tell CodeMirror to ignore the event.
- Be wary that, on some browsers, stopping a keydown does not stop the keypress from firing, whereas on others it does.
- If you respond to an event, you should probably inspect its type property and only do something when it is keydown
- (or keypress for actions that need character data). */
- onKeyEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;
-
- /** Half - period in milliseconds used for cursor blinking. The default blink rate is 530ms. */
- cursorBlinkRate?: number;
-
- /** Determines the height of the cursor. Default is 1 , meaning it spans the whole height of the line.
- For some fonts (and by some tastes) a smaller height (for example 0.85),
- which causes the cursor to not reach all the way to the bottom of the line, looks better */
- cursorHeight?: number;
-
- /** Highlighting is done by a pseudo background - thread that will work for workTime milliseconds,
- and then use timeout to sleep for workDelay milliseconds.
- The defaults are 200 and 300, you can change these options to make the highlighting more or less aggressive. */
- workTime?: number;
-
- /** See workTime. */
- workDelay?: number;
-
- /** Indicates how quickly CodeMirror should poll its input textarea for changes(when focused).
- Most input is captured by events, but some things, like IME input on some browsers, don't generate events that allow CodeMirror to properly detect it.
- Thus, it polls. Default is 100 milliseconds. */
- pollInterval?: number;
-
- /** By default, CodeMirror will combine adjacent tokens into a single span if they have the same class.
- This will result in a simpler DOM tree, and thus perform better. With some kinds of styling(such as rounded corners),
- this will change the way the document looks. You can set this option to false to disable this behavior. */
- flattenSpans?: boolean;
-
- /** When highlighting long lines, in order to stay responsive, the editor will give up and simply style
- the rest of the line as plain text when it reaches a certain position. The default is 10000.
- You can set this to Infinity to turn off this behavior. */
- maxHighlightLength?: number;
-
- /** Specifies the amount of lines that are rendered above and below the part of the document that's currently scrolled into view.
- This affects the amount of updates needed when scrolling, and the amount of work that such an update does.
- You should usually leave it at its default, 10. Can be set to Infinity to make sure the whole document is always rendered,
- and thus the browser's text search works on it. This will have bad effects on performance of big documents. */
- viewportMargin?: number;
-
- /** Optional lint configuration to be used in conjunction with CodeMirror's linter addon. */
- lint?: boolean | LintOptions;
-
- /** Optional value to be used in conduction with CodeMirror’s placeholder add-on. */
- placeholder?: string;
- }
-
- interface TextMarkerOptions {
- /** Assigns a CSS class to the marked stretch of text. */
- className?: string;
-
- /** Determines whether text inserted on the left of the marker will end up inside or outside of it. */
- inclusiveLeft?: boolean;
-
- /** Like inclusiveLeft , but for the right side. */
- inclusiveRight?: boolean;
-
- /** Atomic ranges act as a single unit when cursor movement is concerned — i.e. it is impossible to place the cursor inside of them.
- In atomic ranges, inclusiveLeft and inclusiveRight have a different meaning — they will prevent the cursor from being placed
- respectively directly before and directly after the range. */
- atomic?: boolean;
-
- /** Collapsed ranges do not show up in the display.Setting a range to be collapsed will automatically make it atomic. */
- collapsed?: boolean;
-
- /** When enabled, will cause the mark to clear itself whenever the cursor enters its range.
- This is mostly useful for text - replacement widgets that need to 'snap open' when the user tries to edit them.
- The "clear" event fired on the range handle can be used to be notified when this happens. */
- clearOnEnter?: boolean;
-
- /** Determines whether the mark is automatically cleared when it becomes empty. Default is true. */
- clearWhenEmpty?: boolean;
-
- /** Use a given node to display this range.Implies both collapsed and atomic.
- The given DOM node must be an inline element(as opposed to a block element). */
- replacedWith?: HTMLElement;
-
- /** When replacedWith is given, this determines whether the editor will
- * capture mouse and drag events occurring in this widget. Default is
- * false—the events will be left alone for the default browser handler,
- * or specific handlers on the widget, to capture. */
- handleMouseEvents?: boolean;
-
- /** A read - only span can, as long as it is not cleared, not be modified except by calling setValue to reset the whole document.
- Note: adding a read - only span currently clears the undo history of the editor,
- because existing undo events being partially nullified by read - only spans would corrupt the history (in the current implementation). */
- readOnly?: boolean;
-
- /** When set to true (default is false), adding this marker will create an event in the undo history that can be individually undone(clearing the marker). */
- addToHistory?: boolean;
-
- /** Can be used to specify an extra CSS class to be applied to the leftmost span that is part of the marker. */
- startStyle?: string;
-
- /** Equivalent to startStyle, but for the rightmost span. */
- endStyle?: string;
-
- /** A string of CSS to be applied to the covered text. For example "color: #fe3". */
- css?: string;
-
- /** When given, will give the nodes created for this span a HTML title attribute with the given value. */
- title?: string;
-
- /** When the target document is linked to other documents, you can set shared to true to make the marker appear in all documents.
- By default, a marker appears only in its target document. */
- shared?: boolean;
- }
-
- interface StringStream {
- lastColumnPos: number;
- lastColumnValue: number;
- lineStart: number;
-
- /**
- * Current position in the string.
- */
- pos: number;
-
- /**
- * Where the stream's position was when it was first passed to the token function.
- */
- start: number;
-
- /**
- * The current line's content.
- */
- string: string;
-
- /**
- * Number of spaces per tab character.
- */
- tabSize: number;
-
- /**
- * Returns true only if the stream is at the end of the line.
- */
- eol(): boolean;
-
- /**
- * Returns true only if the stream is at the start of the line.
- */
- sol(): boolean;
-
- /**
- * Returns the next character in the stream without advancing it. Will return an null at the end of the line.
- */
- peek(): string;
-
- /**
- * Returns the next character in the stream and advances it. Also returns null when no more characters are available.
- */
- next(): string;
-
- /**
- * match can be a character, a regular expression, or a function that takes a character and returns a boolean.
- * If the next character in the stream 'matches' the given argument, it is consumed and returned.
- * Otherwise, undefined is returned.
- */
- eat(match: string): string;
- eat(match: RegExp): string;
- eat(match: (char: string) => boolean): string;
-
- /**
- * Repeatedly calls eat with the given argument, until it fails. Returns true if any characters were eaten.
- */
- eatWhile(match: string): boolean;
- eatWhile(match: RegExp): boolean;
- eatWhile(match: (char: string) => boolean): boolean;
-
- /**
- * Shortcut for eatWhile when matching white-space.
- */
- eatSpace(): boolean;
-
- /**
- * Moves the position to the end of the line.
- */
- skipToEnd(): void;
-
- /**
- * Skips to the next occurrence of the given character, if found on the current line (doesn't advance the stream if
- * the character does not occur on the line).
- *
- * Returns true if the character was found.
- */
- skipTo(ch: string): boolean;
-
- /**
- * Act like a multi-character eat - if consume is true or not given - or a look-ahead that doesn't update the stream
- * position - if it is false. pattern can be either a string or a regular expression starting with ^. When it is a
- * string, caseFold can be set to true to make the match case-insensitive. When successfully matching a regular
- * expression, the returned value will be the array returned by match, in case you need to extract matched groups.
- */
- match(pattern: string, consume?: boolean, caseFold?: boolean): boolean;
- match(pattern: RegExp, consume?: boolean): string[];
-
- /**
- * Backs up the stream n characters. Backing it up further than the start of the current token will cause things to
- * break, so be careful.
- */
- backUp(n: number): void;
-
- /**
- * Returns the column (taking into account tabs) at which the current token starts.
- */
- column(): number;
-
- /**
- * Tells you how far the current line has been indented, in spaces. Corrects for tab characters.
- */
- indentation(): number;
-
- /**
- * Get the string between the start of the current token and the current stream position.
- */
- current(): string;
- }
-
- /**
- * A Mode is, in the simplest case, a lexer (tokenizer) for your language — a function that takes a character stream as input,
- * advances it past a token, and returns a style for that token. More advanced modes can also handle indentation for the language.
- */
- interface Mode {
- /**
- * This function should read one token from the stream it is given as an argument, optionally update its state,
- * and return a style string, or null for tokens that do not have to be styled. Multiple styles can be returned, separated by spaces.
- */
- token(stream: StringStream, state: T): string;
-
- /**
- * A function that produces a state object to be used at the start of a document.
- */
- startState?: () => T;
- /**
- * For languages that have significant blank lines, you can define a blankLine(state) method on your mode that will get called
- * whenever a blank line is passed over, so that it can update the parser state.
- */
- blankLine?: (state: T) => void;
- /**
- * Given a state returns a safe copy of that state.
- */
- copyState?: (state: T) => T;
-
- /**
- * The indentation method should inspect the given state object, and optionally the textAfter string, which contains the text on
- * the line that is being indented, and return an integer, the amount of spaces to indent.
- */
- indent?: (state: T, textAfter: string) => number;
-
- /** The four below strings are used for working with the commenting addon. */
- /**
- * String that starts a line comment.
- */
- lineComment?: string;
- /**
- * String that starts a block comment.
- */
- blockCommentStart?: string;
- /**
- * String that ends a block comment.
- */
- blockCommentEnd?: string;
- /**
- * String to put at the start of continued lines in a block comment.
- */
- blockCommentLead?: string;
-
- /**
- * Trigger a reindent whenever one of the characters in the string is typed.
- */
- electricChars?: string;
- /**
- * Trigger a reindent whenever the regex matches the part of the line before the cursor.
- */
- electricinput?: RegExp;
- }
+ var commands: {
+ [key: string]: any;
+ };
- /**
- * A function that, given a CodeMirror configuration object and an optional mode configuration object, returns a mode object.
- */
- interface ModeFactory {
- (config: CodeMirror.EditorConfiguration, modeOptions?: any): Mode;
+ // findMode* functions are from loading the codemirror/mode/meta module
+ interface modespec {
+ ext?: string[];
+ name?: string;
+ mode: string;
+ mime: string;
}
- /**
- * id will be the id for the defined mode. Typically, you should use this second argument to defineMode as your module scope function
- * (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.
- */
- function defineMode(id: string, modefactory: ModeFactory): void;
- function defineMode(
- id: string,
- modefactory: ModeFactory,
- base: any
+ function runMode(
+ code: string,
+ mode: modespec | string,
+ el: HTMLElement
): void;
- /**
- * Define a mimetype.
- */
- function defineMIME(mimetype: string, mode: any): void;
-
- /**
- * A mode that encompasses many mode types.
- */
- function multiplexingMode(...modes: any[]): Mode;
-
- /**
- * The first argument is a configuration object as passed to the mode constructor function, and the second argument
- * is a mode specification as in the EditorConfiguration mode option.
- */
- function getMode(
- config: CodeMirror.EditorConfiguration,
- mode: any
- ): Mode;
-
- /**
- * Utility function from the overlay.js addon that allows modes to be combined. The mode given as the base argument takes care of
- * most of the normal mode functionality, but a second (typically simple) mode is used, which can override the style of text.
- * Both modes get to parse all of the text, but when both assign a non-null style to a piece of code, the overlay wins, unless
- * the combine argument was true and not overridden, or state.overlay.combineTokens was true, in which case the styles are combined.
- */
- function overlayMode(
- base: Mode,
- overlay: Mode,
- combine?: boolean
- ): Mode;
-
- /**
- * async specifies that the lint process runs asynchronously. hasGutters specifies that lint errors should be displayed in the CodeMirror
- * gutter, note that you must use this in conjunction with [ "CodeMirror-lint-markers" ] as an element in the gutters argument on
- * initialization of the CodeMirror instance.
- */
- interface LintStateOptions {
- async: boolean;
- hasGutters: boolean;
- }
+ function findModeByName(name: string): modespec;
+ function findModeByExtension(name: string): modespec;
+ function findModeByFileName(name: string): modespec;
+ function findModeByMIME(mime: string): modespec;
- /**
- * Adds the getAnnotations callback to LintStateOptions which may be overridden by the user if they choose use their own
- * linter.
- */
- interface LintOptions extends LintStateOptions {
- getAnnotations: AnnotationsCallback;
- }
+ var modes: {
+ [key: string]: any;
+ };
- /**
- * A function that calls the updateLintingCallback with any errors found during the linting process.
- */
- interface AnnotationsCallback {
- (
- content: string,
- updateLintingCallback: UpdateLintingCallback,
- options: LintStateOptions,
- codeMirror: Editor
- ): void;
- }
+ var mimeModes: {
+ [key: string]: any;
+ };
- /**
- * A function that, given an array of annotations, updates the CodeMirror linting GUI with those annotations
- */
- interface UpdateLintingCallback {
- (codeMirror: Editor, annotations: Annotation[]): void;
+ // come back to this later
+ interface Context {
+ state: any;
+ doc: Document;
+ line: number;
+ maxLookAhead: number;
+ baseTokens: string[];
+ baseTokenPos: number;
}
- /**
- * An annotation contains a description of a lint error, detailing the location of the error within the code, the severity of the error,
- * and an explanation as to why the error was thrown.
- */
- interface Annotation {
- from: Position;
- message?: string;
- severity?: string;
- to?: Position;
+ interface StringStream {
+ lineOracle: Context;
}
}
-
-declare module 'codemirror' {
- export = CodeMirror;
-}
diff --git a/packages/documentsearch-extension/package.json b/packages/documentsearch-extension/package.json
new file mode 100644
index 000000000000..b098e0fc1929
--- /dev/null
+++ b/packages/documentsearch-extension/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@jupyterlab/documentsearch-extension",
+ "version": "0.19.1",
+ "description": "Search document types",
+ "homepage": "https://github.com/jupyterlab/jupyterlab",
+ "bugs": {
+ "url": "https://github.com/jupyterlab/jupyterlab/issues"
+ },
+ "license": "BSD-3-Clause",
+ "author": "Project Jupyter",
+ "files": [
+ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
+ "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
+ ],
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "directories": {
+ "lib": "lib/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jupyterlab/jupyterlab.git"
+ },
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf lib",
+ "prepublishOnly": "npm run build",
+ "watch": "tsc -w --listEmittedFiles"
+ },
+ "dependencies": {
+ "@jupyterlab/application": "^0.19.1",
+ "@jupyterlab/apputils": "^0.19.1",
+ "@jupyterlab/cells": "^0.19.1",
+ "@jupyterlab/codeeditor": "^0.19.1",
+ "@jupyterlab/codemirror": "^0.19.1",
+ "@jupyterlab/docregistry": "^0.19.1",
+ "@jupyterlab/mainmenu": "^0.8.1",
+ "@jupyterlab/notebook": "^0.19.2",
+ "@phosphor/disposable": "^1.1.2",
+ "@phosphor/messaging": "^1.2.2",
+ "@phosphor/signaling": "^1.2.2",
+ "@phosphor/widgets": "^1.6.0",
+ "codemirror": "~5.42.0",
+ "react": "~16.4.2",
+ "react-dom": "~16.4.2"
+ },
+ "devDependencies": {
+ "rimraf": "~2.6.2",
+ "typescript": "~3.1.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "jupyterlab": {
+ "extension": true,
+ "schemaDir": "schema"
+ }
+}
diff --git a/packages/documentsearch-extension/schema/plugin.json b/packages/documentsearch-extension/schema/plugin.json
new file mode 100644
index 000000000000..33022fb75822
--- /dev/null
+++ b/packages/documentsearch-extension/schema/plugin.json
@@ -0,0 +1,24 @@
+{
+ "title": "Document Search",
+ "description": "Document search plugin.",
+ "jupyter.lab.shortcuts": [
+ {
+ "command": "documentsearch:start",
+ "keys": ["Accel F"],
+ "selector": "body"
+ },
+ {
+ "command": "documentsearch:highlightNext",
+ "keys": ["Accel G"],
+ "selector": "body"
+ },
+ {
+ "command": "documentsearch:highlightPrevious",
+ "keys": ["Accel Shift G"],
+ "selector": "body"
+ }
+ ],
+ "properties": {},
+ "additionalProperties": false,
+ "type": "object"
+}
diff --git a/packages/documentsearch-extension/src/index.ts b/packages/documentsearch-extension/src/index.ts
new file mode 100644
index 000000000000..0a54133a0e39
--- /dev/null
+++ b/packages/documentsearch-extension/src/index.ts
@@ -0,0 +1,285 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+import '../style/index.css';
+
+import { SearchProviderRegistry } from './searchproviderregistry';
+import { SearchInstance } from './searchinstance';
+
+import {
+ JupyterFrontEnd,
+ JupyterFrontEndPlugin
+} from '@jupyterlab/application';
+
+import { ICommandPalette } from '@jupyterlab/apputils';
+
+import { IMainMenu } from '@jupyterlab/mainmenu';
+
+import { ISignal } from '@phosphor/signaling';
+
+export interface ISearchMatch {
+ /**
+ * Text of the exact match itself
+ */
+ readonly text: string;
+
+ /**
+ * Fragment containing match
+ */
+ readonly fragment: string;
+
+ /**
+ * Line number of match
+ */
+ line: number;
+
+ /**
+ * Column location of match
+ */
+ column: number;
+
+ /**
+ * Index among the other matches
+ */
+ index: number;
+}
+
+/**
+ * This interface is meant to enforce that SearchProviders implement the static
+ * canSearchOn function.
+ */
+export interface ISearchProviderConstructor {
+ new (): ISearchProvider;
+ /**
+ * Report whether or not this provider has the ability to search on the given object
+ */
+ canSearchOn(domain: any): boolean;
+}
+
+export interface ISearchProvider {
+ /**
+ * Initialize the search using the provided options. Should update the UI
+ * to highlight all matches and "select" whatever the first match should be.
+ *
+ * @param query A RegExp to be use to perform the search
+ * @param searchTarget The widget to be searched
+ *
+ * @returns A promise that resolves with a list of all matches
+ */
+ startQuery(query: RegExp, searchTarget: any): Promise;
+
+ /**
+ * Clears state of a search provider to prepare for startQuery to be called
+ * in order to start a new query or refresh an existing one.
+ *
+ * @returns A promise that resolves when the search provider is ready to
+ * begin a new search.
+ */
+ endQuery(): Promise;
+
+ /**
+ * Resets UI state as it was before the search process began. Cleans up and
+ * disposes of all internal state.
+ *
+ * @returns A promise that resolves when all state has been cleaned up.
+ */
+ endSearch(): Promise;
+
+ /**
+ * Move the current match indicator to the next match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ highlightNext(): Promise;
+
+ /**
+ * Move the current match indicator to the previous match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ highlightPrevious(): Promise;
+
+ /**
+ * The same list of matches provided by the startQuery promise resoluton
+ */
+ readonly matches: ISearchMatch[];
+
+ /**
+ * Signal indicating that something in the search has changed, so the UI should update
+ */
+ readonly changed: ISignal;
+
+ /**
+ * The current index of the selected match.
+ */
+ readonly currentMatchIndex: number | null;
+}
+
+export interface IDisplayState {
+ /**
+ * The index of the currently selected match
+ */
+ currentIndex: number;
+
+ /**
+ * The total number of matches found in the document
+ */
+ totalMatches: number;
+
+ /**
+ * Should the search be case sensitive?
+ */
+ caseSensitive: boolean;
+
+ /**
+ * Should the search string be treated as a RegExp?
+ */
+ useRegex: boolean;
+
+ /**
+ * The text in the entry
+ */
+ inputText: string;
+
+ /**
+ * The query constructed from the text and the case/regex flags
+ */
+ query: RegExp;
+
+ /**
+ * An error message (used for bad regex syntax)
+ */
+ errorMessage: string;
+
+ /**
+ * Should the focus forced into the input on the next render?
+ */
+ forceFocus: boolean;
+}
+
+/**
+ * Initialization data for the document-search extension.
+ */
+const extension: JupyterFrontEndPlugin = {
+ id: '@jupyterlab/documentsearch:plugin',
+ autoStart: true,
+ requires: [ICommandPalette],
+ optional: [IMainMenu],
+ activate: (
+ app: JupyterFrontEnd,
+ palette: ICommandPalette,
+ mainMenu: IMainMenu | null
+ ) => {
+ // Create registry, retrieve all default providers
+ const registry: SearchProviderRegistry = new SearchProviderRegistry();
+ const activeSearches: {
+ [key: string]: SearchInstance;
+ } = {};
+
+ const startCommand: string = 'documentsearch:start';
+ const nextCommand: string = 'documentsearch:highlightNext';
+ const prevCommand: string = 'documentsearch:highlightPrevious';
+ app.commands.addCommand(startCommand, {
+ label: 'Find…',
+ isEnabled: () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ return registry.getProviderForWidget(currentWidget) !== undefined;
+ },
+ execute: () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ const widgetId = currentWidget.id;
+ let searchInstance = activeSearches[widgetId];
+ if (!searchInstance) {
+ const searchProvider = registry.getProviderForWidget(currentWidget);
+ if (!searchProvider) {
+ return;
+ }
+ searchInstance = new SearchInstance(currentWidget, searchProvider);
+
+ activeSearches[widgetId] = searchInstance;
+ // find next and previous are now enabled
+ app.commands.notifyCommandChanged();
+
+ searchInstance.disposed.connect(() => {
+ delete activeSearches[widgetId];
+ // find next and previous are now not enabled
+ app.commands.notifyCommandChanged();
+ });
+ }
+ searchInstance.focusInput();
+ }
+ });
+
+ app.commands.addCommand(nextCommand, {
+ label: 'Find Next',
+ isEnabled: () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ return !!activeSearches[currentWidget.id];
+ },
+ execute: async () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ const instance = activeSearches[currentWidget.id];
+ if (!instance) {
+ return;
+ }
+
+ await instance.provider.highlightNext();
+ instance.updateIndices();
+ }
+ });
+
+ app.commands.addCommand(prevCommand, {
+ label: 'Find Previous',
+ isEnabled: () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ return !!activeSearches[currentWidget.id];
+ },
+ execute: async () => {
+ const currentWidget = app.shell.currentWidget;
+ if (!currentWidget) {
+ return;
+ }
+ const instance = activeSearches[currentWidget.id];
+ if (!instance) {
+ return;
+ }
+
+ await instance.provider.highlightPrevious();
+ instance.updateIndices();
+ }
+ });
+
+ // Add the command to the palette.
+ palette.addItem({ command: startCommand, category: 'Main Area' });
+ palette.addItem({ command: nextCommand, category: 'Main Area' });
+ palette.addItem({ command: prevCommand, category: 'Main Area' });
+
+ // Add main menu notebook menu.
+ if (mainMenu) {
+ mainMenu.editMenu.addGroup(
+ [
+ { command: startCommand },
+ { command: nextCommand },
+ { command: prevCommand }
+ ],
+ 10
+ );
+ }
+ }
+};
+
+export default extension;
diff --git a/packages/documentsearch-extension/src/providers/codemirrorsearchprovider.ts b/packages/documentsearch-extension/src/providers/codemirrorsearchprovider.ts
new file mode 100644
index 000000000000..a50f08d8b50b
--- /dev/null
+++ b/packages/documentsearch-extension/src/providers/codemirrorsearchprovider.ts
@@ -0,0 +1,410 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+/*
+ Parts of the implementation of the search in this file were derived from
+ CodeMirror's search at:
+ https://github.com/codemirror/CodeMirror/blob/c2676685866c571a1c9c82cb25018cc08b4d42b2/addon/search/search.js
+ which is licensed with the following license:
+
+ MIT License
+
+ Copyright (C) 2017 by Marijn Haverbeke and others
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+import * as CodeMirror from 'codemirror';
+
+import { ISearchProvider, ISearchMatch } from '../index';
+
+import { CodeMirrorEditor } from '@jupyterlab/codemirror';
+import { CodeEditor } from '@jupyterlab/codeeditor';
+
+import { ISignal, Signal } from '@phosphor/signaling';
+
+type MatchMap = { [key: number]: { [key: number]: ISearchMatch } };
+
+export class CodeMirrorSearchProvider implements ISearchProvider {
+ /**
+ * Initialize the search using the provided options. Should update the UI
+ * to highlight all matches and "select" whatever the first match should be.
+ *
+ * @param query A RegExp to be use to perform the search
+ * @param searchTarget The widget to be searched
+ *
+ * @returns A promise that resolves with a list of all matches
+ */
+ async startQuery(query: RegExp, domain: any): Promise {
+ if (domain instanceof CodeMirrorEditor) {
+ this._cm = domain;
+ } else if (domain) {
+ this._cm = domain.content.editor;
+ }
+ await this.endQuery();
+
+ this._query = query;
+
+ CodeMirror.on(this._cm.doc, 'change', this._onDocChanged.bind(this));
+ this._refreshOverlay();
+ this._setInitialMatches(query);
+
+ const matches = this._parseMatchesFromState();
+ if (matches.length === 0) {
+ return [];
+ }
+ if (!this.isSubProvider) {
+ const cursorMatch = this._findNext(false);
+ const match = this._matchState[cursorMatch.from.line][
+ cursorMatch.from.ch
+ ];
+ this._matchIndex = match.index;
+ }
+ return matches;
+ }
+
+ /**
+ * Clears state of a search provider to prepare for startQuery to be called
+ * in order to start a new query or refresh an existing one.
+ *
+ * @returns A promise that resolves when the search provider is ready to
+ * begin a new search.
+ */
+ async endQuery(): Promise {
+ this._matchState = {};
+ this._matchIndex = null;
+ this._cm.removeOverlay(this._overlay);
+ CodeMirror.off(this._cm.doc, 'change', this._onDocChanged.bind(this));
+ }
+
+ /**
+ * Resets UI state, removes all matches.
+ *
+ * @returns A promise that resolves when all state has been cleaned up.
+ */
+ async endSearch(): Promise {
+ if (!this.isSubProvider) {
+ this._cm.focus();
+ }
+ this.endQuery();
+ }
+
+ /**
+ * Move the current match indicator to the next match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ async highlightNext(): Promise {
+ const cursorMatch = this._findNext(false);
+ if (!cursorMatch) {
+ return;
+ }
+ const match = this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
+ this._matchIndex = match.index;
+ return match;
+ }
+
+ /**
+ * Move the current match indicator to the previous match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ async highlightPrevious(): Promise {
+ const cursorMatch = this._findNext(true);
+ if (!cursorMatch) {
+ return;
+ }
+ const match = this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
+ this._matchIndex = match.index;
+ return match;
+ }
+
+ /**
+ * Report whether or not this provider has the ability to search on the given object
+ */
+ static canSearchOn(domain: any): boolean {
+ return domain.content && domain.content.editor instanceof CodeMirrorEditor;
+ }
+
+ /**
+ * The same list of matches provided by the startQuery promise resoluton
+ */
+ get matches(): ISearchMatch[] {
+ return this._parseMatchesFromState();
+ }
+
+ /**
+ * Signal indicating that something in the search has changed, so the UI should update
+ */
+ get changed(): ISignal {
+ return this._changed;
+ }
+
+ /**
+ * The current index of the selected match.
+ */
+ get currentMatchIndex(): number {
+ return this._matchIndex;
+ }
+
+ clearSelection(): void {
+ return null;
+ }
+
+ /**
+ * Set whether or not the CodemirrorSearchProvider will wrap to the beginning
+ * or end of the document on invocations of highlightNext or highlightPrevious, respectively
+ */
+ isSubProvider = false;
+
+ private _onDocChanged(_: any, changeObj: CodeMirror.EditorChange) {
+ // If we get newlines added/removed, the line numbers across the
+ // match state are all shifted, so here we need to recalculate it
+ if (changeObj.text.length > 1 || changeObj.removed.length > 1) {
+ this._setInitialMatches(this._query);
+ this._changed.emit(undefined);
+ }
+ }
+
+ private _refreshOverlay() {
+ this._cm.operation(() => {
+ // clear search first
+ this._cm.removeOverlay(this._overlay);
+ this._overlay = this._getSearchOverlay();
+ this._cm.addOverlay(this._overlay);
+ this._changed.emit(null);
+ });
+ }
+
+ /**
+ * Do a full search on the entire document.
+ *
+ * This manually constructs the initial match state across the whole
+ * document. This must be done manually because the codemirror overlay
+ * is lazy-loaded, so it will only tokenize lines that are in or near
+ * the viewport. This is sufficient for efficiently maintaining the
+ * state when changes are made to the document, as changes occur in or
+ * near the viewport, but to scan the whole document, a manual search
+ * across the entire content is required.
+ *
+ * @param query The search term
+ */
+ private _setInitialMatches(query: RegExp) {
+ this._matchState = {};
+
+ const start = CodeMirror.Pos(this._cm.doc.firstLine(), 0);
+ const end = CodeMirror.Pos(this._cm.doc.lastLine());
+ const content = this._cm.doc.getRange(start, end);
+ const lines = content.split('\n');
+ let totalMatchIndex = 0;
+ lines.forEach((line, lineNumber) => {
+ query.lastIndex = 0;
+ let match = query.exec(line);
+ while (match) {
+ const col = match.index;
+ const matchObj: ISearchMatch = {
+ text: match[0],
+ line: lineNumber,
+ column: col,
+ fragment: line,
+ index: totalMatchIndex
+ };
+ if (!this._matchState[lineNumber]) {
+ this._matchState[lineNumber] = {};
+ }
+ this._matchState[lineNumber][col] = matchObj;
+ match = query.exec(line);
+ }
+ });
+ }
+
+ private _getSearchOverlay() {
+ return {
+ /**
+ * Token function is called when a line needs to be processed -
+ * when the overlay is intially created, it's called on all lines;
+ * when a line is modified and needs to be re-evaluated, it's called
+ * on just that line.
+ *
+ * This implementation of the token function both constructs/maintains
+ * the overlay and keeps track of the match state as the document is
+ * updated while a search is active.
+ */
+ token: (stream: CodeMirror.StringStream) => {
+ const currentPos = stream.pos;
+ this._query.lastIndex = currentPos;
+ const lineText = stream.string;
+ const match = this._query.exec(lineText);
+ const line = (stream as any).lineOracle.line;
+
+ // If starting at position 0, the tokenization of this line has just started.
+ // Blow away everything on this line in the state so it can be updated.
+ if (
+ stream.start === currentPos &&
+ currentPos === 0 &&
+ !!this._matchState[line]
+ ) {
+ this._matchState[line] = {};
+ }
+ if (match && match.index === currentPos) {
+ // found match, add it to state
+ const matchLength = match[0].length;
+ const matchObj: ISearchMatch = {
+ text: lineText.substr(currentPos, matchLength),
+ line: line,
+ column: currentPos,
+ fragment: lineText,
+ index: 0 // fill in index when flattening, later
+ };
+ if (!this._matchState[line]) {
+ this._matchState[line] = {};
+ }
+ this._matchState[line][currentPos] = matchObj;
+ // move the stream along and return searching style for the token
+ stream.pos += matchLength || 1;
+
+ // if the last thing on the line was a match, make sure we still
+ // emit the changed signal so the display can pick up the updates
+ if (stream.eol) {
+ this._changed.emit(undefined);
+ }
+ return 'searching';
+ } else if (match) {
+ // there's a match in the stream, advance the stream to its position
+ stream.pos = match.index;
+ } else {
+ // no matches, consume the rest of the stream
+ this._changed.emit(undefined);
+ stream.skipToEnd();
+ }
+ }
+ };
+ }
+
+ private _findNext(reverse: boolean): Private.ICodeMirrorMatch {
+ return this._cm.operation(() => {
+ const caseSensitive = this._query.ignoreCase;
+ const cursorToGet = reverse ? 'from' : 'to';
+ const lastPosition = this._cm.getCursor(cursorToGet);
+ const position = this._toEditorPos(lastPosition);
+ let cursor: CodeMirror.SearchCursor = this._cm.getSearchCursor(
+ this._query,
+ lastPosition,
+ !caseSensitive
+ );
+ if (!cursor.find(reverse)) {
+ // if we don't want to loop, no more matches found, reset the cursor and exit
+ if (this.isSubProvider) {
+ this._cm.setCursorPosition(position);
+ this._matchIndex = null;
+ return null;
+ }
+
+ // if we do want to loop, try searching from the bottom/top
+ const startOrEnd = reverse
+ ? CodeMirror.Pos(this._cm.lastLine())
+ : CodeMirror.Pos(this._cm.firstLine(), 0);
+ cursor = this._cm.getSearchCursor(
+ this._query,
+ startOrEnd,
+ !caseSensitive
+ );
+ if (!cursor.find(reverse)) {
+ return null;
+ }
+ }
+ const fromPos: CodeMirror.Position = cursor.from();
+ const toPos: CodeMirror.Position = cursor.to();
+ const selRange: CodeEditor.IRange = {
+ start: {
+ line: fromPos.line,
+ column: fromPos.ch
+ },
+ end: {
+ line: toPos.line,
+ column: toPos.ch
+ }
+ };
+
+ this._cm.setSelection(selRange);
+ this._cm.scrollIntoView(
+ {
+ from: fromPos,
+ to: toPos
+ },
+ 100
+ );
+ return {
+ from: fromPos,
+ to: toPos
+ };
+ });
+ }
+
+ private _parseMatchesFromState(): ISearchMatch[] {
+ let index = 0;
+ // Flatten state map and update the index of each match
+ const matches: ISearchMatch[] = Object.keys(this._matchState).reduce(
+ (result: ISearchMatch[], lineNumber: string) => {
+ const lineKey = parseInt(lineNumber, 10);
+ const lineMatches: { [key: number]: ISearchMatch } = this._matchState[
+ lineKey
+ ];
+ Object.keys(lineMatches).forEach((pos: string) => {
+ const posKey = parseInt(pos, 10);
+ const match: ISearchMatch = lineMatches[posKey];
+ match.index = index;
+ index += 1;
+ result.push(match);
+ });
+ return result;
+ },
+ []
+ );
+ return matches;
+ }
+
+ private _toEditorPos(posIn: CodeMirror.Position): CodeEditor.IPosition {
+ return {
+ line: posIn.line,
+ column: posIn.ch
+ };
+ }
+
+ private _query: RegExp;
+ private _cm: CodeMirrorEditor;
+ private _matchIndex: number;
+ private _matchState: MatchMap = {};
+ private _changed = new Signal(this);
+ private _overlay: any;
+}
+
+export class SearchState {
+ public posFrom: CodeMirror.Position;
+ public posTo: CodeMirror.Position;
+ public lastQuery: string;
+ public query: RegExp;
+}
+
+namespace Private {
+ export interface ICodeMirrorMatch {
+ from: CodeMirror.Position;
+ to: CodeMirror.Position;
+ }
+}
diff --git a/packages/documentsearch-extension/src/providers/notebooksearchprovider.ts b/packages/documentsearch-extension/src/providers/notebooksearchprovider.ts
new file mode 100644
index 000000000000..ae1f646bf19f
--- /dev/null
+++ b/packages/documentsearch-extension/src/providers/notebooksearchprovider.ts
@@ -0,0 +1,285 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+import { ISearchProvider, ISearchMatch } from '../index';
+import { CodeMirrorSearchProvider } from './codemirrorsearchprovider';
+
+import { NotebookPanel } from '@jupyterlab/notebook';
+import { CodeMirrorEditor } from '@jupyterlab/codemirror';
+import { Cell, MarkdownCell } from '@jupyterlab/cells';
+
+import { Signal, ISignal } from '@phosphor/signaling';
+
+import CodeMirror from 'codemirror';
+
+interface ICellSearchPair {
+ cell: Cell;
+ provider: ISearchProvider;
+}
+
+export class NotebookSearchProvider implements ISearchProvider {
+ /**
+ * Initialize the search using the provided options. Should update the UI
+ * to highlight all matches and "select" whatever the first match should be.
+ *
+ * @param query A RegExp to be use to perform the search
+ * @param searchTarget The widget to be searched
+ *
+ * @returns A promise that resolves with a list of all matches
+ */
+ async startQuery(
+ query: RegExp,
+ searchTarget: NotebookPanel
+ ): Promise {
+ this._searchTarget = searchTarget;
+ const cells = this._searchTarget.content.widgets;
+
+ this._query = query;
+ // Listen for cell model change to redo the search in case of
+ // new/pasted/deleted cells
+ const cellList = this._searchTarget.model.cells;
+ cellList.changed.connect(
+ this._restartQuery.bind(this),
+ this
+ );
+
+ let indexTotal = 0;
+ const allMatches: ISearchMatch[] = [];
+ // For each cell, create a search provider and collect the matches
+
+ for (let cell of cells) {
+ const cmEditor = cell.editor as CodeMirrorEditor;
+ const cmSearchProvider = new CodeMirrorSearchProvider();
+ cmSearchProvider.isSubProvider = true;
+
+ // If a rendered MarkdownCell contains a match, unrender it so that
+ // CodeMirror can show the match(es). If the MarkdownCell is not
+ // rendered, putting CodeMirror on the page, CodeMirror will not run
+ // the mode, which will prevent the search from occurring.
+ // Keep track so that the cell can be rerendered when the search is ended
+ // or if there are no matches
+ let cellShouldReRender = false;
+ if (cell instanceof MarkdownCell && cell.rendered) {
+ cell.rendered = false;
+ cellShouldReRender = true;
+ }
+
+ // Unhide hidden cells for the same reason as above
+ if (cell.inputHidden) {
+ cell.inputHidden = false;
+ }
+ // chain promises to ensure indexing is sequential
+ const matchesFromCell = await cmSearchProvider.startQuery(
+ query,
+ cmEditor
+ );
+ if (cell instanceof MarkdownCell) {
+ if (matchesFromCell.length !== 0) {
+ // un-render markdown cells with matches
+ this._unRenderedMarkdownCells.push(cell);
+ } else if (cellShouldReRender) {
+ cell.rendered = true;
+ }
+ }
+
+ // update the match indices to reflect the whole document index values
+ matchesFromCell.forEach(match => {
+ match.index = match.index + indexTotal;
+ });
+ indexTotal += matchesFromCell.length;
+
+ // search has been initialized, connect the changed signal
+ cmSearchProvider.changed.connect(
+ this._onCmSearchProviderChanged,
+ this
+ );
+
+ allMatches.concat(matchesFromCell);
+
+ this._cmSearchProviders.push({
+ cell: cell,
+ provider: cmSearchProvider
+ });
+ }
+
+ this._currentMatch = await this._stepNext();
+
+ return allMatches;
+ }
+
+ /**
+ * Clears state of a search provider to prepare for startQuery to be called
+ * in order to start a new query or refresh an existing one.
+ *
+ * @returns A promise that resolves when the search provider is ready to
+ * begin a new search.
+ */
+ async endQuery(): Promise {
+ this._cmSearchProviders.forEach(({ provider }) => {
+ provider.endQuery();
+ provider.changed.disconnect(this._onCmSearchProviderChanged, this);
+ });
+ Signal.disconnectBetween(this._searchTarget.model.cells, this);
+
+ this._cmSearchProviders = [];
+ this._unRenderedMarkdownCells.forEach((cell: MarkdownCell) => {
+ // Guard against the case where markdown cells have been deleted
+ if (!cell.isDisposed) {
+ cell.rendered = true;
+ }
+ });
+ this._unRenderedMarkdownCells = [];
+ }
+
+ /**
+ * Resets UI state, removes all matches.
+ *
+ * @returns A promise that resolves when all state has been cleaned up.
+ */
+ async endSearch(): Promise {
+ Signal.disconnectBetween(this._searchTarget.model.cells, this);
+
+ const index = this._searchTarget.content.activeCellIndex;
+ this._cmSearchProviders.forEach(({ provider }) => {
+ provider.endSearch();
+ provider.changed.disconnect(this._onCmSearchProviderChanged, this);
+ });
+
+ this._cmSearchProviders = [];
+ this._unRenderedMarkdownCells.forEach((cell: MarkdownCell) => {
+ cell.rendered = true;
+ });
+ this._unRenderedMarkdownCells = [];
+
+ this._searchTarget.content.activeCellIndex = index;
+ this._searchTarget.content.mode = 'edit';
+ this._searchTarget = null;
+ this._currentMatch = null;
+ }
+
+ /**
+ * Move the current match indicator to the next match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ async highlightNext(): Promise {
+ this._currentMatch = await this._stepNext();
+ return this._currentMatch;
+ }
+
+ /**
+ * Move the current match indicator to the previous match.
+ *
+ * @returns A promise that resolves once the action has completed.
+ */
+ async highlightPrevious(): Promise {
+ this._currentMatch = await this._stepNext(true);
+ return this._currentMatch;
+ }
+
+ /**
+ * Report whether or not this provider has the ability to search on the given object
+ */
+ static canSearchOn(domain: any): boolean {
+ // check to see if the CMSearchProvider can search on the
+ // first cell, false indicates another editor is present
+ return domain instanceof NotebookPanel;
+ }
+
+ /**
+ * The same list of matches provided by the startQuery promise resoluton
+ */
+ get matches(): ISearchMatch[] {
+ return [].concat(...this._getMatchesFromCells());
+ }
+
+ /**
+ * Signal indicating that something in the search has changed, so the UI should update
+ */
+ get changed(): ISignal {
+ return this._changed;
+ }
+
+ /**
+ * The current index of the selected match.
+ */
+ get currentMatchIndex(): number {
+ if (!this._currentMatch) {
+ return 0;
+ }
+ return this._currentMatch.index;
+ }
+
+ private async _stepNext(
+ reverse = false,
+ steps = 0
+ ): Promise {
+ const notebook = this._searchTarget.content;
+ const activeCell: Cell = notebook.activeCell;
+ const cellIndex = notebook.widgets.indexOf(activeCell);
+ const numCells = notebook.widgets.length;
+ const { provider } = this._cmSearchProviders[cellIndex];
+
+ // highlightNext/Previous will not be able to search rendered MarkdownCells or
+ // hidden code cells, but that is okay here because in startQuery, we unrendered
+ // all cells with matches and unhid all cells
+ const match = reverse
+ ? await provider.highlightPrevious()
+ : await provider.highlightNext();
+ // If there was no match in this cell, try the next cell
+ if (!match) {
+ // We have looped around the whole notebook and have searched the original
+ // cell once more and found no matches. Do not proceed with incrementing the
+ // active cell index so that the active cell doesn't change
+ if (steps === numCells) {
+ return undefined;
+ }
+ notebook.activeCellIndex =
+ ((reverse ? cellIndex - 1 : cellIndex + 1) + numCells) % numCells;
+ const editor = notebook.activeCell.editor as CodeMirrorEditor;
+ // move the cursor of the next cell to the start/end of the cell so it can
+ // search the whole thing
+ const newPosCM = reverse
+ ? CodeMirror.Pos(editor.lastLine())
+ : CodeMirror.Pos(editor.firstLine(), 0);
+ const newPos = {
+ line: newPosCM.line,
+ column: newPosCM.ch
+ };
+ editor.setCursorPosition(newPos);
+ return this._stepNext(reverse, steps + 1);
+ }
+
+ return match;
+ }
+
+ private async _restartQuery() {
+ await this.endQuery();
+ await this.startQuery(this._query, this._searchTarget);
+ this._changed.emit(undefined);
+ }
+
+ private _getMatchesFromCells(): ISearchMatch[][] {
+ let indexTotal = 0;
+ const result: ISearchMatch[][] = [];
+ this._cmSearchProviders.forEach(({ provider }) => {
+ const cellMatches = provider.matches;
+ cellMatches.forEach(match => {
+ match.index = match.index + indexTotal;
+ });
+ indexTotal += cellMatches.length;
+ result.push(cellMatches);
+ });
+ return result;
+ }
+
+ private _onCmSearchProviderChanged() {
+ this._changed.emit(undefined);
+ }
+
+ private _searchTarget: NotebookPanel;
+ private _query: RegExp;
+ private _cmSearchProviders: ICellSearchPair[] = [];
+ private _currentMatch: ISearchMatch;
+ private _unRenderedMarkdownCells: MarkdownCell[] = [];
+ private _changed = new Signal(this);
+}
diff --git a/packages/documentsearch-extension/src/searchinstance.ts b/packages/documentsearch-extension/src/searchinstance.ts
new file mode 100644
index 000000000000..8ea87f9a7d33
--- /dev/null
+++ b/packages/documentsearch-extension/src/searchinstance.ts
@@ -0,0 +1,191 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { IDisplayState, ISearchProvider } from '.';
+import { createSearchOverlay } from './searchoverlay';
+
+import { MainAreaWidget } from '@jupyterlab/apputils';
+
+import { Widget } from '@phosphor/widgets';
+import { ISignal, Signal } from '@phosphor/signaling';
+import { IDisposable } from '@phosphor/disposable';
+
+/**
+ * Represents a search on a single widget.
+ */
+export class SearchInstance implements IDisposable {
+ constructor(widget: Widget, searchProvider: ISearchProvider) {
+ this._widget = widget;
+ this._activeProvider = searchProvider;
+
+ this._searchWidget = createSearchOverlay({
+ widgetChanged: this._displayUpdateSignal,
+ overlayState: this._displayState,
+ onCaseSensitiveToggled: this._onCaseSensitiveToggled.bind(this),
+ onRegexToggled: this._onRegexToggled.bind(this),
+ onHightlightNext: this._highlightNext.bind(this),
+ onHighlightPrevious: this._highlightPrevious.bind(this),
+ onStartQuery: this._startQuery.bind(this),
+ onEndSearch: this.dispose.bind(this)
+ });
+
+ this._widget.disposed.connect(() => {
+ this.dispose();
+ });
+ this._searchWidget.disposed.connect(() => {
+ this.dispose();
+ });
+
+ // TODO: this does not update if the toolbar changes height.
+ if (this._widget instanceof MainAreaWidget) {
+ // Offset the position of the search widget to not cover the toolbar.
+ this._searchWidget.node.style.top = `${
+ this._widget.toolbar.node.clientHeight
+ }px`;
+ }
+ this._displaySearchWidget();
+ }
+
+ /**
+ * The search widget.
+ */
+ get searchWidget() {
+ return this._searchWidget;
+ }
+
+ /**
+ * The search provider.
+ */
+ get provider() {
+ return this._activeProvider;
+ }
+
+ /**
+ * Focus the search widget input.
+ */
+ focusInput(): void {
+ this._displayState.forceFocus = true;
+
+ // Trigger a rerender without resetting the forceFocus.
+ this._displayUpdateSignal.emit(this._displayState);
+ }
+
+ /**
+ * Updates the match index and total display in the search widget.
+ */
+ updateIndices(): void {
+ this._displayState.totalMatches = this._activeProvider.matches.length;
+ this._displayState.currentIndex = this._activeProvider.currentMatchIndex;
+ this._updateDisplay();
+ }
+
+ private _updateDisplay() {
+ // Reset the focus attribute to make sure we don't steal focus.
+ this._displayState.forceFocus = false;
+
+ // Trigger a rerender
+ this._displayUpdateSignal.emit(this._displayState);
+ }
+
+ private async _startQuery(query: RegExp) {
+ // save the last query (or set it to the current query if this is the first)
+ if (this._activeProvider && this._displayState.query) {
+ await this._activeProvider.endQuery();
+ }
+ this._displayState.query = query;
+ await this._activeProvider.startQuery(query, this._widget);
+ this.updateIndices();
+
+ // this signal should get injected when the widget is
+ // created and hooked up to react!
+ this._activeProvider.changed.connect(
+ this.updateIndices,
+ this
+ );
+ }
+
+ /**
+ * Dispose of the resources held by the search instance.
+ */
+ dispose() {
+ if (this.isDisposed) {
+ return;
+ }
+ this._isDisposed = true;
+
+ // If a query hasn't been executed yet, no need to call endSearch
+ if (this._displayState.query) {
+ this._activeProvider.endSearch();
+ }
+
+ this._searchWidget.dispose();
+ this._disposed.emit(undefined);
+ Signal.clearData(this);
+ }
+
+ /**
+ * Test if the object has been disposed.
+ */
+ get isDisposed(): boolean {
+ return this._isDisposed;
+ }
+
+ /**
+ * A signal emitted when the object is disposed.
+ */
+ get disposed(): ISignal {
+ return this._disposed;
+ }
+
+ /**
+ * Display search widget.
+ */
+ _displaySearchWidget() {
+ if (!this._searchWidget.isAttached) {
+ Widget.attach(this._searchWidget, this._widget.node);
+ }
+ }
+
+ private async _highlightNext() {
+ if (!this._displayState.query) {
+ return;
+ }
+ await this._activeProvider.highlightNext();
+ this.updateIndices();
+ }
+
+ private async _highlightPrevious() {
+ if (!this._displayState.query) {
+ return;
+ }
+ await this._activeProvider.highlightPrevious();
+ this.updateIndices();
+ }
+
+ private _onCaseSensitiveToggled() {
+ this._displayState.caseSensitive = !this._displayState.caseSensitive;
+ this._updateDisplay();
+ }
+
+ private _onRegexToggled() {
+ this._displayState.useRegex = !this._displayState.useRegex;
+ this._updateDisplay();
+ }
+
+ private _widget: Widget;
+ private _displayState: IDisplayState = {
+ currentIndex: 0,
+ totalMatches: 0,
+ caseSensitive: false,
+ useRegex: false,
+ inputText: '',
+ query: null,
+ errorMessage: '',
+ forceFocus: true
+ };
+ private _displayUpdateSignal = new Signal(this);
+ private _activeProvider: ISearchProvider;
+ private _searchWidget: Widget;
+ private _isDisposed = false;
+ private _disposed = new Signal(this);
+}
diff --git a/packages/documentsearch-extension/src/searchoverlay.tsx b/packages/documentsearch-extension/src/searchoverlay.tsx
new file mode 100644
index 000000000000..ef63f4ee4366
--- /dev/null
+++ b/packages/documentsearch-extension/src/searchoverlay.tsx
@@ -0,0 +1,309 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+import * as React from 'react';
+
+import '../style/index.css';
+import { ReactWidget, UseSignal } from '@jupyterlab/apputils';
+import { Signal } from '@phosphor/signaling';
+import { Widget } from '@phosphor/widgets';
+import { IDisplayState } from '.';
+import { SearchInstance } from './searchinstance';
+
+const OVERLAY_CLASS = 'jp-DocumentSearch-overlay';
+const INPUT_CLASS = 'jp-DocumentSearch-input';
+const INPUT_WRAPPER_CLASS = 'jp-DocumentSearch-input-wrapper';
+const REGEX_BUTTON_CLASS_OFF =
+ 'jp-DocumentSearch-input-button-off jp-DocumentSearch-regex-button';
+const REGEX_BUTTON_CLASS_ON =
+ 'jp-DocumentSearch-input-button-on jp-DocumentSearch-regex-button';
+const CASE_BUTTON_CLASS_OFF =
+ 'jp-DocumentSearch-input-button-off jp-DocumentSearch-case-button';
+const CASE_BUTTON_CLASS_ON =
+ 'jp-DocumentSearch-input-button-on jp-DocumentSearch-case-button';
+const INDEX_COUNTER_CLASS = 'jp-DocumentSearch-index-counter';
+const UP_DOWN_BUTTON_WRAPPER_CLASS = 'jp-DocumentSearch-up-down-wrapper';
+const UP_BUTTON_CLASS = 'jp-DocumentSearch-up-button';
+const DOWN_BUTTON_CLASS = 'jp-DocumentSearch-down-button';
+const CLOSE_BUTTON_CLASS = 'jp-DocumentSearch-close-button';
+const REGEX_ERROR_CLASS = 'jp-DocumentSearch-regex-error';
+
+interface ISearchEntryProps {
+ onCaseSensitiveToggled: Function;
+ onRegexToggled: Function;
+ onKeydown: Function;
+ onChange: Function;
+ caseSensitive: boolean;
+ useRegex: boolean;
+ inputText: string;
+ forceFocus: boolean;
+}
+
+class SearchEntry extends React.Component {
+ constructor(props: ISearchEntryProps) {
+ super(props);
+ }
+
+ /**
+ * Focus the input.
+ */
+ focusInput() {
+ (this.refs.searchInputNode as HTMLInputElement).focus();
+ }
+
+ componentDidUpdate() {
+ if (this.props.forceFocus) {
+ this.focusInput();
+ }
+ }
+
+ render() {
+ const caseButtonToggleClass = this.props.caseSensitive
+ ? CASE_BUTTON_CLASS_ON
+ : CASE_BUTTON_CLASS_OFF;
+ const regexButtonToggleClass = this.props.useRegex
+ ? REGEX_BUTTON_CLASS_ON
+ : REGEX_BUTTON_CLASS_OFF;
+
+ return (
+