Skip to content

Commit e18726f

Browse files
committed
Merge branch 'main' into joh/registerSingleton-explicit
2 parents 7c25cdb + 918dd89 commit e18726f

File tree

31 files changed

+448
-106
lines changed

31 files changed

+448
-106
lines changed

build/lib/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ export function loadSourcemaps(): NodeJS.ReadWriteStream {
207207
const contents = (<Buffer>f.contents).toString('utf8');
208208

209209
const reg = /\/\/# sourceMappingURL=(.*)$/g;
210-
let lastMatch: RegExpMatchArray | null = null;
211-
let match: RegExpMatchArray | null = null;
210+
let lastMatch: RegExpExecArray | null = null;
211+
let match: RegExpExecArray | null = null;
212212

213213
while (match = reg.exec(contents)) {
214214
lastMatch = match;

extensions/git/src/git.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ export class Git {
427427
let previousProgress = 0;
428428

429429
lineStream.on('data', (line: string) => {
430-
let match: RegExpMatchArray | null = null;
430+
let match: RegExpExecArray | null = null;
431431

432432
if (match = /Counting objects:\s*(\d+)%/i.exec(line)) {
433433
totalProgress = Math.floor(parseInt(match[1]) * 0.1);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
html, body {
7+
width: 100%;
8+
height: 100%;
9+
text-align: center;
10+
}
11+
12+
body {
13+
padding: 5px 10px;
14+
box-sizing: border-box;
15+
-webkit-user-select: none;
16+
user-select: none;
17+
}
18+
19+
.audio-container {
20+
height: 100%;
21+
display: flex;
22+
justify-content: center;
23+
align-items: center;
24+
}
25+
26+
.container.loading,
27+
.container.error {
28+
display: flex;
29+
justify-content: center;
30+
align-items: center;
31+
}
32+
33+
.loading-indicator {
34+
width: 30px;
35+
height: 30px;
36+
background-image: url('./loading.svg');
37+
background-size: cover;
38+
}
39+
40+
.loading-indicator,
41+
.loading-error {
42+
display: none;
43+
}
44+
45+
.loading .loading-indicator,
46+
.error .loading-error {
47+
display: block;
48+
}
49+
50+
.loading-error {
51+
margin: 1em;
52+
}
53+
54+
.vscode-dark .loading-indicator {
55+
background-image: url('./loading-dark.svg');
56+
}
57+
58+
.vscode-high-contrast .loading-indicator {
59+
background-image: url('./loading-hc.svg');
60+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
// @ts-check
6+
"use strict";
7+
8+
(function () {
9+
const vscode = acquireVsCodeApi();
10+
11+
function getSettings() {
12+
const element = document.getElementById('settings');
13+
if (element) {
14+
const data = element.getAttribute('data-settings');
15+
if (data) {
16+
return JSON.parse(data);
17+
}
18+
}
19+
20+
throw new Error(`Could not load settings`);
21+
}
22+
23+
const settings = getSettings();
24+
25+
// State
26+
let hasLoadedMedia = false;
27+
28+
// Elements
29+
const container = document.createElement('div');
30+
container.className = 'audio-container';
31+
document.body.appendChild(container);
32+
33+
const audio = new Audio(settings.src);
34+
audio.controls = true;
35+
36+
audio.addEventListener('error', e => {
37+
if (hasLoadedMedia) {
38+
return;
39+
}
40+
41+
hasLoadedMedia = true;
42+
document.body.classList.add('error');
43+
document.body.classList.remove('loading');
44+
});
45+
46+
audio.addEventListener('canplaythrough', () => {
47+
if (hasLoadedMedia) {
48+
return;
49+
}
50+
hasLoadedMedia = true;
51+
52+
document.body.classList.remove('loading');
53+
document.body.classList.add('ready');
54+
container.append(audio);
55+
});
56+
57+
document.querySelector('.open-file-link').addEventListener('click', () => {
58+
vscode.postMessage({
59+
type: 'reopen-as-text',
60+
});
61+
});
62+
}());

extensions/image-preview/package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"activationEvents": [
2323
"onCustomEditor:imagePreview.previewEditor",
2424
"onCommand:imagePreview.zoomIn",
25-
"onCommand:imagePreview.zoomOut"
25+
"onCommand:imagePreview.zoomOut",
26+
"onCustomEditor:vscode.mediaPreview.audioView"
2627
],
2728
"capabilities": {
2829
"virtualWorkspaces": true,
@@ -41,6 +42,16 @@
4142
"filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp,avif}"
4243
}
4344
]
45+
},
46+
{
47+
"viewType": "vscode.mediaPreview.audioView",
48+
"displayName": "%customEditors.displayName%",
49+
"priority": "builtin",
50+
"selector": [
51+
{
52+
"filenamePattern": "*.{mp3,wav,opus,aac}"
53+
}
54+
]
4455
}
4556
],
4657
"commands": [
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import * as nls from 'vscode-nls';
8+
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
9+
import { Disposable } from './util/dispose';
10+
import { escapeAttribute, getNonce } from './util/dom';
11+
12+
const localize = nls.loadMessageBundle();
13+
14+
class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider {
15+
16+
public static readonly viewType = 'vscode.mediaPreview.audioView';
17+
18+
constructor(
19+
private readonly extensionRoot: vscode.Uri,
20+
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
21+
) { }
22+
23+
public async openCustomDocument(uri: vscode.Uri) {
24+
return { uri, dispose: () => { } };
25+
}
26+
27+
public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise<void> {
28+
new AudioPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry);
29+
}
30+
}
31+
32+
const enum PreviewState {
33+
Disposed,
34+
Visible,
35+
Active,
36+
}
37+
38+
class AudioPreview extends Disposable {
39+
40+
private readonly id: string = `${Date.now()}-${Math.random().toString()}`;
41+
42+
private _previewState = PreviewState.Visible;
43+
private _binarySize: number | undefined;
44+
45+
private readonly emptyAudioDataUri = 'data:audio/wav;base64,';
46+
47+
constructor(
48+
private readonly extensionRoot: vscode.Uri,
49+
private readonly resource: vscode.Uri,
50+
private readonly webviewEditor: vscode.WebviewPanel,
51+
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
52+
) {
53+
super();
54+
55+
const resourceRoot = resource.with({
56+
path: resource.path.replace(/\/[^\/]+?\.\w+$/, '/'),
57+
});
58+
59+
webviewEditor.webview.options = {
60+
enableScripts: true,
61+
enableForms: false,
62+
localResourceRoots: [
63+
resourceRoot,
64+
extensionRoot,
65+
]
66+
};
67+
68+
this._register(webviewEditor.webview.onDidReceiveMessage(message => {
69+
switch (message.type) {
70+
case 'reopen-as-text': {
71+
vscode.commands.executeCommand('vscode.openWith', resource, 'default', webviewEditor.viewColumn);
72+
break;
73+
}
74+
}
75+
}));
76+
77+
this._register(webviewEditor.onDidChangeViewState(() => {
78+
this.update();
79+
}));
80+
81+
this._register(webviewEditor.onDidDispose(() => {
82+
if (this._previewState === PreviewState.Active) {
83+
this.binarySizeStatusBarEntry.hide(this.id);
84+
}
85+
this._previewState = PreviewState.Disposed;
86+
}));
87+
88+
const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*')));
89+
this._register(watcher.onDidChange(e => {
90+
if (e.toString() === this.resource.toString()) {
91+
this.render();
92+
}
93+
}));
94+
this._register(watcher.onDidDelete(e => {
95+
if (e.toString() === this.resource.toString()) {
96+
this.webviewEditor.dispose();
97+
}
98+
}));
99+
100+
vscode.workspace.fs.stat(resource).then(({ size }) => {
101+
this._binarySize = size;
102+
this.update();
103+
});
104+
105+
this.render();
106+
this.update();
107+
}
108+
109+
private async render() {
110+
if (this._previewState === PreviewState.Disposed) {
111+
return;
112+
}
113+
114+
const content = await this.getWebviewContents();
115+
if (this._previewState as PreviewState === PreviewState.Disposed) {
116+
return;
117+
}
118+
119+
this.webviewEditor.webview.html = content;
120+
}
121+
122+
private update() {
123+
if (this._previewState === PreviewState.Disposed) {
124+
return;
125+
}
126+
127+
if (this.webviewEditor.active) {
128+
this._previewState = PreviewState.Active;
129+
this.binarySizeStatusBarEntry.show(this.id, this._binarySize);
130+
} else {
131+
if (this._previewState === PreviewState.Active) {
132+
this.binarySizeStatusBarEntry.hide(this.id);
133+
}
134+
this._previewState = PreviewState.Visible;
135+
}
136+
}
137+
138+
private async getWebviewContents(): Promise<string> {
139+
const version = Date.now().toString();
140+
const settings = {
141+
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
142+
};
143+
144+
const nonce = getNonce();
145+
146+
const cspSource = this.webviewEditor.webview.cspSource;
147+
return /* html */`<!DOCTYPE html>
148+
<html lang="en">
149+
<head>
150+
<meta charset="UTF-8">
151+
152+
<!-- Disable pinch zooming -->
153+
<meta name="viewport"
154+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
155+
156+
<title>Audio Preview</title>
157+
158+
<link rel="stylesheet" href="${escapeAttribute(this.extensionResource('media', 'audioPreview.css'))}" type="text/css" media="screen" nonce="${nonce}">
159+
160+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data: ${cspSource}; media-src ${cspSource}; script-src 'nonce-${nonce}'; style-src ${cspSource} 'nonce-${nonce}';">
161+
<meta id="settings" data-settings="${escapeAttribute(JSON.stringify(settings))}">
162+
</head>
163+
<body class="container loading">
164+
<div class="loading-indicator"></div>
165+
<div class="loading-error">
166+
<p>${localize('preview.audioLoadError', "An error occurred while loading the audio file.")}</p>
167+
<a href="#" class="open-file-link">${localize('preview.audioLoadErrorLink', "Open file using VS Code's standard text/binary editor?")}</a>
168+
</div>
169+
<script src="${escapeAttribute(this.extensionResource('media', 'audioPreview.js'))}" nonce="${nonce}"></script>
170+
</body>
171+
</html>`;
172+
}
173+
174+
private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise<string> {
175+
if (resource.scheme === 'git') {
176+
const stat = await vscode.workspace.fs.stat(resource);
177+
if (stat.size === 0) {
178+
return this.emptyAudioDataUri;
179+
}
180+
}
181+
182+
// Avoid adding cache busting if there is already a query string
183+
if (resource.query) {
184+
return webviewEditor.webview.asWebviewUri(resource).toString();
185+
}
186+
return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
187+
}
188+
189+
private extensionResource(...parts: string[]) {
190+
return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
191+
}
192+
}
193+
194+
export function registerAudioPreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable {
195+
const provider = new AudioPreviewProvider(context.extensionUri, binarySizeStatusBarEntry);
196+
return vscode.window.registerCustomEditorProvider(AudioPreviewProvider.viewType, provider, {
197+
supportsMultipleEditorsPerDocument: true,
198+
webviewOptions: {
199+
retainContextWhenHidden: true,
200+
}
201+
});
202+
}

0 commit comments

Comments
 (0)