Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { PreviewStatusBarEntry } from './ownedStatusBarEntry';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class BinarySize {
|
||||
static readonly KB = 1024;
|
||||
static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
|
||||
static formatSize(size: number): string {
|
||||
if (size < BinarySize.KB) {
|
||||
return localize('sizeB', "{0}B", size);
|
||||
}
|
||||
|
||||
if (size < BinarySize.MB) {
|
||||
return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.GB) {
|
||||
return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.TB) {
|
||||
return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'imagePreview.binarySize',
|
||||
name: localize('sizeStatusBar.name', "Image Binary Size"),
|
||||
alignment: vscode.StatusBarAlignment.Right,
|
||||
priority: 100,
|
||||
});
|
||||
}
|
||||
|
||||
public show(owner: string, size: number | undefined) {
|
||||
if (typeof size === 'number') {
|
||||
super.showItem(owner, BinarySize.formatSize(size));
|
||||
} else {
|
||||
this.hide(owner);
|
||||
}
|
||||
}
|
||||
}
|
42
lib/vscode/extensions/image-preview/src/dispose.ts
Normal file
42
lib/vscode/extensions/image-preview/src/dispose.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]) {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
if (item) {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends vscode.Disposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed() {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
35
lib/vscode/extensions/image-preview/src/extension.ts
Normal file
35
lib/vscode/extensions/image-preview/src/extension.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
|
||||
import { PreviewManager } from './preview';
|
||||
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
|
||||
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const sizeStatusBarEntry = new SizeStatusBarEntry();
|
||||
context.subscriptions.push(sizeStatusBarEntry);
|
||||
|
||||
const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry();
|
||||
context.subscriptions.push(binarySizeStatusBarEntry);
|
||||
|
||||
const zoomStatusBarEntry = new ZoomStatusBarEntry();
|
||||
context.subscriptions.push(zoomStatusBarEntry);
|
||||
|
||||
const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
|
||||
|
||||
context.subscriptions.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, {
|
||||
supportsMultipleEditorsPerDocument: true,
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => {
|
||||
previewManager.activePreview?.zoomIn();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomOut', () => {
|
||||
previewManager.activePreview?.zoomOut();
|
||||
}));
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Disposable } from './dispose';
|
||||
|
||||
export abstract class PreviewStatusBarEntry extends Disposable {
|
||||
private _showOwner: string | undefined;
|
||||
|
||||
protected readonly entry: vscode.StatusBarItem;
|
||||
|
||||
constructor(options: vscode.window.StatusBarItemOptions) {
|
||||
super();
|
||||
this.entry = this._register(vscode.window.createStatusBarItem(options));
|
||||
}
|
||||
|
||||
protected showItem(owner: string, text: string) {
|
||||
this._showOwner = owner;
|
||||
this.entry.text = text;
|
||||
this.entry.show();
|
||||
}
|
||||
|
||||
public hide(owner: string) {
|
||||
if (owner === this._showOwner) {
|
||||
this.entry.hide();
|
||||
this._showOwner = undefined;
|
||||
}
|
||||
}
|
||||
}
|
266
lib/vscode/extensions/image-preview/src/preview.ts
Normal file
266
lib/vscode/extensions/image-preview/src/preview.ts
Normal file
@ -0,0 +1,266 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { Disposable } from './dispose';
|
||||
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
|
||||
import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry';
|
||||
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
|
||||
|
||||
public static readonly viewType = 'imagePreview.previewEditor';
|
||||
|
||||
private readonly _previews = new Set<Preview>();
|
||||
private _activePreview: Preview | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly extensionRoot: vscode.Uri,
|
||||
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
|
||||
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
|
||||
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
|
||||
) { }
|
||||
|
||||
public async openCustomDocument(uri: vscode.Uri) {
|
||||
return { uri, dispose: () => { } };
|
||||
}
|
||||
|
||||
public async resolveCustomEditor(
|
||||
document: vscode.CustomDocument,
|
||||
webviewEditor: vscode.WebviewPanel,
|
||||
): Promise<void> {
|
||||
const preview = new Preview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry);
|
||||
this._previews.add(preview);
|
||||
this.setActivePreview(preview);
|
||||
|
||||
webviewEditor.onDidDispose(() => { this._previews.delete(preview); });
|
||||
|
||||
webviewEditor.onDidChangeViewState(() => {
|
||||
if (webviewEditor.active) {
|
||||
this.setActivePreview(preview);
|
||||
} else if (this._activePreview === preview && !webviewEditor.active) {
|
||||
this.setActivePreview(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get activePreview() { return this._activePreview; }
|
||||
|
||||
private setActivePreview(value: Preview | undefined): void {
|
||||
this._activePreview = value;
|
||||
this.setPreviewActiveContext(!!value);
|
||||
}
|
||||
|
||||
private setPreviewActiveContext(value: boolean) {
|
||||
vscode.commands.executeCommand('setContext', 'imagePreviewFocus', value);
|
||||
}
|
||||
}
|
||||
|
||||
const enum PreviewState {
|
||||
Disposed,
|
||||
Visible,
|
||||
Active,
|
||||
}
|
||||
|
||||
class Preview extends Disposable {
|
||||
|
||||
private readonly id: string = `${Date.now()}-${Math.random().toString()}`;
|
||||
|
||||
private _previewState = PreviewState.Visible;
|
||||
private _imageSize: string | undefined;
|
||||
private _imageBinarySize: number | undefined;
|
||||
private _imageZoom: Scale | undefined;
|
||||
|
||||
private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==';
|
||||
|
||||
constructor(
|
||||
private readonly extensionRoot: vscode.Uri,
|
||||
private readonly resource: vscode.Uri,
|
||||
private readonly webviewEditor: vscode.WebviewPanel,
|
||||
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
|
||||
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
|
||||
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
|
||||
) {
|
||||
super();
|
||||
const resourceRoot = resource.with({
|
||||
path: resource.path.replace(/\/[^\/]+?\.\w+$/, '/'),
|
||||
});
|
||||
|
||||
webviewEditor.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [
|
||||
resourceRoot,
|
||||
extensionRoot,
|
||||
]
|
||||
};
|
||||
|
||||
this._register(webviewEditor.webview.onDidReceiveMessage(message => {
|
||||
switch (message.type) {
|
||||
case 'size':
|
||||
{
|
||||
this._imageSize = message.value;
|
||||
this.update();
|
||||
break;
|
||||
}
|
||||
case 'zoom':
|
||||
{
|
||||
this._imageZoom = message.value;
|
||||
this.update();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'reopen-as-text':
|
||||
{
|
||||
vscode.commands.executeCommand('vscode.openWith', resource, 'default', webviewEditor.viewColumn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(zoomStatusBarEntry.onDidChangeScale(e => {
|
||||
if (this._previewState === PreviewState.Active) {
|
||||
this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(webviewEditor.onDidChangeViewState(() => {
|
||||
this.update();
|
||||
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
|
||||
}));
|
||||
|
||||
this._register(webviewEditor.onDidDispose(() => {
|
||||
if (this._previewState === PreviewState.Active) {
|
||||
this.sizeStatusBarEntry.hide(this.id);
|
||||
this.binarySizeStatusBarEntry.hide(this.id);
|
||||
this.zoomStatusBarEntry.hide(this.id);
|
||||
}
|
||||
this._previewState = PreviewState.Disposed;
|
||||
}));
|
||||
|
||||
const watcher = this._register(vscode.workspace.createFileSystemWatcher(resource.fsPath));
|
||||
this._register(watcher.onDidChange(e => {
|
||||
if (e.toString() === this.resource.toString()) {
|
||||
this.render();
|
||||
}
|
||||
}));
|
||||
this._register(watcher.onDidDelete(e => {
|
||||
if (e.toString() === this.resource.toString()) {
|
||||
this.webviewEditor.dispose();
|
||||
}
|
||||
}));
|
||||
|
||||
vscode.workspace.fs.stat(resource).then(({ size }) => {
|
||||
this._imageBinarySize = size;
|
||||
this.update();
|
||||
});
|
||||
|
||||
this.render();
|
||||
this.update();
|
||||
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
|
||||
}
|
||||
|
||||
public zoomIn() {
|
||||
if (this._previewState === PreviewState.Active) {
|
||||
this.webviewEditor.webview.postMessage({ type: 'zoomIn' });
|
||||
}
|
||||
}
|
||||
|
||||
public zoomOut() {
|
||||
if (this._previewState === PreviewState.Active) {
|
||||
this.webviewEditor.webview.postMessage({ type: 'zoomOut' });
|
||||
}
|
||||
}
|
||||
|
||||
private async render() {
|
||||
if (this._previewState !== PreviewState.Disposed) {
|
||||
this.webviewEditor.webview.html = await this.getWebviewContents();
|
||||
}
|
||||
}
|
||||
|
||||
private update() {
|
||||
if (this._previewState === PreviewState.Disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.webviewEditor.active) {
|
||||
this._previewState = PreviewState.Active;
|
||||
this.sizeStatusBarEntry.show(this.id, this._imageSize || '');
|
||||
this.binarySizeStatusBarEntry.show(this.id, this._imageBinarySize);
|
||||
this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit');
|
||||
} else {
|
||||
if (this._previewState === PreviewState.Active) {
|
||||
this.sizeStatusBarEntry.hide(this.id);
|
||||
this.binarySizeStatusBarEntry.hide(this.id);
|
||||
this.zoomStatusBarEntry.hide(this.id);
|
||||
}
|
||||
this._previewState = PreviewState.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private async getWebviewContents(): Promise<string> {
|
||||
const version = Date.now().toString();
|
||||
const settings = {
|
||||
isMac: process.platform === 'darwin',
|
||||
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
|
||||
};
|
||||
|
||||
const nonce = Date.now().toString();
|
||||
|
||||
return /* html */`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<title>Image Preview</title>
|
||||
|
||||
<link rel="stylesheet" href="${escapeAttribute(this.extensionResource('/media/main.css'))}" type="text/css" media="screen" nonce="${nonce}">
|
||||
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data: ${this.webviewEditor.webview.cspSource}; script-src 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}';">
|
||||
<meta id="image-preview-settings" data-settings="${escapeAttribute(JSON.stringify(settings))}">
|
||||
</head>
|
||||
<body class="container image scale-to-fit loading">
|
||||
<div class="loading-indicator"></div>
|
||||
<div class="image-load-error">
|
||||
<p>${localize('preview.imageLoadError', "An error occurred while loading the image.")}</p>
|
||||
<a href="#" class="open-file-link">${localize('preview.imageLoadErrorLink', "Open file using VS Code's standard text/binary editor?")}</a>
|
||||
</div>
|
||||
<script src="${escapeAttribute(this.extensionResource('/media/main.js'))}" nonce="${nonce}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise<string> {
|
||||
if (resource.scheme === 'git') {
|
||||
const stat = await vscode.workspace.fs.stat(resource);
|
||||
if (stat.size === 0) {
|
||||
return this.emptyPngDataUri;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid adding cache busting if there is already a query string
|
||||
if (resource.query) {
|
||||
return webviewEditor.webview.asWebviewUri(resource).toString();
|
||||
}
|
||||
return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
|
||||
}
|
||||
|
||||
private extensionResource(path: string) {
|
||||
return this.webviewEditor.webview.asWebviewUri(this.extensionRoot.with({
|
||||
path: this.extensionRoot.path + path
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttribute(value: string | vscode.Uri): string {
|
||||
return value.toString().replace(/"/g, '"');
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { PreviewStatusBarEntry } from './ownedStatusBarEntry';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SizeStatusBarEntry extends PreviewStatusBarEntry {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'imagePreview.size',
|
||||
name: localize('sizeStatusBar.name', "Image Size"),
|
||||
alignment: vscode.StatusBarAlignment.Right,
|
||||
priority: 101 /* to the left of editor status (100) */,
|
||||
});
|
||||
}
|
||||
|
||||
public show(owner: string, text: string) {
|
||||
this.showItem(owner, text);
|
||||
}
|
||||
}
|
8
lib/vscode/extensions/image-preview/src/typings/ref.d.ts
vendored
Normal file
8
lib/vscode/extensions/image-preview/src/typings/ref.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { PreviewStatusBarEntry as OwnedStatusBarEntry } from './ownedStatusBarEntry';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel';
|
||||
|
||||
export type Scale = number | 'fit';
|
||||
|
||||
export class ZoomStatusBarEntry extends OwnedStatusBarEntry {
|
||||
|
||||
private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>());
|
||||
public readonly onDidChangeScale = this._onDidChangeScale.event;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'imagePreview.zoom',
|
||||
name: localize('zoomStatusBar.name', "Image Zoom"),
|
||||
alignment: vscode.StatusBarAlignment.Right,
|
||||
priority: 102 /* to the left of editor size entry (101) */,
|
||||
});
|
||||
|
||||
this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => {
|
||||
type MyPickItem = vscode.QuickPickItem & { scale: Scale };
|
||||
|
||||
const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
|
||||
const options = scales.map((scale): MyPickItem => ({
|
||||
label: this.zoomLabel(scale),
|
||||
scale
|
||||
}));
|
||||
|
||||
const pick = await vscode.window.showQuickPick(options, {
|
||||
placeHolder: localize('zoomStatusBar.placeholder', "Select zoom level")
|
||||
});
|
||||
if (pick) {
|
||||
this._onDidChangeScale.fire({ scale: pick.scale });
|
||||
}
|
||||
}));
|
||||
|
||||
this.entry.command = selectZoomLevelCommandId;
|
||||
}
|
||||
|
||||
public show(owner: string, scale: Scale) {
|
||||
this.showItem(owner, this.zoomLabel(scale));
|
||||
}
|
||||
|
||||
private zoomLabel(scale: Scale): string {
|
||||
return scale === 'fit'
|
||||
? localize('zoomStatusBar.wholeImageLabel', "Whole Image")
|
||||
: `${Math.round(scale * 100)}%`;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user