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

Add Search in Cell Selection to Notebook Find Widget #213409

Merged
merged 3 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/editor/contrib/find/browser/findWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/bro
import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
import { IHoverService } from 'vs/platform/hover/browser/hover';

const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.'));
const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.'));
const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.'));

export const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.'));
export const findReplaceIcon = registerIcon('find-replace', Codicon.replace, nls.localize('findReplaceIcon', 'Icon for \'Replace\' in the editor find widget.'));
export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll, nls.localize('findReplaceAllIcon', 'Icon for \'Replace All\' in the editor find widget.'));
export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp, nls.localize('findPreviousMatchIcon', 'Icon for \'Find Previous\' in the editor find widget.'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@

import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';

export interface INotebookFindFiltersChangeEvent {
export interface INotebookFindChangeEvent {
markupInput?: boolean;
markupPreview?: boolean;
codeInput?: boolean;
codeOutput?: boolean;
searchInRanges?: boolean;
}

export class NotebookFindFilters extends Disposable {
private readonly _onDidChange: Emitter<INotebookFindFiltersChangeEvent> = this._register(new Emitter<INotebookFindFiltersChangeEvent>());
readonly onDidChange: Event<INotebookFindFiltersChangeEvent> = this._onDidChange.event;
private readonly _onDidChange: Emitter<INotebookFindChangeEvent> = this._register(new Emitter<INotebookFindChangeEvent>());
readonly onDidChange: Event<INotebookFindChangeEvent> = this._onDidChange.event;

private _markupInput: boolean = true;

Expand Down Expand Up @@ -68,24 +70,53 @@ export class NotebookFindFilters extends Disposable {
}
}

private _searchInRanges: boolean = false;

get searchInRanges(): boolean {
return this._searchInRanges;
}

set searchInRanges(value: boolean) {
if (this._searchInRanges !== value) {
this._searchInRanges = value;
this._onDidChange.fire({ searchInRanges: value });
}
}

private _selectedRanges: ICellRange[] = [];

get selectedRanges(): ICellRange[] {
return this._selectedRanges;
}

set selectedRanges(value: ICellRange[]) {
if (this._selectedRanges !== value) {
this._selectedRanges = value;
this._onDidChange.fire({ searchInRanges: this._searchInRanges });
}
}

private readonly _initialMarkupInput: boolean;
private readonly _initialMarkupPreview: boolean;
private readonly _initialCodeInput: boolean;
private readonly _initialCodeOutput: boolean;


constructor(
markupInput: boolean,
markupPreview: boolean,
codeInput: boolean,
codeOutput: boolean
codeOutput: boolean,
searchInRanges: boolean,
selectedRanges: ICellRange[]
) {
super();

this._markupInput = markupInput;
this._markupPreview = markupPreview;
this._codeInput = codeInput;
this._codeOutput = codeOutput;
this._searchInRanges = searchInRanges;
this._selectedRanges = selectedRanges;

this._initialMarkupInput = markupInput;
this._initialMarkupPreview = markupPreview;
Expand All @@ -94,6 +125,7 @@ export class NotebookFindFilters extends Disposable {
}

isModified(): boolean {
// do not include searchInRanges or selectedRanges in the check. This will incorrectly mark the filter icon as modified
return (
this._markupInput !== this._initialMarkupInput
|| this._markupPreview !== this._initialMarkupPreview
Expand All @@ -107,5 +139,7 @@ export class NotebookFindFilters extends Disposable {
this._markupPreview = v.markupPreview;
this._codeInput = v.codeInput;
this._codeOutput = v.codeOutput;
this._searchInRanges = v.searchInRanges;
this._selectedRanges = v.selectedRanges;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ export class FindModel extends Disposable {
}

private _updateCellStates(e: FindReplaceStateChangedEvent) {
if (!this._state.filters?.markupInput) {
return;
}

if (!this._state.filters?.markupPreview) {
if (!this._state.filters?.markupInput || !this._state.filters?.markupPreview || !this._state.filters?.searchInRanges || !this._state.filters?.selectedRanges) {
return;
}

Expand All @@ -139,7 +135,9 @@ export class FindModel extends Disposable {
includeMarkupInput: true,
includeCodeInput: false,
includeMarkupPreview: false,
includeOutput: false
includeOutput: false,
searchInRanges: this._state.filters?.searchInRanges,
selectedRanges: this._state.filters?.selectedRanges
};

const contentMatches = viewModel.find(this._state.searchString, options);
Expand Down Expand Up @@ -486,7 +484,9 @@ export class FindModel extends Disposable {
includeMarkupInput: this._state.filters?.markupInput ?? true,
includeCodeInput: this._state.filters?.codeInput ?? true,
includeMarkupPreview: !!this._state.filters?.markupPreview,
includeOutput: !!this._state.filters?.codeOutput
includeOutput: !!this._state.filters?.codeOutput,
searchInRanges: this._state.filters?.searchInRanges,
selectedRanges: this._state.filters?.selectedRanges
};

ret = await this._notebookEditor.find(val, options, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { INotebookCommandContext, NotebookMultiCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';

registerNotebookContribution(NotebookFindContrib.id, NotebookFindContrib);

Expand Down Expand Up @@ -55,7 +56,7 @@ registerAction2(class extends Action2 {
}
});

registerAction2(class extends Action2 {
registerAction2(class extends NotebookMultiCellAction {
constructor() {
super({
id: 'notebook.find',
Expand All @@ -68,7 +69,7 @@ registerAction2(class extends Action2 {
});
}

async run(accessor: ServicesAccessor): Promise<void> {
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext): Promise<void> {
const editorService = accessor.get(IEditorService);
const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);

Expand All @@ -77,7 +78,12 @@ registerAction2(class extends Action2 {
}

const controller = editor.getContribution<NotebookFindContrib>(NotebookFindContrib.id);
controller.show();

if (context.selectedCells.length > 1) {
controller.show(undefined, { searchInRanges: true, selectedRanges: editor.getSelections() });
} else {
controller.show(undefined, { searchInRanges: false, selectedRanges: [] });
}
}
});

Expand Down Expand Up @@ -200,4 +206,3 @@ StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeE

return false;
});

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Delayer } from 'vs/base/common/async';
import { KeyCode } from 'vs/base/common/keyCodes';
import 'vs/css!./notebookFindReplaceWidget';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/browser/findState';
import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, SimpleButton } from 'vs/editor/contrib/find/browser/findWidget';
import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, findSelectionIcon, SimpleButton } from 'vs/editor/contrib/find/browser/findWidget';
import * as nls from 'vs/nls';
import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
Expand All @@ -35,19 +35,23 @@ import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIc
import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
import { isSafari } from 'vs/base/common/platform';
import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDeltaDecoration, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle';
import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { Disposable } from 'vs/base/common/lifecycle';
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';


const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match");
// const NLS_FILTER_BTN_LABEL = nls.localize('label.findFilterButton', "Search in View");
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match");
const NLS_FIND_IN_CELL_SELECTION_BTN_LABEL = nls.localize('label.findInCellSelectionButton', "Find in Cell Selection");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace");
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
Expand Down Expand Up @@ -314,6 +318,10 @@ export abstract class SimpleFindReplaceWidget extends Widget {

private _filters: NotebookFindFilters;

private readonly inSelectionToggle: Toggle;
private searchInSelectionEnabled: boolean;
private selectionDecorationIds: string[] = [];

constructor(
@IContextViewService private readonly _contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService,
Expand All @@ -326,14 +334,14 @@ export abstract class SimpleFindReplaceWidget extends Widget {
) {
super();

const findScope = this._configurationService.getValue<{
const findFilters = this._configurationService.getValue<{
markupSource: boolean;
markupPreview: boolean;
codeSource: boolean;
codeOutput: boolean;
}>(NotebookSetting.findScope) ?? { markupSource: true, markupPreview: true, codeSource: true, codeOutput: true };
}>(NotebookSetting.findFilters) ?? { markupSource: true, markupPreview: true, codeSource: true, codeOutput: true };

this._filters = new NotebookFindFilters(findScope.markupSource, findScope.markupPreview, findScope.codeSource, findScope.codeOutput);
this._filters = new NotebookFindFilters(findFilters.markupSource, findFilters.markupPreview, findFilters.codeSource, findFilters.codeOutput, false, []);
this._state.change({ filters: this._filters }, false);

this._filters.onDidChange(() => {
Expand Down Expand Up @@ -430,7 +438,6 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._findInput.setWholeWords(this._state.wholeWord);
this._findInput.setCaseSensitive(this._state.matchCase);
this._replaceInput.setPreserveCase(this._state.preserveCase);
this.findFirst();
}));

this._matchesCount = document.createElement('div');
Expand All @@ -453,6 +460,27 @@ export abstract class SimpleFindReplaceWidget extends Widget {
}
}, hoverService));

this.inSelectionToggle = this._register(new Toggle({
icon: findSelectionIcon,
title: NLS_FIND_IN_CELL_SELECTION_BTN_LABEL,
isChecked: false,
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground),
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
}));

this.inSelectionToggle.onChange(() => {
const checked = this.inSelectionToggle.checked;
this._filters.searchInRanges = checked;
if (checked) {
this._filters.selectedRanges = this._notebookEditor.getSelections();
this.setCellSelectionDecorations();
} else {
this._filters.selectedRanges = [];
this.clearCellSelectionDecorations();
}
});

const closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL,
icon: widgetClose,
Expand All @@ -465,8 +493,25 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._innerFindDomNode.appendChild(this._matchesCount);
this._innerFindDomNode.appendChild(this.prevBtn.domNode);
this._innerFindDomNode.appendChild(this.nextBtn.domNode);
this._innerFindDomNode.appendChild(this.inSelectionToggle.domNode);
this._innerFindDomNode.appendChild(closeBtn.domNode);

this.searchInSelectionEnabled = this._configurationService.getValue<boolean>(NotebookSetting.findScope);
this.inSelectionToggle.domNode.style.display = this.searchInSelectionEnabled ? 'inline' : 'none';

this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(NotebookSetting.findScope)) {
this.searchInSelectionEnabled = this._configurationService.getValue<boolean>(NotebookSetting.findScope);
if (this.searchInSelectionEnabled) {
this.inSelectionToggle.domNode.style.display = 'inline';
} else {
this.inSelectionToggle.domNode.style.display = 'none';
this.inSelectionToggle.checked = false;
this.clearCellSelectionDecorations();
}
}
});

// _domNode wraps _innerDomNode, ensuring that
this._domNode.appendChild(this._innerFindDomNode);

Expand Down Expand Up @@ -599,7 +644,6 @@ export abstract class SimpleFindReplaceWidget extends Widget {

protected abstract onInputChanged(): boolean;
protected abstract find(previous: boolean): void;
protected abstract findFirst(): void;
protected abstract replaceOne(): void;
protected abstract replaceAll(): void;
protected abstract onFocusTrackerFocus(): void;
Expand Down Expand Up @@ -647,6 +691,26 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this.updateButtons(this.foundMatch);
}

private setCellSelectionDecorations() {
const cellHandles: number[] = [];
this._notebookEditor.getSelectionViewModels().forEach(viewModel => {
cellHandles.push(viewModel.handle);
});

const decorations: INotebookDeltaDecoration[] = [];
for (const handle of cellHandles) {
decorations.push({
handle: handle,
options: { className: 'nb-multiCellHighlight', outputClassName: 'nb-multiCellHighlight' }
} satisfies INotebookDeltaDecoration);
}
this.selectionDecorationIds = this._notebookEditor.deltaCellDecorations([], decorations);
}

private clearCellSelectionDecorations() {
this._notebookEditor.deltaCellDecorations(this.selectionDecorationIds, []);
}

protected _updateMatchesCount(): void {
}

Expand Down Expand Up @@ -686,11 +750,20 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._findInput.focus();
}

public show(initialInput?: string, options?: { focus?: boolean }): void {
public show(initialInput?: string, options?: { focus?: boolean; searchInRanges?: boolean; selectedRanges?: ICellRange[] }): void {
if (initialInput) {
this._findInput.setValue(initialInput);
}

if (this.searchInSelectionEnabled && options?.searchInRanges !== undefined) {
this._filters.searchInRanges = options.searchInRanges;
this.inSelectionToggle.checked = options.searchInRanges;
if (options.searchInRanges && options.selectedRanges) {
this._filters.selectedRanges = options.selectedRanges;
this.setCellSelectionDecorations();
}
}

this._isVisible = true;

setTimeout(() => {
Expand Down Expand Up @@ -738,6 +811,9 @@ export abstract class SimpleFindReplaceWidget extends Widget {

public hide(): void {
if (this._isVisible) {
this.inSelectionToggle.checked = false;
this._notebookEditor.deltaCellDecorations(this.selectionDecorationIds, []);

this._domNode.classList.remove('visible-transition');
this._domNode.setAttribute('aria-hidden', 'true');
// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/fi
import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget';
import { CellEditState, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';

const FIND_HIDE_TRANSITION = 'find-hide-transition';
const FIND_SHOW_TRANSITION = 'find-show-transition';
Expand All @@ -39,6 +40,8 @@ export interface IShowNotebookFindWidgetOptions {
matchIndex?: number;
focus?: boolean;
searchStringSeededFrom?: { cell: ICellViewModel; range: Range };
searchInRanges?: boolean;
selectedRanges?: ICellRange[];
}

export class NotebookFindContrib extends Disposable implements INotebookEditorContribution {
Expand Down
Loading
Loading