Skip to content

Commit

Permalink
Add Search in Cell Selection to Notebook Find Widget (#213409)
Browse files Browse the repository at this point in the history
* all search scope functionality except cell decorations

* fix filter icon showing fake useage

* decorations + css tweaking + PR feedback
  • Loading branch information
Yoyokrazy authored May 28, 2024
1 parent 50f2b2e commit 49eedd7
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 39 deletions.
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

0 comments on commit 49eedd7

Please sign in to comment.