Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown Static Preview Scrolling Fixes #123727

Merged
merged 14 commits into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions extensions/markdown-language-features/src/features/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, scrollEditorToLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider';
import { MarkdownEngine } from '../markdownEngine';
Expand Down Expand Up @@ -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</* src: */ string, vscode.FileSystemWatcher>();
private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
public readonly onScroll = this._onScrollEmitter.event;

constructor(
webview: vscode.WebviewPanel,
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand All @@ -529,6 +529,18 @@ 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);
}));

this._register(topmostLineMonitor.onDidChanged(event => {
if (this.preview.isPreviewOf(event.resource)) {
this.preview.scrollTo(event.line);
}
}));

const currentLine = this.preview.state.line ? this.preview.state.line : 0;
this._onScrollEmitter.fire({ line: currentLine, uri: this.preview.resource });
}

private readonly _onDispose = this._register(new vscode.EventEmitter<void>());
Expand All @@ -537,6 +549,9 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
public readonly onDidChangeViewState = this._onDidChangeViewState.event;

private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
public readonly onScroll = this._onScrollEmitter.event;

override dispose() {
this._onDispose.fire();
super.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,18 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
document: vscode.TextDocument,
webview: vscode.WebviewPanel
): Promise<void> {
const lineNumber = this._topmostLineMonitor.previousMDTextEditor?.document.uri.toString() === document.uri.toString() ? this._topmostLineMonitor.previousMDTextEditor?.visibleRanges[0].start.line : undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of exposing previousMDTextEditor, can you hide this behind a method that gets the line for a given uri?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previousMDTextEditor changed to an internal map with public functions for access.

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);
}

Expand Down Expand Up @@ -220,6 +224,11 @@ 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this live in the preview itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, accidentally forgot that I passed an instance of the topmost line monitor into the preview. Preview should now do this.

});

this.trackActive(preview);
return preview;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ import * as vscode from 'vscode';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from './file';

export interface LastScrollLocation {
readonly line: number;
readonly uri: vscode.Uri | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the cases where uri can be undefined? Is this something we'd expect to happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally allowed for undefined for initialization; removed undefined.

}

export class TopmostLineMonitor extends Disposable {

private readonly pendingUpdates = new Map<string, number>();
private readonly throttle = 50;
public previousMDTextEditor: vscode.TextEditor | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there cases where you can more than one markdown file that we need to track the scroll position of? Try seeing if you can hit this using multiple editors and editor groups with different files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the previous text editors to be indexed via string URI so that multiple editor instances can be accessed. This might get populated if the user has a lot of markdown files open (?)

public previousStaticEditorInfo: LastScrollLocation = { line: 0, uri: undefined };

constructor() {
super();
this.previousMDTextEditor = vscode.window.activeTextEditor;
this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
if (isMarkdownFile(event.textEditor.document)) {
const line = getVisibleLine(event.textEditor);
Expand All @@ -22,6 +30,21 @@ 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.
// Also save reference to text editor for line number reference later
if (textEditor && isMarkdownFile(textEditor.document!)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Avoid using ! where possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.


if (this.previousStaticEditorInfo.uri?.toString() === textEditor.document.uri.toString()) {
const line = this.previousStaticEditorInfo.line ? this.previousStaticEditorInfo.line : 0;
scrollEditorToLine(line, textEditor);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the TopMostLineMonitor being scrolling editors or can this live somewhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to MarkdownPreviewManager

}

this.previousMDTextEditor = textEditor;
}
}));
}

private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>());
Expand Down Expand Up @@ -68,3 +91,18 @@ 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);
}