|
@@ -5,11 +5,11 @@ import '../style/index.css';
|
|
|
import { SearchProviderRegistry } from './searchproviderregistry';
|
|
|
|
|
|
import { JupyterLab, JupyterLabPlugin } from '@jupyterlab/application';
|
|
|
-import { ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
|
|
|
+import { ICommandPalette } from '@jupyterlab/apputils';
|
|
|
|
|
|
-import { ISignal, Signal } from '@phosphor/signaling';
|
|
|
-import { createSearchOverlay } from './searchoverlay';
|
|
|
+import { ISignal } from '@phosphor/signaling';
|
|
|
import { Widget } from '@phosphor/widgets';
|
|
|
+import { SearchInstance } from './searchinstance';
|
|
|
|
|
|
export interface ISearchMatch {
|
|
|
/**
|
|
@@ -204,145 +204,6 @@ const extension: JupyterLabPlugin<void> = {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * Represents a search on a single widget.
|
|
|
- */
|
|
|
-export class SearchInstance {
|
|
|
- constructor(widget: Widget, searchProvider: ISearchProvider) {
|
|
|
- this._widget = widget;
|
|
|
- this._activeProvider = searchProvider;
|
|
|
-
|
|
|
- this._searchWidget = createSearchOverlay(
|
|
|
- this._displayUpdateSignal,
|
|
|
- this._displayState,
|
|
|
- this._onCaseSensitiveToggled.bind(this),
|
|
|
- this._onRegexToggled.bind(this),
|
|
|
- this._highlightNext.bind(this),
|
|
|
- this._highlightPrevious.bind(this),
|
|
|
- this._startSearch.bind(this),
|
|
|
- this._endSearch.bind(this)
|
|
|
- );
|
|
|
-
|
|
|
- // 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`;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The search widget.
|
|
|
- */
|
|
|
- get searchWidget() {
|
|
|
- return this._searchWidget;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The search provider.
|
|
|
- */
|
|
|
- get provider() {
|
|
|
- return this._activeProvider;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Focus the search widget input.
|
|
|
- */
|
|
|
- focus(): 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 _startSearch(query: RegExp) {
|
|
|
- // save the last query (or set it to the current query if this is the first)
|
|
|
- this._displayState.query = query;
|
|
|
- let cleanupPromise = Promise.resolve();
|
|
|
- if (this._activeProvider) {
|
|
|
- cleanupPromise = this._activeProvider.endSearch();
|
|
|
- }
|
|
|
- cleanupPromise.then(() =>
|
|
|
- this._activeProvider.startSearch(query, this._widget).then(() => {
|
|
|
- this.updateIndices();
|
|
|
- // this signal should get injected when the widget is
|
|
|
- // created and hooked up to react!
|
|
|
- this._activeProvider.changed.connect(
|
|
|
- this.updateIndices,
|
|
|
- this
|
|
|
- );
|
|
|
- })
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- private _endSearch() {
|
|
|
- this._activeProvider.endSearch().then(() => {
|
|
|
- Signal.disconnectAll(this);
|
|
|
- this._searchWidget.dispose();
|
|
|
- this._activeProvider.changed.disconnect(this.updateIndices, this);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private _highlightNext() {
|
|
|
- if (!this._displayState.query) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this._activeProvider.highlightNext().then(this.updateIndices.bind(this));
|
|
|
- }
|
|
|
-
|
|
|
- private _highlightPrevious() {
|
|
|
- if (!this._displayState.query) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this._activeProvider
|
|
|
- .highlightPrevious()
|
|
|
- .then(this.updateIndices.bind(this));
|
|
|
- }
|
|
|
-
|
|
|
- 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, IDisplayState>(this);
|
|
|
- private _activeProvider: ISearchProvider;
|
|
|
- private _searchWidget: Widget;
|
|
|
-}
|
|
|
-
|
|
|
namespace Private {
|
|
|
export type ActiveSearchMap = {
|
|
|
[key: string]: SearchInstance;
|