UNPKG

@jupyterlab/cells

Version:
359 lines 11.9 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { EditorSearchProvider } from '@jupyterlab/codemirror'; import { signalToPromise } from '@jupyterlab/coreutils'; import { GenericSearchProvider } from '@jupyterlab/documentsearch'; /** * Class applied on highlighted search matches */ export const SELECTED_HIGHLIGHT_CLASS = 'jp-mod-selected'; /** * Search provider for cells. */ export class CellSearchProvider extends EditorSearchProvider { constructor(cell) { super(); this.cell = cell; if (!this.cell.inViewport && !this.cell.editor) { void signalToPromise(cell.inViewportChanged).then(([, inViewport]) => { if (inViewport) { this.cmHandler.setEditor(this.editor); } }); } } /** * Text editor */ get editor() { return this.cell.editor; } /** * Editor content model */ get model() { return this.cell.model; } } /** * Code cell search provider */ class CodeCellSearchProvider extends CellSearchProvider { /** * Constructor * * @param cell Cell widget */ constructor(cell) { super(cell); this.currentProviderIndex = -1; this.outputsProvider = []; const outputs = this.cell.outputArea; this._onOutputsChanged(outputs, outputs.widgets.length).catch(reason => { console.error(`Failed to initialize search on cell outputs.`, reason); }); outputs.outputLengthChanged.connect(this._onOutputsChanged, this); outputs.disposed.connect(() => { outputs.outputLengthChanged.disconnect(this._onOutputsChanged); }, this); } /** * Number of matches in the cell. */ get matchesCount() { if (!this.isActive) { return 0; } return (super.matchesCount + this.outputsProvider.reduce((sum, provider) => { var _a; return sum + ((_a = provider.matchesCount) !== null && _a !== void 0 ? _a : 0); }, 0)); } /** * Clear currently highlighted match. */ async clearHighlight() { await super.clearHighlight(); await Promise.all(this.outputsProvider.map(provider => provider.clearHighlight())); } /** * Dispose the search provider */ dispose() { if (this.isDisposed) { return; } super.dispose(); this.outputsProvider.map(provider => { provider.dispose(); }); this.outputsProvider.length = 0; } /** * Highlight the next match. * * @returns The next match if there is one. */ async highlightNext(loop, options) { if (this.matchesCount === 0 || !this.isActive) { this.currentIndex = null; } else { if (this.currentProviderIndex === -1) { const match = await super.highlightNext(true, options); if (match) { this.currentIndex = this.cmHandler.currentIndex; return match; } else { this.currentProviderIndex = 0; } } while (this.currentProviderIndex < this.outputsProvider.length) { const provider = this.outputsProvider[this.currentProviderIndex]; const match = await provider.highlightNext(false); if (match) { this.currentIndex = super.matchesCount + this.outputsProvider .slice(0, this.currentProviderIndex) .reduce((sum, provider) => { var _a; return (sum += (_a = provider.matchesCount) !== null && _a !== void 0 ? _a : 0); }, 0) + provider.currentMatchIndex; return match; } else { this.currentProviderIndex += 1; } } this.currentProviderIndex = -1; this.currentIndex = null; return undefined; } } /** * Highlight the previous match. * * @returns The previous match if there is one. */ async highlightPrevious() { if (this.matchesCount === 0 || !this.isActive) { this.currentIndex = null; } else { if (this.currentIndex === null) { this.currentProviderIndex = this.outputsProvider.length - 1; } while (this.currentProviderIndex >= 0) { const provider = this.outputsProvider[this.currentProviderIndex]; const match = await provider.highlightPrevious(false); if (match) { this.currentIndex = super.matchesCount + this.outputsProvider .slice(0, this.currentProviderIndex) .reduce((sum, provider) => { var _a; return (sum += (_a = provider.matchesCount) !== null && _a !== void 0 ? _a : 0); }, 0) + provider.currentMatchIndex; return match; } else { this.currentProviderIndex -= 1; } } const match = await super.highlightPrevious(); if (match) { this.currentIndex = this.cmHandler.currentIndex; return match; } else { this.currentIndex = null; return undefined; } } } /** * Initialize the search using the provided options. Should update the UI to highlight * all matches and "select" the first match. * * @param query A RegExp to be use to perform the search * @param filters Filter parameters to pass to provider */ async startQuery(query, filters) { await super.startQuery(query, filters); // Search outputs if ((filters === null || filters === void 0 ? void 0 : filters.output) !== false && this.isActive) { await Promise.all(this.outputsProvider.map(provider => provider.startQuery(query))); } } async endQuery() { var _a; await super.endQuery(); if (((_a = this.filters) === null || _a === void 0 ? void 0 : _a.output) !== false && this.isActive) { await Promise.all(this.outputsProvider.map(provider => provider.endQuery())); } } async _onOutputsChanged(outputArea, changes) { var _a; this.outputsProvider.forEach(provider => { provider.dispose(); }); this.outputsProvider.length = 0; this.currentProviderIndex = -1; this.outputsProvider = this.cell.outputArea.widgets.map(output => new GenericSearchProvider(output)); if (this.isActive && this.query && ((_a = this.filters) === null || _a === void 0 ? void 0 : _a.output) !== false) { await Promise.all([ this.outputsProvider.map(provider => { void provider.startQuery(this.query); }) ]); } this._stateChanged.emit(); } } /** * Markdown cell search provider */ class MarkdownCellSearchProvider extends CellSearchProvider { /** * Constructor * * @param cell Cell widget */ constructor(cell) { super(cell); this._unrenderedByHighligh = false; this.renderedProvider = new GenericSearchProvider(cell.renderer); } /** * Clear currently highlighted match */ async clearHighlight() { await super.clearHighlight(); await this.renderedProvider.clearHighlight(); } /** * Dispose the search provider */ dispose() { if (this.isDisposed) { return; } super.dispose(); this.renderedProvider.dispose(); } /** * Stop the search and clean any UI elements. */ async endQuery() { await super.endQuery(); await this.renderedProvider.endQuery(); } /** * Highlight the next match. * * @returns The next match if there is one. */ async highlightNext() { let match = undefined; if (!this.isActive) { return match; } const cell = this.cell; if (cell.rendered && this.matchesCount > 0) { // Unrender the cell this._unrenderedByHighligh = true; const waitForRendered = signalToPromise(cell.renderedChanged); cell.rendered = false; await waitForRendered; } match = await super.highlightNext(); return match; } /** * Highlight the previous match. * * @returns The previous match if there is one. */ async highlightPrevious() { let match = undefined; const cell = this.cell; if (cell.rendered && this.matchesCount > 0) { // Unrender the cell if there are matches within the cell this._unrenderedByHighligh = true; const waitForRendered = signalToPromise(cell.renderedChanged); cell.rendered = false; await waitForRendered; } match = await super.highlightPrevious(); return match; } /** * Initialize the search using the provided options. Should update the UI * to highlight all matches and "select" the first match. * * @param query A RegExp to be use to perform the search * @param filters Filter parameters to pass to provider */ async startQuery(query, filters) { await super.startQuery(query, filters); const cell = this.cell; if (cell.rendered) { this.onRenderedChanged(cell, cell.rendered); } cell.renderedChanged.connect(this.onRenderedChanged, this); } /** * Replace all matches in the cell source with the provided text * * @param newText The replacement text. * @returns Whether a replace occurred. */ async replaceAllMatches(newText) { const result = await super.replaceAllMatches(newText); // if the cell is rendered force update if (this.cell.rendered) { this.cell.update(); } return result; } /** * Callback on rendered state change * * @param cell Cell that emitted the change * @param rendered New rendered value */ onRenderedChanged(cell, rendered) { var _a; if (!this._unrenderedByHighligh) { this.currentIndex = null; } this._unrenderedByHighligh = false; if (this.isActive) { if (rendered) { void this.renderedProvider.startQuery(this.query); } else { // Force cursor position to ensure reverse search is working as expected (_a = cell.editor) === null || _a === void 0 ? void 0 : _a.setCursorPosition({ column: 0, line: 0 }); void this.renderedProvider.endQuery(); } } } } /** * Factory to create a cell search provider * * @param cell Cell widget * @returns Cell search provider */ export function createCellSearchProvider(cell) { if (cell.isPlaceholder()) { return new CellSearchProvider(cell); } switch (cell.model.type) { case 'code': return new CodeCellSearchProvider(cell); case 'markdown': return new MarkdownCellSearchProvider(cell); default: return new CellSearchProvider(cell); } } //# sourceMappingURL=searchprovider.js.map