diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 42d6f08fc072b..23ce0b495b05b 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, TopmostLineMonitor } from '../util/topmostLineMonitor'; +import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider'; import { MarkdownEngine } from '../markdownEngine'; @@ -120,6 +120,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = []; private readonly _fileWatchersBySrc = new Map(); + private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); + public readonly onScroll = this._onScrollEmitter.event; constructor( webview: vscode.WebviewPanel, @@ -324,7 +326,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private onDidScrollPreview(line: number) { this.line = line; - + this._onScrollEmitter.fire({ line: this.line, uri: this._resource }); const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource); if (!config.scrollEditorWithPreview) { return; @@ -336,13 +338,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } this.isScrolling = true; - 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); + scrollEditorToLine(line, editor); } } @@ -497,11 +493,13 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown webview: vscode.WebviewPanel, contentProvider: MarkdownContentProvider, previewConfigurations: MarkdownPreviewConfigurationManager, + topmostLineMonitor: TopmostLineMonitor, logger: Logger, contributionProvider: MarkdownContributionProvider, engine: MarkdownEngine, + scrollLine?: number, ): StaticMarkdownPreview { - return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider, engine); + return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, engine, scrollLine); } private readonly preview: MarkdownPreview; @@ -511,13 +509,15 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown resource: vscode.Uri, contentProvider: MarkdownContentProvider, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, + topmostLineMonitor: TopmostLineMonitor, logger: Logger, contributionProvider: MarkdownContributionProvider, engine: MarkdownEngine, + scrollLine?: number, ) { super(); - - this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, undefined, { + const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined; + this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, { getAdditionalState: () => { return {}; }, openPreviewLinkToMarkdownFile: () => { /* todo */ } }, engine, contentProvider, _previewConfigurations, logger, contributionProvider)); @@ -529,6 +529,16 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown this._register(this._webviewPanel.onDidChangeViewState(e => { this._onDidChangeViewState.fire(e); })); + + this._register(this.preview.onScroll((scrollInfo) => { + topmostLineMonitor.setPreviousEditorLine(scrollInfo); + })); + + this._register(topmostLineMonitor.onDidChanged(event => { + if (this.preview.isPreviewOf(event.resource)) { + this.preview.scrollTo(event.line); + } + })); } private readonly _onDispose = this._register(new vscode.EventEmitter()); @@ -789,3 +799,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 b93a506105358..7f30abb1c84d6 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -9,9 +9,10 @@ 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'; +import { isMarkdownFile } from '../util/file'; export interface DynamicPreviewSettings { readonly resourceColumn: vscode.ViewColumn; @@ -75,6 +76,17 @@ 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(vscode.window.onDidChangeActiveTextEditor(textEditor => { + + // When at a markdown file, apply existing scroll settings + if (textEditor && textEditor.document && isMarkdownFile(textEditor.document)) { + const line = this._topmostLineMonitor.getPreviousEditorLineByUri(textEditor.document.uri); + if (line) { + scrollEditorToLine(line, textEditor); + } + } + })); } public refresh() { @@ -160,14 +172,18 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise { + const lineNumber = this._topmostLineMonitor.getPreviousEditorLineByUri(document.uri); const preview = StaticMarkdownPreview.revive( document.uri, webview, this._contentProvider, this._previewConfigurations, + this._topmostLineMonitor, this._logger, this._contributions, - this._engine); + this._engine, + lineNumber + ); this.registerStaticPreview(preview); } diff --git a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts index 57395fbc691cc..62f5b5c194b1b 100644 --- a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts +++ b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts @@ -7,18 +7,32 @@ import * as vscode from 'vscode'; import { Disposable } from '../util/dispose'; import { isMarkdownFile } from './file'; +export interface LastScrollLocation { + readonly line: number; + readonly uri: vscode.Uri; +} + export class TopmostLineMonitor extends Disposable { private readonly pendingUpdates = new Map(); private readonly throttle = 50; + private previousEditorInfo = new Map(); + public isPrevEditorCustom = false; constructor() { super(); + + if (vscode.window.activeTextEditor) { + const line = getVisibleLine(vscode.window.activeTextEditor); + this.setPreviousEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 }); + } + this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => { if (isMarkdownFile(event.textEditor.document)) { const line = getVisibleLine(event.textEditor); if (typeof line === 'number') { this.updateLine(event.textEditor.document.uri, line); + this.setPreviousEditorLine({ uri: event.textEditor.document.uri, line: line }); } } })); @@ -27,7 +41,16 @@ export class TopmostLineMonitor extends Disposable { private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>()); public readonly onDidChanged = this._onChanged.event; - private updateLine( + public setPreviousEditorLine(scrollLocation: LastScrollLocation): void { + this.previousEditorInfo.set(scrollLocation.uri.toString(), scrollLocation); + } + + public getPreviousEditorLineByUri(resource: vscode.Uri): number | undefined { + const scrollLoc = this.previousEditorInfo.get(resource.toString()); + return scrollLoc?.line; + } + + public updateLine( resource: vscode.Uri, line: number ) {