diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 71162b60e8f..0254ed42e40 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -12,7 +12,7 @@ import { MarkdownContributionProvider } from '../markdownExtensions'; import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { normalizeResource, WebviewResourceProvider } from '../util/resources'; -import { getVisibleLine, scrollEditorToLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor'; +import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider'; import { MarkdownEngine } from '../markdownEngine'; @@ -286,6 +286,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { }); } + + private async updatePreview(forceUpdate?: boolean): Promise { clearTimeout(this.throttleTimer); this.throttleTimer = undefined; @@ -529,8 +531,9 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown this._register(this._webviewPanel.onDidChangeViewState(e => { this._onDidChangeViewState.fire(e); })); + this._register(this.preview.onScroll((scrollInfo) => { - this._onScrollEmitter.fire(scrollInfo); + topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo); })); this._register(topmostLineMonitor.onDidChanged(event => { @@ -540,7 +543,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown })); const currentLine = this.preview.state.line ? this.preview.state.line : 0; - this._onScrollEmitter.fire({ line: currentLine, uri: this.preview.resource }); + topmostLineMonitor.setPreviousStaticEditorLine({ line: currentLine, uri: this.preview.resource }); } private readonly _onDispose = this._register(new vscode.EventEmitter()); @@ -549,9 +552,6 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter()); public readonly onDidChangeViewState = this._onDidChangeViewState.event; - private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); - public readonly onScroll = this._onScrollEmitter.event; - override dispose() { this._onDispose.fire(); super.dispose(); @@ -804,3 +804,18 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow } } +/** + * Change the top-most visible line of `editor` to be at `line` + */ +export function scrollEditorToLine( + line: number, + editor: vscode.TextEditor +) { + const sourceLine = Math.floor(line); + const fraction = line - sourceLine; + const text = editor.document.lineAt(sourceLine).text; + const start = Math.floor(fraction * text.length); + editor.revealRange( + new vscode.Range(sourceLine, start, sourceLine + 1, 0), + vscode.TextEditorRevealType.AtTop); +} diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 7b94522808e..738286d2091 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -9,7 +9,7 @@ import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; import { Disposable, disposeAll } from '../util/dispose'; import { TopmostLineMonitor } from '../util/topmostLineMonitor'; -import { DynamicMarkdownPreview, ManagedMarkdownPreview, StartingScrollFragment, StaticMarkdownPreview } from './preview'; +import { DynamicMarkdownPreview, ManagedMarkdownPreview, StartingScrollFragment, StaticMarkdownPreview, scrollEditorToLine } from './preview'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContentProvider } from './previewContentProvider'; @@ -75,6 +75,10 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview super(); this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); this._register(vscode.window.registerCustomEditorProvider(this.customEditorViewType, this)); + + this._register(this._topmostLineMonitor.onEditorNeedsScrolling(event => { + scrollEditorToLine(event.line, event.editor); + })); } public refresh() { @@ -160,7 +164,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise { - const lineNumber = this._topmostLineMonitor.previousMDTextEditor?.document.uri.toString() === document.uri.toString() ? this._topmostLineMonitor.previousMDTextEditor?.visibleRanges[0].start.line : undefined; + const lineNumber = this._topmostLineMonitor.getPreviousMDTextEditorLineByUri(document.uri); const preview = StaticMarkdownPreview.revive( document.uri, webview, @@ -224,11 +228,6 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._staticPreviews.delete(preview); }); - // Continuously update the scroll info in case user changes the editor. - preview.onScroll((scrollInfo) => { - this._topmostLineMonitor.previousStaticEditorInfo = scrollInfo; - }); - this.trackActive(preview); return preview; } diff --git a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts index 0b66b92cfc1..b2382b46fe2 100644 --- a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts +++ b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts @@ -9,19 +9,30 @@ import { isMarkdownFile } from './file'; export interface LastScrollLocation { readonly line: number; - readonly uri: vscode.Uri | undefined; + readonly uri: vscode.Uri; } export class TopmostLineMonitor extends Disposable { private readonly pendingUpdates = new Map(); private readonly throttle = 50; - public previousMDTextEditor: vscode.TextEditor | undefined; - public previousStaticEditorInfo: LastScrollLocation = { line: 0, uri: undefined }; + private previousMDTextEditors = new Map(); + private previousStaticEditorInfo = new Map(); + private isPrevEditorCustom = false; + + private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>()); + public readonly onDidChanged = this._onChanged.event; + + private readonly _onEditorNeedsScrolling = this._register(new vscode.EventEmitter<{ readonly line: number, readonly editor: vscode.TextEditor }>()); + public readonly onEditorNeedsScrolling = this._onEditorNeedsScrolling.event; constructor() { super(); - this.previousMDTextEditor = vscode.window.activeTextEditor; + + if (vscode.window.activeTextEditor) { + this.setPreviousMDTextEditorLine(vscode.window.activeTextEditor); + } + this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => { if (isMarkdownFile(event.textEditor.document)) { const line = getVisibleLine(event.textEditor); @@ -33,22 +44,40 @@ export class TopmostLineMonitor extends Disposable { this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => { - // When at a markdown file, apply existing scroll settings from static preview if applicable. + // When at a markdown file, apply existing scroll settings from static preview if last editor was custom. // Also save reference to text editor for line number reference later if (textEditor && isMarkdownFile(textEditor.document!)) { - - if (this.previousStaticEditorInfo.uri?.toString() === textEditor.document.uri.toString()) { - const line = this.previousStaticEditorInfo.line ? this.previousStaticEditorInfo.line : 0; - scrollEditorToLine(line, textEditor); + if (this.isPrevEditorCustom) { + const line = this.getPreviousStaticEditorLineByUri(textEditor.document.uri); + if (line) { + this._onEditorNeedsScrolling.fire({ line: line, editor: textEditor }); + } } - - this.previousMDTextEditor = textEditor; + this.setPreviousMDTextEditorLine(textEditor); } + + this.isPrevEditorCustom = (textEditor === undefined); })); } - private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>()); - public readonly onDidChanged = this._onChanged.event; + public setPreviousMDTextEditorLine(editor: vscode.TextEditor) { + const uri = editor.document.uri; + this.previousMDTextEditors.set(uri.toString(), editor); + } + + public getPreviousMDTextEditorLineByUri(resource: vscode.Uri) { + const editor = this.previousMDTextEditors.get(resource.toString()); + return editor?.visibleRanges[0].start.line; + } + + public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation) { + this.previousStaticEditorInfo.set(scrollLocation.uri.toString(), scrollLocation); + } + + public getPreviousStaticEditorLineByUri(resource: vscode.Uri) { + const scrollLoc = this.previousStaticEditorInfo.get(resource.toString()); + return scrollLoc?.line; + } private updateLine( resource: vscode.Uri, @@ -91,18 +120,3 @@ export function getVisibleLine( const progress = firstVisiblePosition.character / (line.text.length + 2); return lineNumber + progress; } -/** - * Change the top-most visible line of `editor` to be at `line` - */ -export function scrollEditorToLine( - line: number, - editor: vscode.TextEditor -) { - const sourceLine = Math.floor(line); - const fraction = line - sourceLine; - const text = editor.document.lineAt(sourceLine).text; - const start = Math.floor(fraction * text.length); - editor.revealRange( - new vscode.Range(sourceLine, start, sourceLine + 1, 0), - vscode.TextEditorRevealType.AtTop); -}