Add commands to quickly switch between svg preview and text

Fixes #245180
This commit is contained in:
Matt Bierner
2025-03-31 14:36:59 -07:00
parent 2958ac85da
commit f2513b5ea7
7 changed files with 193 additions and 106 deletions

View File

@@ -54,12 +54,12 @@ class AudioPreview extends MediaPreview {
protected async getWebviewContents(): Promise<string> {
const version = Date.now().toString();
const settings = {
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
src: await this.getResourcePath(this._webviewEditor, this._resource, version),
};
const nonce = getNonce();
const cspSource = this.webviewEditor.webview.cspSource;
const cspSource = this._webviewEditor.webview.cspSource;
return /* html */`<!DOCTYPE html>
<html lang="en">
<head>
@@ -104,7 +104,7 @@ class AudioPreview extends MediaPreview {
}
private extensionResource(...parts: string[]) {
return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
return this._webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
}
}

View File

@@ -11,7 +11,7 @@ import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry';
export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
export class ImagePreviewManager implements vscode.CustomReadonlyEditorProvider {
public static readonly viewType = 'imagePreview.previewEditor';
@@ -48,7 +48,20 @@ export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
});
}
public get activePreview() { return this._activePreview; }
public get activePreview() {
return this._activePreview;
}
public getPreviewFor(resource: vscode.Uri, viewColumn?: vscode.ViewColumn): ImagePreview | undefined {
for (const preview of this._previews) {
if (preview.resource.toString() === resource.toString()) {
if (!viewColumn || preview.viewColumn === viewColumn) {
return preview;
}
}
}
return undefined;
}
private setActivePreview(value: ImagePreview | undefined): void {
this._activePreview = value;
@@ -94,12 +107,12 @@ class ImagePreview extends MediaPreview {
this._register(zoomStatusBarEntry.onDidChangeScale(e => {
if (this.previewState === PreviewState.Active) {
this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
this._webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
}
}));
this._register(webviewEditor.onDidChangeViewState(() => {
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
this._webviewEditor.webview.postMessage({ type: 'setActive', value: this._webviewEditor.active });
}));
this._register(webviewEditor.onDidDispose(() => {
@@ -121,22 +134,26 @@ class ImagePreview extends MediaPreview {
this.zoomStatusBarEntry.hide(this);
}
public get viewColumn() {
return this._webviewEditor.viewColumn;
}
public zoomIn() {
if (this.previewState === PreviewState.Active) {
this.webviewEditor.webview.postMessage({ type: 'zoomIn' });
this._webviewEditor.webview.postMessage({ type: 'zoomIn' });
}
}
public zoomOut() {
if (this.previewState === PreviewState.Active) {
this.webviewEditor.webview.postMessage({ type: 'zoomOut' });
this._webviewEditor.webview.postMessage({ type: 'zoomOut' });
}
}
public copyImage() {
if (this.previewState === PreviewState.Active) {
this.webviewEditor.reveal();
this.webviewEditor.webview.postMessage({ type: 'copyImage' });
this._webviewEditor.reveal();
this._webviewEditor.webview.postMessage({ type: 'copyImage' });
}
}
@@ -147,7 +164,7 @@ class ImagePreview extends MediaPreview {
return;
}
if (this.webviewEditor.active) {
if (this._webviewEditor.active) {
this.sizeStatusBarEntry.show(this, this._imageSize || '');
this.zoomStatusBarEntry.show(this, this._imageZoom || 'fit');
} else {
@@ -155,20 +172,21 @@ class ImagePreview extends MediaPreview {
this.zoomStatusBarEntry.hide(this);
}
}
protected override async render(): Promise<void> {
await super.render();
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
this._webviewEditor.webview.postMessage({ type: 'setActive', value: this._webviewEditor.active });
}
protected override async getWebviewContents(): Promise<string> {
const version = Date.now().toString();
const settings = {
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
src: await this.getResourcePath(this._webviewEditor, this._resource, version),
};
const nonce = getNonce();
const cspSource = this.webviewEditor.webview.cspSource;
const cspSource = this._webviewEditor.webview.cspSource;
return /* html */`<!DOCTYPE html>
<html lang="en">
<head>
@@ -212,7 +230,12 @@ class ImagePreview extends MediaPreview {
}
private extensionResource(...parts: string[]) {
return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
return this._webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
}
public async reopenAsText() {
await vscode.commands.executeCommand('reopenActiveEditorWith', 'default');
this._webviewEditor.dispose();
}
}
@@ -226,9 +249,9 @@ export function registerImagePreviewSupport(context: vscode.ExtensionContext, bi
const zoomStatusBarEntry = new ZoomStatusBarEntry();
disposables.push(zoomStatusBarEntry);
const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
const previewManager = new ImagePreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
disposables.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, {
disposables.push(vscode.window.registerCustomEditorProvider(ImagePreviewManager.viewType, previewManager, {
supportsMultipleEditorsPerDocument: true,
}));
@@ -244,5 +267,14 @@ export function registerImagePreviewSupport(context: vscode.ExtensionContext, bi
previewManager.activePreview?.copyImage();
}));
disposables.push(vscode.commands.registerCommand('imagePreview.reopenAsText', async () => {
return previewManager.activePreview?.reopenAsText();
}));
disposables.push(vscode.commands.registerCommand('imagePreview.reopenAsPreview', async () => {
await vscode.commands.executeCommand('reopenActiveEditorWith', ImagePreviewManager.viewType);
}));
return vscode.Disposable.from(...disposables);
}

View File

@@ -8,8 +8,8 @@ import { Utils } from 'vscode-uri';
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
import { Disposable } from './util/dispose';
export function reopenAsText(resource: vscode.Uri, viewColumn: vscode.ViewColumn | undefined) {
vscode.commands.executeCommand('vscode.openWith', resource, 'default', viewColumn);
export async function reopenAsText(resource: vscode.Uri, viewColumn: vscode.ViewColumn | undefined): Promise<void> {
await vscode.commands.executeCommand('vscode.openWith', resource, 'default', viewColumn);
}
export const enum PreviewState {
@@ -25,52 +25,56 @@ export abstract class MediaPreview extends Disposable {
constructor(
extensionRoot: vscode.Uri,
protected readonly resource: vscode.Uri,
protected readonly webviewEditor: vscode.WebviewPanel,
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
protected readonly _resource: vscode.Uri,
protected readonly _webviewEditor: vscode.WebviewPanel,
private readonly _binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
) {
super();
webviewEditor.webview.options = {
_webviewEditor.webview.options = {
enableScripts: true,
enableForms: false,
localResourceRoots: [
Utils.dirname(resource),
Utils.dirname(_resource),
extensionRoot,
]
};
this._register(webviewEditor.onDidChangeViewState(() => {
this._register(_webviewEditor.onDidChangeViewState(() => {
this.updateState();
}));
this._register(webviewEditor.onDidDispose(() => {
this._register(_webviewEditor.onDidDispose(() => {
this.previewState = PreviewState.Disposed;
this.dispose();
}));
const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*')));
const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(_resource, '*')));
this._register(watcher.onDidChange(e => {
if (e.toString() === this.resource.toString()) {
if (e.toString() === this._resource.toString()) {
this.updateBinarySize();
this.render();
}
}));
this._register(watcher.onDidDelete(e => {
if (e.toString() === this.resource.toString()) {
this.webviewEditor.dispose();
if (e.toString() === this._resource.toString()) {
this._webviewEditor.dispose();
}
}));
}
public override dispose() {
super.dispose();
this.binarySizeStatusBarEntry.hide(this);
this._binarySizeStatusBarEntry.hide(this);
}
public get resource() {
return this._resource;
}
protected updateBinarySize() {
vscode.workspace.fs.stat(this.resource).then(({ size }) => {
vscode.workspace.fs.stat(this._resource).then(({ size }) => {
this._binarySize = size;
this.updateState();
});
@@ -86,7 +90,7 @@ export abstract class MediaPreview extends Disposable {
return;
}
this.webviewEditor.webview.html = content;
this._webviewEditor.webview.html = content;
}
protected abstract getWebviewContents(): Promise<string>;
@@ -96,11 +100,11 @@ export abstract class MediaPreview extends Disposable {
return;
}
if (this.webviewEditor.active) {
if (this._webviewEditor.active) {
this.previewState = PreviewState.Active;
this.binarySizeStatusBarEntry.show(this, this._binarySize);
this._binarySizeStatusBarEntry.show(this, this._binarySize);
} else {
this.binarySizeStatusBarEntry.hide(this);
this._binarySizeStatusBarEntry.hide(this);
this.previewState = PreviewState.Visible;
}
}

View File

@@ -56,14 +56,14 @@ class VideoPreview extends MediaPreview {
const version = Date.now().toString();
const configurations = vscode.workspace.getConfiguration('mediaPreview.video');
const settings = {
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
src: await this.getResourcePath(this._webviewEditor, this._resource, version),
autoplay: configurations.get('autoPlay'),
loop: configurations.get('loop'),
};
const nonce = getNonce();
const cspSource = this.webviewEditor.webview.cspSource;
const cspSource = this._webviewEditor.webview.cspSource;
return /* html */`<!DOCTYPE html>
<html lang="en">
<head>
@@ -108,7 +108,7 @@ class VideoPreview extends MediaPreview {
}
private extensionResource(...parts: string[]) {
return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
return this._webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
}
}