mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-28 08:04:00 +01:00
Merge branch 'master' into chrisdias/solongsofar
This commit is contained in:
+69
-15
@@ -5,7 +5,10 @@
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "jsdoc"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"constructor-super": "warn",
|
||||
"curly": "warn",
|
||||
@@ -43,7 +46,9 @@
|
||||
"warn",
|
||||
{
|
||||
"selector": "class",
|
||||
"format": ["PascalCase"]
|
||||
"format": [
|
||||
"PascalCase"
|
||||
]
|
||||
}
|
||||
],
|
||||
"code-no-unused-expressions": [
|
||||
@@ -60,11 +65,26 @@
|
||||
"warn",
|
||||
{
|
||||
"common": [],
|
||||
"node": ["common"],
|
||||
"browser": ["common"],
|
||||
"electron-sandbox": ["common", "browser"],
|
||||
"electron-browser": ["common", "browser", "node", "electron-sandbox"],
|
||||
"electron-main": ["common", "node"]
|
||||
"node": [
|
||||
"common"
|
||||
],
|
||||
"browser": [
|
||||
"common"
|
||||
],
|
||||
"electron-sandbox": [
|
||||
"common",
|
||||
"browser"
|
||||
],
|
||||
"electron-browser": [
|
||||
"common",
|
||||
"browser",
|
||||
"node",
|
||||
"electron-sandbox"
|
||||
],
|
||||
"electron-main": [
|
||||
"common",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
],
|
||||
"code-import-patterns": [
|
||||
@@ -74,7 +94,10 @@
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
{
|
||||
"target": "**/vs/base/common/**",
|
||||
"restrictions": ["vs/nls", "**/vs/base/common/**"]
|
||||
"restrictions": [
|
||||
"vs/nls",
|
||||
"**/vs/base/common/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/vs/base/test/common/**",
|
||||
@@ -432,7 +455,11 @@
|
||||
},
|
||||
{
|
||||
"target": "**/vs/workbench/api/worker/**",
|
||||
"restrictions": ["vscode", "vs/nls", "**/vs/**/{common,worker}/**"]
|
||||
"restrictions": [
|
||||
"vscode",
|
||||
"vs/nls",
|
||||
"**/vs/**/{common,worker}/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/vs/workbench/electron-sandbox/**",
|
||||
@@ -878,7 +905,13 @@
|
||||
},
|
||||
{
|
||||
"target": "**/api/**.test.ts",
|
||||
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "vscode"]
|
||||
"restrictions": [
|
||||
"**/vs/**",
|
||||
"assert",
|
||||
"sinon",
|
||||
"crypto",
|
||||
"vscode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/{node,electron-browser,electron-main}/**/*.test.ts",
|
||||
@@ -903,28 +936,46 @@
|
||||
},
|
||||
{
|
||||
"target": "**/**.test.ts",
|
||||
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "xterm*"]
|
||||
"restrictions": [
|
||||
"**/vs/**",
|
||||
"assert",
|
||||
"sinon",
|
||||
"crypto",
|
||||
"xterm*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/test/**",
|
||||
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "xterm*"]
|
||||
"restrictions": [
|
||||
"**/vs/**",
|
||||
"assert",
|
||||
"sinon",
|
||||
"crypto",
|
||||
"xterm*"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.js"],
|
||||
"files": [
|
||||
"*.js"
|
||||
],
|
||||
"rules": {
|
||||
"jsdoc/no-types": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/vscode.d.ts", "**/vscode.proposed.d.ts"],
|
||||
"files": [
|
||||
"**/vscode.d.ts",
|
||||
"**/vscode.proposed.d.ts"
|
||||
],
|
||||
"rules": {
|
||||
"vscode-dts-create-func": "warn",
|
||||
"vscode-dts-literal-or-types": "warn",
|
||||
"vscode-dts-interface-naming": "warn",
|
||||
"vscode-dts-cancellation": "warn",
|
||||
"vscode-dts-use-thenable": "warn",
|
||||
"vscode-dts-provider-naming": [
|
||||
"warn",
|
||||
{
|
||||
@@ -940,7 +991,10 @@
|
||||
"vscode-dts-event-naming": [
|
||||
"warn",
|
||||
{
|
||||
"allowed": ["onCancellationRequested", "event"],
|
||||
"allowed": [
|
||||
"onCancellationRequested",
|
||||
"event"
|
||||
],
|
||||
"verbs": [
|
||||
"accept",
|
||||
"change",
|
||||
|
||||
@@ -2,6 +2,7 @@ pool:
|
||||
vmImage: "Ubuntu-16.04"
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
module.exports = new class ApiEventNaming {
|
||||
constructor() {
|
||||
this.meta = {
|
||||
messages: {
|
||||
usage: 'Use the Thenable-type instead of the Promise type',
|
||||
}
|
||||
};
|
||||
}
|
||||
create(context) {
|
||||
return {
|
||||
['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node) => {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'usage',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
usage: 'Use the Thenable-type instead of the Promise type',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
|
||||
|
||||
return {
|
||||
['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node: any) => {
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'usage',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -194,6 +194,7 @@ function fromMarketplace(extensionName, version, metadata) {
|
||||
exports.fromMarketplace = fromMarketplace;
|
||||
const excludedExtensions = [
|
||||
'vscode-api-tests',
|
||||
'vscode-colorize-tests',
|
||||
'vscode-test-resolver',
|
||||
'ms-vscode.node-debug',
|
||||
'ms-vscode.node-debug2',
|
||||
|
||||
@@ -28,7 +28,41 @@ if (!/yarn[\w-.]*\.js$|yarnpkg$/.test(process.env['npm_execpath'])) {
|
||||
err = true;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
if (!hasSupportedVisualStudioVersion()) {
|
||||
console.error('\033[1;31m*** Invalid C/C++ Compiler Toolchain. Please check https://github.com/microsoft/vscode/wiki/How-to-Contribute.\033[0;0m');
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function hasSupportedVisualStudioVersion() {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
// Translated over from
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:build/vs_toolchain.py;l=140-175
|
||||
const supportedVersions = ['2019', '2017'];
|
||||
|
||||
const availableVersions = [];
|
||||
for (const version of supportedVersions) {
|
||||
let vsPath = process.env[`vs${version}_install`];
|
||||
if (vsPath && fs.existsSync(vsPath)) {
|
||||
availableVersions.push(version);
|
||||
break;
|
||||
}
|
||||
const programFiles86Path = process.env['ProgramFiles(x86)'];
|
||||
if (programFiles86Path) {
|
||||
vsPath = `${programFiles86Path}/Microsoft Visual Studio/${version}`;
|
||||
const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools'];
|
||||
if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath, vsType)))) {
|
||||
availableVersions.push(version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return availableVersions.length;
|
||||
}
|
||||
|
||||
@@ -467,7 +467,7 @@ export class Git {
|
||||
|
||||
try {
|
||||
const networkPath = await new Promise<string | undefined>(resolve =>
|
||||
realpath.native(`${letter}:`, { encoding: 'utf8' }, (err, resolvedPath) =>
|
||||
realpath.native(`${letter}:\\`, { encoding: 'utf8' }, (err, resolvedPath) =>
|
||||
resolve(err !== null ? undefined : resolvedPath),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export class Keychain {
|
||||
constructor(private context: vscode.ExtensionContext) { }
|
||||
async setToken(token: string): Promise<void> {
|
||||
try {
|
||||
return await this.context.secrets.set(SERVICE_ID, token);
|
||||
return await this.context.secrets.store(SERVICE_ID, token);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
Logger.error(`Setting token failed: ${e}`);
|
||||
|
||||
@@ -15,6 +15,6 @@ export class RenderDocument implements Command {
|
||||
) { }
|
||||
|
||||
public async execute(document: SkinnyTextDocument | string): Promise<string> {
|
||||
return this.engine.render(document);
|
||||
return (await (this.engine.render(document))).html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import { isMarkdownFile } from '../util/file';
|
||||
import { normalizeResource, WebviewResourceProvider } from '../util/resources';
|
||||
import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||
import { MarkdownContentProvider } from './previewContentProvider';
|
||||
import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { urlToUri } from '../util/url';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -118,6 +119,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
private _disposed: boolean = false;
|
||||
private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = [];
|
||||
|
||||
private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
|
||||
|
||||
constructor(
|
||||
webview: vscode.WebviewPanel,
|
||||
resource: vscode.Uri,
|
||||
@@ -208,6 +211,9 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
super.dispose();
|
||||
this._disposed = true;
|
||||
clearTimeout(this.throttleTimer);
|
||||
for (const entry of this._fileWatchersBySrc.values()) {
|
||||
entry.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public get resource(): vscode.Uri {
|
||||
@@ -224,6 +230,10 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The first call immediately refreshes the preview,
|
||||
* calls happening shortly thereafter are debounced.
|
||||
*/
|
||||
public refresh() {
|
||||
// Schedule update if none is pending
|
||||
if (!this.throttleTimer) {
|
||||
@@ -360,7 +370,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
this._webviewPanel.webview.html = this._contentProvider.provideFileNotFoundContent(this._resource);
|
||||
}
|
||||
|
||||
private setContent(html: string): void {
|
||||
private setContent(content: MarkdownContentProviderOutput): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +381,30 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
this._webviewPanel.iconPath = this.iconPath;
|
||||
this._webviewPanel.webview.options = this.getWebviewOptions();
|
||||
|
||||
this._webviewPanel.webview.html = html;
|
||||
this._webviewPanel.webview.html = content.html;
|
||||
|
||||
const srcs = new Set(content.containingImages.map(img => img.src));
|
||||
|
||||
// Delete stale file watchers.
|
||||
for (const [src, watcher] of [...this._fileWatchersBySrc]) {
|
||||
if (!srcs.has(src)) {
|
||||
watcher.dispose();
|
||||
this._fileWatchersBySrc.delete(src);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new file watchers.
|
||||
const root = vscode.Uri.joinPath(this._resource, '../');
|
||||
for (const src of srcs) {
|
||||
const uri = urlToUri(src, root);
|
||||
if (uri && uri.scheme === 'file' && !this._fileWatchersBySrc.has(src)) {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath);
|
||||
watcher.onDidChange(() => {
|
||||
this.refresh();
|
||||
});
|
||||
this._fileWatchersBySrc.set(src, watcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getWebviewOptions(): vscode.WebviewOptions {
|
||||
|
||||
@@ -39,6 +39,12 @@ function escapeAttribute(value: string | vscode.Uri): string {
|
||||
return value.toString().replace(/"/g, '"');
|
||||
}
|
||||
|
||||
export interface MarkdownContentProviderOutput {
|
||||
html: string;
|
||||
containingImages: { src: string }[];
|
||||
}
|
||||
|
||||
|
||||
export class MarkdownContentProvider {
|
||||
constructor(
|
||||
private readonly engine: MarkdownEngine,
|
||||
@@ -54,7 +60,7 @@ export class MarkdownContentProvider {
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
initialLine: number | undefined = undefined,
|
||||
state?: any
|
||||
): Promise<string> {
|
||||
): Promise<MarkdownContentProviderOutput> {
|
||||
const sourceUri = markdownDocument.uri;
|
||||
const config = previewConfigurations.loadAndCacheConfiguration(sourceUri);
|
||||
const initialData = {
|
||||
@@ -75,7 +81,7 @@ export class MarkdownContentProvider {
|
||||
const csp = this.getCsp(resourceProvider, sourceUri, nonce);
|
||||
|
||||
const body = await this.engine.render(markdownDocument);
|
||||
return `<!DOCTYPE html>
|
||||
const html = `<!DOCTYPE html>
|
||||
<html style="${escapeAttribute(this.getSettingsOverrideStyles(config))}">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
@@ -89,11 +95,15 @@ export class MarkdownContentProvider {
|
||||
<base href="${resourceProvider.asWebviewUri(markdownDocument.uri)}">
|
||||
</head>
|
||||
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
||||
${body}
|
||||
${body.html}
|
||||
<div class="code-line" data-line="${markdownDocument.lineCount}"></div>
|
||||
${this.getScripts(resourceProvider, nonce)}
|
||||
</body>
|
||||
</html>`;
|
||||
return {
|
||||
html,
|
||||
containingImages: body.containingImages,
|
||||
};
|
||||
}
|
||||
|
||||
public provideFileNotFoundContent(
|
||||
|
||||
@@ -54,6 +54,15 @@ class TokenCache {
|
||||
}
|
||||
}
|
||||
|
||||
export interface RenderOutput {
|
||||
html: string;
|
||||
containingImages: { src: string }[];
|
||||
}
|
||||
|
||||
interface RenderEnv {
|
||||
containingImages: { src: string }[];
|
||||
}
|
||||
|
||||
export class MarkdownEngine {
|
||||
private md?: Promise<MarkdownIt>;
|
||||
|
||||
@@ -141,7 +150,7 @@ export class MarkdownEngine {
|
||||
return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {});
|
||||
}
|
||||
|
||||
public async render(input: SkinnyTextDocument | string): Promise<string> {
|
||||
public async render(input: SkinnyTextDocument | string): Promise<RenderOutput> {
|
||||
const config = this.getConfig(typeof input === 'string' ? undefined : input.uri);
|
||||
const engine = await this.getEngine(config);
|
||||
|
||||
@@ -149,10 +158,19 @@ export class MarkdownEngine {
|
||||
? this.tokenizeString(input, engine)
|
||||
: this.tokenizeDocument(input, config, engine);
|
||||
|
||||
return engine.renderer.render(tokens, {
|
||||
const env: RenderEnv = {
|
||||
containingImages: []
|
||||
};
|
||||
|
||||
const html = engine.renderer.render(tokens, {
|
||||
...(engine as any).options,
|
||||
...config
|
||||
}, {});
|
||||
}, env);
|
||||
|
||||
return {
|
||||
html,
|
||||
containingImages: env.containingImages
|
||||
};
|
||||
}
|
||||
|
||||
public async parse(document: SkinnyTextDocument): Promise<Token[]> {
|
||||
@@ -192,12 +210,13 @@ export class MarkdownEngine {
|
||||
|
||||
private addImageStabilizer(md: any): void {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules.image = (tokens: any, idx: number, options: any, env: RenderEnv, self: any) => {
|
||||
const token = tokens[idx];
|
||||
token.attrJoin('class', 'loading');
|
||||
|
||||
const src = token.attrGet('src');
|
||||
if (src) {
|
||||
env.containingImages.push({ src });
|
||||
const imgHash = hash(src);
|
||||
token.attrSet('id', `image-hash-${imgHash}`);
|
||||
}
|
||||
@@ -231,6 +250,13 @@ export class MarkdownEngine {
|
||||
return normalizeLink(vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }).toString());
|
||||
}
|
||||
|
||||
// Support file:// links
|
||||
if (isOfScheme(Schemes.file, link)) {
|
||||
// Ensure link is relative by prepending `/` so that it uses the <base> element URI
|
||||
// when resolving the absolute URL
|
||||
return normalizeLink('/' + link.replace(/^file:/, 'file'));
|
||||
}
|
||||
|
||||
// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
|
||||
if (!/^[a-z\-]+:/i.test(link)) {
|
||||
// Use a fake scheme for parsing
|
||||
@@ -241,12 +267,14 @@ export class MarkdownEngine {
|
||||
if (uri.path[0] === '/') {
|
||||
const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!);
|
||||
if (root) {
|
||||
const fileUri = vscode.Uri.joinPath(root.uri, uri.fsPath);
|
||||
uri = fileUri.with({
|
||||
scheme: uri.scheme,
|
||||
const fileUri = vscode.Uri.joinPath(root.uri, uri.fsPath).with({
|
||||
fragment: uri.fragment,
|
||||
query: uri.query,
|
||||
});
|
||||
|
||||
// Ensure fileUri is relative by prepending `/` so that it uses the <base> element URI
|
||||
// when resolving the absolute URL
|
||||
uri = vscode.Uri.parse('markdown-link:' + '/' + fileUri.toString(true).replace(/^\S+?:/, fileUri.scheme));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,9 +297,7 @@ export class MarkdownEngine {
|
||||
private addLinkValidator(md: any): void {
|
||||
const validateLink = md.validateLink;
|
||||
md.validateLink = (link: string) => {
|
||||
// support file:// links
|
||||
return validateLink(link)
|
||||
|| isOfScheme(Schemes.file, link)
|
||||
|| isOfScheme(Schemes.vscode, link)
|
||||
|| isOfScheme(Schemes['vscode-insiders'], link)
|
||||
|| /^data:image\/.*?;/.test(link);
|
||||
|
||||
@@ -21,12 +21,31 @@ suite('markdown.engine', () => {
|
||||
test('Renders a document', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, input);
|
||||
const engine = createNewMarkdownEngine();
|
||||
assert.strictEqual(await engine.render(doc), output);
|
||||
assert.strictEqual((await engine.render(doc)).html, output);
|
||||
});
|
||||
|
||||
test('Renders a string', async () => {
|
||||
const engine = createNewMarkdownEngine();
|
||||
assert.strictEqual(await engine.render(input), output);
|
||||
assert.strictEqual((await engine.render(input)).html, output);
|
||||
});
|
||||
});
|
||||
|
||||
suite('image-caching', () => {
|
||||
const input = ' [](no-img.png)   ';
|
||||
|
||||
test('Extracts all images', async () => {
|
||||
const engine = createNewMarkdownEngine();
|
||||
assert.deepStrictEqual((await engine.render(input)), {
|
||||
html: '<p data-line="0" class="code-line">'
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435"> '
|
||||
+ '<a href="no-img.png" data-href="no-img.png"></a> '
|
||||
+ '<img src="http://example.org/img.png" alt="" class="loading" id="image-hash--1903814170"> '
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435"> '
|
||||
+ '<img src="./img2.png" alt="" class="loading" id="image-hash-265238964">'
|
||||
+ '</p>\n'
|
||||
,
|
||||
containingImages: [{ src: 'img.png' }, { src: 'http://example.org/img.png' }, { src: 'img.png' }, { src: './img2.png' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepStrictEqual } from 'assert';
|
||||
import 'mocha';
|
||||
import { Uri } from 'vscode';
|
||||
import { urlToUri } from '../util/url';
|
||||
|
||||
suite('urlToUri', () => {
|
||||
test('Absolute File', () => {
|
||||
deepStrictEqual(
|
||||
urlToUri('file:///root/test.txt', Uri.parse('file:///usr/home/')),
|
||||
Uri.parse('file:///root/test.txt')
|
||||
);
|
||||
});
|
||||
|
||||
test('Relative File', () => {
|
||||
deepStrictEqual(
|
||||
urlToUri('./file.ext', Uri.parse('file:///usr/home/')),
|
||||
Uri.parse('file:///usr/home/file.ext')
|
||||
);
|
||||
});
|
||||
|
||||
test('Http Basic', () => {
|
||||
deepStrictEqual(
|
||||
urlToUri('http://example.org?q=10&f', Uri.parse('file:///usr/home/')),
|
||||
Uri.parse('http://example.org?q=10&f')
|
||||
);
|
||||
});
|
||||
|
||||
test('Http Encoded Chars', () => {
|
||||
deepStrictEqual(
|
||||
urlToUri('http://example.org/%C3%A4', Uri.parse('file:///usr/home/')),
|
||||
Uri.parse('http://example.org/%C3%A4')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
declare const URL: typeof import('url').URL;
|
||||
|
||||
/**
|
||||
* Tries to convert an url into a vscode uri and returns undefined if this is not possible.
|
||||
* `url` can be absolute or relative.
|
||||
*/
|
||||
export function urlToUri(url: string, base: vscode.Uri): vscode.Uri | undefined {
|
||||
try {
|
||||
// `vscode.Uri.joinPath` cannot be used, since it understands
|
||||
// `src` as path, not as relative url. This is problematic for query args.
|
||||
const parsedUrl = new URL(url, base.toString());
|
||||
const uri = vscode.Uri.parse(parsedUrl.toString());
|
||||
return uri;
|
||||
} catch (e) {
|
||||
// Don't crash if `URL` cannot parse `src`.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export class Keychain {
|
||||
async setToken(token: string): Promise<void> {
|
||||
|
||||
try {
|
||||
return await this.context.secrets.set(SERVICE_ID, token);
|
||||
return await this.context.secrets.store(SERVICE_ID, token);
|
||||
} catch (e) {
|
||||
Logger.error(`Setting token failed: ${e}`);
|
||||
|
||||
|
||||
@@ -223,9 +223,18 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel
|
||||
const metadataOffset = (indentation + _lineNumber + seperator).length;
|
||||
const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length);
|
||||
|
||||
let locations: Required<vscode.LocationLink>[] = [];
|
||||
|
||||
// Allow line number, indentation, etc to take you to definition as well.
|
||||
locations.push({
|
||||
targetRange,
|
||||
targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, 1),
|
||||
targetUri: currentTarget,
|
||||
originSelectionRange: new vscode.Range(i, 0, i, resultStart),
|
||||
});
|
||||
|
||||
let lastEnd = resultStart;
|
||||
let offset = 0;
|
||||
let locations: Required<vscode.LocationLink>[] = [];
|
||||
ELISION_REGEX.lastIndex = resultStart;
|
||||
for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) {
|
||||
locations.push({
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ const jsTsLanguageConfiguration: vscode.LanguageConfiguration = {
|
||||
}, {
|
||||
// e.g. * ...|
|
||||
beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
|
||||
oneLineAboveText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
|
||||
previousLineText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
|
||||
action: { indentAction: vscode.IndentAction.None, appendText: '* ' },
|
||||
}, {
|
||||
// e.g. */|
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.53.0",
|
||||
"distro": "dd764d38c8e0aa3914484c1e62300f51cdd7c5b8",
|
||||
"distro": "826dce393851992f3b4af928e2bf7dd3813843cc",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
"smoketest-no-compile": "cd test/smoke && node test/index.js",
|
||||
"download-builtin-extensions": "node build/lib/builtInExtensions.js",
|
||||
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
|
||||
"tsec-compile-check": "node node_modules/vscode-tsec/bin/tsec -p src/tsconfig.tsec.json",
|
||||
"tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json",
|
||||
"valid-layers-check": "node build/lib/layersChecker.js",
|
||||
"strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes",
|
||||
"update-distro": "node build/npm/update-distro.js",
|
||||
@@ -189,6 +189,7 @@
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^1.0.0",
|
||||
"tsec": "0.1.1",
|
||||
"ts-loader": "^6.2.1",
|
||||
"typescript": "4.2.0-dev.20201207",
|
||||
"typescript-formatter": "7.1.0",
|
||||
@@ -198,7 +199,6 @@
|
||||
"vscode-debugprotocol": "1.43.0",
|
||||
"vscode-nls-dev": "^3.3.1",
|
||||
"vscode-telemetry-extractor": "^1.6.0",
|
||||
"vscode-tsec": "0.0.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-stream": "^5.2.1",
|
||||
|
||||
Binary file not shown.
@@ -126,9 +126,11 @@ export class MenuBar extends Disposable {
|
||||
let eventHandled = true;
|
||||
const key = !!e.key ? e.key.toLocaleLowerCase() : '';
|
||||
|
||||
if (event.equals(KeyCode.LeftArrow) || (isMacintosh && event.equals(KeyCode.Tab | KeyMod.Shift))) {
|
||||
const tabNav = isMacintosh && this.options.compactMode === undefined;
|
||||
|
||||
if (event.equals(KeyCode.LeftArrow) || (tabNav && event.equals(KeyCode.Tab | KeyMod.Shift))) {
|
||||
this.focusPrevious();
|
||||
} else if (event.equals(KeyCode.RightArrow) || (isMacintosh && event.equals(KeyCode.Tab))) {
|
||||
} else if (event.equals(KeyCode.RightArrow) || (tabNav && event.equals(KeyCode.Tab))) {
|
||||
this.focusNext();
|
||||
} else if (event.equals(KeyCode.Escape) && this.isFocused && !this.isOpen) {
|
||||
this.setUnfocusedState();
|
||||
|
||||
@@ -152,7 +152,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
const diffIdentityProvider = options.diffIdentityProvider && wrapIdentityProvider(options.diffIdentityProvider);
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDeep: Infinity });
|
||||
this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDepth: Infinity });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
this._setChildren(parent.element, parentChildren, {
|
||||
diffIdentityProvider,
|
||||
diffDeep: node.depth - parent.depth,
|
||||
diffDepth: node.depth - parent.depth,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
// provider is available, since we know the raw nodes are unchanged.
|
||||
this._setChildren(null, recompressedRootChildren, {
|
||||
diffIdentityProvider: this.identityProvider,
|
||||
diffDeep: Infinity,
|
||||
diffDepth: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface IIndexedSpliceOptions<T, TFilterData> {
|
||||
* If set, child updates will recurse the given number of levels even if
|
||||
* items in the splice operation are unchanged. `Infinity` is a valid value.
|
||||
*/
|
||||
readonly diffDeep?: number;
|
||||
readonly diffDepth?: number;
|
||||
|
||||
/**
|
||||
* Identity provider used to optimize splice() calls in the IndexTree. If
|
||||
@@ -159,7 +159,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
deleteCount: number,
|
||||
toInsertIterable: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
options: IIndexedSpliceOptions<T, TFilterData>,
|
||||
recurseLevels = options.diffDeep ?? 0,
|
||||
recurseLevels = options.diffDepth ?? 0,
|
||||
) {
|
||||
const { parentNode } = this.getParentNodeWithListIndex(location);
|
||||
const toInsert = [...toInsertIterable];
|
||||
|
||||
@@ -531,6 +531,8 @@ export namespace Codicon {
|
||||
export const passFilled = new Codicon('pass-filled', { character: '\\ebb3' });
|
||||
export const circleLargeFilled = new Codicon('circle-large-filled', { character: '\\ebb4' });
|
||||
export const circleLargeOutline = new Codicon('circle-large-outline', { character: '\\ebb5' });
|
||||
export const combine = new Codicon('combine', { character: '\\ebb6' });
|
||||
export const gather = new Codicon('gather', { character: '\\ebb6' });
|
||||
|
||||
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ export class Client extends MessagePortClient implements IDisposable {
|
||||
*/
|
||||
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
|
||||
|
||||
// Assert healthy window to talk to
|
||||
if (window.webContents.isDestroyed()) {
|
||||
throw new Error('ipc.mp#connect: Cannot talk to window because it is closed or destroyed');
|
||||
}
|
||||
|
||||
// Ask to create message channel inside the window
|
||||
// and send over a UUID to correlate the response
|
||||
const nonce = generateUuid();
|
||||
|
||||
@@ -242,7 +242,7 @@ suite('IndexTreeModel', () => {
|
||||
{ element: 0, children: [{ element: 0.1 }] },
|
||||
{ element: 1 },
|
||||
{ element: 2, children: [{ element: 2.1 }, { element: 2.2, children: [{ element: 2.21 }] }] },
|
||||
], { diffIdentityProvider, diffDeep: Infinity });
|
||||
], { diffIdentityProvider, diffDepth: Infinity });
|
||||
|
||||
assert.deepStrictEqual(list.filter(l => l.depth === 1).map(toElements), [
|
||||
-0.5,
|
||||
|
||||
@@ -270,8 +270,13 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
//#region Bootstrap IPC Handlers
|
||||
|
||||
let slowShellResolveWarningShown = false;
|
||||
ipcMain.on('vscode:fetchShellEnv', async event => {
|
||||
const window = this.windowsMainService?.getWindowByWebContents(event.sender);
|
||||
|
||||
// DO NOT remove: not only usual windows are fetching the
|
||||
// shell environment but also shared process, issue reporter
|
||||
// etc, so we need to reply via `webContents` always
|
||||
const webContents = event.sender;
|
||||
|
||||
let replied = false;
|
||||
|
||||
@@ -282,15 +287,25 @@ export class CodeApplication extends Disposable {
|
||||
if (!replied) {
|
||||
replied = true;
|
||||
|
||||
window?.send('vscode:acceptShellEnv', env);
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle slow shell environment resolve calls:
|
||||
// - a warning after 3s but continue to resolve
|
||||
// - an error after 10s and stop trying to resolve
|
||||
// - a warning after 3s but continue to resolve (only once in active window)
|
||||
// - an error after 10s and stop trying to resolve (in every window where this happens)
|
||||
const cts = new CancellationTokenSource();
|
||||
const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning', cts.token), 3000);
|
||||
|
||||
const shellEnvSlowWarningHandle = setTimeout(() => {
|
||||
if (!slowShellResolveWarningShown) {
|
||||
this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token);
|
||||
slowShellResolveWarningShown = true;
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!!
|
||||
const shellEnvTimeoutErrorHandle = setTimeout(() => {
|
||||
cts.dispose(true);
|
||||
window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None);
|
||||
@@ -302,6 +317,9 @@ export class CodeApplication extends Disposable {
|
||||
// a first window was opened from the UI but a second
|
||||
// from the CLI and that has implications for whether to
|
||||
// resolve the shell environment or not.
|
||||
//
|
||||
// Window can be undefined for e.g. the shared process
|
||||
// that is not part of our windows registry!
|
||||
let args: NativeParsedArgs;
|
||||
let env: NodeJS.ProcessEnv;
|
||||
if (window?.config) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
|
||||
import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
||||
export class SharedProcess extends Disposable implements ISharedProcess {
|
||||
|
||||
@@ -49,8 +50,18 @@ export class SharedProcess extends Disposable implements ISharedProcess {
|
||||
// workbench window will communicate directly
|
||||
await this.whenReady();
|
||||
|
||||
// connect to the shared process window
|
||||
const port = await this.connect();
|
||||
|
||||
// Check back if the requesting window meanwhile closed
|
||||
// Since shared process is delayed on startup there is
|
||||
// a chance that the window close before the shared process
|
||||
// was ready for a connection.
|
||||
if (e.sender.isDestroyed()) {
|
||||
return port.close();
|
||||
}
|
||||
|
||||
// send the port back to the requesting window
|
||||
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
|
||||
});
|
||||
}
|
||||
@@ -208,13 +219,9 @@ export class SharedProcess extends Disposable implements ISharedProcess {
|
||||
// Wait for shared process being ready to accept connection
|
||||
await this.whenIpcReady;
|
||||
|
||||
// Assert healthy shared process window
|
||||
if (!this.window || this.window.webContents.isDestroyed()) {
|
||||
throw new Error('Cannot connect to shared process window because the window is closed or destroyed');
|
||||
}
|
||||
|
||||
// Connect and return message port
|
||||
return connectMessagePort(this.window);
|
||||
const window = assertIsDefined(this.window);
|
||||
return connectMessagePort(window);
|
||||
}
|
||||
|
||||
async toggle(): Promise<void> {
|
||||
|
||||
@@ -424,11 +424,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.dispose();
|
||||
});
|
||||
|
||||
// Prevent loading of svgs
|
||||
this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
|
||||
if (details.url.indexOf('.svg') > 0) {
|
||||
const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource]);
|
||||
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
// Prevent loading of remote svgs
|
||||
if (details.url.endsWith('.svg')) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) {
|
||||
if (uri && !svgFileSchemes.has(uri.scheme)) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
}
|
||||
@@ -436,12 +437,24 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return callback({});
|
||||
});
|
||||
|
||||
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
|
||||
this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
|
||||
|
||||
const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
|
||||
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
||||
return callback({ cancel: true });
|
||||
|
||||
if (contentType && Array.isArray(contentType)) {
|
||||
// https://github.com/microsoft/vscode/issues/97564
|
||||
// ensure local svg files have Content-Type image/svg+xml
|
||||
if (details.url.endsWith('.svg')) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && svgFileSchemes.has(uri.scheme)) {
|
||||
responseHeaders['Content-Type'] = ['image/svg+xml'];
|
||||
return callback({ cancel: false, responseHeaders });
|
||||
}
|
||||
}
|
||||
|
||||
if (contentType.some(x => x.toLowerCase().includes('image/svg'))) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
}
|
||||
|
||||
return callback({ cancel: false });
|
||||
@@ -1254,7 +1267,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (this._win) {
|
||||
if (this._win.isDestroyed()) {
|
||||
if (this._win.webContents.isDestroyed()) {
|
||||
this.logService.warn(`Sending IPC message to channel ${channel} for window that is destroyed`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview: https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview: https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types default TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget domLineBreaksComputer editorViewLayer extensionHostWorker insane notebookOutputRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost webWorkerExtensionHost;">
|
||||
</head>
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
if (value === '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>') {
|
||||
return value;
|
||||
}
|
||||
// throw new Error('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
|
||||
console.trace('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
|
||||
return value;
|
||||
throw new Error('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
|
||||
// console.trace('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
|
||||
// return value;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+14
-14
@@ -3,15 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
|
||||
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { isAbsolute, join } from 'vs/base/common/path';
|
||||
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { findFreePort, randomPort } from 'vs/base/node/ports';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
@@ -69,10 +69,10 @@ export async function main(argv: string[]): Promise<any> {
|
||||
|
||||
// Validate
|
||||
if (
|
||||
!source || !target || source === target || // make sure source and target are provided and are not the same
|
||||
!paths.isAbsolute(source) || !paths.isAbsolute(target) || // make sure both source and target are absolute paths
|
||||
!fs.existsSync(source) || !fs.statSync(source).isFile() || // make sure source exists as file
|
||||
!fs.existsSync(target) || !fs.statSync(target).isFile() // make sure target exists as file
|
||||
!source || !target || source === target || // make sure source and target are provided and are not the same
|
||||
!isAbsolute(source) || !isAbsolute(target) || // make sure both source and target are absolute paths
|
||||
!existsSync(source) || !statSync(source).isFile() || // make sure source exists as file
|
||||
!existsSync(target) || !statSync(target).isFile() // make sure target exists as file
|
||||
) {
|
||||
throw new Error('Using --file-write with invalid arguments.');
|
||||
}
|
||||
@@ -83,15 +83,15 @@ export async function main(argv: string[]): Promise<any> {
|
||||
let targetMode: number = 0;
|
||||
let restoreMode = false;
|
||||
if (!!args['file-chmod']) {
|
||||
targetMode = fs.statSync(target).mode;
|
||||
targetMode = statSync(target).mode;
|
||||
if (!(targetMode & 128) /* readonly */) {
|
||||
fs.chmodSync(target, targetMode | 128);
|
||||
chmodSync(target, targetMode | 128);
|
||||
restoreMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Write source to target
|
||||
const data = fs.readFileSync(source);
|
||||
const data = readFileSync(source);
|
||||
if (isWindows) {
|
||||
// On Windows we use a different strategy of saving the file
|
||||
// by first truncating the file and then writing with r+ mode.
|
||||
@@ -99,7 +99,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
// (see https://github.com/microsoft/vscode/issues/931) and
|
||||
// prevent removing alternate data streams
|
||||
// (see https://github.com/microsoft/vscode/issues/6363)
|
||||
fs.truncateSync(target, 0);
|
||||
truncateSync(target, 0);
|
||||
writeFileSync(target, data, { flag: 'r+' });
|
||||
} else {
|
||||
writeFileSync(target, data);
|
||||
@@ -107,7 +107,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
|
||||
// Restore previous mode as needed
|
||||
if (restoreMode) {
|
||||
fs.chmodSync(target, targetMode);
|
||||
chmodSync(target, targetMode);
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `Error using --file-write: ${error.message}`;
|
||||
@@ -215,7 +215,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
throw new Error('Failed to find free ports for profiler. Make sure to shutdown all instances of the editor first.');
|
||||
}
|
||||
|
||||
const filenamePrefix = paths.join(os.homedir(), 'prof-' + Math.random().toString(16).slice(-4));
|
||||
const filenamePrefix = join(homedir(), 'prof-' + Math.random().toString(16).slice(-4));
|
||||
|
||||
addArg(argv, `--inspect-brk=${portMain}`);
|
||||
addArg(argv, `--remote-debugging-port=${portRenderer}`);
|
||||
@@ -338,7 +338,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
|
||||
// Make sure to delete the tmp stdin file if we have any
|
||||
if (stdinFilePath) {
|
||||
fs.unlinkSync(stdinFilePath);
|
||||
unlinkSync(stdinFilePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { release } from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { gracefulify } from 'graceful-fs';
|
||||
import { isAbsolute, join } from 'vs/base/common/path';
|
||||
import { raceTimeout } from 'vs/base/common/async';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
@@ -36,108 +38,119 @@ import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
export class Main {
|
||||
export class CliMain extends Disposable {
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@IExtensionManagementCLIService private readonly extensionManagementCLIService: IExtensionManagementCLIService
|
||||
) { }
|
||||
private argv: NativeParsedArgs
|
||||
) {
|
||||
super();
|
||||
|
||||
async run(argv: NativeParsedArgs): Promise<void> {
|
||||
if (argv['install-source']) {
|
||||
await this.setInstallSource(argv['install-source']);
|
||||
return;
|
||||
// Enable gracefulFs
|
||||
gracefulify(fs);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Dispose on exit
|
||||
process.once('exit', () => this.dispose());
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
// Services
|
||||
const [instantiationService, appenders] = await this.initServices();
|
||||
|
||||
return instantiationService.invokeFunction(async accessor => {
|
||||
const logService = accessor.get(ILogService);
|
||||
const environmentService = accessor.get(INativeEnvironmentService);
|
||||
const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService);
|
||||
|
||||
// Log info
|
||||
logService.info('CLI main', this.argv);
|
||||
|
||||
// Error handler
|
||||
this.registerErrorHandler(logService);
|
||||
|
||||
// Run based on argv
|
||||
await this.doRun(environmentService, extensionManagementCLIService);
|
||||
|
||||
// Flush the remaining data in AI adapter (with 1s timeout)
|
||||
return raceTimeout(combinedAppender(...appenders).flush(), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Environment
|
||||
const environmentService = new NativeEnvironmentService(this.argv);
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
// Init folders
|
||||
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined));
|
||||
|
||||
// Log
|
||||
const logLevel = getLogLevel(environmentService);
|
||||
const loggers: ILogService[] = [];
|
||||
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
|
||||
if (logLevel === LogLevel.Trace) {
|
||||
loggers.push(new ConsoleLogService(logLevel));
|
||||
}
|
||||
|
||||
if (argv['list-extensions']) {
|
||||
await this.extensionManagementCLIService.listExtensions(!!argv['show-versions'], argv['category']);
|
||||
} else if (argv['install-extension'] || argv['install-builtin-extension']) {
|
||||
await this.extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(argv['install-extension'] || []), argv['install-builtin-extension'] || [], !!argv['do-not-sync'], !!argv['force']);
|
||||
} else if (argv['uninstall-extension']) {
|
||||
await this.extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(argv['uninstall-extension']), !!argv['force']);
|
||||
} else if (argv['locate-extension']) {
|
||||
await this.extensionManagementCLIService.locateExtension(argv['locate-extension']);
|
||||
} else if (argv['telemetry']) {
|
||||
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath));
|
||||
}
|
||||
}
|
||||
const logService = this._register(new MultiplexLogService(loggers));
|
||||
services.set(ILogService, logService);
|
||||
|
||||
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
|
||||
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
|
||||
}
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
services.set(IFileService, fileService);
|
||||
|
||||
private setInstallSource(installSource: string): Promise<void> {
|
||||
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
|
||||
}
|
||||
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService));
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
}
|
||||
// Configuration
|
||||
const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService));
|
||||
services.set(IConfigurationService, configurationService);
|
||||
|
||||
const eventPrefix = 'monacoworkbench';
|
||||
// Init config
|
||||
await configurationService.initialize();
|
||||
|
||||
export async function main(argv: NativeParsedArgs): Promise<void> {
|
||||
const services = new ServiceCollection();
|
||||
const disposables = new DisposableStore();
|
||||
// State
|
||||
const stateService = new StateService(environmentService, logService);
|
||||
services.set(IStateService, stateService);
|
||||
|
||||
const environmentService = new NativeEnvironmentService(argv);
|
||||
const logLevel = getLogLevel(environmentService);
|
||||
const loggers: ILogService[] = [];
|
||||
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
|
||||
if (logLevel === LogLevel.Trace) {
|
||||
loggers.push(new ConsoleLogService(logLevel));
|
||||
}
|
||||
const logService = new MultiplexLogService(loggers);
|
||||
process.once('exit', () => logService.dispose());
|
||||
logService.info('main', argv);
|
||||
|
||||
await Promise.all<void | undefined>([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath]
|
||||
.map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
|
||||
|
||||
// Files
|
||||
const fileService = new FileService(logService);
|
||||
disposables.add(fileService);
|
||||
services.set(IFileService, fileService);
|
||||
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
|
||||
disposables.add(diskFileSystemProvider);
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
disposables.add(configurationService);
|
||||
await configurationService.initialize();
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
services.set(ILogService, logService);
|
||||
services.set(IConfigurationService, configurationService);
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
const instantiationService: IInstantiationService = new InstantiationService(services);
|
||||
|
||||
return instantiationService.invokeFunction(async accessor => {
|
||||
const stateService = accessor.get(IStateService);
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
|
||||
|
||||
const services = new ServiceCollection();
|
||||
// Request
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
|
||||
// Extensions
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService));
|
||||
|
||||
// Localizations
|
||||
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
|
||||
// Telemetry
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
|
||||
if (product.aiConfig && product.aiConfig.asimovKey) {
|
||||
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey));
|
||||
appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey));
|
||||
}
|
||||
|
||||
const config: ITelemetryServiceConfig = {
|
||||
@@ -153,17 +166,70 @@ export async function main(argv: NativeParsedArgs): Promise<void> {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
const instantiationService2 = instantiationService.createChild(services);
|
||||
const main = instantiationService2.createInstance(Main);
|
||||
return [new InstantiationService(services), appenders];
|
||||
}
|
||||
|
||||
try {
|
||||
await main.run(argv);
|
||||
private registerErrorHandler(logService: ILogService): void {
|
||||
|
||||
// Flush the remaining data in AI adapter.
|
||||
// If it does not complete in 1 second, exit the process.
|
||||
await raceTimeout(combinedAppender(...appenders).flush(), 1000);
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
// Install handler for unexpected errors
|
||||
setUnexpectedErrorHandler(error => {
|
||||
const message = toErrorMessage(error, true);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
logService.error(message);
|
||||
});
|
||||
}
|
||||
|
||||
private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise<void> {
|
||||
|
||||
// Install Source
|
||||
if (this.argv['install-source']) {
|
||||
return this.setInstallSource(environmentService, this.argv['install-source']);
|
||||
}
|
||||
});
|
||||
|
||||
// List Extensions
|
||||
if (this.argv['list-extensions']) {
|
||||
return extensionManagementCLIService.listExtensions(!!this.argv['show-versions'], this.argv['category']);
|
||||
}
|
||||
|
||||
// Install Extension
|
||||
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
|
||||
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']);
|
||||
}
|
||||
|
||||
// Uninstall Extension
|
||||
else if (this.argv['uninstall-extension']) {
|
||||
return extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force']);
|
||||
}
|
||||
|
||||
// Locate Extension
|
||||
else if (this.argv['locate-extension']) {
|
||||
return extensionManagementCLIService.locateExtension(this.argv['locate-extension']);
|
||||
}
|
||||
|
||||
// Telemetry
|
||||
else if (this.argv['telemetry']) {
|
||||
console.log(buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath));
|
||||
}
|
||||
}
|
||||
|
||||
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
|
||||
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
|
||||
}
|
||||
|
||||
private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise<void> {
|
||||
return writeFile(environmentService.installSourcePath, installSource.slice(0, 30));
|
||||
}
|
||||
}
|
||||
|
||||
export async function main(argv: NativeParsedArgs): Promise<void> {
|
||||
const cliMain = new CliMain(argv);
|
||||
|
||||
try {
|
||||
await cliMain.run();
|
||||
} finally {
|
||||
cliMain.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class MarkdownRenderer {
|
||||
},
|
||||
asyncRenderCallback: () => this._onDidRenderAsync.fire(),
|
||||
actionHandler: {
|
||||
callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError),
|
||||
callback: (content) => this._openerService.open(content, { fromUserGesture: true, allowContributedOpeners: true }).catch(onUnexpectedError),
|
||||
disposeables
|
||||
}
|
||||
};
|
||||
|
||||
@@ -211,10 +211,16 @@ export class OpenerService implements IOpenerService {
|
||||
href = encodeURI(resolved.toString(true));
|
||||
}
|
||||
|
||||
for (const opener of this._externalOpeners) {
|
||||
const didOpen = await opener.openExternal(href, { sourceUri: uri }, CancellationToken.None);
|
||||
if (didOpen) {
|
||||
return true;
|
||||
if (options?.allowContributedOpeners) {
|
||||
const preferredOpenerId = typeof options?.allowContributedOpeners === 'string' ? options?.allowContributedOpeners : undefined;
|
||||
for (const opener of this._externalOpeners) {
|
||||
const didOpen = await opener.openExternal(href, {
|
||||
sourceUri: uri,
|
||||
preferredOpenerId,
|
||||
}, CancellationToken.None);
|
||||
if (didOpen) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ export interface OnEnterRule {
|
||||
/**
|
||||
* This rule will only execute if the text above the this line matches this regular expression.
|
||||
*/
|
||||
oneLineAboveText?: RegExp;
|
||||
previousLineText?: RegExp;
|
||||
/**
|
||||
* The action to execute.
|
||||
*/
|
||||
|
||||
@@ -101,11 +101,11 @@ export class RichEditSupport {
|
||||
return this._electricCharacter;
|
||||
}
|
||||
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
if (!this._onEnterSupport) {
|
||||
return null;
|
||||
}
|
||||
return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
|
||||
return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
|
||||
}
|
||||
|
||||
private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration {
|
||||
@@ -700,17 +700,17 @@ export class LanguageConfigurationRegistryImpl {
|
||||
afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
}
|
||||
|
||||
let oneLineAboveText = '';
|
||||
let previousLineText = '';
|
||||
if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) {
|
||||
// This is not the first line and the entire line belongs to this mode
|
||||
const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1);
|
||||
if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) {
|
||||
// The line above ends with text belonging to the same mode
|
||||
oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent();
|
||||
previousLineText = oneLineAboveScopedLineTokens.getLineContent();
|
||||
}
|
||||
}
|
||||
|
||||
const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
|
||||
const enterResult = richEditSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
|
||||
if (!enterResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class OnEnterSupport {
|
||||
this._regExpRules = opts.onEnterRules || [];
|
||||
}
|
||||
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
// (1): `regExpRules`
|
||||
if (autoIndent >= EditorAutoIndentStrategy.Advanced) {
|
||||
for (let i = 0, len = this._regExpRules.length; i < len; i++) {
|
||||
@@ -61,8 +61,8 @@ export class OnEnterSupport {
|
||||
reg: rule.afterText,
|
||||
text: afterEnterText
|
||||
}, {
|
||||
reg: rule.oneLineAboveText,
|
||||
text: oneLineAboveText
|
||||
reg: rule.previousLineText,
|
||||
text: previousLineText
|
||||
}].every((obj): boolean => {
|
||||
return obj.reg ? obj.reg.test(obj.text) : true;
|
||||
});
|
||||
|
||||
@@ -299,13 +299,13 @@ export class MoveLinesCommand implements ICommand {
|
||||
}
|
||||
}
|
||||
|
||||
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) {
|
||||
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, previousLineText?: string) {
|
||||
let validPrecedingLine = oneLineAbove;
|
||||
while (validPrecedingLine >= 1) {
|
||||
// ship empty lines as empty lines just inherit indentation
|
||||
let lineContent;
|
||||
if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) {
|
||||
lineContent = oneLineAboveText;
|
||||
if (validPrecedingLine === oneLineAbove && previousLineText !== undefined) {
|
||||
lineContent = previousLineText;
|
||||
} else {
|
||||
lineContent = model.getLineContent(validPrecedingLine);
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ export class LinkDetector implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
return this.openerService.open(uri, { openToSide, fromUserGesture });
|
||||
return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true });
|
||||
|
||||
}, err => {
|
||||
const messageOrError =
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { first } from 'vs/base/common/async';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -20,19 +19,26 @@ export const Context = {
|
||||
MultipleSignatures: new RawContextKey<boolean>('parameterHintsMultipleSignatures', false),
|
||||
};
|
||||
|
||||
export function provideSignatureHelp(
|
||||
export async function provideSignatureHelp(
|
||||
model: ITextModel,
|
||||
position: Position,
|
||||
context: modes.SignatureHelpContext,
|
||||
token: CancellationToken
|
||||
): Promise<modes.SignatureHelpResult | null | undefined> {
|
||||
): Promise<modes.SignatureHelpResult | undefined> {
|
||||
|
||||
const supports = modes.SignatureHelpProviderRegistry.ordered(model);
|
||||
|
||||
return first(supports.map(support => () => {
|
||||
return Promise.resolve(support.provideSignatureHelp(model, position, token, context))
|
||||
.catch<modes.SignatureHelpResult | undefined>(e => onUnexpectedExternalError(e));
|
||||
}));
|
||||
for (const support of supports) {
|
||||
try {
|
||||
const result = await support.provideSignatureHelp(model, position, token, context);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
} catch (err) {
|
||||
onUnexpectedExternalError(err);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_executeSignatureHelpProvider', async (accessor, ...args: [URI, IPosition, string?]) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const javascriptOnEnterRules = [
|
||||
}, {
|
||||
// e.g. * ...|
|
||||
beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
|
||||
oneLineAboveText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
|
||||
previousLineText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
|
||||
action: { indentAction: IndentAction.None, appendText: '* ' }
|
||||
}, {
|
||||
// e.g. */|
|
||||
|
||||
@@ -51,8 +51,8 @@ suite('OnEnter', () => {
|
||||
let support = new OnEnterSupport({
|
||||
onEnterRules: javascriptOnEnterRules
|
||||
});
|
||||
let testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => {
|
||||
let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, oneLineAboveText, beforeText, afterText);
|
||||
let testIndentAction = (previousLineText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => {
|
||||
let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, previousLineText, beforeText, afterText);
|
||||
if (expectedIndentAction === null) {
|
||||
assert.strictEqual(actual, null, 'isNull:' + beforeText);
|
||||
} else {
|
||||
|
||||
Vendored
+1
-1
@@ -5417,7 +5417,7 @@ declare namespace monaco.languages {
|
||||
/**
|
||||
* This rule will only execute if the text above the this line matches this regular expression.
|
||||
*/
|
||||
oneLineAboveText?: RegExp;
|
||||
previousLineText?: RegExp;
|
||||
/**
|
||||
* The action to execute.
|
||||
*/
|
||||
|
||||
@@ -42,12 +42,16 @@ export class ExtensionIdentifierWithVersion implements IExtensionIdentifierWithV
|
||||
}
|
||||
}
|
||||
|
||||
export function getExtensionId(publisher: string, name: string): string {
|
||||
return `${publisher}.${name}`;
|
||||
}
|
||||
|
||||
export function adoptToGalleryExtensionId(id: string): string {
|
||||
return id.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
export function getGalleryExtensionId(publisher: string, name: string): string {
|
||||
return `${publisher.toLocaleLowerCase()}.${name.toLocaleLowerCase()}`;
|
||||
return adoptToGalleryExtensionId(getExtensionId(publisher, name));
|
||||
}
|
||||
|
||||
export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] {
|
||||
|
||||
@@ -32,7 +32,11 @@ export type OpenInternalOptions = {
|
||||
readonly fromUserGesture?: boolean;
|
||||
};
|
||||
|
||||
export type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean };
|
||||
export type OpenExternalOptions = {
|
||||
readonly openExternal?: boolean;
|
||||
readonly allowTunneling?: boolean;
|
||||
readonly allowContributedOpeners?: boolean | string;
|
||||
};
|
||||
|
||||
export type OpenOptions = OpenInternalOptions & OpenExternalOptions;
|
||||
|
||||
@@ -47,7 +51,7 @@ export interface IOpener {
|
||||
}
|
||||
|
||||
export interface IExternalOpener {
|
||||
openExternal(href: string, ctx: { sourceUri: URI }, token: CancellationToken): Promise<boolean>;
|
||||
openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise<boolean>;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,24 @@ export interface ITunnelProvider {
|
||||
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
|
||||
}
|
||||
|
||||
export interface ITunnel {
|
||||
remoteAddress: { port: number, host: string };
|
||||
|
||||
/**
|
||||
* The complete local address(ex. localhost:1234)
|
||||
*/
|
||||
localAddress: string;
|
||||
|
||||
public?: boolean;
|
||||
|
||||
/**
|
||||
* Implementers of Tunnel should fire onDidDispose when dispose is called.
|
||||
*/
|
||||
onDidDispose: Event<void>;
|
||||
|
||||
dispose(): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface ITunnelService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -166,6 +184,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
}
|
||||
|
||||
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
|
||||
this.logService.trace(`openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
|
||||
if (!addressProvider) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -176,14 +195,17 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
|
||||
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
|
||||
if (!resolvedTunnel) {
|
||||
this.logService.trace(`Tunnel was not created.`);
|
||||
return resolvedTunnel;
|
||||
}
|
||||
|
||||
return resolvedTunnel.then(tunnel => {
|
||||
if (!tunnel) {
|
||||
this.logService.trace('New tunnel is undefined.');
|
||||
this.removeEmptyTunnelFromMap(remoteHost!, remotePort);
|
||||
return undefined;
|
||||
}
|
||||
this.logService.trace('New tunnel established.');
|
||||
const newTunnel = this.makeTunnel(tunnel);
|
||||
if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) {
|
||||
this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.');
|
||||
@@ -215,6 +237,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
|
||||
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel | undefined> }): Promise<void> {
|
||||
if (tunnel.refcount <= 0) {
|
||||
this.logService.trace(`Tunnel is being disposed ${remoteHost}:${remotePort}.`);
|
||||
const disposePromise: Promise<void> = tunnel.value.then(async (tunnel) => {
|
||||
if (tunnel) {
|
||||
await tunnel.dispose(true);
|
||||
@@ -282,10 +305,13 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined;
|
||||
|
||||
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
|
||||
this.logService.trace(`Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
|
||||
|
||||
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
|
||||
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
|
||||
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic };
|
||||
const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo);
|
||||
this.logService.trace('Tunnel created by provider.');
|
||||
if (tunnel) {
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
if (this._tunnelProvider) {
|
||||
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
|
||||
} else {
|
||||
this.logService.trace(`Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
|
||||
const options: IConnectionOptions = {
|
||||
commit: this.productService.commit,
|
||||
socketFactory: this.socketFactory,
|
||||
@@ -160,6 +161,7 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
};
|
||||
|
||||
const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort);
|
||||
this.logService.trace('Tunnel created without provider.');
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface IStateService {
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void;
|
||||
|
||||
removeItem(key: string): void;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export class StateService implements IStateService {
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue: T | undefined): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined {
|
||||
return this.fileStorage.getItem(key, defaultValue);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
@@ -32,7 +33,7 @@ export class ExtensionsStorageSyncService extends Disposable implements IExtensi
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static toKey(extension: IExtensionIdWithVersion): string {
|
||||
return `extensionKeys/${extension.id}@${extension.version}`;
|
||||
return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`;
|
||||
}
|
||||
|
||||
private static fromKey(key: string): IExtensionIdWithVersion | undefined {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
@@ -73,6 +73,15 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen
|
||||
return extensions;
|
||||
}
|
||||
|
||||
function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary<any> {
|
||||
const extensionStorageValue = storageService.get(getExtensionId(publisher, name) /* use the same id used in extension host */, StorageScope.GLOBAL) || '{}';
|
||||
return JSON.parse(extensionStorageValue);
|
||||
}
|
||||
|
||||
function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary<any>, storageService: IStorageService): void {
|
||||
storageService.store(getExtensionId(publisher, name) /* use the same id used in extension host */, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` });
|
||||
@@ -99,7 +108,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IIgnoredExtensionsManagementService private readonly extensionSyncManagementService: IIgnoredExtensionsManagementService,
|
||||
@IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@@ -125,7 +134,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
|
||||
if (remoteExtensions) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`);
|
||||
@@ -209,7 +218,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
|
||||
private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
@@ -225,7 +234,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
|
||||
private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
|
||||
if (remoteExtensions !== null) {
|
||||
const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions);
|
||||
@@ -285,7 +294,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
|
||||
return this.format(localExtensions);
|
||||
}
|
||||
@@ -363,7 +372,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// Builtin Extension Sync: Enablement & State
|
||||
if (installedExtension && installedExtension.isBuiltin) {
|
||||
if (e.state && installedExtension.manifest.version === e.version) {
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version);
|
||||
this.updateExtensionState(e.state, installedExtension.manifest.publisher, installedExtension.manifest.name, installedExtension.manifest.version);
|
||||
}
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||
@@ -382,14 +391,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier);
|
||||
|
||||
/* Update extension state only if
|
||||
* extension is installed and version is same as synced version or
|
||||
* extension is not installed and installable
|
||||
* extension is installed and version is same as synced version or
|
||||
* extension is not installed and installable
|
||||
*/
|
||||
if (e.state &&
|
||||
(installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */
|
||||
: !!extension /* Installable */)
|
||||
) {
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version);
|
||||
const publisher = installedExtension ? installedExtension.manifest.publisher : extension!.publisher;
|
||||
const name = installedExtension ? installedExtension.manifest.name : extension!.name;
|
||||
this.updateExtensionState(e.state, publisher, name, installedExtension?.manifest.version);
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
@@ -436,15 +447,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
return newSkippedExtensions;
|
||||
}
|
||||
|
||||
private updateExtensionState(state: IStringDictionary<any>, id: string, version?: string): void {
|
||||
const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}');
|
||||
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined;
|
||||
private updateExtensionState(state: IStringDictionary<any>, publisher: string, name: string, version: string | undefined): void {
|
||||
const extensionState = getExtensionStorageState(publisher, name, this.storageService);
|
||||
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id: getGalleryExtensionId(publisher, name), version }) : undefined;
|
||||
if (keys) {
|
||||
keys.forEach(key => extensionState[key] = state[key]);
|
||||
} else {
|
||||
forEach(state, ({ key, value }) => extensionState[key] = value);
|
||||
}
|
||||
this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storeExtensionStorageState(publisher, name, extensionState, this.storageService);
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
@@ -465,8 +476,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
try {
|
||||
const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
|
||||
const extensionStorageState = JSON.parse(extensionStorageValue);
|
||||
const extensionStorageState = getExtensionStorageState(manifest.publisher, manifest.name, this.storageService);
|
||||
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
|
||||
if (keys.includes(key)) {
|
||||
state[key] = extensionStorageState[key];
|
||||
@@ -490,6 +500,7 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@@ -511,12 +522,18 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
const newlyEnabledExtensions: ILocalExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const newExtensionsToSync = new Map<string, ISyncExtension>();
|
||||
const installedExtensionsToSync: ISyncExtension[] = [];
|
||||
const installedExtensionsToSync: { syncExtension: ISyncExtension, installedExtension: ILocalExtension }[] = [];
|
||||
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
|
||||
const toDisable: IExtensionIdentifier[] = [];
|
||||
for (const extension of remoteExtensions) {
|
||||
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
|
||||
installedExtensionsToSync.push(extension);
|
||||
if (this.ignoredExtensionsManagementService.hasToNeverSyncExtension(extension.identifier.id)) {
|
||||
// Skip extension ignored to sync
|
||||
continue;
|
||||
}
|
||||
|
||||
const installedExtension = installedExtensions.find(i => areSameExtensions(i.identifier, extension.identifier));
|
||||
if (installedExtension) {
|
||||
installedExtensionsToSync.push({ syncExtension: extension, installedExtension });
|
||||
if (extension.disabled) {
|
||||
toDisable.push(extension.identifier);
|
||||
}
|
||||
@@ -536,11 +553,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
}
|
||||
|
||||
// 1. Initialise already installed extensions state
|
||||
for (const extensionToSync of installedExtensionsToSync) {
|
||||
if (extensionToSync.state) {
|
||||
const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
for (const { syncExtension, installedExtension } of installedExtensionsToSync) {
|
||||
if (syncExtension.state) {
|
||||
const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService);
|
||||
forEach(syncExtension.state, ({ key, value }) => extensionState[key] = value);
|
||||
storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +577,7 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
try {
|
||||
const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!;
|
||||
if (extensionToSync.state) {
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService);
|
||||
}
|
||||
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
|
||||
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
|
||||
@@ -25,7 +25,8 @@ export namespace WebviewResourceResponse {
|
||||
|
||||
constructor(
|
||||
public readonly stream: VSBufferReadableStream,
|
||||
public readonly mimeType: string
|
||||
public readonly etag: string | undefined,
|
||||
public readonly mimeType: string,
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ export namespace WebviewResourceResponse {
|
||||
}
|
||||
|
||||
interface FileReader {
|
||||
readFileStream(resource: URI): Promise<VSBufferReadableStream>;
|
||||
readFileStream(resource: URI): Promise<{ stream: VSBufferReadableStream, etag?: string }>;
|
||||
}
|
||||
|
||||
export async function loadLocalResource(
|
||||
@@ -73,7 +74,7 @@ export async function loadLocalResource(
|
||||
logService.debug(`loadLocalResource - Loaded over http(s). requestUri=${requestUri}, response=${response.res.statusCode}`);
|
||||
|
||||
if (response.res.statusCode === 200) {
|
||||
return new WebviewResourceResponse.StreamSuccess(response.stream, mime);
|
||||
return new WebviewResourceResponse.StreamSuccess(response.stream, undefined, mime);
|
||||
}
|
||||
return WebviewResourceResponse.Failed;
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export async function loadLocalResource(
|
||||
const contents = await fileReader.readFileStream(resourceToLoad);
|
||||
logService.debug(`loadLocalResource - Loaded using fileReader. requestUri=${requestUri}`);
|
||||
|
||||
return new WebviewResourceResponse.StreamSuccess(contents, mime);
|
||||
return new WebviewResourceResponse.StreamSuccess(contents.stream, contents.etag, mime);
|
||||
} catch (err) {
|
||||
logService.debug(`loadLocalResource - Error using fileReader. requestUri=${requestUri}`);
|
||||
console.log(err);
|
||||
|
||||
@@ -175,10 +175,14 @@ export class WebviewProtocolProvider extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
const fileService = {
|
||||
readFileStream: async (resource: URI): Promise<VSBufferReadableStream> => {
|
||||
const fileReader = {
|
||||
readFileStream: async (resource: URI): Promise<{ stream: VSBufferReadableStream, etag?: string }> => {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
return (await this.fileService.readFileStream(resource)).value;
|
||||
const result = (await this.fileService.readFileStream(resource));
|
||||
return {
|
||||
stream: result.value,
|
||||
etag: result.etag
|
||||
};
|
||||
}
|
||||
|
||||
// Unknown uri scheme. Try delegating the file read back to the renderer
|
||||
@@ -201,7 +205,7 @@ export class WebviewProtocolProvider extends Disposable {
|
||||
throw new FileOperationError('Could not read file', FileOperationResult.FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
return bufferToStream(result);
|
||||
return { stream: bufferToStream(result), etag: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -210,15 +214,41 @@ export class WebviewProtocolProvider extends Disposable {
|
||||
roots: metadata.localResourceRoots,
|
||||
remoteConnectionData: metadata.remoteConnectionData,
|
||||
rewriteUri,
|
||||
}, fileService, this.requestService, this.logService);
|
||||
}, fileReader, this.requestService, this.logService);
|
||||
|
||||
if (result.type === WebviewResourceResponse.Type.Success) {
|
||||
const cacheHeaders: Record<string, string> = result.etag ? {
|
||||
'ETag': result.etag,
|
||||
'Cache-Control': 'no-cache'
|
||||
} : {};
|
||||
|
||||
const ifNoneMatch = request.headers['If-None-Match'];
|
||||
if (ifNoneMatch && result.etag === ifNoneMatch) {
|
||||
/*
|
||||
* Note that the server generating a 304 response MUST
|
||||
* generate any of the following header fields that would
|
||||
* have been sent in a 200 (OK) response to the same request:
|
||||
* Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
|
||||
* (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
|
||||
*/
|
||||
return callback({
|
||||
statusCode: 304, // not modified
|
||||
data: undefined, // The request fails if `data` is not set
|
||||
headers: {
|
||||
'Content-Type': result.mimeType,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
...cacheHeaders
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return callback({
|
||||
statusCode: 200,
|
||||
data: this.streamToNodeReadable(result.stream),
|
||||
headers: {
|
||||
'Content-Type': result.mimeType,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
...cacheHeaders
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Vendored
+52
-1
@@ -4697,6 +4697,10 @@ declare module 'vscode' {
|
||||
* This rule will only execute if the text after the cursor matches this regular expression.
|
||||
*/
|
||||
afterText?: RegExp;
|
||||
/**
|
||||
* This rule will only execute if the text above the current line matches this regular expression.
|
||||
*/
|
||||
previousLineText?: RegExp;
|
||||
/**
|
||||
* The action to execute.
|
||||
*/
|
||||
@@ -5821,6 +5825,11 @@ declare module 'vscode' {
|
||||
setKeysForSync(keys: string[]): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A storage utility for secrets.
|
||||
*/
|
||||
readonly secrets: SecretStorage;
|
||||
|
||||
/**
|
||||
* The uri of the directory containing the extension.
|
||||
*/
|
||||
@@ -5958,6 +5967,48 @@ declare module 'vscode' {
|
||||
update(key: string, value: any): Thenable<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The event data that is fired when a secret is added or removed.
|
||||
*/
|
||||
export interface SecretStorageChangeEvent {
|
||||
/**
|
||||
* The key of the secret that has changed.
|
||||
*/
|
||||
readonly key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a storage utility for secrets, information that is
|
||||
* sensitive.
|
||||
*/
|
||||
export interface SecretStorage {
|
||||
/**
|
||||
* Retrieve a secret that was stored with key. Returns undefined if there
|
||||
* is no password matching that key.
|
||||
* @param key The key the secret was stored under.
|
||||
* @returns The stored value or `undefined`.
|
||||
*/
|
||||
get(key: string): Thenable<string | undefined>;
|
||||
|
||||
/**
|
||||
* Store a secret under a given key.
|
||||
* @param key The key to store the secret under.
|
||||
* @param value The secret.
|
||||
*/
|
||||
store(key: string, value: string): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Remove a secret from storage.
|
||||
* @param key The key the secret was stored under.
|
||||
*/
|
||||
delete(key: string): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Fires when a secret is stored or deleted.
|
||||
*/
|
||||
onDidChange: Event<SecretStorageChangeEvent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a color theme kind.
|
||||
*/
|
||||
@@ -12161,7 +12212,7 @@ declare module 'vscode' {
|
||||
/**
|
||||
* Optional reaction handler for creating and deleting reactions on a [comment](#Comment).
|
||||
*/
|
||||
reactionHandler?: (comment: Comment, reaction: CommentReaction) => Promise<void>;
|
||||
reactionHandler?: (comment: Comment, reaction: CommentReaction) => Thenable<void>;
|
||||
|
||||
/**
|
||||
* Dispose this comment controller.
|
||||
|
||||
Vendored
+34
-66
@@ -881,15 +881,6 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region https://github.com/microsoft/vscode/issues/58440
|
||||
export interface OnEnterRule {
|
||||
/**
|
||||
* This rule will only execute if the text above the this line matches this regular expression.
|
||||
*/
|
||||
oneLineAboveText?: RegExp;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Tree View: https://github.com/microsoft/vscode/issues/61313 @alexr00
|
||||
export interface TreeView<T> extends Disposable {
|
||||
reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable<void>;
|
||||
@@ -1577,16 +1568,16 @@ declare module 'vscode' {
|
||||
* resolve the raw content for `uri` as the resouce is not necessarily a file on disk.
|
||||
*/
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise<NotebookData>;
|
||||
openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Thenable<NotebookData>;
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
// eslint-disable-next-line vscode-dts-cancellation
|
||||
resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise<void>;
|
||||
resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable<void>;
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
|
||||
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Thenable<void>;
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
|
||||
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Thenable<void>;
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise<NotebookDocumentBackup>;
|
||||
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Thenable<NotebookDocumentBackup>;
|
||||
}
|
||||
|
||||
export interface NotebookKernel {
|
||||
@@ -1685,7 +1676,7 @@ declare module 'vscode' {
|
||||
): Disposable;
|
||||
|
||||
export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType;
|
||||
export function openNotebookDocument(uri: Uri, viewType?: string): Promise<NotebookDocument>;
|
||||
export function openNotebookDocument(uri: Uri, viewType?: string): Thenable<NotebookDocument>;
|
||||
export const onDidOpenNotebookDocument: Event<NotebookDocument>;
|
||||
export const onDidCloseNotebookDocument: Event<NotebookDocument>;
|
||||
export const onDidSaveNotebookDocument: Event<NotebookDocument>;
|
||||
@@ -1729,7 +1720,7 @@ declare module 'vscode' {
|
||||
export const onDidChangeActiveNotebookEditor: Event<NotebookEditor | undefined>;
|
||||
export const onDidChangeNotebookEditorSelection: Event<NotebookEditorSelectionChangeEvent>;
|
||||
export const onDidChangeNotebookEditorVisibleRanges: Event<NotebookEditorVisibleRangesChangeEvent>;
|
||||
export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Promise<NotebookEditor>;
|
||||
export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable<NotebookEditor>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1940,7 +1931,7 @@ declare module 'vscode' {
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
export function getTokenInformationAtPosition(document: TextDocument, position: Position): Promise<TokenInformation>;
|
||||
export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable<TokenInformation>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -2367,13 +2358,6 @@ declare module 'vscode' {
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional metadata about the uri being opened
|
||||
*/
|
||||
interface OpenExternalUriContext {
|
||||
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Opener service (https://github.com/microsoft/vscode/issues/109277)
|
||||
@@ -2497,54 +2481,38 @@ declare module 'vscode' {
|
||||
export function registerExternalUriOpener(id: string, schemes: readonly string[], opener: ExternalUriOpener, metadata: ExternalUriOpenerMetadata): Disposable;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region https://github.com/microsoft/vscode/issues/112249
|
||||
|
||||
/**
|
||||
* The event data that is fired when a secret is added or removed.
|
||||
*/
|
||||
export interface SecretStorageChangeEvent {
|
||||
interface OpenExternalOptions {
|
||||
/**
|
||||
* The key of the secret that has changed.
|
||||
*
|
||||
* If `true`, then VS Code will check if any contributed openers can handle the
|
||||
* uri, and fallback to the default opener behavior.
|
||||
*
|
||||
* If it is string, then this specifies the id of the `ExternalUriOpener`
|
||||
* that should be used if it is available. Use `'default'` to force VS Code's
|
||||
* standard external opener to be used.
|
||||
*/
|
||||
key: string;
|
||||
readonly allowContributedOpeners?: boolean | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a storage utility for secrets, information that is
|
||||
* sensitive.
|
||||
*/
|
||||
export interface SecretStorage {
|
||||
/**
|
||||
* Retrieve a secret that was stored with key. Returns undefined if there
|
||||
* is no password matching that key.
|
||||
* @param key The key the password was stored under.
|
||||
* @returns The stored value or `undefined`.
|
||||
*/
|
||||
get(key: string): Thenable<string | undefined>;
|
||||
|
||||
/**
|
||||
* Store a secret under a given key.
|
||||
* @param key The key to store the password under.
|
||||
* @param value The password.
|
||||
*/
|
||||
set(key: string, value: string): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Remove a secret from storage.
|
||||
* @param key The key the password was stored under.
|
||||
*/
|
||||
delete(key: string): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Fires when a secret is set or deleted.
|
||||
*/
|
||||
onDidChange: Event<SecretStorageChangeEvent>;
|
||||
namespace env {
|
||||
export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface ExtensionContext {
|
||||
secrets: SecretStorage;
|
||||
//#endregionn
|
||||
|
||||
//#region https://github.com/Microsoft/vscode/issues/15178
|
||||
|
||||
// TODO@API must be a class
|
||||
export interface OpenEditorInfo {
|
||||
name: string;
|
||||
resource: Uri;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export const openEditors: ReadonlyArray<OpenEditorInfo>;
|
||||
|
||||
// todo@API proper event type
|
||||
export const onDidChangeOpenEditors: Event<void>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { Verbosity } from 'vs/workbench/common/editor';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
|
||||
export interface ITabInfo {
|
||||
name: string;
|
||||
@@ -19,34 +18,62 @@ export interface ITabInfo {
|
||||
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
|
||||
export class MainThreadEditorTabs {
|
||||
|
||||
private readonly _registration: IDisposable;
|
||||
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
|
||||
|
||||
private readonly _dispoables = new DisposableStore();
|
||||
private readonly _groups = new Map<IEditorGroup, IDisposable>();
|
||||
private readonly _proxy: IExtHostEditorTabsShape;
|
||||
|
||||
constructor(
|
||||
_extHostContext: IExtHostContext,
|
||||
extHostContext: IExtHostContext,
|
||||
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
|
||||
) {
|
||||
this._registration = CommandsRegistry.registerCommand('_textEditorTabs', () => {
|
||||
return this._fetchTextEditors();
|
||||
});
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
|
||||
|
||||
this._editorGroupsService.groups.forEach(this._subscribeToGroup, this);
|
||||
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
|
||||
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
|
||||
const subscription = this._groups.get(e);
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
this._groups.delete(e);
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
}));
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._registration.dispose();
|
||||
dispose(this._groups.values());
|
||||
this._dispoables.dispose();
|
||||
}
|
||||
|
||||
private _fetchTextEditors(): ITabInfo[] {
|
||||
const result: ITabInfo[] = [];
|
||||
private _subscribeToGroup(group: IEditorGroup) {
|
||||
this._groups.get(group)?.dispose();
|
||||
const listener = group.onDidGroupChange(e => {
|
||||
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
});
|
||||
this._groups.set(group, listener);
|
||||
}
|
||||
|
||||
private _pushEditorTabs(): void {
|
||||
const tabs: IEditorTabDto[] = [];
|
||||
for (const group of this._editorGroupsService.groups) {
|
||||
for (const editor of group.editors) {
|
||||
if (editor.isDisposed() || !editor.resource) {
|
||||
continue;
|
||||
}
|
||||
result.push({
|
||||
tabs.push({
|
||||
group: group.id,
|
||||
name: editor.getTitle(Verbosity.SHORT) ?? '',
|
||||
resource: editor.resource
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
this._proxy.$acceptEditorTabs(tabs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,7 +688,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
return {
|
||||
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
for (const entry of diff) {
|
||||
@@ -41,6 +41,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting);
|
||||
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
|
||||
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
|
||||
testService.updateRootProviderCount(1);
|
||||
|
||||
for (const { resource, uri } of this.testService.subscriptions) {
|
||||
this.proxy.$subscribeToTests(resource, uri);
|
||||
@@ -64,13 +65,6 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
this.testService.unregisterTestController(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$updateDiscoveringCount(resource: ExtHostTestingResource, uriComponents: UriComponents, delta: number): void {
|
||||
this.testService.updateDiscoveringCount(resource, URI.revive(uriComponents), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -103,6 +97,10 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
// no-op
|
||||
this.testService.updateRootProviderCount(-1);
|
||||
for (const subscription of this.testSubscriptions.values()) {
|
||||
subscription.dispose();
|
||||
}
|
||||
this.testSubscriptions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorit
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
|
||||
@@ -25,7 +26,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
|
||||
@@ -100,6 +102,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
|
||||
if (forward) {
|
||||
return forward.then(tunnel => {
|
||||
this.logService.trace(`MainThreadTunnelService: New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
if (!tunnel) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -110,6 +113,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
|
||||
public: tunnel.public,
|
||||
dispose: async (silent?: boolean) => {
|
||||
this.logService.trace(`MainThreadTunnelService: Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (this.isSupportedLink(webview, URI.parse(link))) {
|
||||
this._openerService.open(link, { fromUserGesture: true });
|
||||
this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape {
|
||||
// called with URI or transformed -> use uri
|
||||
target = uri;
|
||||
}
|
||||
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
|
||||
return this.openerService.open(target, {
|
||||
openExternal: true,
|
||||
allowTunneling: options.allowTunneling,
|
||||
allowContributedOpeners: options.allowContributedOpeners,
|
||||
});
|
||||
}
|
||||
|
||||
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {
|
||||
|
||||
@@ -3,35 +3,32 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { coalesce, } from 'vs/base/common/arrays';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Extensions as ViewletExtensions, ShowViewletAction2, ViewletRegistry } from 'vs/workbench/browser/viewlet';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor {
|
||||
id: string;
|
||||
@@ -255,7 +252,8 @@ const viewsExtensionPoint: IExtensionPoint<ViewExtensionPointType> = ExtensionsR
|
||||
jsonSchema: viewsContribution
|
||||
});
|
||||
|
||||
const TEST_VIEW_CONTAINER_ORDER = 6;
|
||||
const CUSTOM_VIEWS_START_ORDER = 7;
|
||||
|
||||
class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private viewContainersRegistry: IViewContainersRegistry;
|
||||
@@ -271,7 +269,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private handleAndRegisterCustomViewContainers() {
|
||||
this.registerTestViewContainer();
|
||||
viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => {
|
||||
if (removed.length) {
|
||||
this.removeCustomViewContainers(removed);
|
||||
@@ -284,7 +281,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser<ViewContainerExtensionPointType>[], existingViewContainers: ViewContainer[]): void {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1;
|
||||
let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length;
|
||||
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
|
||||
for (let { value, collector, description } of extensionPoints) {
|
||||
forEach(value, entry => {
|
||||
@@ -318,13 +315,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private registerTestViewContainer(): void {
|
||||
const title = localize('test', "Test");
|
||||
const icon = testViewIcon;
|
||||
|
||||
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(viewsContainersDescriptors)) {
|
||||
collector.error(localize('viewcontainer requirearray', "views containers must be an array"));
|
||||
@@ -395,22 +385,15 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}, location);
|
||||
|
||||
// Register Action to Open Viewlet
|
||||
class OpenCustomViewletAction extends ShowViewletAction {
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, id, viewletService, editorGroupService, layoutService);
|
||||
registerAction2(class OpenCustomViewletAction extends ShowViewletAction2 {
|
||||
constructor() {
|
||||
super({ id, f1: true, title: localize('showViewlet', "Show {0}", title), category: CATEGORIES.View.value });
|
||||
}
|
||||
}
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)),
|
||||
`View: Show ${title}`,
|
||||
CATEGORIES.View.value
|
||||
);
|
||||
|
||||
protected viewletId() {
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return viewContainer;
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ExtHostSecretState implements ExtHostSecretStateShape {
|
||||
return this._proxy.$getPassword(extensionId, key);
|
||||
}
|
||||
|
||||
set(extensionId: string, key: string, value: string): Promise<void> {
|
||||
store(extensionId: string, key: string, value: string): Promise<void> {
|
||||
return this._proxy.$setPassword(extensionId, key, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSyste
|
||||
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
|
||||
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
|
||||
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -133,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService));
|
||||
|
||||
// manually create and register addressable instances
|
||||
const extHostEditorTabs = rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, new ExtHostEditorTabs());
|
||||
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
|
||||
@@ -292,8 +294,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get shell() {
|
||||
return extHostTerminalService.getDefaultShell(false, configProvider);
|
||||
},
|
||||
openExternal(uri: URI) {
|
||||
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority });
|
||||
openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) {
|
||||
return extHostWindow.openUri(uri, {
|
||||
allowTunneling: !!initData.remote.authority,
|
||||
allowContributedOpeners: options?.allowContributedOpeners,
|
||||
});
|
||||
},
|
||||
asExternalUri(uri: URI) {
|
||||
if (uri.scheme === initData.environment.appUriScheme) {
|
||||
@@ -680,6 +685,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostUriOpeners.registerUriOpener(extension.identifier, id, schemes, opener, metadata);
|
||||
},
|
||||
get openEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.tabs;
|
||||
},
|
||||
get onDidChangeOpenEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.onDidChangeTabs;
|
||||
}
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
|
||||
@@ -335,7 +335,7 @@ export interface IIndentationRuleDto {
|
||||
export interface IOnEnterRuleDto {
|
||||
beforeText: IRegExpDto;
|
||||
afterText?: IRegExpDto;
|
||||
oneLineAboveText?: IRegExpDto;
|
||||
previousLineText?: IRegExpDto;
|
||||
action: EnterAction;
|
||||
}
|
||||
export interface ILanguageConfigurationDto {
|
||||
@@ -607,15 +607,29 @@ export interface MainThreadEditorInsetsShape extends IDisposable {
|
||||
$postMessage(handle: number, value: any): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface MainThreadEditorTabsShape extends IDisposable {
|
||||
|
||||
}
|
||||
|
||||
export interface ExtHostEditorInsetsShape {
|
||||
$onDidDispose(handle: number): void;
|
||||
$onDidReceiveMessage(handle: number, message: any): void;
|
||||
}
|
||||
|
||||
//#region --- open editors model
|
||||
|
||||
export interface MainThreadEditorTabsShape extends IDisposable {
|
||||
// manage tabs: move, close, rearrange etc
|
||||
}
|
||||
|
||||
export interface IEditorTabDto {
|
||||
group: number;
|
||||
name: string;
|
||||
resource: UriComponents
|
||||
}
|
||||
|
||||
export interface IExtHostEditorTabsShape {
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export type WebviewHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
@@ -980,6 +994,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
|
||||
export interface IOpenUriOptions {
|
||||
readonly allowTunneling?: boolean;
|
||||
readonly allowContributedOpeners?: boolean | string;
|
||||
}
|
||||
|
||||
export interface MainThreadWindowShape extends IDisposable {
|
||||
@@ -1838,7 +1853,6 @@ export interface MainThreadTestingShape {
|
||||
$unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult>;
|
||||
$updateDiscoveringCount(resource: ExtHostTestingResource, uri: UriComponents, delta: number): void;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
@@ -1927,6 +1941,7 @@ export const ExtHostContext = {
|
||||
ExtHostCustomEditors: createExtId<ExtHostCustomEditorsShape>('ExtHostCustomEditors'),
|
||||
ExtHostWebviewViews: createExtId<ExtHostWebviewViewsShape>('ExtHostWebviewViews'),
|
||||
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
|
||||
ExtHostEditorTabs: createExtId<IExtHostEditorTabsShape>('ExtHostEditorTabs'),
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
|
||||
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
|
||||
ExtHostSecretState: createMainId<ExtHostSecretStateShape>('ExtHostSecretState'),
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { IEditorTabDto, IExtHostEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
|
||||
export interface IEditorTab {
|
||||
name: string;
|
||||
group: number;
|
||||
resource: vscode.Uri
|
||||
}
|
||||
|
||||
export class ExtHostEditorTabs implements IExtHostEditorTabsShape {
|
||||
|
||||
private readonly _onDidChangeTabs = new Emitter<void>();
|
||||
readonly onDidChangeTabs: Event<void> = this._onDidChangeTabs.event;
|
||||
|
||||
private _tabs: IEditorTab[] = [];
|
||||
|
||||
get tabs(): readonly IEditorTab[] {
|
||||
return this._tabs;
|
||||
}
|
||||
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void {
|
||||
this._tabs = tabs.map(dto => {
|
||||
return {
|
||||
name: dto.name,
|
||||
group: dto.group,
|
||||
resource: URI.revive(dto.resource)
|
||||
};
|
||||
});
|
||||
this._onDidChangeTabs.fire();
|
||||
}
|
||||
}
|
||||
@@ -1921,7 +1921,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return {
|
||||
beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ export class ExtensionSecrets implements vscode.SecretStorage {
|
||||
return this._secretState.get(this._id, key);
|
||||
}
|
||||
|
||||
set(key: string, value: string): Promise<void> {
|
||||
return this._secretState.set(this._id, key, value);
|
||||
store(key: string, value: string): Promise<void> {
|
||||
return this._secretState.store(this._id, key, value);
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mapFind } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
@@ -19,7 +19,7 @@ import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
@@ -125,19 +125,6 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = 0;
|
||||
const updateCountScheduler = new RunOnceScheduler(() => {
|
||||
if (delta !== 0) {
|
||||
this.proxy.$updateDiscoveringCount(resource, uri, delta);
|
||||
delta = 0;
|
||||
}
|
||||
}, 5);
|
||||
|
||||
const updateDelta = (amount: number) => {
|
||||
delta += amount;
|
||||
updateCountScheduler.schedule();
|
||||
};
|
||||
|
||||
const subscribeFn = (id: string, provider: vscode.TestProvider) => {
|
||||
try {
|
||||
const hierarchy = method!(provider);
|
||||
@@ -145,10 +132,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDelta(1);
|
||||
collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, 1]);
|
||||
disposable.add(hierarchy);
|
||||
collection.addRoot(hierarchy.root, id);
|
||||
Promise.resolve(hierarchy.discoveredInitialTests).then(() => updateDelta(-1));
|
||||
Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1]));
|
||||
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -161,6 +148,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
subscribeFn(id, provider);
|
||||
}
|
||||
|
||||
// note: we don't increment the root count initially -- this is done by the
|
||||
// main thread, incrementing once per extension host. We just push the
|
||||
// diff to signal that roots have been discovered.
|
||||
collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]);
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
|
||||
}
|
||||
|
||||
|
||||
@@ -2315,9 +2315,6 @@ export class FunctionBreakpoint extends Breakpoint {
|
||||
|
||||
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
|
||||
super(enabled, condition, hitCondition, logMessage);
|
||||
if (!functionName) {
|
||||
throw illegalArgument('functionName');
|
||||
}
|
||||
this.functionName = functionName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ import * as fs from 'fs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { promisify } from 'util';
|
||||
import { MovingAverage } from 'vs/base/common/numbers';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
class ExtensionTunnel implements vscode.Tunnel {
|
||||
private _onDispose: Emitter<void> = new Emitter();
|
||||
@@ -142,7 +142,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
|
||||
@@ -234,16 +235,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
|
||||
if (this._forwardPortProvider) {
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
if (providedPort !== undefined) {
|
||||
return asPromise(() => providedPort).then(tunnel => {
|
||||
try {
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
if (providedPort !== undefined) {
|
||||
const tunnel = await providedPort;
|
||||
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
|
||||
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
|
||||
}
|
||||
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
|
||||
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
|
||||
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
|
||||
});
|
||||
return TunnelDto.fromApiTunnel(tunnel);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.trace('$forwardPort: tunnel provider error');
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action
|
||||
import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IAction, Action, SubmenuAction, Separator, toAction } from 'vs/base/common/actions';
|
||||
import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform';
|
||||
@@ -709,7 +709,14 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
const webNavigationActions: IAction[] = [];
|
||||
const href = this.environmentService.options?.homeIndicator?.href;
|
||||
if (href) {
|
||||
webNavigationActions.push(toAction({ id: 'goHome', label: nls.localize('goHome', "Go Home"), run: () => window.location.href = href }));
|
||||
webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true,
|
||||
async (event?: MouseEvent) => {
|
||||
if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) {
|
||||
window.open(href, '_blank');
|
||||
} else {
|
||||
window.location.href = href;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const otherActions = this.getWebNavigationMenuItemActions().map(action => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -24,6 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { Action2 } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
|
||||
@@ -137,7 +138,7 @@ export class ShowViewletAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
|
||||
// Pass focus to viewlet if not open or focused
|
||||
if (this.otherViewletShowing() || !this.sidebarHasFocus()) {
|
||||
if (otherViewletShowing(this.viewletService, this.viewletId) || !sidebarHasFocus(this.viewletService, this.layoutService)) {
|
||||
await this.viewletService.openViewlet(this.viewletId, true);
|
||||
return;
|
||||
}
|
||||
@@ -145,18 +146,42 @@ export class ShowViewletAction extends Action {
|
||||
// Otherwise pass focus to editor group
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private otherViewletShowing(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
/**
|
||||
* A reusable action to show a viewlet with a specific id.
|
||||
*/
|
||||
export abstract class ShowViewletAction2 extends Action2 {
|
||||
/**
|
||||
* Gets the viewlet ID to show.
|
||||
*/
|
||||
protected abstract viewletId(): string;
|
||||
|
||||
return !activeViewlet || activeViewlet.getId() !== this.viewletId;
|
||||
}
|
||||
public async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
|
||||
private sidebarHasFocus(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const activeElement = document.activeElement;
|
||||
const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART);
|
||||
// Pass focus to viewlet if not open or focused
|
||||
if (otherViewletShowing(viewletService, this.viewletId()) || !sidebarHasFocus(viewletService, layoutService)) {
|
||||
await viewletService.openViewlet(this.viewletId(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
|
||||
// Otherwise pass focus to editor group
|
||||
editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
const otherViewletShowing = (viewletService: IViewletService, viewletId: string): boolean => {
|
||||
const activeViewlet = viewletService.getActiveViewlet();
|
||||
return !activeViewlet || activeViewlet.getId() !== viewletId;
|
||||
};
|
||||
|
||||
const sidebarHasFocus = (viewletService: IViewletService, layoutService: IWorkbenchLayoutService): boolean => {
|
||||
const activeViewlet = viewletService.getActiveViewlet();
|
||||
const activeElement = document.activeElement;
|
||||
const sidebarPart = layoutService.getContainer(Parts.SIDEBAR_PART);
|
||||
|
||||
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IComposite {
|
||||
readonly onDidBlur: Event<void>;
|
||||
|
||||
/**
|
||||
* Returns true if the composite has focus.
|
||||
* Returns true if the composite has focus.
|
||||
*/
|
||||
hasFocus(): boolean;
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
|
||||
export const testViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
|
||||
|
||||
export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.'));
|
||||
|
||||
export namespace Extensions {
|
||||
|
||||
@@ -409,14 +409,14 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
|
||||
} else if (URI.isUri(metadata.iconPath)) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
template.icon.style.setProperty('--background-dark', `url("${metadata.iconPath.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-light', `url("${metadata.iconPath.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-dark', dom.asCSSUrl(metadata.iconPath));
|
||||
template.icon.style.setProperty('--background-light', dom.asCSSUrl(metadata.iconPath));
|
||||
|
||||
} else if (metadata.iconPath) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
template.icon.style.setProperty('--background-dark', `url("${metadata.iconPath.dark.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-light', `url("${metadata.iconPath.light.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-dark', dom.asCSSUrl(metadata.iconPath.dark));
|
||||
template.icon.style.setProperty('--background-light', dom.asCSSUrl(metadata.iconPath.light));
|
||||
}
|
||||
|
||||
template.label.setLabel(metadata.label, metadata.description, {
|
||||
@@ -608,14 +608,14 @@ class TextEditElementTemplate {
|
||||
} else if (URI.isUri(iconPath)) {
|
||||
// background-image
|
||||
this._icon.className = 'uri-icon';
|
||||
this._icon.style.setProperty('--background-dark', `url("${iconPath.toString(true)}")`);
|
||||
this._icon.style.setProperty('--background-light', `url("${iconPath.toString(true)}")`);
|
||||
this._icon.style.setProperty('--background-dark', dom.asCSSUrl(iconPath));
|
||||
this._icon.style.setProperty('--background-light', dom.asCSSUrl(iconPath));
|
||||
|
||||
} else {
|
||||
// background-image
|
||||
this._icon.className = 'uri-icon';
|
||||
this._icon.style.setProperty('--background-dark', `url("${iconPath.dark.toString(true)}")`);
|
||||
this._icon.style.setProperty('--background-light', `url("${iconPath.light.toString(true)}")`);
|
||||
this._icon.style.setProperty('--background-dark', dom.asCSSUrl(iconPath.dark));
|
||||
this._icon.style.setProperty('--background-light', dom.asCSSUrl(iconPath.light));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+238
-48
@@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { CharacterPair, CommentRule, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { CharacterPair, CommentRule, EnterAction, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, LanguageConfiguration, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
@@ -31,6 +31,19 @@ interface IIndentationRules {
|
||||
unIndentedLinePattern?: string | IRegExp;
|
||||
}
|
||||
|
||||
interface IEnterAction {
|
||||
indent: 'none' | 'indent' | 'indentOutdent' | 'outdent';
|
||||
appendText?: string;
|
||||
removeText?: number;
|
||||
}
|
||||
|
||||
interface IOnEnterRule {
|
||||
beforeText: string | IRegExp;
|
||||
afterText?: string | IRegExp;
|
||||
previousLineText?: string | IRegExp;
|
||||
action: IEnterAction;
|
||||
}
|
||||
|
||||
interface ILanguageConfiguration {
|
||||
comments?: CommentRule;
|
||||
brackets?: CharacterPair[];
|
||||
@@ -40,6 +53,7 @@ interface ILanguageConfiguration {
|
||||
indentationRules?: IIndentationRules;
|
||||
folding?: FoldingRules;
|
||||
autoCloseBefore?: string;
|
||||
onEnterRules?: IOnEnterRule[];
|
||||
}
|
||||
|
||||
function isStringArr(something: string[] | null): something is string[] {
|
||||
@@ -93,7 +107,7 @@ export class LanguageConfigurationFileHandler {
|
||||
}
|
||||
this._done[languageIdentifier.id] = true;
|
||||
|
||||
let configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language);
|
||||
const configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language);
|
||||
configurationFiles.forEach((configFileLocation) => this._handleConfigFile(languageIdentifier, configFileLocation));
|
||||
}
|
||||
|
||||
@@ -254,18 +268,82 @@ export class LanguageConfigurationFileHandler {
|
||||
return result;
|
||||
}
|
||||
|
||||
// private _mapCharacterPairs(pairs: Array<CharacterPair | IAutoClosingPairConditional>): IAutoClosingPairConditional[] {
|
||||
// return pairs.map(pair => {
|
||||
// if (Array.isArray(pair)) {
|
||||
// return { open: pair[0], close: pair[1] };
|
||||
// }
|
||||
// return <IAutoClosingPairConditional>pair;
|
||||
// });
|
||||
// }
|
||||
private _extractValidOnEnterRules(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): OnEnterRule[] | null {
|
||||
const source = configuration.onEnterRules;
|
||||
if (typeof source === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(source)) {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules\` to be an array.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: OnEnterRule[] | null = null;
|
||||
for (let i = 0, len = source.length; i < len; i++) {
|
||||
const onEnterRule = source[i];
|
||||
if (!types.isObject(onEnterRule)) {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}]\` to be an object.`);
|
||||
continue;
|
||||
}
|
||||
if (!types.isObject(onEnterRule.action)) {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action\` to be an object.`);
|
||||
continue;
|
||||
}
|
||||
let indentAction: IndentAction;
|
||||
if (onEnterRule.action.indent === 'none') {
|
||||
indentAction = IndentAction.None;
|
||||
} else if (onEnterRule.action.indent === 'indent') {
|
||||
indentAction = IndentAction.Indent;
|
||||
} else if (onEnterRule.action.indent === 'indentOutdent') {
|
||||
indentAction = IndentAction.IndentOutdent;
|
||||
} else if (onEnterRule.action.indent === 'outdent') {
|
||||
indentAction = IndentAction.Outdent;
|
||||
} else {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.indent\` to be 'none', 'indent', 'indentOutdent' or 'outdent'.`);
|
||||
continue;
|
||||
}
|
||||
const action: EnterAction = { indentAction };
|
||||
if (onEnterRule.action.appendText) {
|
||||
if (typeof onEnterRule.action.appendText === 'string') {
|
||||
action.appendText = onEnterRule.action.appendText;
|
||||
} else {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.appendText\` to be undefined or a string.`);
|
||||
}
|
||||
}
|
||||
if (onEnterRule.action.removeText) {
|
||||
if (typeof onEnterRule.action.removeText === 'number') {
|
||||
action.removeText = onEnterRule.action.removeText;
|
||||
} else {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.removeText\` to be undefined or a number.`);
|
||||
}
|
||||
}
|
||||
const beforeText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].beforeText`, onEnterRule.beforeText);
|
||||
if (!beforeText) {
|
||||
continue;
|
||||
}
|
||||
const resultingOnEnterRule: OnEnterRule = { beforeText, action };
|
||||
if (onEnterRule.afterText) {
|
||||
const afterText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].afterText`, onEnterRule.afterText);
|
||||
if (afterText) {
|
||||
resultingOnEnterRule.afterText = afterText;
|
||||
}
|
||||
}
|
||||
if (onEnterRule.previousLineText) {
|
||||
const previousLineText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].previousLineText`, onEnterRule.previousLineText);
|
||||
if (previousLineText) {
|
||||
resultingOnEnterRule.previousLineText = previousLineText;
|
||||
}
|
||||
}
|
||||
result = result || [];
|
||||
result.push(resultingOnEnterRule);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _handleConfig(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): void {
|
||||
|
||||
let richEditConfig: LanguageConfiguration = {};
|
||||
const richEditConfig: LanguageConfiguration = {};
|
||||
|
||||
const comments = this._extractValidCommentRule(languageIdentifier, configuration);
|
||||
if (comments) {
|
||||
@@ -293,25 +371,21 @@ export class LanguageConfigurationFileHandler {
|
||||
}
|
||||
|
||||
if (configuration.wordPattern) {
|
||||
try {
|
||||
let wordPattern = this._parseRegex(configuration.wordPattern);
|
||||
if (wordPattern) {
|
||||
richEditConfig.wordPattern = wordPattern;
|
||||
}
|
||||
} catch (error) {
|
||||
// Malformed regexes are ignored
|
||||
const wordPattern = this._parseRegex(languageIdentifier, `wordPattern`, configuration.wordPattern);
|
||||
if (wordPattern) {
|
||||
richEditConfig.wordPattern = wordPattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.indentationRules) {
|
||||
let indentationRules = this._mapIndentationRules(configuration.indentationRules);
|
||||
const indentationRules = this._mapIndentationRules(languageIdentifier, configuration.indentationRules);
|
||||
if (indentationRules) {
|
||||
richEditConfig.indentationRules = indentationRules;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.folding) {
|
||||
let markers = configuration.folding.markers;
|
||||
const markers = configuration.folding.markers;
|
||||
|
||||
richEditConfig.folding = {
|
||||
offSide: configuration.folding.offSide,
|
||||
@@ -319,44 +393,66 @@ export class LanguageConfigurationFileHandler {
|
||||
};
|
||||
}
|
||||
|
||||
const onEnterRules = this._extractValidOnEnterRules(languageIdentifier, configuration);
|
||||
if (onEnterRules) {
|
||||
richEditConfig.onEnterRules = onEnterRules;
|
||||
}
|
||||
|
||||
LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig);
|
||||
}
|
||||
|
||||
private _parseRegex(value: string | IRegExp) {
|
||||
private _parseRegex(languageIdentifier: LanguageIdentifier, confPath: string, value: string | IRegExp) {
|
||||
if (typeof value === 'string') {
|
||||
return new RegExp(value, '');
|
||||
} else if (typeof value === 'object') {
|
||||
return new RegExp(value.pattern, value.flags);
|
||||
try {
|
||||
return new RegExp(value, '');
|
||||
} catch (err) {
|
||||
console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (types.isObject(value)) {
|
||||
if (typeof value.pattern !== 'string') {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.pattern\` to be a string.`);
|
||||
return null;
|
||||
}
|
||||
if (typeof value.flags !== 'undefined' && typeof value.flags !== 'string') {
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.flags\` to be a string.`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new RegExp(value.pattern, value.flags);
|
||||
} catch (err) {
|
||||
console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}\` to be a string or an object.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
private _mapIndentationRules(indentationRules: IIndentationRules): IndentationRule | null {
|
||||
try {
|
||||
let increaseIndentPattern = this._parseRegex(indentationRules.increaseIndentPattern);
|
||||
let decreaseIndentPattern = this._parseRegex(indentationRules.decreaseIndentPattern);
|
||||
|
||||
if (increaseIndentPattern && decreaseIndentPattern) {
|
||||
let result: IndentationRule = {
|
||||
increaseIndentPattern: increaseIndentPattern,
|
||||
decreaseIndentPattern: decreaseIndentPattern
|
||||
};
|
||||
|
||||
if (indentationRules.indentNextLinePattern) {
|
||||
result.indentNextLinePattern = this._parseRegex(indentationRules.indentNextLinePattern);
|
||||
}
|
||||
if (indentationRules.unIndentedLinePattern) {
|
||||
result.unIndentedLinePattern = this._parseRegex(indentationRules.unIndentedLinePattern);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (error) {
|
||||
// Malformed regexes are ignored
|
||||
private _mapIndentationRules(languageIdentifier: LanguageIdentifier, indentationRules: IIndentationRules): IndentationRule | null {
|
||||
const increaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.increaseIndentPattern`, indentationRules.increaseIndentPattern);
|
||||
if (!increaseIndentPattern) {
|
||||
return null;
|
||||
}
|
||||
const decreaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.decreaseIndentPattern`, indentationRules.decreaseIndentPattern);
|
||||
if (!decreaseIndentPattern) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
const result: IndentationRule = {
|
||||
increaseIndentPattern: increaseIndentPattern,
|
||||
decreaseIndentPattern: decreaseIndentPattern
|
||||
};
|
||||
|
||||
if (indentationRules.indentNextLinePattern) {
|
||||
result.indentNextLinePattern = this._parseRegex(languageIdentifier, `indentationRules.indentNextLinePattern`, indentationRules.indentNextLinePattern);
|
||||
}
|
||||
if (indentationRules.unIndentedLinePattern) {
|
||||
result.unIndentedLinePattern = this._parseRegex(languageIdentifier, `indentationRules.unIndentedLinePattern`, indentationRules.unIndentedLinePattern);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,6 +697,100 @@ const schema: IJSONSchema = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onEnterRules: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'),
|
||||
required: ['beforeText', 'action'],
|
||||
properties: {
|
||||
beforeText: {
|
||||
type: ['string', 'object'],
|
||||
description: nls.localize('schema.onEnterRules.beforeText', 'This rule will only execute if the text before the cursor matches this regular expression.'),
|
||||
properties: {
|
||||
pattern: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.beforeText.pattern', 'The RegExp pattern for beforeText.'),
|
||||
default: '',
|
||||
},
|
||||
flags: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.beforeText.flags', 'The RegExp flags for beforeText.'),
|
||||
default: '',
|
||||
pattern: '^([gimuy]+)$',
|
||||
patternErrorMessage: nls.localize('schema.onEnterRules.beforeText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
|
||||
}
|
||||
}
|
||||
},
|
||||
afterText: {
|
||||
type: ['string', 'object'],
|
||||
description: nls.localize('schema.onEnterRules.afterText', 'This rule will only execute if the text after the cursor matches this regular expression.'),
|
||||
properties: {
|
||||
pattern: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.afterText.pattern', 'The RegExp pattern for afterText.'),
|
||||
default: '',
|
||||
},
|
||||
flags: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.afterText.flags', 'The RegExp flags for afterText.'),
|
||||
default: '',
|
||||
pattern: '^([gimuy]+)$',
|
||||
patternErrorMessage: nls.localize('schema.onEnterRules.afterText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
|
||||
}
|
||||
}
|
||||
},
|
||||
previousLineText: {
|
||||
type: ['string', 'object'],
|
||||
description: nls.localize('schema.onEnterRules.previousLineText', 'This rule will only execute if the text above the line matches this regular expression.'),
|
||||
properties: {
|
||||
pattern: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.previousLineText.pattern', 'The RegExp pattern for previousLineText.'),
|
||||
default: '',
|
||||
},
|
||||
flags: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.previousLineText.flags', 'The RegExp flags for previousLineText.'),
|
||||
default: '',
|
||||
pattern: '^([gimuy]+)$',
|
||||
patternErrorMessage: nls.localize('schema.onEnterRules.previousLineText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
|
||||
}
|
||||
}
|
||||
},
|
||||
action: {
|
||||
type: ['string', 'object'],
|
||||
description: nls.localize('schema.onEnterRules.action', 'The action to execute.'),
|
||||
required: ['indent'],
|
||||
default: { 'indent': 'indent' },
|
||||
properties: {
|
||||
indent: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.action.indent', "Describe what to do with the indentation"),
|
||||
default: 'indent',
|
||||
enum: ['none', 'indent', 'indentOutdent', 'outdent'],
|
||||
markdownEnumDescriptions: [
|
||||
nls.localize('schema.onEnterRules.action.indent.none', "Insert new line and copy the previous line's indentation."),
|
||||
nls.localize('schema.onEnterRules.action.indent.indent', "Insert new line and indent once (relative to the previous line's indentation)."),
|
||||
nls.localize('schema.onEnterRules.action.indent.indentOutdent', "Insert two new lines:\n - the first one indented which will hold the cursor\n - the second one at the same indentation level"),
|
||||
nls.localize('schema.onEnterRules.action.indent.outdent', "Insert new line and outdent once (relative to the previous line's indentation).")
|
||||
]
|
||||
},
|
||||
appendText: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.onEnterRules.action.appendText', 'Describes text to be appended after the new line and after the indentation.'),
|
||||
default: '',
|
||||
},
|
||||
removeText: {
|
||||
type: 'number',
|
||||
description: nls.localize('schema.onEnterRules.action.removeText', 'Describes the number of characters to remove from the new line\'s indentation.'),
|
||||
default: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
|
||||
accept: async () => {
|
||||
const pick = await provider.pick();
|
||||
if (pick) {
|
||||
await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: pick.config.type });
|
||||
// Use the type of the provider, not of the config since config sometimes have subtypes (for example "node-terminal")
|
||||
await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: provider.type });
|
||||
this.debugService.startDebugging(pick.launch, pick.config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +478,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
|
||||
|| !this.extension.local
|
||||
|| this.extension.state !== ExtensionState.Installed
|
||||
|| this.extension.type !== ExtensionType.User
|
||||
|| this.extension.enablementState === EnablementState.DisabledByEnvironemt
|
||||
|| this.extension.enablementState === EnablementState.DisabledByEnvironment
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri
|
||||
return { dispose: remove };
|
||||
}
|
||||
|
||||
async openExternal(href: string, ctx: { sourceUri: URI }, token: CancellationToken): Promise<boolean> {
|
||||
async openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise<boolean> {
|
||||
|
||||
const targetUri = typeof href === 'string' ? URI.parse(href) : href;
|
||||
|
||||
@@ -77,11 +77,24 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri
|
||||
return false;
|
||||
}
|
||||
|
||||
// First check to see if we have a configured opener
|
||||
// First see if we have a preferredOpener
|
||||
if (ctx.preferredOpenerId) {
|
||||
if (ctx.preferredOpenerId === defaultExternalUriOpenerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const preferredOpener = allOpeners.get(ctx.preferredOpenerId);
|
||||
if (preferredOpener) {
|
||||
// Skip the `canOpen` check here since the opener was specifically requested.
|
||||
return preferredOpener.openExternalUri(targetUri, ctx, token);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we have a configured opener
|
||||
const configuredOpener = this.getConfiguredOpenerForUri(allOpeners, targetUri);
|
||||
if (configuredOpener) {
|
||||
// Skip the `canOpen` check here since the opener was specifically requested.
|
||||
return configuredOpener === 'default' ? false : configuredOpener.openExternalUri(targetUri, ctx, token);
|
||||
return configuredOpener === defaultExternalUriOpenerId ? false : configuredOpener.openExternalUri(targetUri, ctx, token);
|
||||
}
|
||||
|
||||
// Then check to see if there is a valid opener
|
||||
|
||||
@@ -232,7 +232,8 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
|
||||
this._register(this.editorService.overrideOpenEditor({
|
||||
getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => {
|
||||
|
||||
const currentEditorForResource = group && this.editorService.findEditors(resource, group);
|
||||
const currentEditorsForResource = group && this.editorService.findEditors(resource, group);
|
||||
const currentEditorForResource = currentEditorsForResource && currentEditorsForResource.length ? currentEditorsForResource[0] : undefined;
|
||||
|
||||
const associatedEditors = distinct([
|
||||
...this.getUserAssociatedNotebookEditors(resource),
|
||||
@@ -336,7 +337,24 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
|
||||
}
|
||||
|
||||
if (originalInput instanceof NotebookEditorInput) {
|
||||
return undefined;
|
||||
if (originalInput.viewType === id) {
|
||||
return undefined;
|
||||
} else {
|
||||
return {
|
||||
override: (async () => {
|
||||
const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, originalInput.getName(), id);
|
||||
await group.replaceEditors([{
|
||||
editor: originalInput,
|
||||
replacement: notebookInput
|
||||
}]);
|
||||
if (group.activeEditorPane?.input === notebookInput) {
|
||||
return group.activeEditorPane;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (originalInput instanceof NotebookDiffEditorInput) {
|
||||
|
||||
@@ -427,7 +427,7 @@ var requirejs = (function() {
|
||||
|
||||
if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto)
|
||||
|| matchesScheme(link, Schemas.command)) {
|
||||
this.openerService.open(link, { fromUserGesture: true });
|
||||
this.openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true });
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -253,7 +253,6 @@ export class OutlinePane extends ViewPane {
|
||||
// update: refresh tree
|
||||
this._domNode.classList.remove('message');
|
||||
tree.updateChildren();
|
||||
tree.expandAll();
|
||||
}
|
||||
};
|
||||
updateTree();
|
||||
|
||||
@@ -3,44 +3,59 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@ITunnelService tunnelService: ITunnelService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService
|
||||
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super();
|
||||
const tunnelFactory = environmentService.options?.tunnelProvider?.tunnelFactory;
|
||||
if (tunnelFactory) {
|
||||
this._register(tunnelService.setTunnelProvider({
|
||||
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel> | undefined => {
|
||||
const tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
|
||||
if (!tunnelPromise) {
|
||||
return undefined;
|
||||
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined => {
|
||||
let tunnelPromise: Promise<ITunnel> | undefined;
|
||||
try {
|
||||
tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
|
||||
} catch (e) {
|
||||
logService.trace('tunnelFactory: tunnel provider error');
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
tunnelPromise.then(async (tunnel) => {
|
||||
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
|
||||
const remoteTunnel: RemoteTunnel = {
|
||||
tunnelRemotePort: tunnel.remoteAddress.port,
|
||||
tunnelRemoteHost: tunnel.remoteAddress.host,
|
||||
// The tunnel factory may give us an inaccessible local address.
|
||||
// To make sure this doesn't happen, resolve the uri immediately.
|
||||
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
|
||||
public: !!tunnel.public,
|
||||
dispose: async () => { await tunnel.dispose; }
|
||||
};
|
||||
resolve(remoteTunnel);
|
||||
});
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
if (!tunnelPromise) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
let tunnel: ITunnel;
|
||||
try {
|
||||
tunnel = await tunnelPromise;
|
||||
} catch (e) {
|
||||
logService.trace('tunnelFactory: tunnel provider promise error');
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
|
||||
const remoteTunnel: RemoteTunnel = {
|
||||
tunnelRemotePort: tunnel.remoteAddress.port,
|
||||
tunnelRemoteHost: tunnel.remoteAddress.host,
|
||||
// The tunnel factory may give us an inaccessible local address.
|
||||
// To make sure this doesn't happen, resolve the uri immediately.
|
||||
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
|
||||
public: !!tunnel.public,
|
||||
dispose: async () => { await tunnel.dispose(); }
|
||||
};
|
||||
resolve(remoteTunnel);
|
||||
});
|
||||
}
|
||||
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false, public: false }));
|
||||
|
||||
@@ -239,7 +239,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider<ISymbo
|
||||
|
||||
// Open HTTP(s) links with opener service
|
||||
if (symbolToOpen.location.uri.scheme === Schemas.http || symbolToOpen.location.uri.scheme === Schemas.https) {
|
||||
await this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true });
|
||||
await this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true, allowContributedOpeners: true });
|
||||
}
|
||||
|
||||
// Otherwise open as editor
|
||||
|
||||
@@ -187,7 +187,10 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
}
|
||||
|
||||
private _handleHypertextLink(url: string): void {
|
||||
this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) });
|
||||
this._openerService.open(url, {
|
||||
allowTunneling: !!(this._processManager && this._processManager.remoteAuthority),
|
||||
allowContributedOpeners: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleProtocolLink(link: string): Promise<void> {
|
||||
|
||||
@@ -997,6 +997,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
case 'pwsh.exe':
|
||||
return WindowsShellType.PowerShell;
|
||||
case 'bash.exe':
|
||||
case 'git-cmd.exe':
|
||||
return WindowsShellType.GitBash;
|
||||
case 'wsl.exe':
|
||||
case 'ubuntu.exe':
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import { HierarchicalElement, HierarchicalFolder } from 'vs/workbench/contrib/te
|
||||
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
|
||||
import { NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
|
||||
import { InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
|
||||
/**
|
||||
* Projection that lists tests in their traditional tree view.
|
||||
@@ -46,7 +46,7 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
|
||||
this._register(listener.onFolderChange(this.applyFolderChange, this));
|
||||
|
||||
for (const [folder, collection] of listener.workspaceFolderCollections) {
|
||||
const queue = [collection.rootNodes];
|
||||
const queue = [collection.rootIds];
|
||||
while (queue.length) {
|
||||
for (const id of queue.pop()!) {
|
||||
const node = collection.getNodeById(id)!;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { HierarchicalByLocationProjection as HierarchicalByLocationProjection }
|
||||
import { HierarchicalElement, HierarchicalFolder } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes';
|
||||
import { NodeRenderDirective } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
|
||||
import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
|
||||
/**
|
||||
* Type of test element in the list.
|
||||
|
||||
@@ -95,7 +95,7 @@ export class NodeChangeList<T extends ITestTreeElement & { children: Iterable<T>
|
||||
pruneNodesNotInTree(this.changedParents, tree);
|
||||
pruneNodesNotInTree(this.updatedNodes, tree);
|
||||
|
||||
const diffDeep = this.isFirstApply ? Infinity : 0;
|
||||
const diffDepth = this.isFirstApply ? Infinity : 0;
|
||||
this.isFirstApply = false;
|
||||
|
||||
for (let parent of this.changedParents) {
|
||||
@@ -107,7 +107,7 @@ export class NodeChangeList<T extends ITestTreeElement & { children: Iterable<T>
|
||||
tree.setChildren(
|
||||
parent,
|
||||
this.renderNodeList(renderNode, parent === null ? roots() : parent.children),
|
||||
{ diffIdentityProvider: testIdentityProvider, diffDeep },
|
||||
{ diffIdentityProvider: testIdentityProvider, diffDepth },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/
|
||||
import { isRunningState, NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
|
||||
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
|
||||
import { statesInOrder } from 'vs/workbench/contrib/testing/browser/testExplorerTree';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
|
||||
interface IStatusTestItem extends IncrementalTestCollectionItem {
|
||||
treeElements: Map<TestRunState, TestStateElement>;
|
||||
@@ -109,14 +109,7 @@ export class StateByLocationProjection extends AbstractIncrementalTestCollection
|
||||
|
||||
const firstDiff: TestsDiff = [];
|
||||
for (const [, collection] of listener.workspaceFolderCollections) {
|
||||
const queue = [collection.rootNodes];
|
||||
while (queue.length) {
|
||||
for (const id of queue.pop()!) {
|
||||
const node = collection.getNodeById(id)!;
|
||||
firstDiff.push([TestDiffOpType.Add, node]);
|
||||
queue.push(node.children);
|
||||
}
|
||||
}
|
||||
firstDiff.push(...collection.getReviverDiff());
|
||||
}
|
||||
|
||||
this.apply(firstDiff);
|
||||
|
||||
@@ -17,8 +17,8 @@ import { ListElementType } from 'vs/workbench/contrib/testing/browser/explorerPr
|
||||
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
|
||||
import { isRunningState, NodeChangeList, NodeRenderFn } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
|
||||
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
|
||||
class ListTestStateElement implements ITestTreeElement {
|
||||
public computedState = this.test.item.state.runState;
|
||||
@@ -108,14 +108,7 @@ export class StateByNameProjection extends AbstractIncrementalTestCollection<ISt
|
||||
|
||||
const firstDiff: TestsDiff = [];
|
||||
for (const [, collection] of listener.workspaceFolderCollections) {
|
||||
const queue = [collection.rootNodes];
|
||||
while (queue.length) {
|
||||
for (const id of queue.pop()!) {
|
||||
const node = collection.getNodeById(id)!;
|
||||
firstDiff.push([TestDiffOpType.Add, node]);
|
||||
queue.push(node.children);
|
||||
}
|
||||
}
|
||||
firstDiff.push(...collection.getReviverDiff());
|
||||
}
|
||||
|
||||
this.apply(firstDiff);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/
|
||||
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { testStatesToIconColors } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
|
||||
export const testingViewIcon = registerIcon('testing-view-icon', Codicon.beaker, localize('testingViewIcon', 'View icon of the testing view.'));
|
||||
export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
|
||||
export const testingRunIcon = registerIcon('testing-run-icon', Codicon.debugStart, localize('testingRunIcon', 'Icon of the "run test" action.'));
|
||||
export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAlt, localize('testingDebugIcon', 'Icon of the "debug test" action.'));
|
||||
export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.close, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.'));
|
||||
|
||||
@@ -5,38 +5,35 @@
|
||||
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyAndExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ShowViewletAction2 } from 'vs/workbench/browser/viewlet';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ITestingCollectionService } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { EMPTY_TEST_RESULT, InternalTestItem, RunTestsResult, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { IWorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ITestService, waitForAllRoots } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class FilterableAction extends Action {
|
||||
private visChangeEmitter = new Emitter<boolean>();
|
||||
const category = localize('testing.category', 'Test');
|
||||
|
||||
public onDidChangeVisibility = this.visChangeEmitter.event;
|
||||
public isVisible = true;
|
||||
|
||||
protected _setVisible(isVisible: boolean) {
|
||||
if (isVisible !== this.isVisible) {
|
||||
this.isVisible = isVisible;
|
||||
this.visChangeEmitter.fire(isVisible);
|
||||
}
|
||||
}
|
||||
const enum ActionOrder {
|
||||
Run = 10,
|
||||
Debug,
|
||||
Refresh,
|
||||
Collapse,
|
||||
}
|
||||
|
||||
export const filterVisibleActions = (actions: ReadonlyArray<Action>) =>
|
||||
actions.filter(a => !(a instanceof FilterableAction) || a.isVisible);
|
||||
|
||||
export class DebugAction extends Action {
|
||||
constructor(
|
||||
private readonly tests: Iterable<TestIdWithProvider>,
|
||||
@@ -44,7 +41,7 @@ export class DebugAction extends Action {
|
||||
@ITestService private readonly testService: ITestService
|
||||
) {
|
||||
super(
|
||||
'action.run',
|
||||
'testing.run',
|
||||
localize('debug test', 'Debug Test'),
|
||||
'test-action ' + ThemeIcon.asClassName(icons.testingDebugIcon),
|
||||
/* enabled= */ !isRunning
|
||||
@@ -69,7 +66,7 @@ export class RunAction extends Action {
|
||||
@ITestService private readonly testService: ITestService
|
||||
) {
|
||||
super(
|
||||
'action.run',
|
||||
'testing.run',
|
||||
localize('run test', 'Run Test'),
|
||||
'test-action ' + ThemeIcon.asClassName(icons.testingRunIcon),
|
||||
/* enabled= */ !isRunning,
|
||||
@@ -88,21 +85,14 @@ export class RunAction extends Action {
|
||||
}
|
||||
|
||||
abstract class RunOrDebugAction extends ViewAction<TestingExplorerView> {
|
||||
constructor(id: string, title: string, icon: ThemeIcon) {
|
||||
constructor(id: string, title: string, icon: ThemeIcon, private readonly debug: boolean) {
|
||||
super({
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
viewId: Testing.ExplorerViewId,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: 10,
|
||||
group: 'navigation',
|
||||
when: ContextKeyAndExpr.create([
|
||||
ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId),
|
||||
ContextKeyEqualsExpr.create(TestingContextKeys.isRunning.serialize(), false),
|
||||
])
|
||||
}
|
||||
f1: true,
|
||||
category,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,15 +100,15 @@ abstract class RunOrDebugAction extends ViewAction<TestingExplorerView> {
|
||||
* @override
|
||||
*/
|
||||
public runInView(accessor: ServicesAccessor, view: TestingExplorerView): Promise<RunTestsResult> {
|
||||
const tests = this.getActionableTests(accessor.get(ITestingCollectionService), view.viewModel);
|
||||
const tests = this.getActionableTests(accessor.get(IWorkspaceTestCollectionService), view.viewModel);
|
||||
if (!tests.length) {
|
||||
return Promise.resolve(EMPTY_TEST_RESULT);
|
||||
}
|
||||
|
||||
return accessor.get(ITestService).runTests({ tests, debug: this.debug() });
|
||||
return accessor.get(ITestService).runTests({ tests, debug: this.debug });
|
||||
}
|
||||
|
||||
private getActionableTests(testCollection: ITestingCollectionService, viewModel: TestingExplorerViewModel) {
|
||||
private getActionableTests(testCollection: IWorkspaceTestCollectionService, viewModel: TestingExplorerViewModel) {
|
||||
const selected = viewModel.getSelectedTests();
|
||||
const tests: TestIdWithProvider[] = [];
|
||||
if (!selected.length) {
|
||||
@@ -140,7 +130,6 @@ abstract class RunOrDebugAction extends ViewAction<TestingExplorerView> {
|
||||
return tests;
|
||||
}
|
||||
|
||||
protected abstract debug(): boolean;
|
||||
protected abstract filter(item: InternalTestItem): boolean;
|
||||
}
|
||||
|
||||
@@ -148,19 +137,13 @@ export class RunSelectedAction extends RunOrDebugAction {
|
||||
constructor(
|
||||
) {
|
||||
super(
|
||||
'action.runSelected',
|
||||
'testing.runSelected',
|
||||
localize('runSelectedTests', 'Run Selected Tests'),
|
||||
icons.testingRunIcon,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public debug() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -172,19 +155,13 @@ export class RunSelectedAction extends RunOrDebugAction {
|
||||
export class DebugSelectedAction extends RunOrDebugAction {
|
||||
constructor() {
|
||||
super(
|
||||
'action.debugSelected',
|
||||
'testing.debugSelected',
|
||||
localize('debugSelectedTests', 'Debug Selected Tests'),
|
||||
icons.testingDebugIcon,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public debug() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -193,6 +170,81 @@ export class DebugSelectedAction extends RunOrDebugAction {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RunOrDebugAllAllAction extends Action2 {
|
||||
constructor(id: string, title: string, icon: ThemeIcon, private readonly debug: boolean, private noTestsFoundError: string) {
|
||||
super({
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
f1: true,
|
||||
category,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: debug ? ActionOrder.Debug : ActionOrder.Run,
|
||||
group: 'navigation',
|
||||
when: ContextKeyAndExpr.create([
|
||||
ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId),
|
||||
ContextKeyEqualsExpr.create(TestingContextKeys.isRunning.serialize(), false),
|
||||
])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async run(accessor: ServicesAccessor) {
|
||||
const testService = accessor.get(ITestService);
|
||||
const workspace = accessor.get(IWorkspaceContextService);
|
||||
const notifications = accessor.get(INotificationService);
|
||||
|
||||
const tests: TestIdWithProvider[] = [];
|
||||
await Promise.all(workspace.getWorkspace().folders.map(async (folder) => {
|
||||
const handle = testService.subscribeToDiffs(ExtHostTestingResource.Workspace, folder.uri);
|
||||
try {
|
||||
await waitForAllRoots(handle.collection);
|
||||
|
||||
for (const root of handle.collection.rootIds) {
|
||||
const node = handle.collection.getNodeById(root);
|
||||
if (node && (this.debug ? node.item.debuggable : node.item.runnable)) {
|
||||
tests.push({ testId: node.id, providerId: node.providerId });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
handle.dispose();
|
||||
}
|
||||
}));
|
||||
|
||||
if (tests.length === 0) {
|
||||
notifications.info(this.noTestsFoundError);
|
||||
return;
|
||||
}
|
||||
|
||||
await testService.runTests({ tests, debug: this.debug });
|
||||
}
|
||||
}
|
||||
|
||||
export class RunAllAction extends RunOrDebugAllAllAction {
|
||||
constructor() {
|
||||
super(
|
||||
'testing.runAll',
|
||||
localize('runAllTests', 'Run All Tests'),
|
||||
icons.testingDebugIcon,
|
||||
false,
|
||||
localize('noTestProvider', 'No tests found in this workspace. You may need to install a test provider extension'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugAllAction extends RunOrDebugAllAllAction {
|
||||
constructor() {
|
||||
super(
|
||||
'testing.debugAll',
|
||||
localize('debugAllTests', 'Debug All Tests'),
|
||||
icons.testingDebugIcon,
|
||||
true,
|
||||
localize('noDebugTestProvider', 'No debuggable tests found in this workspace. You may need to install a test provider extension'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CancelTestRunAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -201,7 +253,7 @@ export class CancelTestRunAction extends Action2 {
|
||||
icon: icons.testingCancelIcon,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: 10,
|
||||
order: ActionOrder.Run,
|
||||
group: 'navigation',
|
||||
when: ContextKeyAndExpr.create([
|
||||
ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId),
|
||||
@@ -323,14 +375,43 @@ export class TestingGroupByStatusAction extends ViewAction<TestingExplorerView>
|
||||
}
|
||||
}
|
||||
|
||||
export class CollapseAllAction extends ViewAction<TestingExplorerView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'testing.collapseAll',
|
||||
viewId: Testing.ExplorerViewId,
|
||||
title: localize('testing.collapseAll', "Collapse All Tests"),
|
||||
f1: false,
|
||||
icon: Codicon.collapseAll,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: ActionOrder.Collapse,
|
||||
group: 'navigation',
|
||||
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public runInView(_accessor: ServicesAccessor, view: TestingExplorerView) {
|
||||
view.viewModel.collapseAll();
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshTestsAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'testing.refreshTests',
|
||||
title: localize('testing.refresh', "Refresh Tests"),
|
||||
category,
|
||||
f1: true,
|
||||
icon: Codicon.refresh,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: 0,
|
||||
order: ActionOrder.Refresh,
|
||||
group: 'navigation',
|
||||
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
|
||||
}
|
||||
});
|
||||
@@ -343,3 +424,19 @@ export class RefreshTestsAction extends Action2 {
|
||||
accessor.get(ITestService).resubscribeToAllTests();
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowTestView extends ShowViewletAction2 {
|
||||
constructor() {
|
||||
super({
|
||||
// matches old test action for back-compat
|
||||
id: 'workbench.view.extension.test',
|
||||
title: localize('showTestViewley', "Show Test"),
|
||||
category: CATEGORIES.View.value,
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
|
||||
protected viewletId() {
|
||||
return Testing.ViewletId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browse
|
||||
import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer';
|
||||
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestingCollectionService, TestingCollectionService } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { IWorkspaceTestCollectionService, WorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
@@ -35,15 +35,15 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
|
||||
import * as Action from './testExplorerActions';
|
||||
|
||||
registerSingleton(ITestService, TestService);
|
||||
registerSingleton(ITestingCollectionService, TestingCollectionService);
|
||||
registerSingleton(IWorkspaceTestCollectionService, WorkspaceTestCollectionService);
|
||||
|
||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: Testing.ViewletId,
|
||||
name: localize('testing', "Testing"),
|
||||
name: localize('test', "Test"),
|
||||
ctorDescriptor: new SyncDescriptor(TestingViewPaneContainer),
|
||||
icon: testingViewIcon,
|
||||
alwaysUseContainerInfo: true,
|
||||
order: 5,
|
||||
order: 6,
|
||||
hideIfEmpty: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
|
||||
@@ -87,6 +87,10 @@ registerAction2(Action.DebugSelectedAction);
|
||||
registerAction2(Action.TestingGroupByLocationAction);
|
||||
registerAction2(Action.TestingGroupByStatusAction);
|
||||
registerAction2(Action.RefreshTestsAction);
|
||||
registerAction2(Action.ShowTestView);
|
||||
registerAction2(Action.CollapseAllAction);
|
||||
registerAction2(Action.RunAllAction);
|
||||
registerAction2(Action.DebugAllAction);
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Eventually);
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
@@ -52,7 +51,7 @@ import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/browser
|
||||
import { TestingExplorerFilter, TestingFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
|
||||
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
|
||||
import { TestExplorerViewGrouping, TestExplorerViewMode } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { ITestingCollectionService, TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/testingCollectionService';
|
||||
import { IWorkspaceTestCollectionService, TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -68,7 +67,7 @@ export class TestingExplorerView extends ViewPane {
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@ITestingCollectionService private readonly testCollection: ITestingCollectionService,
|
||||
@IWorkspaceTestCollectionService private readonly testCollection: IWorkspaceTestCollectionService,
|
||||
@ITestService private readonly testService: ITestService,
|
||||
@IProgressService private readonly progress: IProgressService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -106,13 +105,6 @@ export class TestingExplorerView extends ViewPane {
|
||||
this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription, this.filterState);
|
||||
this._register(this.viewModel);
|
||||
|
||||
this.updateProgressIndicator();
|
||||
this._register(this.testService.onBusyStateChange(t => {
|
||||
if (t.resource === ExtHostTestingResource.Workspace && t.busy !== (!!this.finishDiscovery)) {
|
||||
this.updateProgressIndicator();
|
||||
}
|
||||
}));
|
||||
|
||||
this.getProgressIndicator().show(true);
|
||||
|
||||
this._register(this.onDidChangeBodyVisibility(visible => {
|
||||
@@ -135,8 +127,7 @@ export class TestingExplorerView extends ViewPane {
|
||||
this.filter.saveState();
|
||||
}
|
||||
|
||||
private updateProgressIndicator() {
|
||||
const busy = Iterable.some(this.testService.busyTestLocations, s => s.resource === ExtHostTestingResource.Workspace);
|
||||
private updateProgressIndicator(busy: number) {
|
||||
if (!busy && this.finishDiscovery) {
|
||||
this.finishDiscovery();
|
||||
this.finishDiscovery = undefined;
|
||||
@@ -156,7 +147,9 @@ export class TestingExplorerView extends ViewPane {
|
||||
}
|
||||
|
||||
private createSubscription() {
|
||||
return this.testCollection.subscribeToWorkspaceTests();
|
||||
const handle = this.testCollection.subscribeToWorkspaceTests();
|
||||
handle.subscription.onBusyProvidersChange(() => this.updateProgressIndicator(handle.subscription.busyProviders));
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +302,13 @@ export class TestingExplorerViewModel extends Disposable {
|
||||
this.tree.setSelection([item]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse all items in the tree.
|
||||
*/
|
||||
public async collapseAll() {
|
||||
this.tree.collapseAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an editor for the item. If there is a failure associated with the
|
||||
* test item, it will be shown.
|
||||
|
||||
@@ -7,7 +7,8 @@ import { localize } from 'vs/nls';
|
||||
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
|
||||
|
||||
export const enum Testing {
|
||||
ViewletId = 'workbench.view.testing',
|
||||
// marked as "extension" so that any existing test extensions are assigned to it.
|
||||
ViewletId = 'workbench.view.extension.test',
|
||||
ExplorerViewId = 'workbench.view.testing',
|
||||
OutputPeekContributionId = 'editor.contrib.testingOutputPeek',
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { RequiredTestItem, TestItem as ApiTestItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTestItem, TestDiffOpType, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -103,6 +103,14 @@ export class SingleUseTestCollection implements IDisposable {
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a new diff entry onto the collected diff list.
|
||||
*/
|
||||
public pushDiff(diff: TestsDiffOp) {
|
||||
this.diff.push(diff);
|
||||
this.debounceSendDiff.schedule();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const item of this.testItemToInternal.values()) {
|
||||
this.testIdToInternal.delete(item.id);
|
||||
|
||||
@@ -82,15 +82,23 @@ export interface InternalTestItem {
|
||||
}
|
||||
|
||||
export const enum TestDiffOpType {
|
||||
/** Adds a new test (with children) */
|
||||
Add,
|
||||
/** Shallow-updates an existing test */
|
||||
Update,
|
||||
/** Removes a test (and all its children) */
|
||||
Remove,
|
||||
/** Changes the number of providers running initial test discovery. */
|
||||
DeltaDiscoverComplete,
|
||||
/** Changes the number of providers who are yet to publish their collection roots. */
|
||||
DeltaRootsComplete,
|
||||
}
|
||||
|
||||
export type TestsDiffOp =
|
||||
| [op: TestDiffOpType.Add, item: InternalTestItem]
|
||||
| [op: TestDiffOpType.Update, item: InternalTestItem]
|
||||
| [op: TestDiffOpType.Remove, itemId: string];
|
||||
| [op: TestDiffOpType.Remove, itemId: string]
|
||||
| [op: TestDiffOpType.DeltaDiscoverComplete | TestDiffOpType.DeltaRootsComplete, amount: number];
|
||||
|
||||
/**
|
||||
* Utility function to get a unique string for a subscription to a resource,
|
||||
@@ -213,13 +221,38 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TestDiffOpType.DeltaDiscoverComplete:
|
||||
this.updateBusyProviders(op[1]);
|
||||
break;
|
||||
|
||||
case TestDiffOpType.DeltaRootsComplete:
|
||||
this.updatePendingRoots(op[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
changes.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the number of providers who are still discovering items.
|
||||
*/
|
||||
protected updateBusyProviders(delta: number) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the number of test root sources who are yet to report. When
|
||||
* the total pending test roots reaches 0, the roots for all providers
|
||||
* will exist in the collection.
|
||||
*/
|
||||
protected updatePendingRoots(delta: number) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a diff is applied to create a new change collector.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user