From 4aa13a569c181546719a3d6f364f2bd0422e6359 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 12 Mar 2020 16:53:25 -0700 Subject: [PATCH] find in markdown content --- .../browser/viewParts/lines/viewLines.ts | 3 +- .../browser/contrib/notebookFindWidget.ts | 21 +++++------- .../notebook/browser/notebookBrowser.ts | 28 +++++++++++++++- .../notebook/browser/notebookEditor.ts | 21 ++++++++---- .../notebook/browser/view/notebookCellList.ts | 26 +++++++++++++-- .../browser/view/renderers/markdownCell.ts | 21 +++++++----- .../viewModel/notebookCellViewModel.ts | 33 ++++++++++++++++--- .../browser/viewModel/notebookViewModel.ts | 14 +++++--- .../notebook/test/testNotebookEditor.ts | 5 ++- 9 files changed, 132 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 09ee4fb5516..d0752465a68 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -484,7 +484,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, * Returns false if some lines need to be reevaluated (in a slow fashion). */ private _updateLineWidthsFast(): boolean { - return this._updateLineWidths(true); + // TODO@rebornix triggering `updateLineWidthsFast` flushes scroll left. + return this._updateLineWidths(false); } private _updateLineWidthsSlow(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts index a255b40adaa..54dddaec2d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts @@ -6,7 +6,7 @@ import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; @@ -40,12 +40,7 @@ export class NotebookFindWidget extends SimpleFindWidget { if (this._currentMatch !== -1) { const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); - const cellIndex = nextIndex.index; - const matchIndex = nextIndex.remainder; - - this._findMatches[cellIndex].cell.isEditing = true; - this._notebookEditor.setSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); - this._notebookEditor.revealRangeInCenterIfOutsideViewport(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); + this.revealCellRange(nextIndex.index, nextIndex.remainder); } } else { this.set(null); @@ -79,12 +74,14 @@ export class NotebookFindWidget extends SimpleFindWidget { this._currentMatch = nextVal; const nextIndex = this._findMatchesStarts!.getIndexOf(nextVal); - const cellIndex = nextIndex.index; - const matchIndex = nextIndex.remainder; + this.setCurrentFindMatchDecoration(nextIndex.index, nextIndex.remainder); + this.revealCellRange(nextIndex.index, nextIndex.remainder); + } - this.setCurrentFindMatchDecoration(cellIndex, matchIndex); - this._findMatches[cellIndex].cell.isEditing = true; - this._notebookEditor.setSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); + private revealCellRange(cellIndex: number, matchIndex: number) { + this._findMatches[cellIndex].cell.state = CellState.PreviewContent; + this._notebookEditor.selectElement(this._findMatches[cellIndex].cell); + this._notebookEditor.setCellSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); this._notebookEditor.revealRangeInCenterIfOutsideViewport(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 6af89b780af..1b03bb7b0ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -34,6 +34,11 @@ export interface INotebookEditor { */ focus(): void; + /** + * Select & focus cell + */ + selectElement(cell: CellViewModel): void; + /** * Layout info for the notebook editor */ @@ -141,7 +146,7 @@ export interface INotebookEditor { */ revealRangeInCenterIfOutsideViewport(cell: CellViewModel, range: Range): void; - setSelection(cell: CellViewModel, selection: Range): void; + setCellSelection(cell: CellViewModel, selection: Range): void; /** * Change the decorations on cells. @@ -194,3 +199,24 @@ export enum CellRevealPosition { Top, Center } + +export enum CellState { + /** + * Default state. + * For markdown cell, it's Markdown preview. + * For code cell, the browser focus should be on the container instead of the editor + */ + Read, + + /** + * Content preview mode. + * For markdown cell, the source is now rendered in the editor. When the cell is not longer focsued/selected, it will fall back to Read mode. + * For code cell, this state is the same as Editing + */ + PreviewContent, + + /** + * Eding mode. Source for markdown or code is rendered in editors and the state will be persistent. + */ + Editing +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 3a7d6ffba28..15d67268960 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -21,7 +21,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, textBlockQuo import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento, ICompositeCodeEditor, IEditorCloseEvent } from 'vs/workbench/common/editor'; -import { INotebookEditor, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, NotebookLayoutInfo, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -411,6 +411,15 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { //#region Editor Features + selectElement(cell: CellViewModel) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.setSelection([index]); + this.list?.setFocus([index]); + } + } + revealInView(cell: CellViewModel) { const index = this.notebookViewModel?.getViewCellIndex(cell); @@ -483,7 +492,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } } - setSelection(cell: CellViewModel, range: Range): void { + setCellSelection(cell: CellViewModel, range: Range): void { const index = this.notebookViewModel?.getViewCellIndex(cell); if (index !== undefined) { @@ -547,7 +556,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list?.setFocus([insertIndex]); if (type === CellKind.Markdown) { - newCell.isEditing = true; + newCell.state = CellState.Editing; } DOM.scheduleAtNextAnimationFrame(() => { @@ -563,13 +572,13 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } editNotebookCell(cell: CellViewModel): void { - cell.isEditing = true; + cell.state = CellState.Editing; this.renderedEditors.get(cell)?.focus(); } saveNotebookCell(cell: CellViewModel): void { - cell.isEditing = false; + cell.state = CellState.Read; } getActiveCell() { @@ -593,7 +602,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { (document.activeElement as HTMLElement).blur(); } - cell.isEditing = false; + cell.state = CellState.Read; } this.list?.setFocus([index]); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index beff5d89d68..3e9002fb4e7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -18,13 +18,17 @@ import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/n import { EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Range } from 'vs/editor/common/core/range'; import { CellRevealType, CellRevealPosition } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { TestHistoryService } from 'vs/workbench/test/browser/workbenchTestServices'; -export class NotebookCellList extends WorkbenchList { +export class NotebookCellList extends WorkbenchList implements IDisposable { get onWillScroll(): Event { return this.view.onWillScroll; } get rowsContainer(): HTMLElement { return this.view.containerDomNode; } + private _previousSelectedElements: CellViewModel[] = []; + private _localDisposableStore = new DisposableStore(); constructor( private listUser: string, @@ -40,6 +44,16 @@ export class NotebookCellList extends WorkbenchList { ) { super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); + + this._previousSelectedElements = this.getSelectedElements(); + this._localDisposableStore.add(this.onDidChangeSelection((e) => { + this._previousSelectedElements.forEach(element => { + if (e.elements.indexOf(element) < 0) { + element.onDeselect(); + } + }); + this._previousSelectedElements = e.elements; + })); } domElementAtIndex(index: number): HTMLElement | null { @@ -264,9 +278,17 @@ export class NotebookCellList extends WorkbenchList { setCellSelection(index: number, range: Range) { const element = this.view.element(index); - element.setSelection(range); + if (element.editorAttached) { + element.setSelection(range); + } else { + getEditorAttachedPromise(element).then(() => { element.setSelection(range); }); + } } + dispose() { + this._localDisposableStore.dispose(); + super.dispose(); + } } function getEditorAttachedPromise(element: CellViewModel) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index a8a8ab08a38..993eee2a545 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -68,7 +68,6 @@ export class StatefullMarkdownCell extends Disposable { } }, {}); - viewCell.attachTextEditor(this.editor); const cts = new CancellationTokenSource(); this._register({ dispose() { cts.dispose(true); } }); @@ -78,7 +77,6 @@ export class StatefullMarkdownCell extends Disposable { } this.editor!.setModel(model); - viewCell.attachTextEditor(this.editor!); if (notebookEditor.getActiveCell() === viewCell) { this.editor!.focus(); } @@ -93,6 +91,8 @@ export class StatefullMarkdownCell extends Disposable { ); } + viewCell.attachTextEditor(this.editor!); + this.localDisposables.add(model.onDidChangeContent(() => { viewCell.setText(model.getLinesContent()); let clientHeight = this.cellContainer.clientHeight; @@ -132,12 +132,17 @@ export class StatefullMarkdownCell extends Disposable { }, () => { let newWidth = cellWidthResizeObserver.getWidth(); let realContentHeight = this.editor!.getContentHeight(); - this.editor!.layout( - { - width: newWidth, - height: realContentHeight - } - ); + let layoutInfo = this.editor!.getLayoutInfo(); + + // the dimension generated by the resize observer are float numbers, let's round it a bit to avoid relayout. + if (newWidth < layoutInfo.width - 0.3 || layoutInfo.width + 0.3 < newWidth) { + this.editor!.layout( + { + width: newWidth, + height: realContentHeight + } + ); + } }); cellWidthResizeObserver.startObserving(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel.ts index 81d02d503bc..6f7e90d9a7d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel.ts @@ -16,7 +16,7 @@ import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; import { CellKind, EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING, ICell, IOutput, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { CellFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatch, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export class CellViewModel extends Disposable { @@ -50,8 +50,27 @@ export class CellViewModel extends Disposable { get isEditing(): boolean { return this._isEditing; } - set isEditing(newState: boolean) { - this._isEditing = newState; + + private _state: CellState = CellState.Read; + + get state(): CellState { + return this._state; + } + + set state(newState: CellState) { + // no downgrade from Editing to PreviewContent + if (this._state === CellState.Editing && newState === CellState.PreviewContent) { + return; + } + + this._state = newState; + + if (newState !== CellState.Read) { + this._isEditing = true; + } else { + this._isEditing = false; + } + this._onDidChangeEditingState.fire(); } @@ -300,7 +319,7 @@ export class CellViewModel extends Disposable { } revealRangeInCenter(range: Range) { - this._textEditor?.revealRangeInCenter(range); + this._textEditor?.revealRangeInCenter(range, editorCommon.ScrollType.Immediate); } setSelection(range: Range) { @@ -353,6 +372,12 @@ export class CellViewModel extends Disposable { return ret; } + onDeselect() { + if (this.state === CellState.PreviewContent) { + this.state = CellState.Read; + } + } + getMarkdownRenderer() { if (!this._mdRenderer) { this._mdRenderer = this._instaService.createInstance(MarkdownRenderer); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 8bfae55b3d1..955b1f1b27d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -12,7 +12,7 @@ import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/n import { NotebookCellsSplice, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { CellFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatch, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export interface INotebookEditorViewState { editingCells: { [key: number]: boolean }; @@ -89,7 +89,7 @@ export class NotebookViewModel extends Disposable { hide() { this.viewCells.forEach(cell => { if (cell.getText() !== '') { - cell.isEditing = false; + cell.state = CellState.Read; } }); } @@ -154,7 +154,7 @@ export class NotebookViewModel extends Disposable { const isEditing = viewState.editingCells && viewState.editingCells[cell.handle]; const editorViewState = viewState.editorViewStates && viewState.editorViewStates[cell.handle]; - cell.isEditing = isEditing; + cell.state = isEditing ? CellState.Editing : CellState.Read; cell.restoreEditorViewState(editorViewState); }); } @@ -196,7 +196,9 @@ export class NotebookViewModel extends Disposable { } const data = mapping.get(ownerId)!; - data.oldDecorations = oldDecoration.decorations; + if (data) { + data.oldDecorations = oldDecoration.decorations; + } }); newDecorations.forEach(newDecoration => { @@ -211,7 +213,9 @@ export class NotebookViewModel extends Disposable { } const data = mapping.get(ownerId)!; - data.newDecorations = newDecoration.decorations; + if (data) { + data.newDecorations = newDecoration.decorations; + } }); const ret: ICellModelDecorations[] = []; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 1fe130a5f8d..d9ae452a054 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -90,8 +90,11 @@ export class TestNotebookEditor implements INotebookEditor { constructor( ) { } + selectElement(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } - setSelection(cell: CellViewModel, selection: Range): void { + setCellSelection(cell: CellViewModel, selection: Range): void { throw new Error('Method not implemented.'); } revealRangeInView(cell: CellViewModel, range: Range): void { -- GitLab