Scroll sync markdown editor with markdown preview (#44454)

Fixes #19459

Syncs the markdown preview's viewport with the markdown editor's using the proposed visible ranges API
This commit is contained in:
Matt Bierner 2018-02-26 16:46:54 -08:00 committed by GitHub
parent 5f25d3c167
commit 2279b4d252
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 73 deletions

View file

@ -75,12 +75,13 @@
* @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
*/
function getElementsForSourceLine(targetLine) {
const lineNumber = Math.floor(targetLine)
const lines = getCodeLineElements();
let previous = lines[0] || null;
for (const entry of lines) {
if (entry.line === targetLine) {
if (entry.line === lineNumber) {
return { previous: entry, next: null };
} else if (entry.line > targetLine) {
} else if (entry.line > lineNumber) {
return { previous, next: entry };
}
previous = entry;
@ -124,10 +125,6 @@
return { previous };
}
function getSourceRevealAddedOffset() {
return -(window.innerHeight * 1 / 5);
}
/**
* Attempt to reveal the element for a source line in the editor.
*
@ -136,17 +133,21 @@
function scrollToRevealSourceLine(line) {
const { previous, next } = getElementsForSourceLine(line);
marker.update(previous && previous.element);
if (previous && settings.scrollPreviewWithEditorSelection) {
if (previous && settings.scrollPreviewWithEditor) {
let scrollTo = 0;
if (next) {
const rect = previous.element.getBoundingClientRect();
const previousTop = rect.top;
if (next && next.line !== previous.line) {
// Between two elements. Go to percentage offset between them.
const betweenProgress = (line - previous.line) / (next.line - previous.line);
const elementOffset = next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top;
scrollTo = previous.element.getBoundingClientRect().top + betweenProgress * elementOffset;
const elementOffset = next.element.getBoundingClientRect().top - previousTop;
scrollTo = previousTop + betweenProgress * elementOffset;
} else {
scrollTo = previous.element.getBoundingClientRect().top;
scrollTo = previousTop;
}
window.scroll(0, Math.max(1, window.scrollY + scrollTo + getSourceRevealAddedOffset()));
window.scroll(0, Math.max(1, window.scrollY + scrollTo));
}
}
@ -192,7 +193,7 @@
const settings = JSON.parse(document.getElementById('vscode-markdown-preview-data').getAttribute('data-settings'));
function onLoad() {
if (settings.scrollPreviewWithEditorSelection) {
if (settings.scrollPreviewWithEditor) {
setTimeout(() => {
const initialLine = +settings.line;
if (!isNaN(initialLine)) {

View file

@ -272,10 +272,10 @@
"description": "%markdown.preview.lineHeight.desc%",
"scope": "resource"
},
"markdown.preview.scrollPreviewWithEditorSelection": {
"markdown.preview.scrollPreviewWithEditor": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollPreviewWithEditorSelection.desc%",
"description": "%markdown.preview.scrollPreviewWithEditor.desc%",
"scope": "resource"
},
"markdown.preview.markEditorSelection": {

View file

@ -8,8 +8,8 @@
"markdown.preview.fontSize.desc": "Controls the font size in pixels used in the markdown preview.",
"markdown.preview.lineHeight.desc": "Controls the line height used in the markdown preview. This number is relative to the font size.",
"markdown.preview.markEditorSelection.desc": "Mark the current editor selection in the markdown preview.",
"markdown.preview.scrollEditorWithPreview.desc": "When the markdown preview is scrolled, update the view of the editor.",
"markdown.preview.scrollPreviewWithEditorSelection.desc": "Scrolls the markdown preview to reveal the currently selected line from the editor.",
"markdown.preview.scrollEditorWithPreview.desc": "When a markdown preview is scrolled, update the view of the editor.",
"markdown.preview.scrollPreviewWithEditor.desc": "When a markdown editor preview is scrolled, update the view of the preview.",
"markdown.preview.title" : "Open Preview",
"markdown.previewFrontMatter.dec": "Sets how YAML front matter should be rendered in the markdown preview. 'hide' removes the front matter. Otherwise, the front matter is treated as markdown content.",
"markdown.previewSide.title" : "Open Preview to the Side",

View file

@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Command } from '../commandManager';
export class DidClickCommand implements Command {
public readonly id = '_markdown.didClick';
public execute(uri: string, line: number) {
const sourceUri = vscode.Uri.parse(decodeURIComponent(uri));
return vscode.workspace.openTextDocument(sourceUri)
.then(document => vscode.window.showTextDocument(document))
.then(editor =>
vscode.commands.executeCommand('revealLine', { lineNumber: Math.floor(line), at: 'center' })
.then(() => editor))
.then(editor => {
if (editor) {
editor.selection = new vscode.Selection(
new vscode.Position(Math.floor(line), 0),
new vscode.Position(Math.floor(line), 0));
}
});
}
}

View file

@ -10,5 +10,4 @@ export { ShowSourceCommand } from './showSource';
export { RefreshPreviewCommand } from './refreshPreview';
export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
export { RevealLineCommand } from './revealLine';
export { DidClickCommand } from './didClick';
export { MoveCursorToPositionCommand } from './moveCursorToPosition';

View file

@ -6,29 +6,19 @@
import * as vscode from 'vscode';
import { Command } from '../commandManager';
import { Logger } from '../logger';
import { isMarkdownFile } from '../features/previewContentProvider';
import { MarkdownPreviewManager } from '../features/previewContentProvider';
export class RevealLineCommand implements Command {
public readonly id = '_markdown.revealLine';
public constructor(
private readonly logger: Logger
private readonly logger: Logger,
private readonly previewManager: MarkdownPreviewManager
) { }
public execute(uri: string, line: number) {
const sourceUri = vscode.Uri.parse(decodeURIComponent(uri));
this.logger.log('revealLine', { uri, sourceUri: sourceUri.toString(), line });
vscode.window.visibleTextEditors
.filter(editor => isMarkdownFile(editor.document) && editor.document.uri.fsPath === sourceUri.fsPath)
.forEach(editor => {
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);
});
this.previewManager.revealLine(sourceUri, line);
}
}

View file

@ -45,11 +45,10 @@ export function activate(context: vscode.ExtensionContext) {
commandManager.register(new commands.ShowPinnedPreviewToSideCommand(previewManager, telemetryReporter));
commandManager.register(new commands.ShowSourceCommand());
commandManager.register(new commands.RefreshPreviewCommand(previewManager));
commandManager.register(new commands.RevealLineCommand(logger));
commandManager.register(new commands.RevealLineCommand(logger, previewManager));
commandManager.register(new commands.MoveCursorToPositionCommand());
commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector));
commandManager.register(new commands.OnPreviewStyleLoadErrorCommand());
commandManager.register(new commands.DidClickCommand());
commandManager.register(new commands.OpenDocumentLinkCommand(engine));
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {

View file

@ -33,7 +33,7 @@ export class MarkdownPreviewConfig {
public readonly lineBreaks: boolean;
public readonly doubleClickToSwitchToEditor: boolean;
public readonly scrollEditorWithPreview: boolean;
public readonly scrollPreviewWithEditorSelection: boolean;
public readonly scrollPreviewWithEditor: boolean;
public readonly markEditorSelection: boolean;
public readonly lineHeight: number;
@ -54,7 +54,7 @@ export class MarkdownPreviewConfig {
}
this.previewFrontMatter = markdownConfig.get<string>('previewFrontMatter', 'hide');
this.scrollPreviewWithEditorSelection = !!markdownConfig.get<boolean>('preview.scrollPreviewWithEditorSelection', true);
this.scrollPreviewWithEditor = !!markdownConfig.get<boolean>('preview.scrollPreviewWithEditor', true);
this.scrollEditorWithPreview = !!markdownConfig.get<boolean>('preview.scrollEditorWithPreview', true);
this.lineBreaks = !!markdownConfig.get<boolean>('preview.breaks', false);
this.doubleClickToSwitchToEditor = !!markdownConfig.get<boolean>('preview.doubleClickToSwitchToEditor', true);
@ -227,7 +227,7 @@ export class MarkdownContentProvider {
const initialData = {
source: sourceUri.toString(),
line: initialLine,
scrollPreviewWithEditorSelection: config.scrollPreviewWithEditorSelection,
scrollPreviewWithEditor: config.scrollPreviewWithEditor,
scrollEditorWithPreview: config.scrollEditorWithPreview,
doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor,
disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings()
@ -279,11 +279,16 @@ class MarkdownPreview {
public static previewScheme = 'vscode-markdown-preview';
private static previewCount = 0;
public isScrolling = false;
private readonly webview: vscode.Webview;
private throttleTimer: any;
private initialLine: number | undefined = undefined;
private readonly disposables: vscode.Disposable[] = [];
<<<<<<< HEAD
private firstUpdate = true;
=======
>>>>>>> Scroll sync markdown editor with markdown preview
private currentVersion?: { resource: vscode.Uri, version: number };
constructor(
@ -320,10 +325,11 @@ class MarkdownPreview {
}
}, null, this.disposables);
vscode.window.onDidChangeTextEditorSelection(event => {
vscode.window.onDidChangeTextEditorVisibleRanges(event => {
if (isMarkdownFile(event.textEditor.document) && this.isPreviewOf(event.textEditor.document.uri)) {
const resource = event.textEditor.document.uri;
this.updateForSelection(resource, event.selections[0].active.line);
const line = getVisibleLine(event.textEditor);
this.updateForView(resource, line);
}
}, null, this.disposables);
}
@ -347,7 +353,7 @@ class MarkdownPreview {
public update(resource: vscode.Uri) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.fsPath === resource.fsPath) {
this.initialLine = editor.selection.active.line;
this.initialLine = getVisibleLine(editor);
} else {
this.initialLine = undefined;
}
@ -409,14 +415,19 @@ class MarkdownPreview {
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
}
private updateForSelection(resource: vscode.Uri, line: number) {
private updateForView(resource: vscode.Uri, topLine: number) {
if (!this.isPreviewOf(resource)) {
return;
}
this.logger.log('updatePreviewForSelection', { markdownFile: resource });
this.initialLine = line;
this.webview.postMessage({ line, source: resource.toString() });
if (this.isScrolling) {
this.isScrolling = false;
return;
}
this.logger.log('updateForView', { markdownFile: resource });
this.initialLine = topLine;
this.webview.postMessage({ line: topLine, source: resource.toString() });
}
private async doUpdate(): Promise<void> {
@ -426,7 +437,7 @@ class MarkdownPreview {
const document = await vscode.workspace.openTextDocument(resource);
if (this.currentVersion && this.currentVersion.resource.fsPath === resource.fsPath && this.currentVersion.version === document.version) {
if (this.initialLine) {
this.updateForSelection(resource, this.initialLine);
this.updateForView(resource, this.initialLine);
}
return;
}
@ -528,6 +539,31 @@ export class MarkdownPreviewManager {
preview.update(resource);
}
public revealLine(
resource: vscode.Uri,
line: number
) {
for (const editor of vscode.window.visibleTextEditors) {
if (!isMarkdownFile(editor.document) || editor.document.uri.fsPath !== resource.fsPath) {
continue;
}
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);
}
for (const preview of this.previews) {
if (preview.isPreviewOf(resource)) {
preview.isScrolling = true;
}
}
}
private getExistingPreview(
resource: vscode.Uri,
previewSettings: PreviewSettings
@ -545,4 +581,11 @@ function disposeAll(disposables: vscode.Disposable[]) {
item.dispose();
}
}
}
function getVisibleLine(editor: vscode.TextEditor): number {
const lineNumber = editor.visibleRanges[0].start.line;
const line = editor.document.lineAt(lineNumber);
const progress = Math.min(0.999, editor.visibleRanges[0].start.character / (line.text.length + 1));
return lineNumber + progress;
}

View file

@ -122,7 +122,7 @@ export class MainThreadTextEditorProperties {
delta.visibleRanges = this.visibleRanges;
}
if (delta.selections || delta.options) {
if (delta.selections || delta.options || delta.visibleRanges) {
// something changed
return delta;
}