Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixing search and replace with cell outputs #16260

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -11,7 +11,7 @@ import { SearchProvider } from '../searchprovider';
import { ITranslator } from '@jupyterlab/translation';

export const FOUND_CLASSES = ['cm-string', 'cm-overlay', 'cm-searching'];
const SELECTED_CLASSES = ['CodeMirror-selectedtext'];
const SELECTED_CLASSES = ['CodeMirror-selectedtext', 'jp-current-match'];

/**
* HTML search engine
Expand Down Expand Up @@ -367,12 +367,17 @@ export class GenericSearchProvider extends SearchProvider<Widget> {
if (this._currentMatchIndex === -1) {
this._currentMatchIndex = reverse ? this.matches.length - 1 : 0;
} else {
// Update the CSS classes for the current match
const hit = this._markNodes[this._currentMatchIndex];
hit.classList.remove(...SELECTED_CLASSES);
hit.classList.add(...FOUND_CLASSES);

// Update the index for the next match
this._currentMatchIndex = reverse
? this._currentMatchIndex - 1
: this._currentMatchIndex + 1;

// If we have looping enabled and are out of bounds after the index update
if (
loop &&
(this._currentMatchIndex < 0 ||
Expand All @@ -390,6 +395,7 @@ export class GenericSearchProvider extends SearchProvider<Widget> {
this._currentMatchIndex < this._matches.length
) {
const hit = this._markNodes[this._currentMatchIndex];
hit.classList.remove(...FOUND_CLASSES);
hit.classList.add(...SELECTED_CLASSES);
// If not in view, scroll just enough to see it
if (!elementInViewport(hit)) {
Expand Down
26 changes: 24 additions & 2 deletions packages/documentsearch/src/searchmodel.ts
Expand Up @@ -182,6 +182,20 @@ export class SearchDocumentModel
}
}

/**
* Whether the replace button is enabled or not.
*/
get replaceEnabled(): boolean {
return this._replaceEnabled;
}

/**
* Whether the replace all button is enabled or not.
*/
get replaceAllEnabled(): boolean {
return this._replaceAllEnabled;
}

/**
* Search expression
*/
Expand Down Expand Up @@ -270,7 +284,10 @@ export class SearchDocumentModel
* Highlight the next match.
*/
async highlightNext(): Promise<void> {
await this.searchProvider.highlightNext();
const match = await this.searchProvider.highlightNext();
if (match) {
this._replaceEnabled = match.node == undefined;
}
// Emit state change as the index needs to be updated
this.stateChanged.emit();
}
Expand All @@ -279,7 +296,10 @@ export class SearchDocumentModel
* Highlight the previous match
*/
async highlightPrevious(): Promise<void> {
await this.searchProvider.highlightPrevious();
const match = await this.searchProvider.highlightPrevious();
if (match) {
this._replaceEnabled = match.node == undefined;
}
// Emit state change as the index needs to be updated
this.stateChanged.emit();
}
Expand Down Expand Up @@ -386,6 +406,8 @@ export class SearchDocumentModel
private _initialQuery = '';
private _filters: IFilters = {};
private _replaceText: string = '';
private _replaceEnabled = true;
private _replaceAllEnabled = true;
private _searchActive = false;
private _searchDebouncer: Debouncer;
private _searchExpression = '';
Expand Down
13 changes: 12 additions & 1 deletion packages/documentsearch/src/searchview.tsx
Expand Up @@ -46,6 +46,7 @@ const SEARCH_OPTIONS_CLASS = 'jp-DocumentSearch-search-options';
const SEARCH_FILTER_DISABLED_CLASS = 'jp-DocumentSearch-search-filter-disabled';
const SEARCH_FILTER_CLASS = 'jp-DocumentSearch-search-filter';
const REPLACE_BUTTON_CLASS = 'jp-DocumentSearch-replace-button';
const REPLACE_BUTTON_DISABLED_CLASS = 'jp-DocumentSearch-replace-button-disabled';
const REPLACE_BUTTON_WRAPPER_CLASS = 'jp-DocumentSearch-replace-button-wrapper';
const REPLACE_WRAPPER_CLASS = 'jp-DocumentSearch-replace-wrapper-class';
const REPLACE_TOGGLE_CLASS = 'jp-DocumentSearch-replace-toggle';
Expand Down Expand Up @@ -218,6 +219,7 @@ interface IReplaceEntryProps {
preserveCase: boolean;
replaceOptionsSupport: IReplaceOptionsSupport | undefined;
replaceText: string;
replaceEnabled: boolean;
translator?: ITranslator;
}

Expand Down Expand Up @@ -259,9 +261,12 @@ function ReplaceEntry(props: IReplaceEntryProps): JSX.Element {
className={REPLACE_BUTTON_WRAPPER_CLASS}
onClick={() => props.onReplaceCurrent()}
tabIndex={0}
disabled={!props.replaceEnabled}
>
<span
className={`${REPLACE_BUTTON_CLASS} ${BUTTON_CONTENT_CLASS}`}
className={props.replaceEnabled
? `${REPLACE_BUTTON_CLASS} ${BUTTON_CONTENT_CLASS}`
: `${REPLACE_BUTTON_DISABLED_CLASS} ${BUTTON_CONTENT_CLASS}`}
tabIndex={0}
>
{trans.__('Replace')}
Expand Down Expand Up @@ -466,6 +471,10 @@ interface ISearchOverlayProps {
* Replacement expression
*/
replaceText: string;
/**
* Whether the replace button is enabled.
*/
replaceEnabled: boolean;
/**
* The text in the search entry
*/
Expand Down Expand Up @@ -777,6 +786,7 @@ class SearchOverlay extends React.Component<ISearchOverlayProps> {
onReplaceAll={() => this.props.onReplaceAll()}
replaceOptionsSupport={this.props.replaceOptionsSupport}
replaceText={this.props.replaceText}
replaceEnabled={this.props.replaceEnabled}
preserveCase={this.props.preserveCase}
translator={this.translator}
/>
Expand Down Expand Up @@ -912,6 +922,7 @@ export class SearchDocumentView extends VDomRenderer<SearchDocumentModel> {
filtersVisible={this._showFilters}
replaceOptionsSupport={this.model.replaceOptionsSupport}
replaceText={this.model.replaceText}
replaceEnabled={this.model.replaceEnabled}
initialSearchText={this.model.initialQuery}
searchInputRef={
this._searchInput as React.RefObject<HTMLTextAreaElement>
Expand Down
5 changes: 5 additions & 0 deletions packages/documentsearch/src/tokens.ts
Expand Up @@ -174,6 +174,11 @@ export interface ISearchMatch {
* Start location of the match (in a text, this is the column)
*/
position: number;

/**
* Node containing the match
*/
node?: Text;
}

/**
Expand Down
13 changes: 13 additions & 0 deletions packages/documentsearch/style/base.css
Expand Up @@ -237,6 +237,19 @@ button:not(:disabled) > .jp-DocumentSearch-up-down-button:active {
height: 100%;
}

.jp-DocumentSearch-replace-button-disabled {
display: inline-block;
text-align: center;
cursor: not-allowed;
box-sizing: border-box;
color: var(--jp-ui-font-color2);

/* height - 2 * (padding of wrapper) */
line-height: calc(var(--jp-private-document-search-button-height) - 2px);
width: 100%;
height: 100%;
}

.jp-DocumentSearch-replace-button:focus {
outline: none;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/notebook/src/searchprovider.ts
Expand Up @@ -237,7 +237,7 @@ export class NotebookSearchProvider extends SearchProvider<NotebookPanel> {
'Search in the cell outputs (not available when replace options are shown).'
),
default: false,
supportReplace: false
supportReplace: true
},
selection: {
title:
Expand Down