Skip to content

Commit

Permalink
live update index
Browse files Browse the repository at this point in the history
  • Loading branch information
aschlaep committed Jan 7, 2019
1 parent 8aea812 commit 7d3989e
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 57 deletions.
18 changes: 10 additions & 8 deletions packages/documentsearch-extension/src/executor.ts
@@ -1,9 +1,11 @@
import { SearchProviderRegistry } from './searchproviderregistry';

import { ISearchMatch, ISearchProvider } from './index';
import { Widget } from '@phosphor/widgets';

import { ApplicationShell } from '@jupyterlab/application';

import { ISignal } from '@phosphor/signaling';
import { Widget } from '@phosphor/widgets';

export class Executor {
constructor(registry: SearchProviderRegistry, shell: ApplicationShell) {
this._registry = registry;
Expand All @@ -17,12 +19,8 @@ export class Executor {
cleanupPromise = this._activeProvider.endSearch();
}
this._currentWidget = this._shell.currentWidget;
// I want widget.content.editor for cmsp
const compatibleProviders = this._registry.providers.filter(p =>
p.canSearchOn(this._currentWidget)
);
// If multiple providers match, just use the first one.
const provider = compatibleProviders[0];

const provider = this._registry.getProviderForWidget(this._currentWidget);
if (!provider) {
console.warn(
'Unable to search on current widget, no compatible search provider'
Expand Down Expand Up @@ -61,6 +59,10 @@ export class Executor {
return this._activeProvider.matches;
}

get changed(): ISignal<ISearchProvider, void> {
return this._activeProvider.changed;
}

private _registry: SearchProviderRegistry;
private _activeProvider: ISearchProvider;
private _currentWidget: Widget;
Expand Down
41 changes: 21 additions & 20 deletions packages/documentsearch-extension/src/index.ts
Expand Up @@ -2,20 +2,16 @@
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/

import { JupyterLab, JupyterLabPlugin } from '@jupyterlab/application';

import { ICommandPalette } from '@jupyterlab/apputils';

// import { Widget } from '@phosphor/widgets';
import '../style/index.css';

import { SearchBox } from './searchbox';

import { Executor } from './executor';

import { SearchProviderRegistry } from './searchproviderregistry';

import '../style/index.css';
import { JupyterLab, JupyterLabPlugin } from '@jupyterlab/application';
import { ICommandPalette } from '@jupyterlab/apputils';

import { ISignal } from '@phosphor/signaling';

export interface ISearchMatch {
/**
Expand Down Expand Up @@ -86,6 +82,11 @@ export interface ISearchProvider {
*/
readonly matches: ISearchMatch[];

/**
* Signal indicating that something in the search has changed, so the UI should update
*/
readonly changed: ISignal<ISearchProvider, void>;

/**
* The current index of the selected match.
*/
Expand All @@ -107,32 +108,32 @@ const extension: JupyterLabPlugin<void> = {
// Create widget, attach to signals
const widget: SearchBox = new SearchBox();

const updateWidget = () => {
widget.totalMatches = executor.matches.length;
widget.currentIndex = executor.currentMatchIndex;
};

const startSearchFn = (_: any, searchOptions: any) => {
executor.startSearch(searchOptions).then((matches: ISearchMatch[]) => {
widget.totalMatches = executor.matches.length;
widget.currentIndex = executor.currentMatchIndex;
executor.startSearch(searchOptions).then(() => {
updateWidget();
executor.changed.connect(updateWidget);
});
};

const endSearchFn = () => {
executor.endSearch().then(() => {
widget.totalMatches = 0;
widget.currentIndex = 0;
executor.changed.disconnect(updateWidget);
});
};

const highlightNextFn = () => {
executor.highlightNext().then(() => {
widget.totalMatches = executor.matches.length;
widget.currentIndex = executor.currentMatchIndex;
});
executor.highlightNext().then(updateWidget);
};

const highlightPreviousFn = () => {
executor.highlightPrevious().then(() => {
widget.totalMatches = executor.matches.length;
widget.currentIndex = executor.currentMatchIndex;
});
executor.highlightPrevious().then(updateWidget);
};

// Default to just searching on the current widget, could eventually
Expand Down
Expand Up @@ -5,6 +5,8 @@ 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 {
Expand All @@ -23,7 +25,11 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
state.query = query;
// clear search first
this._cm.removeOverlay(state.overlay);
state.overlay = Private.searchOverlay(state.query, this._matchState);
state.overlay = Private.searchOverlay(
state.query,
this._matchState,
this._changed
);
this._cm.addOverlay(state.overlay);
// skips show matches on scroll bar here
state.posFrom = state.posTo = this._cm.getCursor();
Expand All @@ -48,6 +54,8 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
}

endSearch(): Promise<void> {
this._matchState = {};
this._matchIndex = 0;
Private.clearSearch(this._cm);
return Promise.resolve();
}
Expand Down Expand Up @@ -90,6 +98,10 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
return Private.parseMatchesFromState(this._matchState);
}

get changed(): ISignal<this, void> {
return this._changed;
}

get currentMatchIndex(): number {
return this._matchIndex;
}
Expand All @@ -107,6 +119,7 @@ export class CodeMirrorSearchProvider implements ISearchProvider {
private _matchIndex: number;
private _matchState: MatchMap = {};
private _shouldLoop: boolean = true;
private _changed = new Signal<this, void>(this);
}

export class SearchState {
Expand Down Expand Up @@ -164,7 +177,17 @@ namespace Private {
}
};

// const localCursor = cm.cursorCoords(true, 'local');
// const pageCursor = cm.cursorCoords(true, 'page');
// const windowCursor = cm.cursorCoords(true, 'window');
// console.log('localCursor: ', localCursor);
// console.log('pageCursor: ', pageCursor);
// console.log('windowCursor: ', windowCursor);
// console.log('scroller element: ', cm.getScrollerElement());
cm.setSelection(selRange);
// const scrollY = reverse ? pageCursor.top : pageCursor.bottom;
// console.log('------- scrolling to x, y: ', pageCursor.left, ', ', scrollY);
// cm.scrollTo(pageCursor.left, scrollY);
cm.scrollIntoView(
{
from: fromPos,
Expand All @@ -188,7 +211,11 @@ namespace Private {
return cm.state.search;
}

export function searchOverlay(query: RegExp, matchState: MatchMap) {
export function searchOverlay(
query: RegExp,
matchState: MatchMap,
changed: Signal<ISearchProvider, void>
) {
return {
/**
* Token function is called when a line needs to be processed -
Expand Down Expand Up @@ -230,7 +257,6 @@ namespace Private {
matchState[line] = {};
}
matchState[line][currentPos] = matchObj;

// move the stream along and return searching style for the token
stream.pos += matchLength || 1;
return 'searching';
Expand All @@ -239,6 +265,7 @@ namespace Private {
stream.pos = match.index;
} else {
// no matches, consume the rest of the stream
changed.emit(undefined);
stream.skipToEnd();
}
}
Expand Down
@@ -1,32 +1,38 @@
// import * as CodeMirror from 'codemirror';

import { ISearchProvider, ISearchMatch } from '../index';

import { CodeMirrorSearchProvider } from './codemirrorsearchprovider';

import { NotebookPanel, Notebook } 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;
}

// TODO: re-examine indexing of cells

export class NotebookSearchProvider implements ISearchProvider {
startSearch(
query: RegExp,
searchTarget: NotebookPanel
): Promise<ISearchMatch[]> {
this._searchTarget = searchTarget;
const cells = this._searchTarget.content.widgets;

const cellModel = this._searchTarget.model.cells;
Signal.disconnectBetween(cellModel, this);
cellModel.changed.connect(
this._restartSearch.bind(this, query, searchTarget),
this
);

const activeCell = this._searchTarget.content.activeCell;
const matchPromises: Promise<ISearchMatch[]>[] = [];
let indexTotal = 0;
let matchPromise = Promise.resolve([]);
const allMatches: ISearchMatch[] = [];
// For each cell, create a search provider and collect the matches
cells.forEach((cell: Cell) => {
const cmEditor = cell.editor as CodeMirrorEditor;
Expand All @@ -40,7 +46,8 @@ export class NotebookSearchProvider implements ISearchProvider {
if (cell.inputHidden) {
cell.inputHidden = false;
}
matchPromises.push(
// chain promises to ensure indexing is sequential
matchPromise = matchPromise.then(() =>
cmSearchProvider
.startSearch(query, cmEditor)
.then((matchesFromCell: ISearchMatch[]) => {
Expand All @@ -58,6 +65,12 @@ export class NotebookSearchProvider implements ISearchProvider {
});
indexTotal += matchesFromCell.length;

// search has been initialized, connect the changed signal
cmSearchProvider.changed.connect(
this._onCmSearchProviderChanged,
this
);

// In the active cell, select the next match after the cursor
if (activeCell === cell) {
return cmSearchProvider
Expand All @@ -69,7 +82,7 @@ export class NotebookSearchProvider implements ISearchProvider {
}

indexTotal += matchesFromCell.length;
return matchesFromCell;
allMatches.concat(matchesFromCell);
})
);

Expand All @@ -79,19 +92,14 @@ export class NotebookSearchProvider implements ISearchProvider {
});
});

// Flatten matches into one array
return Promise.all(matchPromises).then(matchesFromCells => {
let result: ISearchMatch[] = [];
matchesFromCells.forEach((cellMatches: ISearchMatch[]) => {
result.concat(cellMatches);
});
return result;
});
// Execute cell searches sequentially to ensure indexes are correct
return matchPromise.then(() => allMatches);
}

endSearch(): Promise<void> {
this._cmSearchProviders.forEach(({ provider }) => {
provider.endSearch();
provider.changed.disconnect(this._onCmSearchProviderChanged, this);
});
this._cmSearchProviders = [];
this._unRenderedMarkdownCells.forEach((cell: MarkdownCell) => {
Expand Down Expand Up @@ -136,17 +144,34 @@ export class NotebookSearchProvider implements ISearchProvider {
return [].concat(...Private.getMatchesFromCells(this._cmSearchProviders));
}

get changed(): ISignal<this, void> {
return this._changed;
}

get currentMatchIndex(): number {
if (!this._currentMatch) {
return 0;
}
return this._currentMatch.index;
}

private _restartSearch(query: RegExp, searchTarget: NotebookPanel) {
console.log('restarting search!');
this.endSearch();
this.startSearch(query, searchTarget).then(() =>
this._changed.emit(undefined)
);
}

private _onCmSearchProviderChanged() {
this._changed.emit(undefined);
}

private _searchTarget: NotebookPanel;
private _cmSearchProviders: ICellSearchPair[] = [];
private _currentMatch: ISearchMatch;
private _unRenderedMarkdownCells: MarkdownCell[] = [];
private _changed = new Signal<this, void>(this);
}

namespace Private {
Expand Down

0 comments on commit 7d3989e

Please sign in to comment.