Polish #154913. Set current find match based on notebook selection. (#157337)

* Polish #154913. Set current find match based on notebook selection.

* update current match when query is seeded from selection.

* update test
This commit is contained in:
Peng Lyu
2022-08-08 08:29:36 -07:00
committed by GitHub
parent f3d5abc497
commit 12b1f7ceb4
4 changed files with 101 additions and 33 deletions
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async';
import { INotebookEditor, CellFindMatch, CellEditState, CellFindMatchWithIndex, OutputFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditor, CellFindMatch, CellEditState, CellFindMatchWithIndex, OutputFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { Range } from 'vs/editor/common/core/range';
import { FindDecorations } from 'vs/editor/contrib/find/browser/findDecorations';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
@@ -88,6 +88,34 @@ export class FindModel extends Disposable {
};
}
refreshCurrentMatch(focus: { cell: ICellViewModel; range: Range }) {
const findMatchIndex = this.findMatches.findIndex(match => match.cell === focus.cell);
if (findMatchIndex === -1) {
return;
}
const findMatch = this.findMatches[findMatchIndex];
const index = findMatch.matches.slice(0, findMatch.modelMatchCount).findIndex(match => (match as FindMatch).range.intersectRanges(focus.range) !== null);
if (index === undefined) {
return;
}
const matchesBefore = findMatchIndex === 0 ? 0 : (this._findMatchesStarts?.getPrefixSum(findMatchIndex - 1) ?? 0);
this._currentMatch = matchesBefore + index;
this.highlightCurrentFindMatchDecoration(findMatchIndex, index).then(offset => {
this.revealCellRange(findMatchIndex, index, offset);
this._state.changeMatchInfo(
this._currentMatch,
this._findMatches.reduce((p, c) => p + c.matches.length, 0),
undefined
);
});
}
find(option: { previous: boolean } | { index: number }) {
if (!this.findMatches.length) {
return;
@@ -189,20 +217,28 @@ export class FindModel extends Disposable {
return;
}
const findFirstMatchAfterCellIndex = (cellIndex: number) => {
const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= cellIndex);
this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection));
};
if (this._currentMatch === -1) {
// no active current match
this.set(findMatches, false);
return;
if (this._notebookEditor.getLength() === 0) {
this.set(findMatches, false);
return;
} else {
const focus = this._notebookEditor.getFocus().start;
findFirstMatchAfterCellIndex(focus);
this.set(findMatches, false);
return;
}
}
const oldCurrIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch);
const oldCurrCell = this._findMatches[oldCurrIndex.index].cell;
const oldCurrMatchCellIndex = this._notebookEditor.getCellIndex(oldCurrCell);
const findFirstMatchAfterCellIndex = (cellIndex: number) => {
const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= cellIndex);
this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection));
};
if (oldCurrMatchCellIndex < 0) {
// the cell containing the active match is deleted
@@ -18,7 +18,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget';
import { IShowNotebookFindWidgetOptions, NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget';
import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -92,21 +92,27 @@ function notebookContainsTextModel(uri: URI, textModel: ITextModel) {
return false;
}
function getSearchString(editor: ICodeEditor, opts: IFindStartOptions) {
function getSearchStringOptions(editor: ICodeEditor, opts: IFindStartOptions) {
// Get the search string result, following the same logic in _start function in 'vs/editor/contrib/find/browser/findController'
let searchString = '';
if (opts.seedSearchStringFromSelection === 'single') {
const selectionSearchString = getSelectionSearchString(editor, opts.seedSearchStringFromSelection, opts.seedSearchStringFromNonEmptySelection);
if (selectionSearchString) {
searchString = selectionSearchString;
return {
searchString: selectionSearchString,
selection: editor.getSelection()
};
}
} else if (opts.seedSearchStringFromSelection === 'multiple' && !opts.updateSearchScope) {
const selectionSearchString = getSelectionSearchString(editor, opts.seedSearchStringFromSelection);
if (selectionSearchString) {
searchString = selectionSearchString;
return {
searchString: selectionSearchString,
selection: editor.getSelection()
};
}
}
return searchString;
return undefined;
}
@@ -118,6 +124,10 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor:
return false;
}
if (!codeEditor.hasModel()) {
return false;
}
if (!editor.hasEditorFocus() && !editor.hasWebviewFocus()) {
const codeEditorService = accessor.get(ICodeEditorService);
// check if the active pane contains the active text editor
@@ -131,7 +141,7 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor:
const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
const searchString = getSearchString(codeEditor, {
const searchStringOptions = getSearchStringOptions(codeEditor, {
forceRevealReplace: false,
seedSearchStringFromSelection: codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',
seedSearchStringFromNonEmptySelection: codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',
@@ -142,7 +152,19 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor:
loop: codeEditor.getOption(EditorOption.find).loop
});
controller.show(searchString);
let options: IShowNotebookFindWidgetOptions | undefined = undefined;
const uri = codeEditor.getModel().uri;
const data = CellUri.parse(uri);
if (searchStringOptions?.selection && data) {
const cell = editor.getCellByHandle(data.handle);
if (cell) {
options = {
searchStringSeededFrom: { cell, range: searchStringOptions.selection },
};
}
}
controller.show(searchStringOptions?.searchString, options);
return true;
});
@@ -154,9 +176,13 @@ StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeE
return false;
}
if (!codeEditor.hasModel()) {
return false;
}
const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
const searchString = getSearchString(codeEditor, {
const searchStringOptions = getSearchStringOptions(codeEditor, {
forceRevealReplace: false,
seedSearchStringFromSelection: codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',
seedSearchStringFromNonEmptySelection: codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',
@@ -168,7 +194,7 @@ StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeE
});
if (controller) {
controller.replace(searchString);
controller.replace(searchStringOptions?.searchString);
return true;
}
@@ -23,7 +23,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel';
import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget';
import { CellEditState, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
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';
const FIND_HIDE_TRANSITION = 'find-hide-transition';
@@ -37,6 +37,7 @@ export interface IShowNotebookFindWidgetOptions {
matchCase?: boolean;
matchIndex?: number;
focus?: boolean;
searchStringSeededFrom?: { cell: ICellViewModel; range: Range };
}
export class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution {
@@ -214,6 +215,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
protected onFindInputFocusTrackerBlur(): void { }
override async show(initialInput?: string, options?: IShowNotebookFindWidgetOptions): Promise<void> {
const searchStringUpdate = this._state.searchString !== initialInput;
super.show(initialInput, options);
this._state.change({ searchString: initialInput ?? '', isRevealed: true }, false);
@@ -226,6 +228,10 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
this._findInput.select();
}
if (!searchStringUpdate && options?.searchStringSeededFrom) {
this._findModel.refreshCurrentMatch(options.searchStringSeededFrom);
}
if (this._showTimeout === null) {
if (this._hideTimeout !== null) {
window.clearTimeout(this._hideTimeout);
@@ -67,13 +67,13 @@ suite('Notebook Find', () => {
state.change({ searchString: '1' }, true);
await found;
assert.strictEqual(model.findMatches.length, 2);
assert.strictEqual(model.currentMatch, -1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
assert.strictEqual(editor.textModel.length, 3);
@@ -88,7 +88,7 @@ suite('Notebook Find', () => {
await found2;
assert.strictEqual(editor.textModel.length, 4);
assert.strictEqual(model.findMatches.length, 3);
assert.strictEqual(model.currentMatch, 0);
assert.strictEqual(model.currentMatch, 1);
});
});
@@ -114,13 +114,13 @@ suite('Notebook Find', () => {
await found;
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 3);
const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => {
if (e.matchesCount) { resolve(true); }
@@ -131,15 +131,15 @@ suite('Notebook Find', () => {
await found2;
assert.strictEqual(model.findMatches.length, 3);
assert.strictEqual(model.currentMatch, 2);
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: true });
assert.strictEqual(model.currentMatch, 1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 3);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
});
});
@@ -165,7 +165,7 @@ suite('Notebook Find', () => {
await found;
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: true });
assert.strictEqual(model.currentMatch, 4);
@@ -207,11 +207,11 @@ suite('Notebook Find', () => {
await found;
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
model.find({ previous: false });
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
assert.strictEqual(model.currentMatch, 3);
const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => {
if (e.matchesCount) { resolve(true); }
}));
@@ -243,13 +243,13 @@ suite('Notebook Find', () => {
state.change({ searchString: '1' }, true);
await found;
assert.strictEqual(model.findMatches.length, 2);
assert.strictEqual(model.currentMatch, -1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
assert.strictEqual(editor.textModel.length, 3);