Selaa lähdekoodia

escape to close, address focus issue, add docs to searchproviders

Andrew Schlaepfer 6 vuotta sitten
vanhempi
commit
38224e7700

+ 5 - 3
packages/documentsearch-extension/src/index.ts

@@ -230,9 +230,7 @@ namespace Private {
   ): void {
     const widgetId = currentWidget.id;
     if (activeSearches[widgetId]) {
-      activeSearches[widgetId].focus();
-      // TODO: focusing when the notebook is in edit mode somehow does not
-      // actually focus. Perhaps something in the notebook is stealing focus?
+      activeSearches[widgetId].focusInput();
       return;
     }
     const searchProvider = registry.getProviderForWidget(currentWidget);
@@ -247,6 +245,10 @@ namespace Private {
       delete activeSearches[widgetId];
     });
     Widget.attach(searchInstance.searchWidget, currentWidget.node);
+    // Focusing after attach even though we're focusing on componentDidMount
+    // because the notebook steals focus when switching to command mode on blur.
+    // This is a bit of a kludge to be addressed later.
+    searchInstance.focusInput();
   }
 
   export async function onNextCommand(instance: SearchInstance) {

+ 30 - 1
packages/documentsearch-extension/src/providers/codemirrorsearchprovider.ts

@@ -52,12 +52,13 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
    * @returns A promise that resolves with a list of all matches
    */
   async startSearch(query: RegExp, domain: any): Promise<ISearchMatch[]> {
-    this._query = query;
     if (domain instanceof CodeMirrorEditor) {
       this._cm = domain;
     } else if (domain) {
       this._cm = domain.content.editor;
     }
+
+    this._query = query;
     this._clearSearch();
 
     CodeMirror.on(this._cm.doc, 'change', this._onDocChanged.bind(this));
@@ -79,7 +80,13 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
     return matches;
   }
 
+  /**
+   * Resets UI state, removes all matches.
+   *
+   * @returns A promise that resolves when all state has been cleaned up.
+   */
   async endSearch(): Promise<void> {
+    this._cm.focus();
     this._matchState = {};
     this._matchIndex = 0;
     if (this._cm) {
@@ -88,6 +95,11 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
     CodeMirror.off(this._cm.doc, 'change', this._onDocChanged.bind(this));
   }
 
+  /**
+   * Move the current match indicator to the next match.
+   *
+   * @returns A promise that resolves once the action has completed.
+   */
   async highlightNext(): Promise<ISearchMatch | undefined> {
     const cursorMatch = this._findNext(false);
     if (!cursorMatch) {
@@ -98,6 +110,11 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
     return match;
   }
 
+  /**
+   * Move the current match indicator to the previous match.
+   *
+   * @returns A promise that resolves once the action has completed.
+   */
   async highlightPrevious(): Promise<ISearchMatch | undefined> {
     const cursorMatch = this._findNext(true);
     if (!cursorMatch) {
@@ -108,18 +125,30 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
     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 startSearch 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<this, void> {
     return this._changed;
   }
 
+  /**
+   * The current index of the selected match.
+   */
   get currentMatchIndex(): number {
     return this._matchIndex;
   }

+ 45 - 4
packages/documentsearch-extension/src/providers/notebooksearchprovider.ts

@@ -3,7 +3,7 @@
 import { ISearchProvider, ISearchMatch } from '../index';
 import { CodeMirrorSearchProvider } from './codemirrorsearchprovider';
 
-import { NotebookPanel, Notebook } from '@jupyterlab/notebook';
+import { NotebookPanel } from '@jupyterlab/notebook';
 import { CodeMirrorEditor } from '@jupyterlab/codemirror';
 import { Cell, MarkdownCell } from '@jupyterlab/cells';
 
@@ -17,18 +17,28 @@ interface ICellSearchPair {
 }
 
 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 startSearch(
     query: RegExp,
     searchTarget: NotebookPanel
   ): Promise<ISearchMatch[]> {
     this._searchTarget = searchTarget;
     const cells = this._searchTarget.content.widgets;
+    console.log('startSearch: ', query, this._searchTarget);
 
     // 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._restartSearch.bind(this, query, searchTarget),
+      this._restartSearch.bind(this, query, this._searchTarget),
       this
     );
 
@@ -96,12 +106,21 @@ export class NotebookSearchProvider implements ISearchProvider {
     return allMatches;
   }
 
+  /**
+   * Resets UI state, removes all matches.
+   *
+   * @returns A promise that resolves when all state has been cleaned up.
+   */
   async endSearch(): Promise<void> {
     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._searchTarget.content.activeCellIndex = index;
+    this._searchTarget.content.mode = 'edit';
     this._cmSearchProviders = [];
     this._unRenderedMarkdownCells.forEach((cell: MarkdownCell) => {
       cell.rendered = true;
@@ -111,30 +130,52 @@ export class NotebookSearchProvider implements ISearchProvider {
     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<ISearchMatch | undefined> {
     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<ISearchMatch | undefined> {
     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 startSearch 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<this, void> {
     return this._changed;
   }
 
+  /**
+   * The current index of the selected match.
+   */
   get currentMatchIndex(): number {
     if (!this._currentMatch) {
       return 0;
@@ -185,9 +226,9 @@ export class NotebookSearchProvider implements ISearchProvider {
     return match;
   }
 
-  private async _restartSearch(query: RegExp, searchTarget: NotebookPanel) {
+  private async _restartSearch(query: RegExp) {
     await this.endSearch();
-    await this.startSearch(query, searchTarget);
+    await this.startSearch(query, this._searchTarget);
     this._changed.emit(undefined);
   }
 

+ 1 - 1
packages/documentsearch-extension/src/searchinstance.ts

@@ -55,7 +55,7 @@ export class SearchInstance implements IDisposable {
   /**
    * Focus the search widget input.
    */
-  focus(): void {
+  focusInput(): void {
     this._displayState.forceFocus = true;
 
     // Trigger a rerender without resetting the forceFocus.

+ 9 - 3
packages/documentsearch-extension/src/searchoverlay.tsx

@@ -150,10 +150,16 @@ class SearchOverlay extends React.Component<
   }
 
   private _onKeydown(event: KeyboardEvent) {
-    if (event.keyCode !== 13) {
-      return;
+    if (event.keyCode === 13) {
+      event.preventDefault();
+      event.stopPropagation();
+      this._executeSearch(!event.shiftKey);
+    }
+    if (event.keyCode === 27) {
+      event.preventDefault();
+      event.stopPropagation();
+      this.props.onEndSearch();
     }
-    this._executeSearch(!event.shiftKey);
   }
 
   private _executeSearch(goForward: boolean) {