From 5bf8cc76184aab48b602a0af0d52d16de7d25903 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Sat, 21 Jan 2017 18:03:05 -0800 Subject: [PATCH 1/2] Add Markdown Preview to Editor Scroll Syncronization Adds an initial implementation of scroll synrconization from the markdown preview to the editor. When the preview is scrolled, automatically scrolls the editor to reveal the same location. This required adding a new supported reveal type `AtTop` in our API. This is already supported and used internally, but not really exposed in the public apis (except for the `revealLine` command). --- extensions/markdown/media/main.js | 21 ++++++++++----- extensions/markdown/package.json | 5 ++++ extensions/markdown/package.nls.json | 3 ++- extensions/markdown/src/extension.ts | 25 ++++++++++++----- .../editor/browser/widget/diffEditorWidget.ts | 4 +++ src/vs/editor/common/commonCodeEditor.ts | 8 ++++++ src/vs/editor/common/editorCommon.ts | 5 ++++ src/vs/monaco.d.ts | 4 +++ src/vs/vscode.d.ts | 6 ++++- src/vs/workbench/api/node/extHostTypes.ts | 3 ++- .../api/node/mainThreadEditorsTracker.ts | 27 ++++++++++++------- 11 files changed, 85 insertions(+), 26 deletions(-) diff --git a/extensions/markdown/media/main.js b/extensions/markdown/media/main.js index 1c3414b843c..a458b9249aa 100644 --- a/extensions/markdown/media/main.js +++ b/extensions/markdown/media/main.js @@ -83,7 +83,7 @@ let line = 0; if (next) { const betweenProgress = (offset - window.scrollY - previous.element.getBoundingClientRect().top) / (next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top); - line = previous.line + Math.floor(betweenProgress * (next.line - previous.line)); + line = previous.line + betweenProgress * (next.line - previous.line); } else { line = previous.line; } @@ -91,7 +91,7 @@ const args = [window.initialData.source, line]; window.parent.postMessage({ command: "did-click-link", - data: `command:_markdown.didClick?${encodeURIComponent(JSON.stringify(args))}` + data: `command:_markdown.revealLine?${encodeURIComponent(JSON.stringify(args))}` }, "file://"); } } @@ -119,6 +119,7 @@ } } + var scrollDisabled = false; var pageHeight = 0; var marker = new ActiveLineMarker(); @@ -127,6 +128,7 @@ if (window.initialData.enablePreviewSync) { const initialLine = +window.initialData.line || 0; + scrollDisabled = true; scrollToRevealSourceLine(initialLine); } }; @@ -144,6 +146,7 @@ window.addEventListener('message', event => { const line = +event.data.line; if (!isNaN(line)) { + scrollDisabled = true; scrollToRevealSourceLine(line); } }, false); @@ -153,10 +156,14 @@ didUpdateScrollPosition(offset); }; - /** - window.onscroll = () => { - didUpdateScrollPosition(window.scrollY); - }; - */ + if (window.initialData.enableScrollSync) { + window.onscroll = () => { + if (scrollDisabled) { + scrollDisabled = false; + } else { + didUpdateScrollPosition(window.scrollY); + } + }; + } } }()); \ No newline at end of file diff --git a/extensions/markdown/package.json b/extensions/markdown/package.json index fcec5143c1f..d04d8c15efa 100644 --- a/extensions/markdown/package.json +++ b/extensions/markdown/package.json @@ -152,6 +152,11 @@ "type": "boolean", "default": true, "description": "%markdown.preview.experimentalSyncronizationEnabled.desc%" + }, + "markdown.preview.synchronizePreviewScrollingToEditor": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.synchronizePreviewScrollingToEditor.desc%" } } } diff --git a/extensions/markdown/package.nls.json b/extensions/markdown/package.nls.json index be901187647..3525054ac59 100644 --- a/extensions/markdown/package.nls.json +++ b/extensions/markdown/package.nls.json @@ -7,5 +7,6 @@ "markdown.preview.fontFamily.desc": "Controls the font family used in the markdown preview.", "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.experimentalSyncronizationEnabled.desc": "Enable experimental syncronization between the markdown preview and the editor" + "markdown.preview.experimentalSyncronizationEnabled.desc": "Enable experimental syncronization between the markdown preview and the editor", + "markdown.preview.synchronizePreviewScrollingToEditor.desc": "When the preview is scrolled, update the view of the editor" } \ No newline at end of file diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index a0ec0c1bbf2..e499b9c7e9c 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -40,10 +40,19 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(uri, true))); context.subscriptions.push(vscode.commands.registerCommand('markdown.showSource', showSource)); - context.subscriptions.push(vscode.commands.registerCommand('_markdown.didClick', (uri, line) => { - return vscode.workspace.openTextDocument(vscode.Uri.parse(decodeURIComponent(uri))) - .then(document => vscode.window.showTextDocument(document)) - .then(editor => vscode.commands.executeCommand('revealLine', { lineNumber: line, at: 'top' })); + context.subscriptions.push(vscode.commands.registerCommand('_markdown.revealLine', (uri, line) => { + const sourceUri = vscode.Uri.parse(decodeURIComponent(uri)); + vscode.window.visibleTextEditors + .filter(editor => editor.document.uri.path === sourceUri.path) + .forEach(editor => { + const sourceLine = Math.floor(line); + const text = editor.document.getText(new vscode.Range(sourceLine, 0, sourceLine + 1, 0)); + const fraction = line - Math.floor(line); + const start = fraction * text.length; + editor.revealRange( + new vscode.Range(sourceLine, start, sourceLine + 1, 0), + vscode.TextEditorRevealType.AtTop); + }); })); context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(document => { @@ -246,8 +255,9 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { return vscode.workspace.openTextDocument(sourceUri).then(document => { const scrollBeyondLastLine = vscode.workspace.getConfiguration('editor')['scrollBeyondLastLine']; const wordWrap = vscode.workspace.getConfiguration('editor')['wordWrap']; - const enablePreviewSync = vscode.workspace.getConfiguration('markdown').get('preview.experimentalSyncronizationEnabled', true); - const previewFrontMatter = vscode.workspace.getConfiguration('markdown')['previewFrontMatter']; + + const markdownConfig = vscode.workspace.getConfiguration('markdown'); + const previewFrontMatter = markdownConfig.get('previewFrontMatter', 'hide'); let initialLine = 0; const editor = vscode.window.activeTextEditor; @@ -273,7 +283,8 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { window.initialData = { source: "${encodeURIComponent(sourceUri.scheme + '://' + sourceUri.path)}", line: ${initialLine}, - enablePreviewSync: ${!!enablePreviewSync} + enablePreviewSync: ${!!markdownConfig.get('preview.experimentalSyncronizationEnabled', true)}, + enableScrollSync: ${!!markdownConfig.get('synchronizePreviewScrollingToEditor', true)} }; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 1480b724903..6a82ca683b3 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -612,6 +612,10 @@ export class DiffEditorWidget extends EventEmitter implements editorBrowser.IDif this.modifiedEditor.revealRangeInCenterIfOutsideViewport(range); } + public revealRangeAtTop(range: editorCommon.IRange): void { + this.modifiedEditor.revealRangeAtTop(range); + } + public _addAction(descriptor: editorCommon.IActionDescriptor): IAddedAction { return this.modifiedEditor._addAction(descriptor); } diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 250b2ae7c71..a0b1987a033 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -460,6 +460,14 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom ); } + public revealRangeAtTop(range: editorCommon.IRange): void { + this._revealRange( + range, + editorCommon.VerticalRevealType.Top, + true + ); + } + private _revealRange(range: editorCommon.IRange, verticalType: editorCommon.VerticalRevealType, revealHorizontal: boolean): void { if (!Range.isIRange(range)) { throw new Error('Invalid arguments'); diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index c83b481fde0..d8f489bb3e9 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -3615,6 +3615,11 @@ export interface IEditor { */ revealRangeInCenter(range: IRange): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range at the top of the viewport. + */ + revealRangeAtTop(range: IRange): void; + /** * Scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ec1104105b3..3a6894fa563 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3007,6 +3007,10 @@ declare module monaco.editor { * Scroll vertically or horizontally as necessary and reveal a range centered vertically. */ revealRangeInCenter(range: IRange): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range at the top of the viewport. + */ + revealRangeAtTop(range: IRange): void; /** * Scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index e51b03adac2..63312799d8c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -651,7 +651,11 @@ declare module 'vscode' { * If the range is outside the viewport, it will be revealed in the center of the viewport. * Otherwise, it will be revealed with as little scrolling as possible. */ - InCenterIfOutsideViewport = 2 + InCenterIfOutsideViewport = 2, + /** + * The range will always be revealed at the top of the viewport. + */ + AtTop = 3 } /** diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index b74137ff36e..0913f814145 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -920,7 +920,8 @@ export enum TextDocumentSaveReason { export enum TextEditorRevealType { Default = 0, InCenter = 1, - InCenterIfOutsideViewport = 2 + InCenterIfOutsideViewport = 2, + AtTop = 3 } export enum TextEditorSelectionChangeKind { diff --git a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts index 127ff1c9373..137d43b0ee4 100644 --- a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts +++ b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts @@ -56,7 +56,8 @@ export interface IFocusTracker { export enum TextEditorRevealType { Default = 0, InCenter = 1, - InCenterIfOutsideViewport = 2 + InCenterIfOutsideViewport = 2, + AtTop = 3 } export interface IUndoStopOptions { @@ -288,14 +289,22 @@ export class MainThreadTextEditor { console.warn('revealRange on invisible editor'); return; } - if (revealType === TextEditorRevealType.Default) { - this._codeEditor.revealRange(range); - } else if (revealType === TextEditorRevealType.InCenter) { - this._codeEditor.revealRangeInCenter(range); - } else if (revealType === TextEditorRevealType.InCenterIfOutsideViewport) { - this._codeEditor.revealRangeInCenterIfOutsideViewport(range); - } else { - console.warn('Unknown revealType'); + switch (revealType) { + case TextEditorRevealType.Default: + this._codeEditor.revealRange(range); + break; + case TextEditorRevealType.InCenter: + this._codeEditor.revealRangeInCenter(range); + break;; + case TextEditorRevealType.InCenterIfOutsideViewport: + this._codeEditor.revealRangeInCenterIfOutsideViewport(range); + break; + case TextEditorRevealType.AtTop: + this._codeEditor.revealRangeAtTop(range); + break; + default: + console.warn('Unknown revealType'); + break; } } From bc27d5c88e7be6b54642b0b35b178ca32231bb22 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Sun, 22 Jan 2017 01:04:18 -0800 Subject: [PATCH 2/2] Restore double click to switch to editor behavior --- extensions/markdown/media/main.js | 32 +++++++++++++++++----------- extensions/markdown/src/extension.ts | 9 +++++++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/extensions/markdown/media/main.js b/extensions/markdown/media/main.js index a458b9249aa..fb99d8043f2 100644 --- a/extensions/markdown/media/main.js +++ b/extensions/markdown/media/main.js @@ -77,23 +77,17 @@ } } - function didUpdateScrollPosition(offset) { + function getEditorLineNumberForPageOffset(offset) { const {previous, next} = getLineElementsAtPageOffset(offset); if (previous) { - let line = 0; if (next) { const betweenProgress = (offset - window.scrollY - previous.element.getBoundingClientRect().top) / (next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top); - line = previous.line + betweenProgress * (next.line - previous.line); + return previous.line + betweenProgress * (next.line - previous.line); } else { - line = previous.line; + return previous.line; } - - const args = [window.initialData.source, line]; - window.parent.postMessage({ - command: "did-click-link", - data: `command:_markdown.revealLine?${encodeURIComponent(JSON.stringify(args))}` - }, "file://"); } + return null; } @@ -153,7 +147,14 @@ document.ondblclick = (e) => { const offset = e.pageY; - didUpdateScrollPosition(offset); + const line = getEditorLineNumberForPageOffset(offset); + if (!isNaN(line)) { + const args = [window.initialData.source, line]; + window.parent.postMessage({ + command: "did-click-link", + data: `command:_markdown.didClick?${encodeURIComponent(JSON.stringify(args))}` + }, "file://"); + } }; if (window.initialData.enableScrollSync) { @@ -161,7 +162,14 @@ if (scrollDisabled) { scrollDisabled = false; } else { - didUpdateScrollPosition(window.scrollY); + const line = getEditorLineNumberForPageOffset(window.scrollY); + if (!isNaN(line)) { + const args = [window.initialData.source, line]; + window.parent.postMessage({ + command: "did-click-link", + data: `command:_markdown.revealLine?${encodeURIComponent(JSON.stringify(args))}` + }, "file://"); + } } }; } diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index e499b9c7e9c..231b42f5a64 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -55,6 +55,13 @@ export function activate(context: vscode.ExtensionContext) { }); })); + context.subscriptions.push(vscode.commands.registerCommand('_markdown.didClick', (uri, line) => { + 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: 'top' })); + })); + context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(document => { if (isMarkdownFile(document)) { const uri = getMarkdownUri(document.uri); @@ -284,7 +291,7 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { source: "${encodeURIComponent(sourceUri.scheme + '://' + sourceUri.path)}", line: ${initialLine}, enablePreviewSync: ${!!markdownConfig.get('preview.experimentalSyncronizationEnabled', true)}, - enableScrollSync: ${!!markdownConfig.get('synchronizePreviewScrollingToEditor', true)} + enableScrollSync: ${!!markdownConfig.get('preview.synchronizePreviewScrollingToEditor', true)} };