mirror of
https://github.com/Microsoft/vscode
synced 2024-10-06 03:17:00 +00:00
Merge branch 'main' into build-integrated-cli
This commit is contained in:
commit
081133f8e7
2
.vscode/notebooks/endgame.github-issues
vendored
2
.vscode/notebooks/endgame.github-issues
vendored
|
@ -7,7 +7,7 @@
|
|||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"August 2022\""
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"September 2022\""
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
|
|
2
.vscode/notebooks/my-endgame.github-issues
vendored
2
.vscode/notebooks/my-endgame.github-issues
vendored
|
@ -7,7 +7,7 @@
|
|||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\r\n\r\n$MILESTONE=milestone:\"August 2022\"\r\n\r\n$MINE=assignee:@me"
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"September 2022\"\n\n$MINE=assignee:@me"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
{ "open": "(", "close": ")" },
|
||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] }
|
||||
{ "open": "/*", "close": "*/", "notIn": ["string", "comment"] },
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
".conf",
|
||||
".properties",
|
||||
".cfg",
|
||||
".conf",
|
||||
".directory",
|
||||
".gitattributes",
|
||||
".gitconfig",
|
||||
|
|
|
@ -61,7 +61,8 @@
|
|||
"filenames": [
|
||||
"babel.config.json",
|
||||
".babelrc.json",
|
||||
".ember-cli"
|
||||
".ember-cli",
|
||||
"typedoc.json"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
|
|
|
@ -47,7 +47,11 @@ const inheritIconFromLanguage = {
|
|||
"postcss": 'css',
|
||||
"django-html": 'html',
|
||||
"blade": 'php'
|
||||
}
|
||||
};
|
||||
|
||||
const ignoreExtAssociation = {
|
||||
"properties": true
|
||||
};
|
||||
|
||||
const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo
|
||||
|
||||
|
@ -399,7 +403,7 @@ exports.update = function () {
|
|||
if (!nonBuiltInLanguages[lang] && !inheritIconFromLanguage[lang]) {
|
||||
for (let i2 = 0; i2 < exts.length; i2++) {
|
||||
// remove the extension association, unless it is different from the preferred
|
||||
if (ext2Def[exts[i2]] === preferredDef) {
|
||||
if (ext2Def[exts[i2]] === preferredDef || ignoreExtAssociation[exts[i2]]) {
|
||||
delete ext2Def[exts[i2]];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"git": {
|
||||
"name": "seti-ui",
|
||||
"repositoryUrl": "https://github.com/jesseweed/seti-ui",
|
||||
"commitHash": "2d10473b7575ec00c47eda751ea9caeec6b0b606"
|
||||
"commitHash": "fd20793e5a75b350eab8d489165fb9b420df3f62"
|
||||
}
|
||||
},
|
||||
"version": "0.1.0"
|
||||
|
|
Binary file not shown.
|
@ -1575,8 +1575,6 @@
|
|||
"cfm": "_coldfusion",
|
||||
"litcoffee": "_coffee",
|
||||
"config": "_config",
|
||||
"cfg": "_config",
|
||||
"conf": "_config",
|
||||
"cr": "_crystal",
|
||||
"ecr": "_crystal_embedded",
|
||||
"slang": "_crystal_embedded",
|
||||
|
@ -1731,7 +1729,6 @@
|
|||
"webp": "_image",
|
||||
"sublime-project": "_sublime",
|
||||
"sublime-workspace": "_sublime",
|
||||
"fish": "_shell",
|
||||
"mov": "_video",
|
||||
"ogv": "_video",
|
||||
"webm": "_video",
|
||||
|
@ -1774,7 +1771,6 @@
|
|||
"direnv": "_config",
|
||||
"env": "_config",
|
||||
"static": "_config",
|
||||
"editorconfig": "_config",
|
||||
"slugignore": "_config",
|
||||
"tmp": "_clock_1",
|
||||
"htaccess": "_config",
|
||||
|
@ -1835,14 +1831,19 @@
|
|||
"yarn.lock": "_yarn",
|
||||
"webpack.config.js": "_webpack",
|
||||
"webpack.config.cjs": "_webpack",
|
||||
"webpack.config.ts": "_webpack",
|
||||
"webpack.config.build.js": "_webpack",
|
||||
"webpack.config.build.cjs": "_webpack",
|
||||
"webpack.config.build.ts": "_webpack",
|
||||
"webpack.common.js": "_webpack",
|
||||
"webpack.common.cjs": "_webpack",
|
||||
"webpack.common.ts": "_webpack",
|
||||
"webpack.dev.js": "_webpack",
|
||||
"webpack.dev.cjs": "_webpack",
|
||||
"webpack.dev.ts": "_webpack",
|
||||
"webpack.prod.js": "_webpack",
|
||||
"webpack.prod.cjs": "_webpack",
|
||||
"webpack.prod.ts": "_webpack",
|
||||
"license": "_license",
|
||||
"licence": "_license",
|
||||
"license.txt": "_license",
|
||||
|
@ -1887,7 +1888,7 @@
|
|||
"groovy": "_grails",
|
||||
"handlebars": "_mustache",
|
||||
"html": "_html_3",
|
||||
"properties": "_java",
|
||||
"properties": "_config",
|
||||
"java": "_java",
|
||||
"javascriptreact": "_react",
|
||||
"javascript": "_javascript",
|
||||
|
@ -1976,8 +1977,6 @@
|
|||
"cfm": "_coldfusion_light",
|
||||
"litcoffee": "_coffee_light",
|
||||
"config": "_config_light",
|
||||
"cfg": "_config_light",
|
||||
"conf": "_config_light",
|
||||
"cr": "_crystal_light",
|
||||
"ecr": "_crystal_embedded_light",
|
||||
"slang": "_crystal_embedded_light",
|
||||
|
@ -2132,7 +2131,6 @@
|
|||
"webp": "_image_light",
|
||||
"sublime-project": "_sublime_light",
|
||||
"sublime-workspace": "_sublime_light",
|
||||
"fish": "_shell_light",
|
||||
"mov": "_video_light",
|
||||
"ogv": "_video_light",
|
||||
"webm": "_video_light",
|
||||
|
@ -2175,7 +2173,6 @@
|
|||
"direnv": "_config_light",
|
||||
"env": "_config_light",
|
||||
"static": "_config_light",
|
||||
"editorconfig": "_config_light",
|
||||
"slugignore": "_config_light",
|
||||
"tmp": "_clock_1_light",
|
||||
"htaccess": "_config_light",
|
||||
|
@ -2206,7 +2203,7 @@
|
|||
"groovy": "_grails_light",
|
||||
"handlebars": "_mustache_light",
|
||||
"html": "_html_3_light",
|
||||
"properties": "_java_light",
|
||||
"properties": "_config_light",
|
||||
"java": "_java_light",
|
||||
"javascriptreact": "_react_light",
|
||||
"javascript": "_javascript_light",
|
||||
|
@ -2314,14 +2311,19 @@
|
|||
"yarn.lock": "_yarn_light",
|
||||
"webpack.config.js": "_webpack_light",
|
||||
"webpack.config.cjs": "_webpack_light",
|
||||
"webpack.config.ts": "_webpack_light",
|
||||
"webpack.config.build.js": "_webpack_light",
|
||||
"webpack.config.build.cjs": "_webpack_light",
|
||||
"webpack.config.build.ts": "_webpack_light",
|
||||
"webpack.common.js": "_webpack_light",
|
||||
"webpack.common.cjs": "_webpack_light",
|
||||
"webpack.common.ts": "_webpack_light",
|
||||
"webpack.dev.js": "_webpack_light",
|
||||
"webpack.dev.cjs": "_webpack_light",
|
||||
"webpack.dev.ts": "_webpack_light",
|
||||
"webpack.prod.js": "_webpack_light",
|
||||
"webpack.prod.cjs": "_webpack_light",
|
||||
"webpack.prod.ts": "_webpack_light",
|
||||
"license": "_license_light",
|
||||
"licence": "_license_light",
|
||||
"license.txt": "_license_light",
|
||||
|
@ -2344,5 +2346,5 @@
|
|||
"npm-debug.log": "_npm_ignored_light"
|
||||
}
|
||||
},
|
||||
"version": "https://github.com/jesseweed/seti-ui/commit/2d10473b7575ec00c47eda751ea9caeec6b0b606"
|
||||
"version": "https://github.com/jesseweed/seti-ui/commit/fd20793e5a75b350eab8d489165fb9b420df3f62"
|
||||
}
|
|
@ -139,6 +139,10 @@
|
|||
{
|
||||
"fileMatch": "jsconfig.*.json",
|
||||
"url": "./schemas/jsconfig.schema.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": "typedoc.json",
|
||||
"url": "https://typedoc.org/schema.json"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
|
||||
import * as tas from 'vscode-tas-client';
|
||||
|
||||
export interface IExperimentationTelemetryReporter extends tas.IExperimentationTelemetry, vscode.Disposable {
|
||||
postEventObj(eventName: string, props: { [prop: string]: string }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This reporter *supports* experimentation telemetry,
|
||||
* but will only do so when passed to an {@link ExperimentationService}.
|
||||
*/
|
||||
|
||||
export class ExperimentationTelemetryReporter
|
||||
implements IExperimentationTelemetryReporter {
|
||||
private _sharedProperties: Record<string, string> = {};
|
||||
private _reporter: VsCodeTelemetryReporter;
|
||||
constructor(reporter: VsCodeTelemetryReporter) {
|
||||
this._reporter = reporter;
|
||||
}
|
||||
|
||||
setSharedProperty(name: string, value: string): void {
|
||||
this._sharedProperties[name] = value;
|
||||
}
|
||||
|
||||
postEvent(eventName: string, props: Map<string, string>): void {
|
||||
const propsObject = {
|
||||
...this._sharedProperties,
|
||||
...Object.fromEntries(props),
|
||||
};
|
||||
this._reporter.sendTelemetryEvent(eventName, propsObject);
|
||||
}
|
||||
|
||||
postEventObj(eventName: string, props: { [prop: string]: string }) {
|
||||
this._reporter.sendTelemetryEvent(eventName, {
|
||||
...this._sharedProperties,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._reporter.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,20 +4,21 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
|
||||
import * as tas from 'vscode-tas-client';
|
||||
|
||||
import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
|
||||
interface ExperimentTypes {
|
||||
// None for now.
|
||||
}
|
||||
|
||||
export class ExperimentationService implements vscode.Disposable {
|
||||
export class ExperimentationService {
|
||||
private _experimentationServicePromise: Promise<tas.IExperimentationService>;
|
||||
private _telemetryReporter: ExperimentTelemetryReporter;
|
||||
private _telemetryReporter: IExperimentationTelemetryReporter;
|
||||
|
||||
constructor(private readonly _extensionContext: vscode.ExtensionContext) {
|
||||
this._telemetryReporter = new ExperimentTelemetryReporter(_extensionContext);
|
||||
this._experimentationServicePromise = this.createExperimentationService();
|
||||
constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) {
|
||||
this._telemetryReporter = telemetryReporter;
|
||||
this._experimentationServicePromise = createExperimentationService(this._telemetryReporter, id, version, globalState);
|
||||
}
|
||||
|
||||
public async getTreatmentVariable<K extends keyof ExperimentTypes>(name: K, defaultValue: ExperimentTypes[K]): Promise<ExperimentTypes[K]> {
|
||||
|
@ -29,70 +30,33 @@ export class ExperimentationService implements vscode.Disposable {
|
|||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private async createExperimentationService(): Promise<tas.IExperimentationService> {
|
||||
let targetPopulation: tas.TargetPopulation;
|
||||
switch (vscode.env.uriScheme) {
|
||||
case 'vscode':
|
||||
targetPopulation = tas.TargetPopulation.Public;
|
||||
break;
|
||||
case 'vscode-insiders':
|
||||
targetPopulation = tas.TargetPopulation.Insiders;
|
||||
break;
|
||||
case 'vscode-exploration':
|
||||
targetPopulation = tas.TargetPopulation.Internal;
|
||||
break;
|
||||
case 'code-oss':
|
||||
targetPopulation = tas.TargetPopulation.Team;
|
||||
break;
|
||||
default:
|
||||
targetPopulation = tas.TargetPopulation.Public;
|
||||
break;
|
||||
}
|
||||
|
||||
const id = this._extensionContext.extension.id;
|
||||
const version = this._extensionContext.extension.packageJSON.version || '';
|
||||
const experimentationService = tas.getExperimentationService(id, version, targetPopulation, this._telemetryReporter, this._extensionContext.globalState);
|
||||
await experimentationService.initialFetch;
|
||||
return experimentationService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public dispose() {
|
||||
this._telemetryReporter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExperimentTelemetryReporter
|
||||
implements tas.IExperimentationTelemetry, vscode.Disposable {
|
||||
private _sharedProperties: Record<string, string> = {};
|
||||
private _reporter: VsCodeTelemetryReporter;
|
||||
constructor(ctxt: vscode.ExtensionContext) {
|
||||
const extension = ctxt.extension;
|
||||
const packageJSON = extension.packageJSON;
|
||||
this._reporter = new VsCodeTelemetryReporter(
|
||||
extension.id,
|
||||
packageJSON.version || '',
|
||||
packageJSON.aiKey || '');
|
||||
|
||||
export async function createExperimentationService(
|
||||
reporter: IExperimentationTelemetryReporter,
|
||||
id: string,
|
||||
version: string,
|
||||
globalState: vscode.Memento): Promise<tas.IExperimentationService> {
|
||||
let targetPopulation: tas.TargetPopulation;
|
||||
switch (vscode.env.uriScheme) {
|
||||
case 'vscode':
|
||||
targetPopulation = tas.TargetPopulation.Public;
|
||||
break;
|
||||
case 'vscode-insiders':
|
||||
targetPopulation = tas.TargetPopulation.Insiders;
|
||||
break;
|
||||
case 'vscode-exploration':
|
||||
targetPopulation = tas.TargetPopulation.Internal;
|
||||
break;
|
||||
case 'code-oss':
|
||||
targetPopulation = tas.TargetPopulation.Team;
|
||||
break;
|
||||
default:
|
||||
targetPopulation = tas.TargetPopulation.Public;
|
||||
break;
|
||||
}
|
||||
|
||||
setSharedProperty(name: string, value: string): void {
|
||||
this._sharedProperties[name] = value;
|
||||
}
|
||||
|
||||
postEvent(eventName: string, props: Map<string, string>): void {
|
||||
const propsObject = {
|
||||
...this._sharedProperties,
|
||||
...Object.fromEntries(props),
|
||||
};
|
||||
this._reporter.sendTelemetryEvent(eventName, propsObject);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._reporter.dispose();
|
||||
}
|
||||
const experimentationService = tas.getExperimentationService(id, version, targetPopulation, reporter, globalState);
|
||||
await experimentationService.initialFetch;
|
||||
return experimentationService;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { Api, getExtensionApi } from './api';
|
||||
import { CommandManager } from './commands/commandManager';
|
||||
import { registerBaseCommands } from './commands/index';
|
||||
|
@ -17,6 +18,8 @@ import API from './utils/api';
|
|||
import { TypeScriptServiceConfiguration } from './utils/configuration';
|
||||
import { BrowserServiceConfigurationProvider } from './utils/configuration.browser';
|
||||
import { PluginManager } from './utils/plugins';
|
||||
import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
import { getPackageInfo } from './utils/packageInfo';
|
||||
|
||||
class StaticVersionProvider implements ITypeScriptVersionProvider {
|
||||
|
||||
|
@ -57,6 +60,15 @@ export function activate(
|
|||
vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(),
|
||||
API.fromSimpleString('4.8.2')));
|
||||
|
||||
let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
const packageInfo = getPackageInfo(context);
|
||||
if (packageInfo) {
|
||||
const { name: id, version, aiKey } = packageInfo;
|
||||
const vscTelemetryReporter = new VsCodeTelemetryReporter(id, version, aiKey);
|
||||
experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
|
||||
context.subscriptions.push(experimentTelemetryReporter);
|
||||
}
|
||||
|
||||
const lazyClientHost = createLazyClientHost(context, false, {
|
||||
pluginManager,
|
||||
commandManager,
|
||||
|
@ -66,6 +78,7 @@ export function activate(
|
|||
processFactory: WorkerServerProcess,
|
||||
activeJsTsEditorTracker,
|
||||
serviceConfigurationProvider: new BrowserServiceConfigurationProvider(),
|
||||
experimentTelemetryReporter,
|
||||
}, item => {
|
||||
onCompletionAccepted.fire(item);
|
||||
});
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { Api, getExtensionApi } from './api';
|
||||
import { CommandManager } from './commands/commandManager';
|
||||
import { registerBaseCommands } from './commands/index';
|
||||
import { ExperimentationService } from './experimentationService';
|
||||
import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
|
||||
import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron';
|
||||
import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron';
|
||||
|
@ -20,6 +22,7 @@ import { ElectronServiceConfigurationProvider } from './utils/configuration.elec
|
|||
import { onCaseInsensitiveFileSystem } from './utils/fileSystem.electron';
|
||||
import { PluginManager } from './utils/plugins';
|
||||
import * as temp from './utils/temp.electron';
|
||||
import { getPackageInfo } from './utils/packageInfo';
|
||||
|
||||
export function activate(
|
||||
context: vscode.ExtensionContext
|
||||
|
@ -42,6 +45,19 @@ export function activate(
|
|||
const jsWalkthroughState = new JsWalkthroughState();
|
||||
context.subscriptions.push(jsWalkthroughState);
|
||||
|
||||
let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
const packageInfo = getPackageInfo(context);
|
||||
if (packageInfo) {
|
||||
const { name: id, version, aiKey } = packageInfo;
|
||||
const vscTelemetryReporter = new VsCodeTelemetryReporter(id, version, aiKey);
|
||||
experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
|
||||
context.subscriptions.push(experimentTelemetryReporter);
|
||||
|
||||
// Currently we have no experiments, but creating the service adds the appropriate
|
||||
// shared properties to the ExperimentationTelemetryReporter we just created.
|
||||
new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState);
|
||||
}
|
||||
|
||||
const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), {
|
||||
pluginManager,
|
||||
commandManager,
|
||||
|
@ -51,6 +67,7 @@ export function activate(
|
|||
processFactory: new ElectronServiceProcessFactory(),
|
||||
activeJsTsEditorTracker,
|
||||
serviceConfigurationProvider: new ElectronServiceConfigurationProvider(),
|
||||
experimentTelemetryReporter,
|
||||
}, item => {
|
||||
onCompletionAccepted.fire(item);
|
||||
});
|
||||
|
@ -58,9 +75,6 @@ export function activate(
|
|||
registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
|
||||
registerJsNodeWalkthrough(commandManager, jsWalkthroughState);
|
||||
|
||||
// Currently no variables in use.
|
||||
context.subscriptions.push(new ExperimentationService(context));
|
||||
|
||||
import('./task/taskProvider').then(module => {
|
||||
context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient)));
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { CommandManager } from './commands/commandManager';
|
||||
import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
|
||||
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
|
||||
import { TsServerProcessFactory } from './tsServer/server';
|
||||
|
@ -30,6 +31,7 @@ export function createLazyClientHost(
|
|||
processFactory: TsServerProcessFactory;
|
||||
activeJsTsEditorTracker: ActiveJsTsEditorTracker;
|
||||
serviceConfigurationProvider: ServiceConfigurationProvider;
|
||||
experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
},
|
||||
onCompletionAccepted: (item: vscode.CompletionItem) => void,
|
||||
): Lazy<TypeScriptServiceClientHost> {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { CommandManager } from './commands/commandManager';
|
||||
import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
import { DiagnosticKind } from './languageFeatures/diagnostics';
|
||||
import FileConfigurationManager from './languageFeatures/fileConfigurationManager';
|
||||
import LanguageProvider from './languageProvider';
|
||||
|
@ -72,6 +73,7 @@ export default class TypeScriptServiceClientHost extends Disposable {
|
|||
processFactory: TsServerProcessFactory;
|
||||
activeJsTsEditorTracker: ActiveJsTsEditorTracker;
|
||||
serviceConfigurationProvider: ServiceConfigurationProvider;
|
||||
experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
},
|
||||
onCompletionAccepted: (item: vscode.CompletionItem) => void,
|
||||
) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
|
||||
import { DiagnosticKind, DiagnosticsManager } from './languageFeatures/diagnostics';
|
||||
import * as Proto from './protocol';
|
||||
import { EventName } from './protocol.const';
|
||||
|
@ -137,6 +138,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
versionProvider: ITypeScriptVersionProvider;
|
||||
processFactory: TsServerProcessFactory;
|
||||
serviceConfigurationProvider: ServiceConfigurationProvider;
|
||||
experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
},
|
||||
allModeIds: readonly string[]
|
||||
) {
|
||||
|
@ -205,14 +207,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
}
|
||||
}, this, this._disposables);
|
||||
|
||||
this.telemetryReporter = this._register(new VSCodeTelemetryReporter(() => {
|
||||
this.telemetryReporter = new VSCodeTelemetryReporter(services.experimentTelemetryReporter, () => {
|
||||
if (this.serverState.type === ServerState.Type.Running) {
|
||||
if (this.serverState.tsserverVersion) {
|
||||
return this.serverState.tsserverVersion;
|
||||
}
|
||||
}
|
||||
return this.apiVersion.fullVersionString;
|
||||
}));
|
||||
});
|
||||
|
||||
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface PackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
export function getPackageInfo(context: vscode.ExtensionContext) {
|
||||
const packageJSON = context.extension.packageJSON;
|
||||
if (packageJSON && typeof packageJSON === 'object') {
|
||||
return {
|
||||
name: packageJSON.name ?? '',
|
||||
version: packageJSON.version ?? '',
|
||||
aiKey: packageJSON.aiKey ?? '',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -3,15 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { memoize } from './memoize';
|
||||
|
||||
interface PackageInfo {
|
||||
readonly name: string;
|
||||
readonly version: string;
|
||||
readonly aiKey: string;
|
||||
}
|
||||
import { IExperimentationTelemetryReporter } from '../experimentTelemetryReporter';
|
||||
|
||||
export interface TelemetryProperties {
|
||||
readonly [prop: string]: string | number | boolean | undefined;
|
||||
|
@ -19,14 +11,11 @@ export interface TelemetryProperties {
|
|||
|
||||
export interface TelemetryReporter {
|
||||
logTelemetry(eventName: string, properties?: TelemetryProperties): void;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class VSCodeTelemetryReporter implements TelemetryReporter {
|
||||
private _reporter: VsCodeTelemetryReporter | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly reporter: IExperimentationTelemetryReporter | undefined,
|
||||
private readonly clientVersionDelegate: () => string
|
||||
) { }
|
||||
|
||||
|
@ -43,38 +32,6 @@ export class VSCodeTelemetryReporter implements TelemetryReporter {
|
|||
*/
|
||||
properties['version'] = this.clientVersionDelegate();
|
||||
|
||||
reporter.sendTelemetryEvent(eventName, properties);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._reporter) {
|
||||
this._reporter.dispose();
|
||||
this._reporter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get reporter(): VsCodeTelemetryReporter | null {
|
||||
if (this.packageInfo?.aiKey) {
|
||||
this._reporter = new VsCodeTelemetryReporter(
|
||||
this.packageInfo.name,
|
||||
this.packageInfo.version,
|
||||
this.packageInfo.aiKey);
|
||||
return this._reporter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get packageInfo(): PackageInfo | null {
|
||||
const { packageJSON } = vscode.extensions.getExtension('vscode.typescript-language-features')!;
|
||||
if (packageJSON) {
|
||||
return {
|
||||
name: packageJSON.name,
|
||||
version: packageJSON.version,
|
||||
aiKey: packageJSON.aiKey
|
||||
};
|
||||
}
|
||||
return null;
|
||||
reporter.postEventObj(eventName, properties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,13 +86,13 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "5.1.0-beta.1",
|
||||
"xterm": "5.1.0-beta.10",
|
||||
"xterm-addon-canvas": "0.3.0-beta.1",
|
||||
"xterm-addon-search": "0.11.0-beta.1",
|
||||
"xterm-addon-serialize": "0.9.0-beta.1",
|
||||
"xterm-addon-serialize": "0.9.0-beta.2",
|
||||
"xterm-addon-unicode11": "0.5.0-beta.1",
|
||||
"xterm-addon-webgl": "0.14.0-beta.2",
|
||||
"xterm-headless": "5.1.0-beta.1",
|
||||
"xterm-addon-webgl": "0.14.0-beta.8",
|
||||
"xterm-headless": "5.1.0-beta.10",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "5.1.0-beta.1",
|
||||
"xterm": "5.1.0-beta.10",
|
||||
"xterm-addon-canvas": "0.3.0-beta.1",
|
||||
"xterm-addon-search": "0.11.0-beta.1",
|
||||
"xterm-addon-serialize": "0.9.0-beta.1",
|
||||
"xterm-addon-serialize": "0.9.0-beta.2",
|
||||
"xterm-addon-unicode11": "0.5.0-beta.1",
|
||||
"xterm-addon-webgl": "0.14.0-beta.2",
|
||||
"xterm-headless": "5.1.0-beta.1",
|
||||
"xterm-addon-webgl": "0.14.0-beta.8",
|
||||
"xterm-headless": "5.1.0-beta.10",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
"tas-client-umd": "0.1.6",
|
||||
"vscode-oniguruma": "1.6.1",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "5.1.0-beta.1",
|
||||
"xterm": "5.1.0-beta.10",
|
||||
"xterm-addon-canvas": "0.3.0-beta.1",
|
||||
"xterm-addon-search": "0.11.0-beta.1",
|
||||
"xterm-addon-unicode11": "0.5.0-beta.1",
|
||||
"xterm-addon-webgl": "0.14.0-beta.2"
|
||||
"xterm-addon-webgl": "0.14.0-beta.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,12 +83,12 @@ xterm-addon-unicode11@0.5.0-beta.1:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0-beta.1.tgz#8a9e9356018e082318abbe2be1f9599fcc6b46a2"
|
||||
integrity sha512-uAErX4gwhW6N524stLG6oZR3yBGgPnFmZ2Tv4vyYy7tcgDuHRoc22xYSCDgO1ohz1FLlOm8JGXRjXliwO9ic3A==
|
||||
|
||||
xterm-addon-webgl@0.14.0-beta.2:
|
||||
version "0.14.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.14.0-beta.2.tgz#832c31b52b78fb67a65bbd23c9fb850caceb43ae"
|
||||
integrity sha512-1ccbkJiUZ5ojnoAEJsbdV0jMZaYSnZ02wfV8yBU243u6TTgvCzZ7nq5BR9bT+5K/ESFWiekobfybxHwuYnylmQ==
|
||||
xterm-addon-webgl@0.14.0-beta.8:
|
||||
version "0.14.0-beta.8"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.14.0-beta.8.tgz#486ae22b2eb88a12ebded366c4019ee26409cbb8"
|
||||
integrity sha512-G0F70f6zGWtXuZxKiTn9BQswaVz85wcCuadnWRdPFDBlgdEfcboCvVZgQetklOIkluVpt8tYYK013/25iMRKTA==
|
||||
|
||||
xterm@5.1.0-beta.1:
|
||||
version "5.1.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0-beta.1.tgz#a6617c6887066d166632d1e69b6eb83a179d8b63"
|
||||
integrity sha512-ml7bqjO23bh4yu7qXKogXtCy4SbDTV21rfDXUvLPPaxrlQus6NoN1byy1eFH4ONWpv5ZHGeItRdQ/X00et9Pcw==
|
||||
xterm@5.1.0-beta.10:
|
||||
version "5.1.0-beta.10"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0-beta.10.tgz#2f55e4e6d63b45152c768857accc06c47920f898"
|
||||
integrity sha512-McztCKJJ2QvY28oXwK9ACMbbNTKiKQSbli+tZJIROkICmA7QFizwsQBQDoOtAw0po0dP1CInLJXwurqZmfCymQ==
|
||||
|
|
|
@ -798,30 +798,30 @@ xterm-addon-search@0.11.0-beta.1:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.11.0-beta.1.tgz#fe7178d70246cde73550447c5524672575467499"
|
||||
integrity sha512-fKj8KnnhH1nC4oZpKsgnhtgxkTctoa9kGLMpTJjsNzFu0VvXvLGIRezTPI75UEIQdEdaxcwB7/aKelQTO+72LA==
|
||||
|
||||
xterm-addon-serialize@0.9.0-beta.1:
|
||||
version "0.9.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.9.0-beta.1.tgz#44a8047ec85abe4db232acc58c53355dd314bf6d"
|
||||
integrity sha512-jVkpU5GC728ko0k190o+M1xubMkhRolKj18160rxlZhd0Sm/1yHUtFneC9pYSsLypynd3Te5LnZnHfEgVmka4g==
|
||||
xterm-addon-serialize@0.9.0-beta.2:
|
||||
version "0.9.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.9.0-beta.2.tgz#2f37ba57cabcdbf6dfe56bce8209de04dcfaa8ef"
|
||||
integrity sha512-oCRHXdlrlzcNmxRxHJlYq6FiJlgQDXft62DcE+WY7Y7R5rjPB8ahrSQHBwjc3ZIK0GRL8fRReuwxL1+Jy4Dt4Q==
|
||||
|
||||
xterm-addon-unicode11@0.5.0-beta.1:
|
||||
version "0.5.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0-beta.1.tgz#8a9e9356018e082318abbe2be1f9599fcc6b46a2"
|
||||
integrity sha512-uAErX4gwhW6N524stLG6oZR3yBGgPnFmZ2Tv4vyYy7tcgDuHRoc22xYSCDgO1ohz1FLlOm8JGXRjXliwO9ic3A==
|
||||
|
||||
xterm-addon-webgl@0.14.0-beta.2:
|
||||
version "0.14.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.14.0-beta.2.tgz#832c31b52b78fb67a65bbd23c9fb850caceb43ae"
|
||||
integrity sha512-1ccbkJiUZ5ojnoAEJsbdV0jMZaYSnZ02wfV8yBU243u6TTgvCzZ7nq5BR9bT+5K/ESFWiekobfybxHwuYnylmQ==
|
||||
xterm-addon-webgl@0.14.0-beta.8:
|
||||
version "0.14.0-beta.8"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.14.0-beta.8.tgz#486ae22b2eb88a12ebded366c4019ee26409cbb8"
|
||||
integrity sha512-G0F70f6zGWtXuZxKiTn9BQswaVz85wcCuadnWRdPFDBlgdEfcboCvVZgQetklOIkluVpt8tYYK013/25iMRKTA==
|
||||
|
||||
xterm-headless@5.1.0-beta.1:
|
||||
version "5.1.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.1.0-beta.1.tgz#badec2e97e47aa44267a4de2c1b42b4d23ad49a2"
|
||||
integrity sha512-V3G7l4pN6/HW//vKXryOCdDXVKdrQTQmtHEqkZ8waD68cJdeMdIoGYJuzavd5rHpxCqm/KR5O8ztI41jridong==
|
||||
xterm-headless@5.1.0-beta.10:
|
||||
version "5.1.0-beta.10"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.1.0-beta.10.tgz#2a747a1fa96a877c26aea3311b0a62ddae7e2578"
|
||||
integrity sha512-tRoXL1e87XOIuZ5yIjK43q3x9/MqZ+K24Na7UTl+AqmkXjb5svXfShMV3x8HiNAyxcrnL/MXNilfLoniQGacIA==
|
||||
|
||||
xterm@5.1.0-beta.1:
|
||||
version "5.1.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0-beta.1.tgz#a6617c6887066d166632d1e69b6eb83a179d8b63"
|
||||
integrity sha512-ml7bqjO23bh4yu7qXKogXtCy4SbDTV21rfDXUvLPPaxrlQus6NoN1byy1eFH4ONWpv5ZHGeItRdQ/X00et9Pcw==
|
||||
xterm@5.1.0-beta.10:
|
||||
version "5.1.0-beta.10"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0-beta.10.tgz#2f55e4e6d63b45152c768857accc06c47920f898"
|
||||
integrity sha512-McztCKJJ2QvY28oXwK9ACMbbNTKiKQSbli+tZJIROkICmA7QFizwsQBQDoOtAw0po0dP1CInLJXwurqZmfCymQ==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
|
@ -9,40 +9,21 @@ set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests
|
|||
|
||||
:: Figure out which Electron to use for running tests
|
||||
if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
|
||||
:: Run out of sources: no need to compile as code.bat takes care of it
|
||||
chcp 65001
|
||||
set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat
|
||||
set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1
|
||||
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'.
|
||||
echo Storing log files into '%VSCODELOGSDIR%'.
|
||||
echo Running integration tests out of sources.
|
||||
) else (
|
||||
:: Run from a built: need to compile all test extensions
|
||||
:: because we run extension tests from their source folders
|
||||
:: and the build bundles extensions into .build webpacked
|
||||
:: call yarn gulp compile-extension:vscode-api-tests^
|
||||
:: compile-extension:vscode-colorize-tests^
|
||||
:: compile-extension:markdown-language-features^
|
||||
:: compile-extension:typescript-language-features^
|
||||
:: compile-extension:emmet^
|
||||
:: compile-extension:css-language-features-server^
|
||||
:: compile-extension:html-language-features-server^
|
||||
:: compile-extension:json-language-features-server^
|
||||
:: compile-extension:git^
|
||||
:: compile-extension:ipynb^
|
||||
:: compile-extension:configuration-editing^
|
||||
:: compile-extension-media
|
||||
|
||||
:: Configuration for more verbose output
|
||||
set VSCODE_CLI=1
|
||||
set ELECTRON_ENABLE_LOGGING=1
|
||||
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'.
|
||||
echo Storing log files into '%VSCODELOGSDIR%'.
|
||||
echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build.
|
||||
)
|
||||
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'.
|
||||
echo Storing log files into '%VSCODELOGSDIR%'.
|
||||
|
||||
|
||||
:: Tests standalone (AMD)
|
||||
|
||||
|
|
|
@ -20,38 +20,19 @@ cd $ROOT
|
|||
# Figure out which Electron to use for running tests
|
||||
if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ]
|
||||
then
|
||||
# Run out of sources: no need to compile as code.sh takes care of it
|
||||
INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh"
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
echo "Running integration tests out of sources."
|
||||
else
|
||||
# Run from a built: need to compile all test extensions
|
||||
# because we run extension tests from their source folders
|
||||
# and the build bundles extensions into .build webpacked
|
||||
# yarn gulp compile-extension:vscode-api-tests \
|
||||
# compile-extension:vscode-colorize-tests \
|
||||
# compile-extension:markdown-language-features \
|
||||
# compile-extension:typescript-language-features \
|
||||
# compile-extension:emmet \
|
||||
# compile-extension:css-language-features-server \
|
||||
# compile-extension:html-language-features-server \
|
||||
# compile-extension:json-language-features-server \
|
||||
# compile-extension:git \
|
||||
# compile-extension:ipynb \
|
||||
# compile-extension:configuration-editing \
|
||||
# compile-extension-media
|
||||
|
||||
# Configuration for more verbose output
|
||||
export VSCODE_CLI=1
|
||||
export ELECTRON_ENABLE_LOGGING=1
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build."
|
||||
fi
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
|
||||
|
||||
# Tests standalone (AMD)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ IF "%VSCODEUSERDATADIR%" == "" (
|
|||
set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
|
||||
)
|
||||
|
||||
set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH%
|
||||
set REMOTE_EXT_PATH=%AUTHORITY%%EXT_PATH%
|
||||
set VSCODECRASHDIR=%~dp0\..\.build\crashes
|
||||
set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests-remote
|
||||
set TESTRESOLVER_DATA_FOLDER=%TMP%\testresolverdatafolder-%RANDOM%-%TIME:~6,5%
|
||||
|
@ -32,45 +32,80 @@ if "%VSCODE_REMOTE_SERVER_PATH%"=="" (
|
|||
echo Using '%VSCODE_REMOTE_SERVER_PATH%' as server path
|
||||
)
|
||||
|
||||
set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR%
|
||||
|
||||
:: Figure out which Electron to use for running tests
|
||||
if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'
|
||||
echo Storing log files into '%VSCODELOGSDIR%'
|
||||
chcp 65001
|
||||
set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat
|
||||
set API_TESTS_EXTRA_ARGS_BUILT=
|
||||
|
||||
:: Tests in the extension host running from sources
|
||||
call .\scripts\code.bat --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call .\scripts\code.bat --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
echo Running integration tests out of sources.
|
||||
) else (
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'
|
||||
echo Storing log files into '%VSCODELOGSDIR%'
|
||||
echo Using %INTEGRATION_TEST_ELECTRON_PATH% as Electron path
|
||||
|
||||
:: Run from a built: need to compile all test extensions
|
||||
:: because we run extension tests from their source folders
|
||||
:: and the build bundles extensions into .build webpacked
|
||||
:: call yarn gulp compile-extension:vscode-api-tests^
|
||||
:: compile-extension:microsoft-authentication^
|
||||
:: compile-extension:github-authentication^
|
||||
:: compile-extension:vscode-test-resolver
|
||||
|
||||
:: Configuration for more verbose output
|
||||
set VSCODE_CLI=1
|
||||
set ELECTRON_ENABLE_LOGGING=1
|
||||
set ELECTRON_ENABLE_STACK_DUMPING=1
|
||||
|
||||
:: Tests in the extension host running from built version (both client and server)
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
:: Extra arguments only when running against a built version
|
||||
set API_TESTS_EXTRA_ARGS_BUILT=--extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests
|
||||
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
echo Using %INTEGRATION_TEST_ELECTRON_PATH% as Electron path
|
||||
)
|
||||
|
||||
echo Storing crash reports into '%VSCODECRASHDIR%'
|
||||
echo Storing log files into '%VSCODELOGSDIR%'
|
||||
|
||||
|
||||
:: Tests in the extension host
|
||||
|
||||
set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR%
|
||||
|
||||
echo.
|
||||
echo ### API tests (folder)
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/vscode-api-tests --extensionTestsPath=%REMOTE_EXT_PATH%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### API tests (workspace)
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_EXT_PATH%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/vscode-api-tests --extensionTestsPath=%REMOTE_EXT_PATH%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### TypeScript tests
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/typescript-language-features/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/typescript-language-features --extensionTestsPath=%REMOTE_EXT_PATH%/typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Markdown tests
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/markdown-language-features/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/markdown-language-features --extensionTestsPath=%REMOTE_EXT_PATH%/markdown-language-features/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Emmet tests
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/emmet/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/emmet --extensionTestsPath=%REMOTE_EXT_PATH%/emmet/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Git tests
|
||||
for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i
|
||||
set GITWORKSPACE=%TEMPDIR%\git-%RANDOM%
|
||||
mkdir %GITWORKSPACE%
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%%GITWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/git --extensionTestsPath=%REMOTE_EXT_PATH%/git/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Ipynb tests
|
||||
set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM%
|
||||
mkdir %IPYNBWORKSPACE%
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%%IPYNBWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/ipynb --extensionTestsPath=%REMOTE_EXT_PATH%/ipynb/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Configuration editing tests
|
||||
set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM%
|
||||
mkdir %CFWORKSPACE%
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%/%CFWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/configuration-editing --extensionTestsPath=%REMOTE_EXT_PATH%/configuration-editing/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
:: Cleanup
|
||||
|
||||
IF "%3" == "" (
|
||||
rmdir /s /q %VSCODEUSERDATADIR%
|
||||
)
|
||||
|
|
|
@ -34,40 +34,19 @@ export REMOTE_VSCODE=$AUTHORITY$EXT_PATH
|
|||
# Figure out which Electron to use for running tests
|
||||
if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ]
|
||||
then
|
||||
# Run out of sources: no need to compile as code.sh takes care of it
|
||||
INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh"
|
||||
|
||||
# No extra arguments when running out of sources
|
||||
EXTRA_INTEGRATION_TEST_ARGUMENTS=""
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
echo "Running remote integration tests out of sources."
|
||||
else
|
||||
# Run from a built: need to compile all test extensions
|
||||
# because we run extension tests from their source folders
|
||||
# and the build bundles extensions into .build webpacked
|
||||
# yarn gulp compile-extension:vscode-api-tests \
|
||||
# compile-extension:vscode-test-resolver \
|
||||
# compile-extension:markdown-language-features \
|
||||
# compile-extension:typescript-language-features \
|
||||
# compile-extension:emmet \
|
||||
# compile-extension:git \
|
||||
# compile-extension:ipynb \
|
||||
# compile-extension:configuration-editing \
|
||||
# compile-extension:microsoft-authentication \
|
||||
# compile-extension:github-authentication \
|
||||
# compile-extension-media
|
||||
|
||||
# Configuration for more verbose output
|
||||
export VSCODE_CLI=1
|
||||
export ELECTRON_ENABLE_LOGGING=1
|
||||
|
||||
# Running from a build, we need to enable the vscode-test-resolver extension
|
||||
EXTRA_INTEGRATION_TEST_ARGUMENTS="--extensions-dir=$EXT_PATH --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests"
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
echo "Running remote integration tests with $INTEGRATION_TEST_ELECTRON_PATH as build."
|
||||
fi
|
||||
|
||||
|
@ -91,6 +70,12 @@ fi
|
|||
|
||||
API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --disable-keytar --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR"
|
||||
|
||||
echo "Storing crash reports into '$VSCODECRASHDIR'."
|
||||
echo "Storing log files into '$VSCODELOGSDIR'."
|
||||
|
||||
|
||||
# Tests in the extension host
|
||||
|
||||
echo
|
||||
echo "### API tests (folder)"
|
||||
echo
|
||||
|
|
|
@ -18,20 +18,11 @@ IF "%~1" == "" (
|
|||
set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH%
|
||||
|
||||
if "%VSCODE_REMOTE_SERVER_PATH%"=="" (
|
||||
chcp 65001
|
||||
|
||||
echo Using remote server out of sources for integration web tests
|
||||
) else (
|
||||
echo Using '%VSCODE_REMOTE_SERVER_PATH%' as server path for web integration tests
|
||||
|
||||
:: Run from a built: need to compile all test extensions
|
||||
:: because we run extension tests from their source folders
|
||||
:: and the build bundles extensions into .build webpacked
|
||||
:: call yarn gulp compile-extension:vscode-api-tests^
|
||||
:: compile-extension:markdown-language-features^
|
||||
:: compile-extension:typescript-language-features^
|
||||
:: compile-extension:emmet^
|
||||
:: compile-extension:configuration-editing^
|
||||
:: compile-extension:git^
|
||||
:: compile-extension-media
|
||||
)
|
||||
|
||||
if not exist ".\test\integration\browser\out\index.js" (
|
||||
|
@ -39,6 +30,9 @@ if not exist ".\test\integration\browser\out\index.js" (
|
|||
call yarn playwright-install
|
||||
)
|
||||
|
||||
|
||||
:: Tests in the extension host
|
||||
|
||||
echo.
|
||||
echo ### API tests (folder)
|
||||
call node .\test\integration\browser\out\index.js --workspacePath=.\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=.\extensions\vscode-api-tests --extensionTestsPath=.\extensions\vscode-api-tests\out\singlefolder-tests %*
|
||||
|
@ -72,9 +66,20 @@ mkdir %GITWORKSPACE%
|
|||
call node .\test\integration\browser\out\index.js --workspacePath=%GITWORKSPACE% --extensionDevelopmentPath=.\extensions\git --extensionTestsPath=.\extensions\git\out\test %*
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Ipynb tests
|
||||
set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM%
|
||||
mkdir %IPYNBWORKSPACE%
|
||||
call node .\test\integration\browser\out\index.js --workspacePath=%IPYNBWORKSPACE% --extensionDevelopmentPath=.\extensions\ipynb --extensionTestsPath=.\extensions\ipynb\out\test %*
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Configuration editing tests
|
||||
set CFWORKSPACE=%TEMPDIR%\git-%RANDOM%
|
||||
mkdir %CFWORKSPACE%
|
||||
call node .\test\integration\browser\out\index.js --workspacePath=%CFWORKSPACE% --extensionDevelopmentPath=.\extensions\configuration-editing --extensionTestsPath=.\extensions\configuration-editing\out\test %*
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
popd
|
||||
|
||||
endlocal
|
||||
|
|
|
@ -15,18 +15,6 @@ then
|
|||
echo "Using remote server out of sources for integration web tests"
|
||||
else
|
||||
echo "Using $VSCODE_REMOTE_SERVER_PATH as server path for web integration tests"
|
||||
|
||||
# Run from a built: need to compile all test extensions
|
||||
# because we run extension tests from their source folders
|
||||
# and the build bundles extensions into .build webpacked
|
||||
# yarn gulp compile-extension:vscode-api-tests \
|
||||
# compile-extension:markdown-language-features \
|
||||
# compile-extension:typescript-language-features \
|
||||
# compile-extension:emmet \
|
||||
# compile-extension:git \
|
||||
# compile-extension:ipynb \
|
||||
# compile-extension:configuration-editing \
|
||||
# compile-extension-media
|
||||
fi
|
||||
|
||||
if [ ! -e 'test/integration/browser/out/index.js' ];then
|
||||
|
@ -34,6 +22,7 @@ if [ ! -e 'test/integration/browser/out/index.js' ];then
|
|||
yarn playwright-install
|
||||
fi
|
||||
|
||||
|
||||
# Tests in the extension host
|
||||
|
||||
echo
|
||||
|
|
|
@ -406,8 +406,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||
delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this.scrollableElement.delegateScrollFromMouseWheelEvent(browserEvent);
|
||||
}
|
||||
|
||||
delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) {
|
||||
this.scrollableElement.delegateVerticalScrollbarPointerDown(browserEvent);
|
||||
}
|
||||
|
||||
updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void {
|
||||
|
|
|
@ -334,7 +334,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
|||
this._revealOnScroll = value;
|
||||
}
|
||||
|
||||
public triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
public delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this._onMouseWheel(new StandardWheelEvent(browserEvent));
|
||||
}
|
||||
|
||||
|
|
|
@ -700,6 +700,7 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
private readonly actionbar: ActionBar;
|
||||
private width = 0;
|
||||
private right = 0;
|
||||
private top = 0;
|
||||
|
||||
readonly _onDidDisable = new Emitter<void>();
|
||||
readonly onDidDisable = this._onDidDisable.event;
|
||||
|
@ -757,11 +758,18 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
|
||||
const startRight = this.right;
|
||||
const startX = e.pageX;
|
||||
const startTop = this.top;
|
||||
const startY = e.pageY;
|
||||
this.elements.grab.classList.add('grabbing');
|
||||
|
||||
const transition = this.elements.root.style.transition;
|
||||
this.elements.root.style.transition = 'unset';
|
||||
|
||||
const update = (e: MouseEvent) => {
|
||||
const deltaX = e.pageX - startX;
|
||||
this.right = startRight - deltaX;
|
||||
const deltaY = e.pageY - startY;
|
||||
this.top = startTop + deltaY;
|
||||
this.layout();
|
||||
};
|
||||
|
||||
|
@ -769,6 +777,7 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
disposables.add(onWindowMouseUp.event(e => {
|
||||
update(e);
|
||||
this.elements.grab.classList.remove('grabbing');
|
||||
this.elements.root.style.transition = transition;
|
||||
disposables.dispose();
|
||||
}));
|
||||
}));
|
||||
|
@ -779,6 +788,7 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
|
||||
this._register(onGrabKeyDown((e): any => {
|
||||
let right: number | undefined;
|
||||
let top: number | undefined;
|
||||
|
||||
if (e.keyCode === KeyCode.LeftArrow) {
|
||||
right = Number.POSITIVE_INFINITY;
|
||||
|
@ -788,12 +798,30 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
right = this.right === 0 ? Number.POSITIVE_INFINITY : 0;
|
||||
}
|
||||
|
||||
if (e.keyCode === KeyCode.UpArrow) {
|
||||
top = 0;
|
||||
} else if (e.keyCode === KeyCode.DownArrow) {
|
||||
top = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
if (right !== undefined) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.right = right;
|
||||
this.layout();
|
||||
}
|
||||
|
||||
if (top !== undefined) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.top = top;
|
||||
const transition = this.elements.root.style.transition;
|
||||
this.elements.root.style.transition = 'unset';
|
||||
this.layout();
|
||||
setTimeout(() => {
|
||||
this.elements.root.style.transition = transition;
|
||||
}, 0);
|
||||
}
|
||||
}));
|
||||
|
||||
this.onDidChangeValue = this.findInput.onDidChange;
|
||||
|
@ -824,6 +852,8 @@ class FindWidget<T, TFilterData> extends Disposable {
|
|||
this.width = width;
|
||||
this.right = clamp(this.right, 0, Math.max(0, width - 212));
|
||||
this.elements.root.style.right = `${this.right}px`;
|
||||
this.top = clamp(this.top, 0, 24);
|
||||
this.elements.root.style.top = `${this.top}px`;
|
||||
}
|
||||
|
||||
showMessage(message: IMessage): void {
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
}
|
||||
|
||||
.monaco-tree-type-filter.disabled {
|
||||
top: -40px;
|
||||
top: -40px !important;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-grab {
|
||||
|
|
|
@ -219,7 +219,7 @@ export class Separator implements IAction {
|
|||
|
||||
readonly label: string = '';
|
||||
readonly tooltip: string = '';
|
||||
readonly class: string = '';
|
||||
readonly class: string = 'separator';
|
||||
readonly enabled: boolean = false;
|
||||
readonly checked: boolean = false;
|
||||
async run() { }
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
const LANGUAGE_DEFAULT = 'en';
|
||||
export const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
let _isWindows = false;
|
||||
let _isMacintosh = false;
|
||||
|
|
|
@ -26,7 +26,11 @@ export interface ISandboxConfiguration {
|
|||
windowId: number;
|
||||
|
||||
/**
|
||||
* Absolute installation path.
|
||||
* Root path of the JavaScript sources.
|
||||
*
|
||||
* Note: This is NOT the installation root
|
||||
* directory itself but contained in it at
|
||||
* a level that is platform dependent.
|
||||
*/
|
||||
appRoot: string;
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class ProfileExtensionsCleaner extends Disposable {
|
|||
|
||||
private async uninstallExtensionsNotInProfiles(): Promise<void> {
|
||||
const installed = await this.extensionManagementService.getAllUserInstalled();
|
||||
const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
|
||||
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
|
||||
if (toUninstall.length) {
|
||||
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ class ProfileExtensionsCleaner extends Disposable {
|
|||
|
||||
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
|
||||
const installed = await this.extensionManagementService.getAllUserInstalled();
|
||||
const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version)));
|
||||
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version)));
|
||||
if (toUninstall.length) {
|
||||
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
|
||||
}
|
||||
|
|
|
@ -918,6 +918,14 @@ export class CodeApplication extends Disposable {
|
|||
// or if no window is open (macOS only)
|
||||
shouldOpenInNewWindow ||= isMacintosh && windowsMainService.getWindowCount() === 0;
|
||||
|
||||
// Pass along whether the application is being opened via a Continue On flow
|
||||
const continueOn = params.get('continueOn');
|
||||
if (continueOn !== null) {
|
||||
environmentService.continueOn = continueOn ?? undefined;
|
||||
params.delete('continueOn');
|
||||
uri = uri.with({ query: params.toString() });
|
||||
}
|
||||
|
||||
// Check for URIs to open in window
|
||||
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
|
||||
logService.trace('app#handleURL: windowOpenableFromProtocolLink = ', windowOpenableFromProtocolLink);
|
||||
|
|
|
@ -197,7 +197,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
|||
if (!model || !this._isEnabled || model.isTooLargeForTokenization() || !this.hiddenRangeModel) {
|
||||
return;
|
||||
}
|
||||
if (!state || state.lineCount !== model.getLineCount()) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ export interface NativeParsedArgs {
|
|||
'logsPath'?: string;
|
||||
'__enable-file-policy'?: boolean;
|
||||
editSessionId?: string;
|
||||
continueOn?: string;
|
||||
'locate-shell-integration-path'?: string;
|
||||
'profile'?: string;
|
||||
'profile-temp'?: boolean;
|
||||
|
|
|
@ -65,6 +65,7 @@ export interface IEnvironmentService {
|
|||
sync: 'on' | 'off' | undefined;
|
||||
|
||||
// --- continue edit session
|
||||
continueOn?: string;
|
||||
editSessionId?: string;
|
||||
editSessionsLogResource: URI;
|
||||
|
||||
|
@ -124,6 +125,13 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
|||
args: NativeParsedArgs;
|
||||
|
||||
// --- data paths
|
||||
/**
|
||||
* Root path of the JavaScript sources.
|
||||
*
|
||||
* Note: This is NOT the installation root
|
||||
* directory itself but contained in it at
|
||||
* a level that is platform dependent.
|
||||
*/
|
||||
appRoot: string;
|
||||
userHome: URI;
|
||||
appSettingsHome: URI;
|
||||
|
|
|
@ -247,6 +247,14 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
|||
|
||||
editSessionId: string | undefined = this.args['editSessionId'];
|
||||
|
||||
get continueOn(): string | undefined {
|
||||
return this.args['continueOn'];
|
||||
}
|
||||
|
||||
set continueOn(value: string | undefined) {
|
||||
this.args['continueOn'] = value;
|
||||
}
|
||||
|
||||
get args(): NativeParsedArgs { return this._args; }
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -129,6 +129,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
|||
'logsPath': { type: 'string' },
|
||||
'__enable-file-policy': { type: 'boolean' },
|
||||
'editSessionId': { type: 'string' },
|
||||
'continueOn': { type: 'string' },
|
||||
'locate-shell-integration-path': { type: 'string', args: ['bash', 'pwsh', 'zsh', 'fish'] },
|
||||
|
||||
'enable-coi': { type: 'boolean' },
|
||||
|
|
|
@ -50,16 +50,16 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>();
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
readonly onInstallExtension = this._onInstallExtension.event;
|
||||
get onInstallExtension() { return this._onInstallExtension.event; }
|
||||
|
||||
protected readonly _onDidInstallExtensions = this._register(new Emitter<InstallExtensionResult[]>());
|
||||
readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
|
||||
get onDidInstallExtensions() { return this._onDidInstallExtensions.event; }
|
||||
|
||||
protected readonly _onUninstallExtension = this._register(new Emitter<UninstallExtensionEvent>());
|
||||
readonly onUninstallExtension = this._onUninstallExtension.event;
|
||||
get onUninstallExtension() { return this._onUninstallExtension.event; }
|
||||
|
||||
protected _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
|
||||
readonly onDidUninstallExtension = this._onDidUninstallExtension.event;
|
||||
get onDidUninstallExtension() { return this._onDidUninstallExtension.event; }
|
||||
|
||||
private readonly participants: IExtensionManagementParticipant[] = [];
|
||||
|
||||
|
|
|
@ -163,6 +163,11 @@ export interface ICommonNativeHostService {
|
|||
toggleSharedProcessWindow(): Promise<void>;
|
||||
sendInputEvent(event: MouseInputEvent): Promise<void>;
|
||||
|
||||
// Perf Introspection
|
||||
startHeartbeat(session: string): Promise<boolean>;
|
||||
sendHeartbeat(session: string): Promise<boolean>;
|
||||
stopHeartbeat(session: string): Promise<boolean>;
|
||||
|
||||
// Connectivity
|
||||
resolveProxy(url: string): Promise<string | undefined>;
|
||||
findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride?: number): Promise<number>;
|
||||
|
|
|
@ -41,6 +41,8 @@ import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/worksp
|
|||
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl';
|
||||
import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfiling';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
|
||||
|
||||
|
@ -59,7 +61,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
|||
@ILogService private readonly logService: ILogService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService,
|
||||
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService
|
||||
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -777,6 +780,44 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
|||
|
||||
//#endregion
|
||||
|
||||
// #region Performance
|
||||
|
||||
private readonly _profilingSessions = new Map<number, WindowProfiler>();
|
||||
|
||||
async startHeartbeat(windowId: number | undefined, sessionId: string): Promise<boolean> {
|
||||
const win = this.windowById(windowId);
|
||||
if (!win || !win.win) {
|
||||
return false;
|
||||
}
|
||||
if (!this._profilingSessions.has(win.id)) {
|
||||
const session = new WindowProfiler(win.win, sessionId, this.logService, this.telemetryService);
|
||||
this._profilingSessions.set(win.id, session);
|
||||
session.start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendHeartbeat(windowId: number | undefined, _sessionId: string): Promise<boolean> {
|
||||
const win = this.windowById(windowId);
|
||||
if (!win || !this._profilingSessions.has(win.id)) {
|
||||
return false;
|
||||
}
|
||||
this._profilingSessions.get(win.id)!.receiveHeartbeat();
|
||||
return false;
|
||||
}
|
||||
|
||||
async stopHeartbeat(windowId: number | undefined, _sessionId: string): Promise<boolean> {
|
||||
const win = this.windowById(windowId);
|
||||
if (!win || !this._profilingSessions.has(win.id)) {
|
||||
return false;
|
||||
}
|
||||
this._profilingSessions.get(win.id)!.stop();
|
||||
this._profilingSessions.delete(win.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
|
||||
//#region Registry (windows)
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ export namespace Utils {
|
|||
export function rewriteAbsolutePaths(profile: IV8Profile, replace: string = 'noAbsolutePaths') {
|
||||
for (const node of profile.nodes) {
|
||||
if (node.callFrame && node.callFrame.url) {
|
||||
if (isAbsolute(node.callFrame.url) || /^\w[\w\d+.-]*:\/\/\//.test(node.callFrame.url)) {
|
||||
if (isAbsolute(node.callFrame.url) || /^\w[\w\d+.-]*:\/\/\/?/.test(node.callFrame.url)) {
|
||||
node.callFrame.url = join(replace, basename(node.callFrame.url));
|
||||
}
|
||||
}
|
||||
|
|
261
src/vs/platform/profiling/electron-main/windowProfiling.ts
Normal file
261
src/vs/platform/profiling/electron-main/windowProfiling.ts
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { Profile, ProfileNode, ProfileResult } from 'v8-inspect-profiler';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Promises } from 'vs/base/node/pfs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Utils } from 'vs/platform/profiling/common/profiling';
|
||||
|
||||
|
||||
type TelemetrySampleData = {
|
||||
sessionId: string;
|
||||
selfTime: number;
|
||||
totalTime: number;
|
||||
functionName: string;
|
||||
callstack: string;
|
||||
};
|
||||
|
||||
type TelemetrySampleDataClassification = {
|
||||
owner: 'jrieken';
|
||||
comment: 'A callstack that took a long time to execute';
|
||||
sessionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Session identifier that allows to correlate samples from one profile' };
|
||||
selfTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Self time of the sample' };
|
||||
totalTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Total time of the sample' };
|
||||
functionName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the sample' };
|
||||
callstack: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The stacktrace leading into the sample' };
|
||||
};
|
||||
|
||||
class Node {
|
||||
|
||||
// these are set later
|
||||
parent: Node | undefined;
|
||||
children: Node[] = [];
|
||||
selfTime: number = -1;
|
||||
totalTime: number = -1;
|
||||
|
||||
constructor(
|
||||
readonly node: ProfileNode,
|
||||
readonly callFrame: typeof node.callFrame,
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.callFrame.url}#${this.callFrame.functionName}@${this.callFrame.lineNumber}:${this.callFrame.columnNumber}`;
|
||||
}
|
||||
|
||||
static makeTotals(call: Node) {
|
||||
if (call.totalTime !== -1) {
|
||||
return call.totalTime;
|
||||
}
|
||||
let result = call.selfTime;
|
||||
for (const child of call.children) {
|
||||
result += Node.makeTotals(child);
|
||||
}
|
||||
call.totalTime = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class WindowProfiler {
|
||||
|
||||
private _profileAtOrAfter: number = 0;
|
||||
private _session = new DisposableStore();
|
||||
private _isProfiling?: Promise<any>;
|
||||
|
||||
private _isStarted: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _window: BrowserWindow,
|
||||
private readonly _sessionId: string,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
async stop() {
|
||||
|
||||
await this._isProfiling;
|
||||
|
||||
this._logService.warn('[perf] STOPPING to monitor renderer', this._sessionId);
|
||||
this._session.clear();
|
||||
|
||||
try {
|
||||
const inspector = this._window.webContents.debugger;
|
||||
await inspector.sendCommand('Profiler.disable');
|
||||
inspector.detach();
|
||||
} catch (error) {
|
||||
this._logService.error('[perf] FAILED to disable profiler', this._sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
receiveHeartbeat(): void {
|
||||
this._profileAtOrAfter = Date.now() + 1000;
|
||||
// this._logService.info('[perf] received heartbeat', this.id);
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this._isStarted) {
|
||||
this._logService.warn('[perf] already STARTED, ignoring request', this._sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const inspector = this._window.webContents.debugger;
|
||||
inspector.attach();
|
||||
await inspector.sendCommand('Profiler.enable');
|
||||
} catch (error) {
|
||||
this._logService.error('[perf] FAILED to enable profiler', this._sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.warn('[perf] started to EXPECT frequent heartbeat', this._sessionId);
|
||||
|
||||
this._session.clear();
|
||||
this._profileAtOrAfter = Date.now();
|
||||
|
||||
const handle = setInterval(() => {
|
||||
if (Date.now() >= this._profileAtOrAfter) {
|
||||
clearInterval(handle);
|
||||
this._captureRendererProfile();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
this._session.add(toDisposable(() => {
|
||||
this._isStarted = false;
|
||||
clearInterval(handle);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private async _captureRendererProfile(): Promise<void> {
|
||||
this._logService.warn('[perf] MISSED heartbeat, trying to profile renderer', this._sessionId);
|
||||
|
||||
const profiling = (async () => {
|
||||
const inspector = this._window.webContents.debugger;
|
||||
await inspector.sendCommand('Profiler.start');
|
||||
this._logService.warn('[perf] profiling STARTED', this._sessionId);
|
||||
await timeout(5000);
|
||||
const res: ProfileResult = await inspector.sendCommand('Profiler.stop');
|
||||
this._logService.warn('[perf] profiling DONE', this._sessionId);
|
||||
await this._store(res.profile);
|
||||
this._digest(res.profile);
|
||||
})();
|
||||
|
||||
this._isProfiling = profiling
|
||||
.catch(err => {
|
||||
this._logService.error('[perf] profiling the renderer FAILED', this._sessionId);
|
||||
this._logService.error(err);
|
||||
}).finally(() => {
|
||||
this._isProfiling = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private async _store(profile: Profile): Promise<void> {
|
||||
try {
|
||||
const path = join(tmpdir(), `renderer-profile-${Date.now()}.cpuprofile`);
|
||||
await Promises.writeFile(path, JSON.stringify(profile));
|
||||
this._logService.info('[perf] stored profile to DISK', this._sessionId, path);
|
||||
} catch (error) {
|
||||
this._logService.error('[perf] FAILED to write profile to disk', this._sessionId, error);
|
||||
}
|
||||
}
|
||||
|
||||
private _digest(profile: Profile): void {
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile
|
||||
|
||||
if (!profile.samples || !profile.timeDeltas) {
|
||||
this._logService.warn('[perf] INVALID profile: no samples or timeDeltas', this._sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// PII removal - no absolute paths
|
||||
Utils.rewriteAbsolutePaths(profile, 'piiRemoved');
|
||||
|
||||
// create nodes
|
||||
const all = new Map<number, Node>();
|
||||
for (const node of profile.nodes) {
|
||||
all.set(node.id, new Node(node, node.callFrame));
|
||||
}
|
||||
|
||||
// set children/parents
|
||||
for (const node of profile.nodes) {
|
||||
if (node.children) {
|
||||
const parent = all.get(node.id)!;
|
||||
for (const id of node.children) {
|
||||
const child = all.get(id)!;
|
||||
parent.children.push(child);
|
||||
child.parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SELF times
|
||||
const duration = (profile.endTime - profile.startTime);
|
||||
let lastNodeTime = duration - profile.timeDeltas[0];
|
||||
for (let i = 0; i < profile.samples.length - 1; i++) {
|
||||
const sample = profile.samples[i];
|
||||
const node = all.get(sample);
|
||||
if (node) {
|
||||
const duration = profile.timeDeltas[i + 1];
|
||||
node.selfTime += duration;
|
||||
lastNodeTime -= duration;
|
||||
}
|
||||
}
|
||||
const lastNode = all.get(profile.samples[profile.samples.length - 1]);
|
||||
if (lastNode) {
|
||||
lastNode.selfTime += lastNodeTime;
|
||||
}
|
||||
|
||||
// TOTAL times
|
||||
all.forEach(Node.makeTotals);
|
||||
|
||||
const sorted = Array.from(all.values()).sort((a, b) => b.selfTime - a.selfTime);
|
||||
|
||||
if (sorted[0].callFrame.functionName === '(idle)') {
|
||||
this._logService.warn('[perf] top stack is IDLE, ignoring this profile...', this._sessionId);
|
||||
this._telemetryService.publicLog2<TelemetrySampleData, TelemetrySampleDataClassification>('prof.sample', {
|
||||
sessionId: this._sessionId,
|
||||
selfTime: 0,
|
||||
totalTime: 0,
|
||||
functionName: '(idle)',
|
||||
callstack: ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
if (i > 4) {
|
||||
// report top 5
|
||||
break;
|
||||
}
|
||||
const node = sorted[i];
|
||||
const callstack: string[] = [];
|
||||
let candidate: Node | undefined = node;
|
||||
while (candidate) {
|
||||
callstack.push(candidate.toString());
|
||||
candidate = candidate.parent;
|
||||
}
|
||||
|
||||
const data: TelemetrySampleData = {
|
||||
sessionId: this._sessionId,
|
||||
selfTime: node.selfTime / 1000,
|
||||
totalTime: node.totalTime / 1000,
|
||||
functionName: node.callFrame.functionName,
|
||||
callstack: callstack.join('\n')
|
||||
};
|
||||
this._telemetryService.publicLog2<TelemetrySampleData, TelemetrySampleDataClassification>('prof.freeze.sample', data);
|
||||
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -153,6 +153,7 @@ export interface ICommandDetectionCapability {
|
|||
readonly hasInput: boolean | undefined;
|
||||
readonly onCommandStarted: Event<ITerminalCommand>;
|
||||
readonly onCommandFinished: Event<ITerminalCommand>;
|
||||
readonly onCommandExecuted: Event<void>;
|
||||
readonly onCommandInvalidated: Event<ITerminalCommand[]>;
|
||||
readonly onCurrentCommandInvalidated: Event<ICommandInvalidationRequest>;
|
||||
setCwd(value: string): void;
|
||||
|
|
|
@ -97,6 +97,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
|
|||
readonly onBeforeCommandFinished = this._onBeforeCommandFinished.event;
|
||||
private readonly _onCommandFinished = new Emitter<ITerminalCommand>();
|
||||
readonly onCommandFinished = this._onCommandFinished.event;
|
||||
private readonly _onCommandExecuted = new Emitter<void>();
|
||||
readonly onCommandExecuted = this._onCommandExecuted.event;
|
||||
private readonly _onCommandInvalidated = new Emitter<ITerminalCommand[]>();
|
||||
readonly onCommandInvalidated = this._onCommandInvalidated.event;
|
||||
private readonly _onCurrentCommandInvalidated = new Emitter<ICommandInvalidationRequest>();
|
||||
|
@ -422,6 +424,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
|
|||
if (y === commandExecutedLine) {
|
||||
this._currentCommand.command += this._terminal.buffer.active.getLine(commandExecutedLine)?.translateToString(true, undefined, this._currentCommand.commandExecutedX) || '';
|
||||
}
|
||||
this._onCommandExecuted.fire();
|
||||
}
|
||||
|
||||
private _handleCommandExecutedWindows(): void {
|
||||
|
@ -431,6 +434,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
|
|||
this._onCursorMoveListener = undefined;
|
||||
this._evaluateCommandMarkersWindows();
|
||||
this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX;
|
||||
this._onCommandExecuted.fire();
|
||||
this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,15 +9,19 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
|||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { toFormattedString } from 'vs/base/common/jsonFormatter';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
|
||||
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
|
||||
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
@ -27,6 +31,7 @@ import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult,
|
|||
import { IMergeResult as IExtensionMergeResult, merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { Change, IRemoteUserData, ISyncData, ISyncExtension, ISyncExtensionWithVersion, ISyncResourceHandle, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
|
||||
|
||||
type IExtensionResourceMergeResult = IAcceptResult & IExtensionMergeResult;
|
||||
|
||||
|
@ -98,15 +103,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService
|
||||
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
@IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
|
||||
this.profileLocation = profile.extensionsResource;
|
||||
|
@ -114,8 +120,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
Event.any<any>(
|
||||
Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)),
|
||||
this.extensionEnablementService.onDidChangeEnablement,
|
||||
this.extensionStorageService.onDidChangeExtensionStorageToSync)(() => this.triggerLocalChange()));
|
||||
Event.filter(userDataSyncProfilesStorageService.onDidChange, e => e.valueChanges.some(({ profile, changes }) => this.syncResource.profile.id === profile.id && changes.some(change => change.key === DISABLED_EXTENSIONS_STORAGE_PATH))),
|
||||
extensionStorageService.onDidChangeExtensionStorageToSync)(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
|
@ -124,7 +130,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const localExtensions = await this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
|
||||
if (remoteExtensions) {
|
||||
|
@ -162,7 +168,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
protected async hasRemoteChanged(lastSyncUserData: ILastSyncUserData): Promise<boolean> {
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const localExtensions = await this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const { remote } = merge(localExtensions, lastSyncExtensions, lastSyncExtensions, lastSyncUserData.skippedExtensions || [], ignoredExtensions);
|
||||
return remote !== null;
|
||||
|
@ -292,7 +298,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
|
||||
const localExtensions = (await this.getLocalExtensions(installedExtensions)).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
|
||||
return this.stringify(localExtensions, true);
|
||||
}
|
||||
|
||||
|
@ -340,7 +346,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const localExtensions = await this.getLocalExtensions(installedExtensions);
|
||||
if (localExtensions.some(e => e.installed || e.disabled)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -351,159 +357,178 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
}
|
||||
|
||||
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
|
||||
const removeFromSkipped: IExtensionIdentifier[] = [];
|
||||
const addToSkipped: ISyncExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
return this.withProfileScopedServices(async (extensionEnablementService, extensionStorageService) => {
|
||||
const removeFromSkipped: IExtensionIdentifier[] = [];
|
||||
const addToSkipped: ISyncExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation);
|
||||
|
||||
if (removed.length) {
|
||||
const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r)));
|
||||
await Promises.settled(extensionsToRemove.map(async extensionToRemove => {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true, profileLocation: this.profileLocation });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
|
||||
removeFromSkipped.push(extensionToRemove.identifier);
|
||||
}));
|
||||
}
|
||||
if (removed.length) {
|
||||
const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r)));
|
||||
await Promises.settled(extensionsToRemove.map(async extensionToRemove => {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true, profileLocation: this.profileLocation });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
|
||||
removeFromSkipped.push(extensionToRemove.identifier);
|
||||
}));
|
||||
}
|
||||
|
||||
if (added.length || updated.length) {
|
||||
await Promises.settled([...added, ...updated].map(async e => {
|
||||
const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier));
|
||||
if (added.length || updated.length) {
|
||||
await Promises.settled([...added, ...updated].map(async e => {
|
||||
const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier));
|
||||
|
||||
// Builtin Extension Sync: Enablement & State
|
||||
if (installedExtension && installedExtension.isBuiltin) {
|
||||
if (e.state && installedExtension.manifest.version === e.version) {
|
||||
this.updateExtensionState(e.state, installedExtension, installedExtension.manifest.version);
|
||||
}
|
||||
const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier));
|
||||
if (isDisabled !== !!e.disabled) {
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||
await this.extensionEnablementService.disableExtension(e.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id);
|
||||
} else {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id);
|
||||
await this.extensionEnablementService.enableExtension(e.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id);
|
||||
// Builtin Extension Sync: Enablement & State
|
||||
if (installedExtension && installedExtension.isBuiltin) {
|
||||
if (e.state && installedExtension.manifest.version === e.version) {
|
||||
this.updateExtensionState(e.state, installedExtension, installedExtension.manifest.version, extensionStorageService);
|
||||
}
|
||||
}
|
||||
removeFromSkipped.push(e.identifier);
|
||||
return;
|
||||
}
|
||||
|
||||
// User Extension Sync: Install/Update, Enablement & State
|
||||
const extension = (await this.extensionGalleryService.getExtensions([{ ...e.identifier, preRelease: e.preRelease }], CancellationToken.None))[0];
|
||||
|
||||
/* Update extension state only if
|
||||
* 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, installedExtension || extension, installedExtension?.manifest.version);
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
try {
|
||||
const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier));
|
||||
const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier));
|
||||
if (isDisabled !== !!e.disabled) {
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionEnablementService.disableExtension(extension.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version);
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||
await extensionEnablementService.disableExtension(e.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id);
|
||||
} else {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionEnablementService.enableExtension(extension.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id);
|
||||
await extensionEnablementService.enableExtension(e.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!installedExtension // Install if the extension does not exist
|
||||
|| installedExtension.preRelease !== e.preRelease // Install if the extension pre-release preference has changed
|
||||
) {
|
||||
if (await this.extensionManagementService.canInstall(extension)) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease, profileLocation: this.profileLocation } /* set isMachineScoped value to prevent install and sync dialog in web */);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
} else {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because it cannot be installed.`, extension.displayName || extension.identifier.id);
|
||||
addToSkipped.push(e);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
addToSkipped.push(e);
|
||||
if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatiblePreRelease, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id);
|
||||
} else {
|
||||
this.logService.error(error);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
|
||||
}
|
||||
removeFromSkipped.push(e.identifier);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addToSkipped.push(e);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the extension is not found.`, e.identifier.id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const newSkippedExtensions: ISyncExtension[] = [];
|
||||
for (const skippedExtension of skippedExtensions) {
|
||||
if (!removeFromSkipped.some(e => areSameExtensions(e, skippedExtension.identifier))) {
|
||||
newSkippedExtensions.push(skippedExtension);
|
||||
// User Extension Sync: Install/Update, Enablement & State
|
||||
const extension = (await this.extensionGalleryService.getExtensions([{ ...e.identifier, preRelease: e.preRelease }], CancellationToken.None))[0];
|
||||
|
||||
/* Update extension state only if
|
||||
* 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, installedExtension || extension, installedExtension?.manifest.version, extensionStorageService);
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
try {
|
||||
const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier));
|
||||
if (isDisabled !== !!e.disabled) {
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version);
|
||||
await extensionEnablementService.disableExtension(extension.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version);
|
||||
} else {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version);
|
||||
await extensionEnablementService.enableExtension(extension.identifier);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
|
||||
}
|
||||
}
|
||||
|
||||
if (!installedExtension // Install if the extension does not exist
|
||||
|| installedExtension.preRelease !== e.preRelease // Install if the extension pre-release preference has changed
|
||||
) {
|
||||
if (await this.extensionManagementService.canInstall(extension)) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease, profileLocation: this.profileLocation } /* set isMachineScoped value to prevent install and sync dialog in web */);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
} else {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because it cannot be installed.`, extension.displayName || extension.identifier.id);
|
||||
addToSkipped.push(e);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
addToSkipped.push(e);
|
||||
if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatiblePreRelease, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id);
|
||||
} else {
|
||||
this.logService.error(error);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addToSkipped.push(e);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the extension is not found.`, e.identifier.id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
for (const skippedExtension of addToSkipped) {
|
||||
if (!newSkippedExtensions.some(e => areSameExtensions(e.identifier, skippedExtension.identifier))) {
|
||||
newSkippedExtensions.push(skippedExtension);
|
||||
|
||||
const newSkippedExtensions: ISyncExtension[] = [];
|
||||
for (const skippedExtension of skippedExtensions) {
|
||||
if (!removeFromSkipped.some(e => areSameExtensions(e, skippedExtension.identifier))) {
|
||||
newSkippedExtensions.push(skippedExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newSkippedExtensions;
|
||||
for (const skippedExtension of addToSkipped) {
|
||||
if (!newSkippedExtensions.some(e => areSameExtensions(e.identifier, skippedExtension.identifier))) {
|
||||
newSkippedExtensions.push(skippedExtension);
|
||||
}
|
||||
}
|
||||
return newSkippedExtensions;
|
||||
});
|
||||
}
|
||||
|
||||
private updateExtensionState(state: IStringDictionary<any>, extension: ILocalExtension | IGalleryExtension, version: string | undefined): void {
|
||||
const extensionState = this.extensionStorageService.getExtensionState(extension, true) || {};
|
||||
const keys = version ? this.extensionStorageService.getKeysForSync({ id: extension.identifier.id, version }) : undefined;
|
||||
private updateExtensionState(state: IStringDictionary<any>, extension: ILocalExtension | IGalleryExtension, version: string | undefined, extensionStorageService: IExtensionStorageService): void {
|
||||
const extensionState = extensionStorageService.getExtensionState(extension, true) || {};
|
||||
const keys = version ? extensionStorageService.getKeysForSync({ id: extension.identifier.id, version }) : undefined;
|
||||
if (keys) {
|
||||
keys.forEach(key => { extensionState[key] = state[key]; });
|
||||
} else {
|
||||
Object.keys(state).forEach(key => extensionState[key] = state[key]);
|
||||
}
|
||||
this.extensionStorageService.setExtensionState(extension, extensionState, true);
|
||||
extensionStorageService.setExtensionState(extension, extensionState, true);
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
|
||||
private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtensionWithVersion[] {
|
||||
const disabledExtensions = this.extensionEnablementService.getDisabledExtensions();
|
||||
return installedExtensions
|
||||
.map(extension => {
|
||||
const { identifier, isBuiltin, manifest, preRelease } = extension;
|
||||
const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version };
|
||||
if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) {
|
||||
syncExntesion.disabled = true;
|
||||
}
|
||||
if (!isBuiltin) {
|
||||
syncExntesion.installed = true;
|
||||
}
|
||||
try {
|
||||
const keys = this.extensionStorageService.getKeysForSync({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageState = this.extensionStorageService.getExtensionState(extension, true) || {};
|
||||
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
|
||||
if (keys.includes(key)) {
|
||||
state[key] = extensionStorageState[key];
|
||||
}
|
||||
return state;
|
||||
}, {});
|
||||
private getLocalExtensions(installedExtensions: ILocalExtension[]): Promise<ISyncExtensionWithVersion[]> {
|
||||
return this.withProfileScopedServices(async (extensionEnablementService, extensionStorageService) => {
|
||||
const disabledExtensions = extensionEnablementService.getDisabledExtensions();
|
||||
return installedExtensions
|
||||
.map(extension => {
|
||||
const { identifier, isBuiltin, manifest, preRelease } = extension;
|
||||
const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version };
|
||||
if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) {
|
||||
syncExntesion.disabled = true;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
if (!isBuiltin) {
|
||||
syncExntesion.installed = true;
|
||||
}
|
||||
try {
|
||||
const keys = extensionStorageService.getKeysForSync({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageState = extensionStorageService.getExtensionState(extension, true) || {};
|
||||
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
|
||||
if (keys.includes(key)) {
|
||||
state[key] = extensionStorageState[key];
|
||||
}
|
||||
return state;
|
||||
}, {});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
}
|
||||
return syncExntesion;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async withProfileScopedServices<T>(fn: (extensionEnablementService: IGlobalExtensionEnablementService, extensionStorageService: IExtensionStorageService) => Promise<T>): Promise<T> {
|
||||
return this.userDataSyncProfilesStorageService.withProfileScopedStorageService(this.syncResource.profile,
|
||||
async storageService => {
|
||||
const disposables = new DisposableStore();
|
||||
const instantiationService = this.instantiationService.createChild(new ServiceCollection([IStorageService, storageService]));
|
||||
const extensionEnablementService = disposables.add(instantiationService.createInstance(GlobalExtensionEnablementService));
|
||||
const extensionStorageService = disposables.add(instantiationService.createInstance(ExtensionStorageService));
|
||||
try {
|
||||
return await fn(extensionEnablementService, extensionStorageService);
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
}
|
||||
return syncExntesion;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,11 @@ export interface IUserDataSyncProfilesStorageService {
|
|||
* @param target Storage target of the data
|
||||
*/
|
||||
updateStorageData(profile: IUserDataProfile, data: Map<string, string | undefined | null>, target: StorageTarget): Promise<void>;
|
||||
|
||||
/**
|
||||
* Calls a function with a storage service scoped to given profile.
|
||||
*/
|
||||
withProfileScopedStorageService<T>(profile: IUserDataProfile, fn: (storageService: IStorageService) => Promise<T>): Promise<T>;
|
||||
}
|
||||
|
||||
export abstract class AbstractUserDataSyncProfilesStorageService extends Disposable implements IUserDataSyncProfilesStorageService {
|
||||
|
@ -62,34 +67,25 @@ export abstract class AbstractUserDataSyncProfilesStorageService extends Disposa
|
|||
}
|
||||
|
||||
async readStorageData(profile: IUserDataProfile): Promise<Map<string, IStorageValue>> {
|
||||
// Use current storage service if the profile is same
|
||||
if (this.storageService.hasScope(profile)) {
|
||||
return this.getItems(this.storageService);
|
||||
}
|
||||
|
||||
const storageDatabase = await this.createStorageDatabase(profile);
|
||||
const storageService = new StorageService(storageDatabase);
|
||||
try {
|
||||
await storageService.initialize();
|
||||
return this.getItems(storageService);
|
||||
} finally {
|
||||
storageService.dispose();
|
||||
await this.closeAndDispose(storageDatabase);
|
||||
}
|
||||
return this.withProfileScopedStorageService(profile, async storageService => this.getItems(storageService));
|
||||
}
|
||||
|
||||
async updateStorageData(profile: IUserDataProfile, data: Map<string, string | undefined | null>, target: StorageTarget): Promise<void> {
|
||||
// Use current storage service if the profile is same
|
||||
return this.withProfileScopedStorageService(profile, async storageService => this.writeItems(storageService, data, target));
|
||||
}
|
||||
|
||||
async withProfileScopedStorageService<T>(profile: IUserDataProfile, fn: (storageService: IStorageService) => Promise<T>): Promise<T> {
|
||||
if (this.storageService.hasScope(profile)) {
|
||||
return this.writeItems(this.storageService, data, target);
|
||||
return fn(this.storageService);
|
||||
}
|
||||
|
||||
const storageDatabase = await this.createStorageDatabase(profile);
|
||||
const storageService = new StorageService(storageDatabase);
|
||||
try {
|
||||
await storageService.initialize();
|
||||
this.writeItems(storageService, data, target);
|
||||
const result = await fn(storageService);
|
||||
await storageService.flush();
|
||||
return result;
|
||||
} finally {
|
||||
storageService.dispose();
|
||||
await this.closeAndDispose(storageDatabase);
|
||||
|
|
|
@ -973,6 +973,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
|||
|
||||
configuration.isInitialStartup = false; // since this is a reload
|
||||
configuration.policiesData = this.policyService.serialize(); // set policies data again
|
||||
configuration.continueOn = this.environmentMainService.continueOn;
|
||||
configuration.profiles = {
|
||||
all: this.userDataProfilesService.profiles,
|
||||
profile: this.profile || this.userDataProfilesService.defaultProfile
|
||||
|
|
|
@ -1367,6 +1367,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
|||
accessibilitySupport: app.accessibilitySupportEnabled,
|
||||
colorScheme: this.themeMainService.getColorScheme(),
|
||||
policiesData: this.policyService.serialize(),
|
||||
continueOn: this.environmentMainService.continueOn,
|
||||
};
|
||||
|
||||
let window: ICodeWindow | undefined;
|
||||
|
|
|
@ -331,10 +331,10 @@ function isSerializedRecentFile(data: any): data is ISerializedRecentFile {
|
|||
export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined, logService: ILogService): IRecentlyOpened {
|
||||
const result: IRecentlyOpened = { workspaces: [], files: [] };
|
||||
if (data) {
|
||||
const restoreGracefully = function <T>(entries: T[], func: (entry: T, index: number) => void) {
|
||||
const restoreGracefully = function <T>(entries: T[], onEntry: (entry: T, index: number) => void) {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
try {
|
||||
func(entries[i], i);
|
||||
onEntry(entries[i], i);
|
||||
} catch (e) {
|
||||
logService.warn(`Error restoring recent entry ${JSON.stringify(entries[i])}: ${e.toString()}. Skip entry.`);
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine
|
|||
|
||||
const storedRecents = data as ISerializedRecentlyOpened;
|
||||
if (Array.isArray(storedRecents.entries)) {
|
||||
restoreGracefully(storedRecents.entries, (entry) => {
|
||||
restoreGracefully(storedRecents.entries, entry => {
|
||||
const label = entry.label;
|
||||
const remoteAuthority = entry.remoteAuthority;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import { IApplicationStorageMainService } from 'vs/platform/storage/electron-mai
|
|||
import { IRecent, IRecentFile, IRecentFolder, IRecentlyOpened, IRecentWorkspace, isRecentFile, isRecentFolder, isRecentWorkspace, restoreRecentlyOpened, toStoreData } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
|
||||
export const IWorkspacesHistoryMainService = createDecorator<IWorkspacesHistoryMainService>('workspacesHistoryMainService');
|
||||
|
||||
|
@ -73,28 +74,28 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
|||
//#region Workspaces History
|
||||
|
||||
async addRecentlyOpened(recentToAdd: IRecent[]): Promise<void> {
|
||||
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
|
||||
const files: IRecentFile[] = [];
|
||||
let workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
|
||||
let files: IRecentFile[] = [];
|
||||
|
||||
for (const recent of recentToAdd) {
|
||||
|
||||
// Workspace
|
||||
if (isRecentWorkspace(recent)) {
|
||||
if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && this.indexOfWorkspace(workspaces, recent.workspace) === -1) {
|
||||
if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && !this.containsWorkspace(workspaces, recent.workspace)) {
|
||||
workspaces.push(recent);
|
||||
}
|
||||
}
|
||||
|
||||
// Folder
|
||||
else if (isRecentFolder(recent)) {
|
||||
if (this.indexOfFolder(workspaces, recent.folderUri) === -1) {
|
||||
if (!this.containsFolder(workspaces, recent.folderUri)) {
|
||||
workspaces.push(recent);
|
||||
}
|
||||
}
|
||||
|
||||
// File
|
||||
else {
|
||||
const alreadyExistsInHistory = this.indexOfFile(files, recent.fileUri) >= 0;
|
||||
const alreadyExistsInHistory = this.containsFile(files, recent.fileUri);
|
||||
const shouldBeFiltered = recent.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(recent.fileUri)) >= 0;
|
||||
|
||||
if (!alreadyExistsInHistory && !shouldBeFiltered) {
|
||||
|
@ -108,7 +109,9 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
|||
}
|
||||
}
|
||||
|
||||
await this.addEntriesFromStorage(workspaces, files);
|
||||
const mergedEntries = await this.mergeEntriesFromStorage({ workspaces, files });
|
||||
workspaces = mergedEntries.workspaces;
|
||||
files = mergedEntries.files;
|
||||
|
||||
if (workspaces.length > WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES) {
|
||||
workspaces.length = WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES;
|
||||
|
@ -163,35 +166,53 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
|||
}
|
||||
|
||||
async getRecentlyOpened(): Promise<IRecentlyOpened> {
|
||||
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
|
||||
const files: IRecentFile[] = [];
|
||||
|
||||
await this.addEntriesFromStorage(workspaces, files);
|
||||
|
||||
return { workspaces, files };
|
||||
return this.mergeEntriesFromStorage();
|
||||
}
|
||||
|
||||
private async addEntriesFromStorage(workspaces: Array<IRecentFolder | IRecentWorkspace>, files: IRecentFile[]): Promise<void> {
|
||||
private async mergeEntriesFromStorage(existingEntries?: IRecentlyOpened): Promise<IRecentlyOpened> {
|
||||
|
||||
// Get from storage
|
||||
const recents = await this.getRecentlyOpenedFromStorage();
|
||||
for (const recent of recents.workspaces) {
|
||||
const index = isRecentFolder(recent) ? this.indexOfFolder(workspaces, recent.folderUri) : this.indexOfWorkspace(workspaces, recent.workspace);
|
||||
if (index >= 0) {
|
||||
workspaces[index].label = workspaces[index].label || recent.label;
|
||||
} else {
|
||||
workspaces.push(recent);
|
||||
// Build maps for more efficient lookup of existing entries that
|
||||
// are passed in by storing based on workspace/file identifier
|
||||
|
||||
const mapWorkspaceIdToWorkspace = new ResourceMap<IRecentFolder | IRecentWorkspace>(uri => extUriBiasedIgnorePathCase.getComparisonKey(uri));
|
||||
if (existingEntries?.workspaces) {
|
||||
for (const workspace of existingEntries.workspaces) {
|
||||
mapWorkspaceIdToWorkspace.set(this.location(workspace), workspace);
|
||||
}
|
||||
}
|
||||
|
||||
for (const recent of recents.files) {
|
||||
const index = this.indexOfFile(files, recent.fileUri);
|
||||
if (index >= 0) {
|
||||
files[index].label = files[index].label || recent.label;
|
||||
} else {
|
||||
files.push(recent);
|
||||
const mapFileIdToFile = new ResourceMap<IRecentFile>(uri => extUriBiasedIgnorePathCase.getComparisonKey(uri));
|
||||
if (existingEntries?.files) {
|
||||
for (const file of existingEntries.files) {
|
||||
mapFileIdToFile.set(this.location(file), file);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in entries from storage, preserving existing known entries
|
||||
|
||||
const recentFromStorage = await this.getRecentlyOpenedFromStorage();
|
||||
for (const recentWorkspaceFromStorage of recentFromStorage.workspaces) {
|
||||
const existingRecentWorkspace = mapWorkspaceIdToWorkspace.get(this.location(recentWorkspaceFromStorage));
|
||||
if (existingRecentWorkspace) {
|
||||
existingRecentWorkspace.label = existingRecentWorkspace.label ?? recentWorkspaceFromStorage.label;
|
||||
} else {
|
||||
mapWorkspaceIdToWorkspace.set(this.location(recentWorkspaceFromStorage), recentWorkspaceFromStorage);
|
||||
}
|
||||
}
|
||||
|
||||
for (const recentFileFromStorage of recentFromStorage.files) {
|
||||
const existingRecentFile = mapFileIdToFile.get(this.location(recentFileFromStorage));
|
||||
if (existingRecentFile) {
|
||||
existingRecentFile.label = existingRecentFile.label ?? recentFileFromStorage.label;
|
||||
} else {
|
||||
mapFileIdToFile.set(this.location(recentFileFromStorage), recentFileFromStorage);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
workspaces: [...mapWorkspaceIdToWorkspace.values()],
|
||||
files: [...mapFileIdToFile.values()]
|
||||
};
|
||||
}
|
||||
|
||||
private async getRecentlyOpenedFromStorage(): Promise<IRecentlyOpened> {
|
||||
|
@ -235,16 +256,16 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
|||
return recent.workspace.configPath;
|
||||
}
|
||||
|
||||
private indexOfWorkspace(recents: IRecent[], candidate: IWorkspaceIdentifier): number {
|
||||
return recents.findIndex(recent => isRecentWorkspace(recent) && recent.workspace.id === candidate.id);
|
||||
private containsWorkspace(recents: IRecent[], candidate: IWorkspaceIdentifier): boolean {
|
||||
return !!recents.find(recent => isRecentWorkspace(recent) && recent.workspace.id === candidate.id);
|
||||
}
|
||||
|
||||
private indexOfFolder(recents: IRecent[], candidate: URI): number {
|
||||
return recents.findIndex(recent => isRecentFolder(recent) && extUriBiasedIgnorePathCase.isEqual(recent.folderUri, candidate));
|
||||
private containsFolder(recents: IRecent[], candidate: URI): boolean {
|
||||
return !!recents.find(recent => isRecentFolder(recent) && extUriBiasedIgnorePathCase.isEqual(recent.folderUri, candidate));
|
||||
}
|
||||
|
||||
private indexOfFile(recents: IRecentFile[], candidate: URI): number {
|
||||
return recents.findIndex(recent => extUriBiasedIgnorePathCase.isEqual(recent.fileUri, candidate));
|
||||
private containsFile(recents: IRecentFile[], candidate: URI): boolean {
|
||||
return !!recents.find(recent => extUriBiasedIgnorePathCase.isEqual(recent.fileUri, candidate));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -170,9 +170,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
if (terminalInstance) {
|
||||
this._terminalService.setActiveInstance(terminalInstance);
|
||||
if (terminalInstance.target === TerminalLocation.Editor) {
|
||||
this._terminalEditorService.revealActiveEditor(preserveFocus);
|
||||
await this._terminalEditorService.revealActiveEditor(preserveFocus);
|
||||
} else {
|
||||
this._terminalGroupService.showPanel(!preserveFocus);
|
||||
await this._terminalGroupService.showPanel(!preserveFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,9 +162,9 @@ const viewDescriptor: IJSONSchema = {
|
|||
localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.")
|
||||
]
|
||||
},
|
||||
size: {
|
||||
initialSize: {
|
||||
type: 'number',
|
||||
description: localize('vscode.extension.contributs.view.size', "The size of the view. Using a number will behave like the css 'flex' property, and the size will set the initial size when the view is first shown. In the side bar, this is the height of the view."),
|
||||
description: localize('vscode.extension.contributs.view.size', "The initial size of the view. The size will behave like the css 'flex' property, and will set the initial size when the view is first shown. In the side bar, this is the height of the view. This value is only respected when the same extension owns both the view and the view container."),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -439,7 +439,7 @@ const newCommands: ApiCommand[] = [
|
|||
),
|
||||
// --- continue edit session
|
||||
new ApiCommand(
|
||||
'vscode.experimental.editSession.continue', '_workbench.experimental.editSessions.actions.continueEditSession', 'Continue the current edit session in a different workspace',
|
||||
'vscode.experimental.editSession.continue', '_workbench.editSessions.actions.continueEditSession', 'Continue the current edit session in a different workspace',
|
||||
[ApiCommandArgument.Uri.with('workspaceUri', 'The target workspace to continue the current edit session in')],
|
||||
ApiCommandResult.Void
|
||||
)
|
||||
|
|
|
@ -3,32 +3,38 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Language } from 'vs/base/common/platform';
|
||||
import { LANGUAGE_DEFAULT } from 'vs/base/common/platform';
|
||||
import { format } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostLocalizationShape, IStringDetails, MainContext, MainThreadLocalizationShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
|
||||
export class ExtHostLocalizationService implements ExtHostLocalizationShape {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _proxy: MainThreadLocalizationShape;
|
||||
private readonly currentLanguage: string;
|
||||
private readonly isDefaultLanguage: boolean;
|
||||
|
||||
private readonly bundleCache: Map<string, { contents: { [key: string]: string }; uri: URI }> = new Map();
|
||||
|
||||
constructor(
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@IExtHostRpcService rpc: IExtHostRpcService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
this._proxy = rpc.getProxy(MainContext.MainThreadLocalization);
|
||||
this.currentLanguage = initData.environment.appLanguage;
|
||||
this.isDefaultLanguage = this.currentLanguage === LANGUAGE_DEFAULT;
|
||||
}
|
||||
|
||||
getMessage(extensionId: string, details: IStringDetails): string {
|
||||
const { message, args, comment } = details;
|
||||
if (Language.isDefault()) {
|
||||
if (this.isDefaultLanguage) {
|
||||
return format(message, args);
|
||||
}
|
||||
|
||||
|
@ -52,7 +58,7 @@ export class ExtHostLocalizationService implements ExtHostLocalizationShape {
|
|||
}
|
||||
|
||||
async initializeLocalizedMessages(extension: IExtensionDescription): Promise<void> {
|
||||
if (Language.isDefault()
|
||||
if (this.isDefaultLanguage
|
||||
// TODO: support builtin extensions
|
||||
|| !extension.l10n
|
||||
) {
|
||||
|
@ -69,7 +75,7 @@ export class ExtHostLocalizationService implements ExtHostLocalizationShape {
|
|||
this.logService.error(`No bundle location found for extension ${extension.identifier.value}`);
|
||||
return;
|
||||
}
|
||||
const bundleUri = URI.joinPath(bundleLocation, `bundle.l10n.${Language.value()}.json`);
|
||||
const bundleUri = URI.joinPath(bundleLocation, `bundle.l10n.${this.currentLanguage}.json`);
|
||||
|
||||
try {
|
||||
const response = await this._proxy.$fetchBundleContents(bundleUri);
|
||||
|
|
|
@ -46,7 +46,7 @@ import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/bro
|
|||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
|
||||
import { IUserDataProfileService, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
|
||||
interface IPlaceholderViewContainer {
|
||||
readonly id: string;
|
||||
|
@ -621,25 +621,16 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart
|
|||
}
|
||||
|
||||
private createProfilesActivity(): IProfileActivity {
|
||||
const icon = this.userDataProfileService.currentProfile.shortName ? ThemeIcon.fromString(this.userDataProfileService.currentProfile.shortName) : undefined;
|
||||
const shortName = this.userDataProfileService.getShortName(this.userDataProfileService.currentProfile);
|
||||
const icon = ThemeIcon.fromString(shortName);
|
||||
return {
|
||||
id: 'workbench.actions.profiles',
|
||||
name: icon ? this.userDataProfileService.currentProfile.name : this.getProfileEntryDisplayName(this.userDataProfileService.currentProfile),
|
||||
name: icon ? this.userDataProfileService.currentProfile.name : shortName,
|
||||
cssClass: icon ? `${ThemeIcon.asClassName(icon)} profile-activity-item` : 'profile-activity-item',
|
||||
icon: !!icon
|
||||
};
|
||||
}
|
||||
|
||||
private getProfileEntryDisplayName(profile: IUserDataProfile): string {
|
||||
if (profile.shortName) {
|
||||
return profile.shortName;
|
||||
}
|
||||
if (profile.isTransient) {
|
||||
return `T${this.userDataProfileService.currentProfile.name.charAt(this.userDataProfileService.currentProfile.name.length - 1)}`;
|
||||
}
|
||||
return this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction } {
|
||||
let compositeActions = this.compositeActions.get(compositeId);
|
||||
if (!compositeActions) {
|
||||
|
|
|
@ -72,6 +72,7 @@ export class CommandCenterControl {
|
|||
// label: just workspace name and optional decorations
|
||||
const label = this._getLabel();
|
||||
const labelElement = document.createElement('span');
|
||||
labelElement.classList.add('search-label');
|
||||
labelElement.innerText = label;
|
||||
reset(left, searchIcon, labelElement);
|
||||
|
||||
|
|
|
@ -100,21 +100,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen {
|
||||
display: flex;
|
||||
color: var(--vscode-commandCenter-foreground);
|
||||
background-color: var(--vscode-commandCenter-background);
|
||||
border: 1px solid var(--vscode-commandCenter-border);
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen:HOVER {
|
||||
color: var(--vscode-commandCenter-activeForeground);
|
||||
background-color: var(--vscode-commandCenter-activeBackground);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.command-center {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
@ -127,16 +112,19 @@
|
|||
border-bottom-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
|
||||
|
||||
width: 38vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.command-center .left {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
|
||||
/* width */
|
||||
width: 38vw;
|
||||
max-width: 600px;
|
||||
min-width: 32px;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.command-center .left .search-icon {
|
||||
|
@ -152,7 +140,7 @@
|
|||
|
||||
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.command-center .right {
|
||||
margin-left: auto;
|
||||
padding: 2px 3px 0 0;
|
||||
padding: 2px 2px 0 0;
|
||||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
@ -15,7 +15,7 @@ import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm
|
|||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, relativePath } from 'vs/base/common/resources';
|
||||
import { basename, joinPath, relativePath } from 'vs/base/common/resources';
|
||||
import { encodeBase64 } from 'vs/base/common/buffer';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
|
@ -23,8 +23,8 @@ import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/
|
|||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { getFileNamesMessage, IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
@ -53,24 +53,27 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe
|
|||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IOutputService } from 'vs/workbench/services/output/common/output';
|
||||
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
||||
import { sha1Hex } from 'vs/base/browser/hash';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
|
||||
registerSingleton(IEditSessionsLogService, EditSessionsLogService, false);
|
||||
registerSingleton(IEditSessionsStorageService, EditSessionsWorkbenchService, false);
|
||||
|
||||
const continueWorkingOnCommand: IAction2Options = {
|
||||
id: '_workbench.experimental.editSessions.actions.continueEditSession',
|
||||
id: '_workbench.editSessions.actions.continueEditSession',
|
||||
title: { value: localize('continue working on', "Continue Working On..."), original: 'Continue Working On...' },
|
||||
precondition: WorkspaceFolderCountContext.notEqualsTo('0'),
|
||||
f1: true
|
||||
};
|
||||
const openLocalFolderCommand: IAction2Options = {
|
||||
id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder',
|
||||
id: '_workbench.editSessions.actions.continueEditSession.openLocalFolder',
|
||||
title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' },
|
||||
category: EDIT_SESSION_SYNC_CATEGORY,
|
||||
precondition: IsWebContext
|
||||
};
|
||||
const showOutputChannelCommand: IAction2Options = {
|
||||
id: 'workbench.experimental.editSessions.actions.showOutputChannel',
|
||||
id: 'workbench.editSessions.actions.showOutputChannel',
|
||||
title: { value: localize('show log', 'Show Log'), original: 'Show Log' },
|
||||
category: EDIT_SESSION_SYNC_CATEGORY
|
||||
};
|
||||
|
@ -80,16 +83,17 @@ const resumingProgressOptions = {
|
|||
title: `[${localize('resuming edit session window', 'Resuming edit session...')}](command:${showOutputChannelCommand.id})`
|
||||
};
|
||||
const queryParamName = 'editSessionId';
|
||||
const experimentalSettingName = 'workbench.experimental.editSessions.enabled';
|
||||
|
||||
const useEditSessionsWithContinueOn = 'workbench.experimental.editSessions.continueOn';
|
||||
const useEditSessionsWithContinueOn = 'workbench.editSessions.continueOn';
|
||||
export class EditSessionsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private registered = false;
|
||||
private continueEditSessionOptions: ContinueEditSessionItem[] = [];
|
||||
|
||||
private readonly shouldShowViewsContext: IContextKey<boolean>;
|
||||
|
||||
private static APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY = 'applicationLaunchedViaContinueOn';
|
||||
private accountsMenuBadgeDisposable = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
@IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
|
@ -110,18 +114,14 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
@ICommandService private commandService: ICommandService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.autoResumeEditSession();
|
||||
|
||||
this.configurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration(experimentalSettingName)) {
|
||||
this.registerActions();
|
||||
}
|
||||
});
|
||||
|
||||
this.registerActions();
|
||||
this.registerViews();
|
||||
this.registerContributedEditSessionOptions();
|
||||
|
@ -130,6 +130,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
|
||||
this._register(this.fileService.registerProvider(EditSessionsFileSystemProvider.SCHEMA, new EditSessionsFileSystemProvider(this.editSessionsStorageService)));
|
||||
this.lifecycleService.onWillShutdown((e) => e.join(this.autoStoreEditSession(), { id: 'autoStoreEditSession', label: localize('autoStoreEditSession', 'Storing current edit session...') }));
|
||||
this._register(this.editSessionsStorageService.onDidSignIn(() => this.updateAccountsMenuBadge()));
|
||||
}
|
||||
|
||||
private autoResumeEditSession() {
|
||||
|
@ -143,22 +144,58 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
this.telemetryService.publicLog2<ResumeEvent, ResumeClassification>('editSessions.continue.resume');
|
||||
|
||||
if (this.environmentService.editSessionId !== undefined) {
|
||||
this.logService.info(`Resuming edit session, reason: found editSessionId ${this.environmentService.editSessionId} in environment service...`);
|
||||
await this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
|
||||
} else if (
|
||||
this.configurationService.getValue('workbench.experimental.editSessions.enabled') === true &&
|
||||
this.configurationService.getValue('workbench.experimental.editSessions.autoResume') === 'onReload' &&
|
||||
this.configurationService.getValue('workbench.editSessions.autoResume') === 'onReload' &&
|
||||
this.editSessionsStorageService.isSignedIn
|
||||
) {
|
||||
this.logService.info('Resuming edit session, reason: edit sessions enabled...');
|
||||
// Attempt to resume edit session based on edit workspace identifier
|
||||
// Note: at this point if the user is not signed into edit sessions,
|
||||
// we don't want them to be prompted to sign in and should just return early
|
||||
await this.resumeEditSession(undefined, true);
|
||||
} else {
|
||||
// The application has previously launched via a protocol URL Continue On flow
|
||||
const hasApplicationLaunchedFromContinueOnFlow = this.storageService.getBoolean(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, StorageScope.APPLICATION, false);
|
||||
|
||||
if ((this.environmentService.continueOn !== undefined) &&
|
||||
!this.editSessionsStorageService.isSignedIn &&
|
||||
// and user has not yet been prompted to sign in on this machine
|
||||
hasApplicationLaunchedFromContinueOnFlow === false
|
||||
) {
|
||||
this.storageService.store(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
|
||||
await this.editSessionsStorageService.initialize(true);
|
||||
if (this.editSessionsStorageService.isSignedIn) {
|
||||
await this.resumeEditSession(undefined, true);
|
||||
} else {
|
||||
this.updateAccountsMenuBadge();
|
||||
}
|
||||
// store the fact that we prompted the user
|
||||
} else if (!this.editSessionsStorageService.isSignedIn &&
|
||||
// and user has been prompted to sign in on this machine
|
||||
hasApplicationLaunchedFromContinueOnFlow === true
|
||||
) {
|
||||
// display a badge in the accounts menu but do not prompt the user to sign in again
|
||||
this.updateAccountsMenuBadge();
|
||||
// attempt a resume if we are in a pending state and the user just signed in
|
||||
this._register(this.editSessionsStorageService.onDidSignIn(async () => this.resumeEditSession(undefined, true)));
|
||||
}
|
||||
}
|
||||
|
||||
performance.mark('code/didResumeEditSessionFromIdentifier');
|
||||
});
|
||||
}
|
||||
|
||||
private updateAccountsMenuBadge() {
|
||||
if (this.editSessionsStorageService.isSignedIn) {
|
||||
return this.accountsMenuBadgeDisposable.clear();
|
||||
}
|
||||
|
||||
const badge = new NumberBadge(1, () => localize('check for pending edit sessions', 'Check for pending edit sessions'));
|
||||
this.accountsMenuBadgeDisposable.value = this.activityService.showAccountsActivity({ badge, priority: 1 });
|
||||
}
|
||||
|
||||
private async autoStoreEditSession() {
|
||||
if (this.configurationService.getValue('workbench.experimental.editSessions.autoStore') === 'onShutdown') {
|
||||
await this.progressService.withProgress({
|
||||
|
@ -186,11 +223,6 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
}
|
||||
|
||||
private registerActions() {
|
||||
if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) {
|
||||
this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.registerContinueEditSessionAction();
|
||||
|
||||
this.registerResumeLatestEditSessionAction();
|
||||
|
@ -200,8 +232,6 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
|
||||
this.registerShowEditSessionViewAction();
|
||||
this.registerShowEditSessionOutputChannelAction();
|
||||
|
||||
this.registered = true;
|
||||
}
|
||||
|
||||
private registerShowEditSessionOutputChannelAction() {
|
||||
|
@ -267,7 +297,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
if (ref !== undefined && uri !== 'noDestinationUri') {
|
||||
const encodedRef = encodeURIComponent(ref);
|
||||
uri = uri.with({
|
||||
query: uri.query.length > 0 ? (uri.query + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}`
|
||||
query: uri.query.length > 0 ? (uri.query + `&${queryParamName}=${encodedRef}&continueOn=1`) : `${queryParamName}=${encodedRef}&continueOn=1`
|
||||
});
|
||||
|
||||
// Open the URI
|
||||
|
@ -289,7 +319,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.experimental.editSessions.actions.resumeLatest',
|
||||
id: 'workbench.editSessions.actions.resumeLatest',
|
||||
title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' },
|
||||
category: EDIT_SESSION_SYNC_CATEGORY,
|
||||
f1: true,
|
||||
|
@ -315,7 +345,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.experimental.editSessions.actions.storeCurrent',
|
||||
id: 'workbench.editSessions.actions.storeCurrent',
|
||||
title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' },
|
||||
category: EDIT_SESSION_SYNC_CATEGORY,
|
||||
f1: true,
|
||||
|
@ -367,57 +397,27 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
}
|
||||
|
||||
try {
|
||||
const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = [];
|
||||
let hasLocalUncommittedChanges = false;
|
||||
const workspaceFolders = this.contextService.getWorkspace().folders;
|
||||
const { changes, conflictingChanges } = await this.generateChanges(editSession, ref);
|
||||
|
||||
for (const folder of editSession.folders) {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
let folderRoot: IWorkspaceFolder | undefined;
|
||||
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
|
||||
if (conflictingChanges.length > 0) {
|
||||
const yes = localize('resume edit session yes', 'Yes');
|
||||
const cancel = localize('resume edit session cancel', 'Cancel');
|
||||
// Allow to show edit sessions
|
||||
|
||||
if (folder.canonicalIdentity) {
|
||||
// Look for an edit session identifier that we can use
|
||||
for (const f of workspaceFolders) {
|
||||
const identity = await this.editSessionIdentityService.getEditSessionIdentifier(f, cancellationTokenSource);
|
||||
this.logService.info(`Matching identity ${identity} against edit session folder identity ${folder.canonicalIdentity}...`);
|
||||
if (equals(identity, folder.canonicalIdentity)) {
|
||||
folderRoot = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
folderRoot = workspaceFolders.find((f) => f.name === folder.name);
|
||||
}
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Warning,
|
||||
changes.length > 1 ?
|
||||
localize('resume edit session warning many', 'Resuming your edit session will overwrite the following {0} files. Do you want to proceed?', changes.length) :
|
||||
localize('resume edit session warning 1', 'Resuming your edit session will overwrite {0}. Do you want to proceed?', basename(changes[0].uri)),
|
||||
[cancel, yes],
|
||||
{
|
||||
custom: true,
|
||||
detail: changes.length > 1 ? getFileNamesMessage(conflictingChanges.map((c) => c.uri)) : undefined,
|
||||
cancelId: 0
|
||||
});
|
||||
|
||||
if (!folderRoot) {
|
||||
this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no matching workspace folder was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const repository of this.scmService.repositories) {
|
||||
if (repository.provider.rootUri !== undefined &&
|
||||
this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name === folder.name &&
|
||||
this.getChangedResources(repository).length > 0
|
||||
) {
|
||||
hasLocalUncommittedChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const { relativeFilePath, contents, type } of folder.workingChanges) {
|
||||
const uri = joinPath(folderRoot.uri, relativeFilePath);
|
||||
changes.push({ uri: uri, type: type, contents: contents });
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLocalUncommittedChanges) {
|
||||
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
|
||||
type: 'warning',
|
||||
title: EDIT_SESSION_SYNC_CATEGORY.value
|
||||
});
|
||||
if (!result.confirmed) {
|
||||
if (result.choice === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -439,6 +439,77 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
}
|
||||
}
|
||||
|
||||
private async generateChanges(editSession: EditSession, ref: string) {
|
||||
const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = [];
|
||||
const conflictingChanges = [];
|
||||
const workspaceFolders = this.contextService.getWorkspace().folders;
|
||||
|
||||
for (const folder of editSession.folders) {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
let folderRoot: IWorkspaceFolder | undefined;
|
||||
|
||||
if (folder.canonicalIdentity) {
|
||||
// Look for an edit session identifier that we can use
|
||||
for (const f of workspaceFolders) {
|
||||
const identity = await this.editSessionIdentityService.getEditSessionIdentifier(f, cancellationTokenSource);
|
||||
this.logService.info(`Matching identity ${identity} against edit session folder identity ${folder.canonicalIdentity}...`);
|
||||
if (equals(identity, folder.canonicalIdentity)) {
|
||||
folderRoot = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
folderRoot = workspaceFolders.find((f) => f.name === folder.name);
|
||||
}
|
||||
|
||||
if (!folderRoot) {
|
||||
this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no matching workspace folder was found.`);
|
||||
return { changes: [], conflictingChanges: [] };
|
||||
}
|
||||
|
||||
const localChanges = new Set<string>();
|
||||
for (const repository of this.scmService.repositories) {
|
||||
if (repository.provider.rootUri !== undefined &&
|
||||
this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name === folder.name
|
||||
) {
|
||||
const repositoryChanges = this.getChangedResources(repository);
|
||||
repositoryChanges.forEach((change) => localChanges.add(change.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
for (const change of folder.workingChanges) {
|
||||
const uri = joinPath(folderRoot.uri, change.relativeFilePath);
|
||||
|
||||
changes.push({ uri, type: change.type, contents: change.contents });
|
||||
if (await this.willChangeLocalContents(localChanges, uri, change)) {
|
||||
conflictingChanges.push({ uri, type: change.type, contents: change.contents });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { changes, conflictingChanges };
|
||||
}
|
||||
|
||||
private async willChangeLocalContents(localChanges: Set<string>, uriWithIncomingChanges: URI, incomingChange: Change) {
|
||||
if (!localChanges.has(uriWithIncomingChanges.toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { contents, type } = incomingChange;
|
||||
|
||||
switch (type) {
|
||||
case (ChangeType.Addition): {
|
||||
const [originalContents, incomingContents] = await Promise.all([sha1Hex(contents), sha1Hex(encodeBase64((await this.fileService.readFile(uriWithIncomingChanges)).value))]);
|
||||
return originalContents !== incomingContents;
|
||||
}
|
||||
case (ChangeType.Deletion): {
|
||||
return await this.fileService.exists(uriWithIncomingChanges);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unhandled change type.');
|
||||
}
|
||||
}
|
||||
|
||||
async storeEditSession(fromStoreCommand: boolean): Promise<string | undefined> {
|
||||
const folders: Folder[] = [];
|
||||
let hasEdits = false;
|
||||
|
@ -530,17 +601,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
|||
}
|
||||
|
||||
private getChangedResources(repository: ISCMRepository) {
|
||||
const trackedUris = repository.provider.groups.elements.reduce((resources, resourceGroups) => {
|
||||
return repository.provider.groups.elements.reduce((resources, resourceGroups) => {
|
||||
resourceGroups.elements.forEach((resource) => resources.add(resource.sourceUri));
|
||||
return resources;
|
||||
}, new Set<URI>()); // A URI might appear in more than one resource group
|
||||
|
||||
return [...trackedUris];
|
||||
}
|
||||
|
||||
private hasEditSession() {
|
||||
for (const repository of this.scmService.repositories) {
|
||||
if (this.getChangedResources(repository).length > 0) {
|
||||
if (this.getChangedResources(repository).size > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -741,23 +810,28 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
|||
'default': 'off',
|
||||
'markdownDescription': localize('autoStore', "Controls whether to automatically store an available edit session for the current workspace."),
|
||||
},
|
||||
'workbench.experimental.editSessions.enabled': {
|
||||
'type': 'boolean',
|
||||
'tags': ['experimental', 'usesOnlineServices'],
|
||||
'default': true,
|
||||
'markdownDescription': localize('editSessionsEnabled', "Controls whether to display cloud-enabled actions to store and resume uncommitted changes when switching between web, desktop, or devices."),
|
||||
},
|
||||
'workbench.experimental.editSessions.autoResume': {
|
||||
'workbench.editSessions.autoResume': {
|
||||
enum: ['onReload', 'off'],
|
||||
enumDescriptions: [
|
||||
localize('autoResume.onReload', "Automatically resume available edit session on window reload."),
|
||||
localize('autoResume.off', "Never attempt to resume an edit session.")
|
||||
],
|
||||
'type': 'string',
|
||||
'tags': ['experimental', 'usesOnlineServices'],
|
||||
'tags': ['usesOnlineServices'],
|
||||
'default': 'onReload',
|
||||
'markdownDescription': localize('autoResume', "Controls whether to automatically resume an available edit session for the current workspace."),
|
||||
},
|
||||
'workbench.editSessions.continueOn': {
|
||||
enum: ['prompt', 'off'],
|
||||
enumDescriptions: [
|
||||
localize('continueOn.promptForAuth', 'Prompt the user to sign in to store edit sessions with Continue Working On.'),
|
||||
localize('continueOn.off', 'Do not use edit sessions with Continue Working On unless the user has already turned on edit sessions.')
|
||||
],
|
||||
type: 'string',
|
||||
tags: ['usesOnlineServices'],
|
||||
default: 'prompt',
|
||||
markdownDescription: localize('continueOn', 'Controls whether to prompt the user to store edit sessions when using Continue Working On.')
|
||||
},
|
||||
'workbench.experimental.editSessions.continueOn': {
|
||||
enum: ['prompt', 'off'],
|
||||
enumDescriptions: [
|
||||
|
@ -767,7 +841,25 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
|||
type: 'string',
|
||||
tags: ['experimental', 'usesOnlineServices'],
|
||||
default: 'prompt',
|
||||
markdownDeprecationMessage: localize('continueOnDeprecated', 'This setting is deprecated in favor of {0}.', '`#workbench.experimental.continueOn#`'),
|
||||
markdownDescription: localize('continueOn', 'Controls whether to prompt the user to store edit sessions when using Continue Working On.')
|
||||
}
|
||||
},
|
||||
'workbench.experimental.editSessions.enabled': {
|
||||
'type': 'boolean',
|
||||
'tags': ['experimental', 'usesOnlineServices'],
|
||||
'default': true,
|
||||
'markdownDeprecationMessage': localize('editSessionsEnabledDeprecated', "This setting is deprecated as Edit Sessions are no longer experimental. Please see {0} and {1} for configuring behavior related to Edit Sessions.", '`#workbench.editSessions.autoResume#`', '`#workbench.editSessions.continueOn#`')
|
||||
},
|
||||
'workbench.experimental.editSessions.autoResume': {
|
||||
enum: ['onReload', 'off'],
|
||||
enumDescriptions: [
|
||||
localize('autoResume.onReload', "Automatically resume available edit session on window reload."),
|
||||
localize('autoResume.off', "Never attempt to resume an edit session.")
|
||||
],
|
||||
'type': 'string',
|
||||
'tags': ['experimental', 'usesOnlineServices'],
|
||||
'default': 'onReload',
|
||||
'markdownDeprecationMessage': localize('autoResumeDeprecated', "This setting is deprecated in favor of {0}.", '`#workbench.editSessions.autoResume#`')
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ import { isWeb } from 'vs/base/common/platform';
|
|||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
|
||||
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
|
||||
|
@ -50,6 +51,11 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
|||
return this.existingSessionId !== undefined;
|
||||
}
|
||||
|
||||
private _didSignIn = new Emitter<void>();
|
||||
get onDidSignIn() {
|
||||
return this._didSignIn.event;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
|
@ -162,6 +168,9 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
|||
}
|
||||
this.initialized = await this.doInitialize(fromContinueOn);
|
||||
this.signedInContext.set(this.initialized);
|
||||
if (this.initialized) {
|
||||
this._didSignIn.fire();
|
||||
}
|
||||
return this.initialized;
|
||||
|
||||
}
|
||||
|
@ -293,7 +302,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
|||
|
||||
quickpick.onDidTriggerItemButton(async (e) => {
|
||||
if (e.button.tooltip === configureContinueOnPreference.tooltip) {
|
||||
await this.commandService.executeCommand('workbench.action.openSettings', 'workbench.experimental.editSessions.continueOn');
|
||||
await this.commandService.executeCommand('workbench.action.openSettings', 'workbench.editSessions.continueOn');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ export class EditSessionsDataViews extends Disposable {
|
|||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const editSessionId = URI.parse(handle.$treeItemHandle).path.substring(1);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
await commandService.executeCommand('workbench.experimental.editSessions.actions.resumeLatest', editSessionId);
|
||||
await commandService.executeCommand('workbench.editSessions.actions.resumeLatest', editSessionId);
|
||||
await treeView.refresh();
|
||||
}
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ export class EditSessionsDataViews extends Disposable {
|
|||
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
await commandService.executeCommand('workbench.experimental.editSessions.actions.storeCurrent');
|
||||
await commandService.executeCommand('workbench.editSessions.actions.storeCurrent');
|
||||
await treeView.refresh();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = {
|
||||
original: 'Edit Sessions',
|
||||
|
@ -23,6 +24,7 @@ export interface IEditSessionsStorageService {
|
|||
_serviceBrand: undefined;
|
||||
|
||||
readonly isSignedIn: boolean;
|
||||
readonly onDidSignIn: Event<void>;
|
||||
|
||||
initialize(fromContinueOn: boolean): Promise<boolean>;
|
||||
read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>;
|
||||
|
|
|
@ -35,6 +35,7 @@ import { Event } from 'vs/base/common/event';
|
|||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
const folderName = 'test-folder';
|
||||
const folderUri = URI.file(`/${folderName}`);
|
||||
|
@ -65,10 +66,17 @@ suite('Edit session sync', () => {
|
|||
override onWillShutdown = Event.None;
|
||||
});
|
||||
instantiationService.stub(INotificationService, new TestNotificationService());
|
||||
instantiationService.stub(IEditSessionsStorageService, new class extends mock<IEditSessionsStorageService>() { });
|
||||
instantiationService.stub(IEditSessionsStorageService, new class extends mock<IEditSessionsStorageService>() {
|
||||
override onDidSignIn = Event.None;
|
||||
});
|
||||
instantiationService.stub(IProgressService, ProgressService);
|
||||
instantiationService.stub(ISCMService, SCMService);
|
||||
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
|
||||
instantiationService.stub(IDialogService, new class extends mock<IDialogService>() {
|
||||
override async show() {
|
||||
return { choice: 1 };
|
||||
}
|
||||
});
|
||||
instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { editSessions: { enabled: true } } } }));
|
||||
instantiationService.stub(IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() {
|
||||
override getWorkspace() {
|
||||
|
@ -133,6 +141,10 @@ suite('Edit session sync', () => {
|
|||
const readStub = sandbox.stub().returns({ editSession, ref: '0' });
|
||||
instantiationService.stub(IEditSessionsStorageService, 'read', readStub);
|
||||
|
||||
// Ensure that user does not get prompted here
|
||||
const dialogServiceShowStub = sandbox.stub();
|
||||
instantiationService.stub(IDialogService, 'show', dialogServiceShowStub);
|
||||
|
||||
// Create root folder
|
||||
await fileService.createFolder(folderUri);
|
||||
|
||||
|
@ -141,6 +153,7 @@ suite('Edit session sync', () => {
|
|||
|
||||
// Verify edit session was correctly applied
|
||||
assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents);
|
||||
assert.equal(dialogServiceShowStub.called, false);
|
||||
});
|
||||
|
||||
test('Edit session not stored if there are no edits', async function () {
|
||||
|
|
|
@ -904,10 +904,14 @@ export class ExtensionEditor extends EditorPane {
|
|||
resources.push([localize('Marketplace', "Marketplace"), URI.parse(extension.url)]);
|
||||
}
|
||||
if (extension.repository) {
|
||||
resources.push([localize('repository', "Repository"), URI.parse(extension.repository)]);
|
||||
try {
|
||||
resources.push([localize('repository', "Repository"), URI.parse(extension.repository)]);
|
||||
} catch (error) {/* Ignore */ }
|
||||
}
|
||||
if (extension.url && extension.licenseUrl) {
|
||||
resources.push([localize('license', "License"), URI.parse(extension.licenseUrl)]);
|
||||
try {
|
||||
resources.push([localize('license', "License"), URI.parse(extension.licenseUrl)]);
|
||||
} catch (error) {/* Ignore */ }
|
||||
}
|
||||
if (extension.publisherUrl) {
|
||||
resources.push([extension.publisherDisplayName, extension.publisherUrl]);
|
||||
|
|
|
@ -138,7 +138,7 @@ async function setupTest() {
|
|||
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []);
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{ extensions: [], onDidChangeExtensions: Event.None, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false });
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{ extensions: [], onDidChangeExtensions: Event.None, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) });
|
||||
(<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).reset();
|
||||
|
||||
instantiationService.stub(IUserDataSyncEnablementService, instantiationService.createInstance(UserDataSyncEnablementService));
|
||||
|
@ -783,6 +783,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
@ -837,6 +838,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
|
@ -853,6 +855,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
|
@ -868,6 +871,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally)
|
||||
|
@ -889,6 +893,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(aLocalExtension('a'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None)
|
||||
|
@ -905,6 +910,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(aLocalExtension('a'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None)
|
||||
|
@ -923,6 +929,7 @@ suite('ExtensionsActions', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
|
@ -979,7 +986,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1003,7 +1011,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => true
|
||||
canAddExtension: (extension) => true,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1024,7 +1033,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1047,7 +1057,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
|
@ -1069,7 +1080,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(local)],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => true,
|
||||
canAddExtension: (extension) => true
|
||||
canAddExtension: (extension) => true,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1087,7 +1099,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1112,7 +1125,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => true,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
|
@ -1140,7 +1154,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const local = aLocalExtension('a', { version: '1.0.1' });
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
|
@ -1163,7 +1178,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
|
@ -1185,7 +1201,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
|
@ -1205,7 +1222,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const local = aLocalExtension('a');
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
|
@ -1226,7 +1244,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const local = aLocalExtension('a');
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
|
@ -1246,7 +1265,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const local = aLocalExtension('a', { version: '1.0.1' });
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
|
@ -1271,7 +1291,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('b'))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1292,7 +1313,8 @@ suite('ReloadAction', () => {
|
|||
extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))],
|
||||
onDidChangeExtensions: Event.None,
|
||||
canRemoveExtension: (extension) => false,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1320,7 +1342,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(remoteExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
|
||||
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
|
||||
|
@ -1353,7 +1376,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(remoteExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
|
||||
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
|
||||
|
@ -1391,7 +1415,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1429,7 +1454,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1465,7 +1491,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(localExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
|
||||
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
|
||||
|
@ -1499,7 +1526,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(localExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1530,7 +1558,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(remoteExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1561,7 +1590,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(localExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
@ -1592,7 +1622,8 @@ suite('ReloadAction', () => {
|
|||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
extensions: [toExtensionDescription(remoteExtension)],
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
canAddExtension: (extension) => false,
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
|
|
@ -48,7 +48,7 @@ import { platform } from 'vs/base/common/platform';
|
|||
import { arch } from 'vs/base/common/process';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
suite('ExtensionsListView Tests', () => {
|
||||
suite('ExtensionsViews Tests', () => {
|
||||
|
||||
let instantiationService: TestInstantiationService;
|
||||
let testableView: ExtensionsListView;
|
||||
|
@ -184,7 +184,8 @@ suite('ExtensionsListView Tests', () => {
|
|||
toExtensionDescription(localRandom),
|
||||
toExtensionDescription(builtInTheme),
|
||||
toExtensionDescription(builtInBasic)
|
||||
]
|
||||
],
|
||||
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
|
||||
});
|
||||
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally);
|
||||
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally);
|
||||
|
@ -398,7 +399,7 @@ suite('ExtensionsListView Tests', () => {
|
|||
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', allRecommendedExtensions);
|
||||
|
||||
return testableView.show('@recommended').then(result => {
|
||||
const extensionInfos: IExtensionInfo[] = target.args[0][0];
|
||||
const extensionInfos: IExtensionInfo[] = target.args[1][0];
|
||||
|
||||
assert.strictEqual(extensionInfos.length, allRecommendedExtensions.length);
|
||||
assert.strictEqual(result.length, allRecommendedExtensions.length);
|
||||
|
@ -423,7 +424,7 @@ suite('ExtensionsListView Tests', () => {
|
|||
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', allRecommendedExtensions);
|
||||
|
||||
return testableView.show('@recommended:all').then(result => {
|
||||
const extensionInfos: IExtensionInfo[] = target.args[0][0];
|
||||
const extensionInfos: IExtensionInfo[] = target.args[1][0];
|
||||
|
||||
assert.strictEqual(extensionInfos.length, allRecommendedExtensions.length);
|
||||
assert.strictEqual(result.length, allRecommendedExtensions.length);
|
||||
|
|
|
@ -54,6 +54,7 @@ import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/noteb
|
|||
import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
|
||||
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
|
||||
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService';
|
||||
import { CellEditType, CellKind, CellUri, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
|
@ -324,7 +325,7 @@ registerAction2(class extends Action2 {
|
|||
id: '_interactive.open',
|
||||
title: { value: localize('interactive.open', "Open Interactive Window"), original: 'Open Interactive Window' },
|
||||
f1: false,
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
description: {
|
||||
description: localize('interactive.open', "Open Interactive Window"),
|
||||
args: [
|
||||
|
@ -440,7 +441,7 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.execute',
|
||||
title: { value: localize('interactive.execute', "Execute Code"), original: 'Execute Code' },
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
keybinding: {
|
||||
// when: NOTEBOOK_CELL_LIST_FOCUSED,
|
||||
when: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive),
|
||||
|
@ -474,6 +475,7 @@ registerAction2(class extends Action2 {
|
|||
const editorService = accessor.get(IEditorService);
|
||||
const bulkEditService = accessor.get(IBulkEditService);
|
||||
const historyService = accessor.get(IInteractiveHistoryService);
|
||||
const notebookEditorService = accessor.get(INotebookEditorService);
|
||||
let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
|
||||
if (context) {
|
||||
if (context.scheme === Schemas.vscodeInteractive) {
|
||||
|
@ -514,6 +516,7 @@ registerAction2(class extends Action2 {
|
|||
outputCollapsed: false
|
||||
} :
|
||||
undefined;
|
||||
|
||||
await bulkEditService.apply([
|
||||
new ResourceNotebookCellEdit(notebookDocument.uri,
|
||||
{
|
||||
|
@ -534,8 +537,16 @@ registerAction2(class extends Action2 {
|
|||
]);
|
||||
|
||||
// reveal the cell into view first
|
||||
editorControl.notebookEditor.revealCellRangeInView({ start: index, end: index + 1 });
|
||||
const range = { start: index, end: index + 1 };
|
||||
editorControl.notebookEditor.revealCellRangeInView(range);
|
||||
await editorControl.notebookEditor.executeNotebookCells(editorControl.notebookEditor.getCellsInRange({ start: index, end: index + 1 }));
|
||||
|
||||
// update the selection and focus in the extension host model
|
||||
const editor = notebookEditorService.getNotebookEditor(editorControl.notebookEditor.getId());
|
||||
if (editor) {
|
||||
editor.setSelections([range]);
|
||||
editor.setFocus(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -546,7 +557,7 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.input.clear',
|
||||
title: { value: localize('interactive.input.clear', "Clear the interactive window input editor contents"), original: 'Clear the interactive window input editor contents' },
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
f1: false
|
||||
});
|
||||
}
|
||||
|
@ -572,7 +583,7 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.history.previous',
|
||||
title: { value: localize('interactive.history.previous', "Previous value in history"), original: 'Previous value in history' },
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
f1: false,
|
||||
keybinding: {
|
||||
when: ContextKeyExpr.and(
|
||||
|
@ -611,7 +622,7 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.history.next',
|
||||
title: { value: localize('interactive.history.next', "Next value in history"), original: 'Next value in history' },
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
f1: false,
|
||||
keybinding: {
|
||||
when: ContextKeyExpr.and(
|
||||
|
@ -657,7 +668,7 @@ registerAction2(class extends Action2 {
|
|||
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
},
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -686,7 +697,7 @@ registerAction2(class extends Action2 {
|
|||
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
},
|
||||
category: 'Interactive',
|
||||
category: 'Interactive Window',
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -710,8 +721,8 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.input.focus',
|
||||
title: { value: localize('interactive.input.focus', "Focus input editor in the interactive window"), original: 'Focus input editor in the interactive window' },
|
||||
category: 'Interactive',
|
||||
f1: false
|
||||
category: 'Interactive Window',
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -745,8 +756,9 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: 'interactive.history.focus',
|
||||
title: { value: localize('interactive.history.focus', "Focus history in the interactive window"), original: 'Focus input editor in the interactive window' },
|
||||
category: 'Interactive',
|
||||
f1: false
|
||||
category: 'Interactive Window',
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
|||
import { localize } from 'vs/nls';
|
||||
import { ILocalizedString } from 'vs/platform/action/common/action';
|
||||
import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
@ -17,7 +18,7 @@ import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mer
|
|||
import { IMergeEditorInputModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel';
|
||||
import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
|
||||
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
|
||||
import { ctxIsMergeEditor, ctxMergeEditorLayout, ctxMergeEditorShowBase, ctxMergeEditorShowNonConflictingChanges } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
|
||||
import { ctxIsMergeEditor, ctxMergeEditorLayout, ctxMergeEditorShowBase, ctxMergeEditorShowBaseAtTop, ctxMergeEditorShowNonConflictingChanges } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
abstract class MergeEditorAction extends Action2 {
|
||||
|
@ -276,6 +277,35 @@ export class ShowHideBase extends Action2 {
|
|||
}
|
||||
}
|
||||
|
||||
export class ShowHideAtTopBase extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'merge.showBaseAtTop',
|
||||
title: {
|
||||
value: localize('layout.showBaseAtTop', 'Show Base At Top'),
|
||||
original: 'Show Base At Top',
|
||||
},
|
||||
toggled: ctxMergeEditorShowBaseAtTop.isEqualTo(true),
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.EditorTitle,
|
||||
when: ctxIsMergeEditor,
|
||||
group: '2_merge',
|
||||
order: 10,
|
||||
},
|
||||
],
|
||||
precondition: ContextKeyExpr.and(ctxIsMergeEditor, ctxMergeEditorShowBase),
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const { activeEditorPane } = accessor.get(IEditorService);
|
||||
if (activeEditorPane instanceof MergeEditor) {
|
||||
activeEditorPane.toggleShowBaseAtTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mergeEditorCategory: ILocalizedString = {
|
||||
value: localize('mergeEditor', 'Merge Editor'),
|
||||
original: 'Merge Editor',
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { AcceptAllInput1, AcceptAllInput2, AcceptMerge, CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextUnhandledConflict, GoToPreviousUnhandledConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, ResetDirtyConflictsToBaseCommand, ResetToBaseAndAutoMergeCommand, SetColumnLayout, SetMixedLayout, ShowHideBase, ShowNonConflictingChanges, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
|
||||
import { AcceptAllInput1, AcceptAllInput2, AcceptMerge, CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextUnhandledConflict, GoToPreviousUnhandledConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, ResetDirtyConflictsToBaseCommand, ResetToBaseAndAutoMergeCommand, SetColumnLayout, SetMixedLayout, ShowHideAtTopBase, ShowHideBase, ShowNonConflictingChanges, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
|
||||
import { MergeEditorCopyContentsToJSON, MergeEditorLoadContentsFromFolder, MergeEditorSaveContentsToFolder } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands';
|
||||
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
|
||||
import { MergeEditor, MergeEditorOpenHandlerContribution, MergeEditorResolverContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
|
||||
|
@ -52,6 +52,7 @@ registerAction2(OpenResultResource);
|
|||
registerAction2(SetMixedLayout);
|
||||
registerAction2(SetColumnLayout);
|
||||
registerAction2(ShowHideBase);
|
||||
registerAction2(ShowHideAtTopBase);
|
||||
registerAction2(OpenMergeEditor);
|
||||
registerAction2(OpenBaseFile);
|
||||
registerAction2(ShowNonConflictingChanges);
|
||||
|
|
|
@ -547,6 +547,13 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
|||
});
|
||||
}
|
||||
|
||||
public toggleShowBaseAtTop(): void {
|
||||
this.setLayout({
|
||||
...this._layoutMode.value,
|
||||
showBaseAtTop: !this._layoutMode.value.showBaseAtTop,
|
||||
});
|
||||
}
|
||||
|
||||
public setLayoutKind(kind: MergeEditorLayoutKind): void {
|
||||
this.setLayout({
|
||||
...this._layoutMode.value,
|
||||
|
@ -590,13 +597,17 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
|||
|
||||
if (layout.kind === 'mixed') {
|
||||
this.setGrid([
|
||||
layout.showBase ? {
|
||||
layout.showBaseAtTop && layout.showBase ? {
|
||||
size: 38,
|
||||
data: this.baseView.get()!.view
|
||||
} : undefined,
|
||||
{
|
||||
size: 38,
|
||||
groups: [{ data: this.input1View.view }, { data: this.input2View.view }]
|
||||
groups: [
|
||||
{ data: this.input1View.view },
|
||||
!layout.showBaseAtTop && layout.showBase ? { data: this.baseView.get()!.view } : undefined,
|
||||
{ data: this.input2View.view }
|
||||
].filter(isDefined)
|
||||
},
|
||||
{
|
||||
size: 62,
|
||||
|
@ -696,18 +707,19 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
|||
interface IMergeEditorLayout {
|
||||
readonly kind: MergeEditorLayoutKind;
|
||||
readonly showBase: boolean;
|
||||
readonly showBaseAtTop: boolean;
|
||||
}
|
||||
|
||||
// TODO use PersistentStore
|
||||
class MergeEditorLayoutStore {
|
||||
private static readonly _key = 'mergeEditor/layout';
|
||||
private _value: IMergeEditorLayout = { kind: 'mixed', showBase: false };
|
||||
private _value: IMergeEditorLayout = { kind: 'mixed', showBase: false, showBaseAtTop: true };
|
||||
|
||||
constructor(@IStorageService private _storageService: IStorageService) {
|
||||
const value = _storageService.get(MergeEditorLayoutStore._key, StorageScope.PROFILE, 'mixed');
|
||||
|
||||
if (value === 'mixed' || value === 'columns') {
|
||||
this._value = { kind: value, showBase: false };
|
||||
this._value = { kind: value, showBase: false, showBaseAtTop: true };
|
||||
} else if (value) {
|
||||
try {
|
||||
this._value = JSON.parse(value);
|
||||
|
|
|
@ -12,6 +12,7 @@ export const ctxIsMergeEditor = new RawContextKey<boolean>('isMergeEditor', fals
|
|||
export const ctxIsMergeResultEditor = new RawContextKey<boolean>('isMergeResultEditor', false, { type: 'boolean', description: localize('isr', 'The editor is a the result editor of a merge editor.') });
|
||||
export const ctxMergeEditorLayout = new RawContextKey<MergeEditorLayoutKind>('mergeEditorLayout', 'mixed', { type: 'string', description: localize('editorLayout', 'The layout mode of a merge editor') });
|
||||
export const ctxMergeEditorShowBase = new RawContextKey<boolean>('mergeEditorShowBase', false, { type: 'boolean', description: localize('showBase', 'If the merge editor shows the base version') });
|
||||
export const ctxMergeEditorShowBaseAtTop = new RawContextKey<boolean>('mergeEditorShowBaseAtTop', false, { type: 'boolean', description: localize('showBaseAtTop', 'If base should be shown at the top') });
|
||||
export const ctxMergeEditorShowNonConflictingChanges = new RawContextKey<boolean>('mergeEditorShowNonConflictingChanges', false, { type: 'boolean', description: localize('showNonConflictingChanges', 'If the merge editor shows non-conflicting changes') });
|
||||
|
||||
export const ctxMergeBaseUri = new RawContextKey<string>('mergeEditorBaseUri', '', { type: 'string', description: localize('baseUri', 'The uri of the baser of a merge editor') });
|
||||
|
|
|
@ -1253,6 +1253,7 @@ export class InsertElement extends SingleSideDiffElement {
|
|||
|
||||
export class ModifiedElement extends AbstractElementRenderer {
|
||||
private _editor?: DiffEditorWidget;
|
||||
private _editorViewStateChanged: boolean;
|
||||
private _editorContainer!: HTMLElement;
|
||||
private _inputToolbarContainer!: HTMLElement;
|
||||
protected _toolbar!: ToolBar;
|
||||
|
@ -1279,6 +1280,7 @@ export class ModifiedElement extends AbstractElementRenderer {
|
|||
super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService);
|
||||
this.cell = cell;
|
||||
this.templateData = templateData;
|
||||
this._editorViewStateChanged = false;
|
||||
}
|
||||
|
||||
init() { }
|
||||
|
@ -1598,7 +1600,26 @@ export class ModifiedElement extends AbstractElementRenderer {
|
|||
modified: modifiedTextModel
|
||||
});
|
||||
|
||||
this._editor!.restoreViewState(this.cell.getSourceEditorViewState() as editorCommon.IDiffEditorViewState);
|
||||
const handleViewStateChange = () => {
|
||||
this._editorViewStateChanged = true;
|
||||
};
|
||||
|
||||
const handleScrollChange = (e: editorCommon.IScrollEvent) => {
|
||||
if (e.scrollTopChanged || e.scrollLeftChanged) {
|
||||
this._editorViewStateChanged = true;
|
||||
}
|
||||
};
|
||||
|
||||
this._register(this._editor!.getOriginalEditor().onDidChangeCursorSelection(handleViewStateChange));
|
||||
this._register(this._editor!.getOriginalEditor().onDidScrollChange(handleScrollChange));
|
||||
this._register(this._editor!.getModifiedEditor().onDidChangeCursorSelection(handleViewStateChange));
|
||||
this._register(this._editor!.getModifiedEditor().onDidScrollChange(handleScrollChange));
|
||||
|
||||
const editorViewState = this.cell.getSourceEditorViewState() as editorCommon.IDiffEditorViewState | null;
|
||||
if (editorViewState) {
|
||||
console.log('restore view state', this.cell.modified.handle, editorViewState);
|
||||
this._editor!.restoreViewState(editorViewState);
|
||||
}
|
||||
|
||||
const contentHeight = this._editor!.getContentHeight();
|
||||
this.cell.editorHeight = contentHeight;
|
||||
|
@ -1645,7 +1666,7 @@ export class ModifiedElement extends AbstractElementRenderer {
|
|||
}
|
||||
|
||||
override dispose() {
|
||||
if (this._editor) {
|
||||
if (this._editor && this._editorViewStateChanged) {
|
||||
this.cell.saveSpirceEditorViewState(this._editor.saveViewState());
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
width: 50%;
|
||||
} */
|
||||
|
||||
.notebook-text-diff-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notebook-text-diff-editor .cell-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -61,7 +65,7 @@
|
|||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
.notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row {
|
||||
.notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
@ -142,13 +146,13 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row {
|
||||
.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row,
|
||||
.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover,
|
||||
.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused {
|
||||
.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row,
|
||||
.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover,
|
||||
.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused {
|
||||
outline: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
@ -287,3 +291,18 @@
|
|||
left: 4px !important;
|
||||
width: 15px !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .scrollbar.visible {
|
||||
z-index: var(--z-index-notebook-scrollbar);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.notebook-text-diff-editor .notebook-overview-ruler-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.notebook-text-diff-editor .notebook-overview-ruler-container .diffViewport {
|
||||
z-index: var(--notebook-diff-view-viewport-slider);
|
||||
}
|
||||
|
|
|
@ -30,9 +30,12 @@ export interface INotebookTextDiffEditor {
|
|||
notebookOptions: NotebookOptions;
|
||||
readonly textModel?: NotebookTextModel;
|
||||
onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>;
|
||||
onDidScroll: Event<void>;
|
||||
onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel; output: ICellOutputViewModel }>;
|
||||
getOverflowContainerDomNode(): HTMLElement;
|
||||
getLayoutInfo(): NotebookLayoutInfo;
|
||||
getScrollTop(): number;
|
||||
getScrollHeight(): number;
|
||||
layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void;
|
||||
createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void;
|
||||
showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void;
|
||||
|
@ -42,6 +45,7 @@ export interface INotebookTextDiffEditor {
|
|||
* Trigger the editor to scroll from scroll event programmatically
|
||||
*/
|
||||
triggerScroll(event: IMouseWheelEvent): void;
|
||||
delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent): void;
|
||||
getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel;
|
||||
focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise<void>;
|
||||
focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise<void>;
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel';
|
||||
import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher';
|
||||
import { INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser';
|
||||
|
||||
export class NotebookDiffOverviewRuler extends Themable {
|
||||
private readonly _domNode: FastDomNode<HTMLCanvasElement>;
|
||||
private readonly _overviewViewportDomElement: FastDomNode<HTMLElement>;
|
||||
|
||||
private _diffElementViewModels: DiffElementViewModelBase[] = [];
|
||||
private _lanes = 2;
|
||||
|
||||
private _insertColor: Color | null;
|
||||
private _insertColorHex: string | null;
|
||||
private _removeColor: Color | null;
|
||||
private _removeColorHex: string | null;
|
||||
|
||||
private _disposables: DisposableStore;
|
||||
private _renderAnimationFrame: IDisposable | null;
|
||||
|
||||
constructor(readonly notebookEditor: INotebookTextDiffEditor, readonly width: number, container: HTMLElement, @IThemeService themeService: IThemeService) {
|
||||
super(themeService);
|
||||
this._insertColor = null;
|
||||
this._removeColor = null;
|
||||
this._insertColorHex = null;
|
||||
this._removeColorHex = null;
|
||||
this._disposables = this._register(new DisposableStore());
|
||||
this._renderAnimationFrame = null;
|
||||
this._domNode = createFastDomNode(document.createElement('canvas'));
|
||||
this._domNode.setPosition('relative');
|
||||
this._domNode.setLayerHinting(true);
|
||||
this._domNode.setContain('strict');
|
||||
|
||||
container.appendChild(this._domNode.domNode);
|
||||
|
||||
this._overviewViewportDomElement = createFastDomNode(document.createElement('div'));
|
||||
this._overviewViewportDomElement.setClassName('diffViewport');
|
||||
this._overviewViewportDomElement.setPosition('absolute');
|
||||
this._overviewViewportDomElement.setWidth(width);
|
||||
container.appendChild(this._overviewViewportDomElement.domNode);
|
||||
|
||||
this._register(browser.PixelRatio.onDidChange(() => {
|
||||
this._scheduleRender();
|
||||
}));
|
||||
|
||||
this._register(this.themeService.onDidColorThemeChange(e => {
|
||||
const colorChanged = this.applyColors(e);
|
||||
if (colorChanged) {
|
||||
this._scheduleRender();
|
||||
}
|
||||
}));
|
||||
this.applyColors(this.themeService.getColorTheme());
|
||||
|
||||
this._register(this.notebookEditor.onDidScroll(() => {
|
||||
this._renderOverviewViewport();
|
||||
}));
|
||||
|
||||
this._register(DOM.addStandardDisposableListener(this._overviewViewportDomElement.domNode, DOM.EventType.POINTER_DOWN, (e) => {
|
||||
this.notebookEditor.delegateVerticalScrollbarPointerDown(e);
|
||||
}));
|
||||
}
|
||||
|
||||
private applyColors(theme: IColorTheme): boolean {
|
||||
const newInsertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2);
|
||||
const newRemoveColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2);
|
||||
const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor);
|
||||
this._insertColor = newInsertColor;
|
||||
this._removeColor = newRemoveColor;
|
||||
if (this._insertColor) {
|
||||
this._insertColorHex = Color.Format.CSS.formatHexA(this._insertColor);
|
||||
}
|
||||
|
||||
if (this._removeColor) {
|
||||
this._removeColorHex = Color.Format.CSS.formatHexA(this._removeColor);
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
layout() {
|
||||
this._layoutNow();
|
||||
}
|
||||
|
||||
updateViewModels(elements: DiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) {
|
||||
this._disposables.clear();
|
||||
|
||||
this._diffElementViewModels = elements;
|
||||
|
||||
if (eventDispatcher) {
|
||||
this._disposables.add(eventDispatcher.onDidChangeLayout(() => {
|
||||
this._scheduleRender();
|
||||
}));
|
||||
|
||||
this._disposables.add(eventDispatcher.onDidChangeCellLayout(() => {
|
||||
this._scheduleRender();
|
||||
}));
|
||||
}
|
||||
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
private _scheduleRender(): void {
|
||||
if (this._renderAnimationFrame === null) {
|
||||
this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100);
|
||||
}
|
||||
}
|
||||
|
||||
private _onRenderScheduled(): void {
|
||||
this._renderAnimationFrame = null;
|
||||
this._layoutNow();
|
||||
}
|
||||
|
||||
private _layoutNow() {
|
||||
const layoutInfo = this.notebookEditor.getLayoutInfo();
|
||||
const height = layoutInfo.height;
|
||||
const ratio = browser.PixelRatio.value;
|
||||
this._domNode.setWidth(this.width);
|
||||
this._domNode.setHeight(height);
|
||||
this._domNode.domNode.width = this.width * ratio;
|
||||
this._domNode.domNode.height = height * ratio;
|
||||
const ctx = this._domNode.domNode.getContext('2d')!;
|
||||
ctx.clearRect(0, 0, this.width * ratio, height * ratio);
|
||||
this._renderCanvas(ctx, this.width * ratio, ratio);
|
||||
this._renderOverviewViewport();
|
||||
}
|
||||
|
||||
private _renderOverviewViewport(): void {
|
||||
const layout = this._computeOverviewViewport();
|
||||
if (!layout) {
|
||||
this._overviewViewportDomElement.setTop(0);
|
||||
this._overviewViewportDomElement.setHeight(0);
|
||||
} else {
|
||||
this._overviewViewportDomElement.setTop(layout.top);
|
||||
this._overviewViewportDomElement.setHeight(layout.height);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeOverviewViewport(): { height: number; top: number } | null {
|
||||
const layoutInfo = this.notebookEditor.getLayoutInfo();
|
||||
if (!layoutInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scrollTop = this.notebookEditor.getScrollTop();
|
||||
const scrollHeight = this.notebookEditor.getScrollHeight();
|
||||
|
||||
const computedAvailableSize = Math.max(0, layoutInfo.height);
|
||||
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0);
|
||||
const computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0;
|
||||
|
||||
const computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio));
|
||||
const computedSliderPosition = Math.floor(scrollTop * computedRatio);
|
||||
|
||||
return {
|
||||
height: computedSliderSize,
|
||||
top: computedSliderPosition
|
||||
};
|
||||
}
|
||||
|
||||
private _renderCanvas(ctx: CanvasRenderingContext2D, width: number, ratio: number) {
|
||||
if (!this._insertColorHex || !this._removeColorHex) {
|
||||
// no op when colors are not yet known
|
||||
return;
|
||||
}
|
||||
|
||||
const laneWidth = width / this._lanes;
|
||||
let currentFrom = 0;
|
||||
for (let i = 0; i < this._diffElementViewModels.length; i++) {
|
||||
const element = this._diffElementViewModels[i];
|
||||
|
||||
const cellHeight = element.layoutInfo.totalHeight * ratio;
|
||||
switch (element.type) {
|
||||
case 'insert':
|
||||
ctx.fillStyle = this._insertColorHex;
|
||||
ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);
|
||||
break;
|
||||
case 'delete':
|
||||
ctx.fillStyle = this._removeColorHex;
|
||||
ctx.fillRect(0, currentFrom, laneWidth, cellHeight);
|
||||
break;
|
||||
case 'unchanged':
|
||||
break;
|
||||
case 'modified':
|
||||
ctx.fillStyle = this._removeColorHex;
|
||||
ctx.fillRect(0, currentFrom, laneWidth, cellHeight);
|
||||
ctx.fillStyle = this._insertColorHex;
|
||||
ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);
|
||||
break;
|
||||
}
|
||||
|
||||
currentFrom += cellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
if (this._renderAnimationFrame !== null) {
|
||||
this._renderAnimationFrame.dispose();
|
||||
this._renderAnimationFrame = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground);
|
||||
if (scrollbarSliderBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.notebook-text-diff-editor .diffViewport {
|
||||
background: ${scrollbarSliderBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
|
||||
if (scrollbarSliderHoverBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.notebook-text-diff-editor .diffViewport:hover {
|
||||
background: ${scrollbarSliderHoverBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground);
|
||||
if (scrollbarSliderActiveBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.notebook-text-diff-editor .diffViewport:active {
|
||||
background: ${scrollbarSliderActiveBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
|
@ -44,6 +44,8 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co
|
|||
import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange';
|
||||
import { NotebookDiffOverviewRuler } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler';
|
||||
import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
|
@ -82,11 +84,15 @@ class NotebookDiffEditorSelection implements IEditorPaneSelection {
|
|||
}
|
||||
|
||||
export class NotebookTextDiffEditor extends EditorPane implements INotebookTextDiffEditor, INotebookDelegateForWebview, IEditorPaneWithSelection {
|
||||
public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = 30;
|
||||
creationOptions: INotebookEditorCreationOptions = getDefaultNotebookCreationOptions();
|
||||
static readonly ID: string = NOTEBOOK_DIFF_EDITOR_ID;
|
||||
|
||||
private _rootElement!: HTMLElement;
|
||||
private _listViewContainer!: HTMLElement;
|
||||
private _overflowContainer!: HTMLElement;
|
||||
private _overviewRulerContainer!: HTMLElement;
|
||||
private _overviewRuler!: NotebookDiffOverviewRuler;
|
||||
private _dimension: DOM.Dimension | null = null;
|
||||
private _diffElementViewModels: DiffElementViewModelBase[] = [];
|
||||
private _list!: NotebookTextDiffList;
|
||||
|
@ -97,6 +103,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
|
||||
private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>());
|
||||
public readonly onMouseUp = this._onMouseUp.event;
|
||||
private readonly _onDidScroll = this._register(new Emitter<void>());
|
||||
readonly onDidScroll: Event<void> = this._onDidScroll.event;
|
||||
private _eventDispatcher: NotebookDiffEditorEventDispatcher | undefined;
|
||||
protected _scopeContextKeyService!: IContextKeyService;
|
||||
private _model: INotebookDiffEditorModel | null = null;
|
||||
|
@ -166,6 +174,18 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
// throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getScrollTop() {
|
||||
return this._list?.scrollTop ?? 0;
|
||||
}
|
||||
|
||||
getScrollHeight() {
|
||||
return this._list?.scrollHeight ?? 0;
|
||||
}
|
||||
|
||||
delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) {
|
||||
this._list?.delegateVerticalScrollbarPointerDown(browserEvent);
|
||||
}
|
||||
|
||||
updateOutputHeight(cellInfo: IDiffCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void {
|
||||
const diffElement = cellInfo.diffElement;
|
||||
const cell = this.getCellByInfo(cellInfo);
|
||||
|
@ -217,10 +237,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
this.instantiationService.createInstance(CellDiffSideBySideRenderer, this),
|
||||
];
|
||||
|
||||
this._listViewContainer = DOM.append(this._rootElement, DOM.$('.notebook-diff-list-view'));
|
||||
|
||||
this._list = this.instantiationService.createInstance(
|
||||
NotebookTextDiffList,
|
||||
'NotebookTextDiff',
|
||||
this._rootElement,
|
||||
this._listViewContainer,
|
||||
this.instantiationService.createInstance(NotebookCellTextDiffListDelegate),
|
||||
renderers,
|
||||
this.contextKeyService,
|
||||
|
@ -274,8 +296,17 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
}
|
||||
}));
|
||||
|
||||
this._register(this._list.onDidScroll(() => {
|
||||
this._onDidScroll.fire();
|
||||
}));
|
||||
|
||||
this._register(this._list.onDidChangeFocus(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.USER })));
|
||||
|
||||
this._overviewRulerContainer = document.createElement('div');
|
||||
this._overviewRulerContainer.classList.add('notebook-overview-ruler-container');
|
||||
this._rootElement.appendChild(this._overviewRulerContainer);
|
||||
this._registerOverviewRuler();
|
||||
|
||||
// transparent cover
|
||||
this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover'));
|
||||
this._webviewTransparentCover.style.display = 'none';
|
||||
|
@ -296,8 +327,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
this._register(this._list.onDidScroll(e => {
|
||||
this._webviewTransparentCover!.style.top = `${e.scrollTop}px`;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private _registerOverviewRuler() {
|
||||
this._overviewRuler = this._register(this.instantiationService.createInstance(NotebookDiffOverviewRuler, this, NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH, this._overviewRulerContainer!));
|
||||
}
|
||||
|
||||
private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView<IDiffCellInfo>, getActiveNestedCell: (diffElement: DiffElementViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) {
|
||||
|
@ -526,6 +559,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
private _setViewModel(viewModels: DiffElementViewModelBase[]) {
|
||||
this._diffElementViewModels = viewModels;
|
||||
this._list.splice(0, this._list.length, this._diffElementViewModels);
|
||||
this._overviewRuler.updateViewModels(this._diffElementViewModels, this._eventDispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -942,12 +976,13 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
layout(dimension: DOM.Dimension): void {
|
||||
this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);
|
||||
this._rootElement.classList.toggle('narrow-width', dimension.width < 600);
|
||||
this._dimension = dimension;
|
||||
this._rootElement.style.height = `${dimension.height}px`;
|
||||
this._dimension = dimension.with(dimension.width - NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH);
|
||||
|
||||
this._listViewContainer.style.height = `${dimension.height}px`;
|
||||
this._listViewContainer.style.width = `${this._dimension.width}px`;
|
||||
|
||||
this._list?.layout(this._dimension.height, this._dimension.width);
|
||||
|
||||
|
||||
if (this._modifiedWebview) {
|
||||
this._modifiedWebview.element.style.width = `calc(50% - 16px)`;
|
||||
this._modifiedWebview.element.style.left = `calc(50%)`;
|
||||
|
@ -959,10 +994,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
}
|
||||
|
||||
if (this._webviewTransparentCover) {
|
||||
this._webviewTransparentCover.style.height = `${dimension.height}px`;
|
||||
this._webviewTransparentCover.style.width = `${dimension.width}px`;
|
||||
this._webviewTransparentCover.style.height = `${this._dimension.height}px`;
|
||||
this._webviewTransparentCover.style.width = `${this._dimension.width}px`;
|
||||
}
|
||||
|
||||
this._overviewRuler.layout();
|
||||
|
||||
this._eventDispatcher?.emit([new NotebookDiffLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
|
||||
}
|
||||
|
||||
|
@ -974,6 +1011,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
}
|
||||
}
|
||||
|
||||
registerZIndex(ZIndex.Base, 10, 'notebook-diff-view-viewport-slider');
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const cellBorderColor = theme.getColor(notebookCellBorder);
|
||||
if (cellBorderColor) {
|
||||
|
|
|
@ -336,7 +336,11 @@ export class NotebookTextDiffList extends WorkbenchList<DiffElementViewModelBase
|
|||
}
|
||||
|
||||
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this.view.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||
this.view.delegateScrollFromMouseWheelEvent(browserEvent);
|
||||
}
|
||||
|
||||
delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) {
|
||||
this.view.delegateVerticalScrollbarPointerDown(browserEvent);
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
|
|
@ -934,7 +934,11 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
}
|
||||
|
||||
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this.view.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||
this.view.delegateScrollFromMouseWheelEvent(browserEvent);
|
||||
}
|
||||
|
||||
delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) {
|
||||
this.view.delegateVerticalScrollbarPointerDown(browserEvent);
|
||||
}
|
||||
|
||||
isElementAboveViewport(index: number) {
|
||||
|
|
|
@ -8,6 +8,14 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||
import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { StartupProfiler } from './startupProfiler';
|
||||
import { StartupTimings } from './startupTimings';
|
||||
import { RendererProfiling } from 'vs/workbench/contrib/performance/electron-sandbox/rendererAutoProfiler';
|
||||
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(
|
||||
RendererProfiling,
|
||||
'RendererProfiling',
|
||||
LifecyclePhase.Eventually
|
||||
);
|
||||
|
||||
// -- startup profiler
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
|
||||
export class RendererProfiling {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@ITimerService timerService: ITimerService,
|
||||
@INativeHostService nativeHostService: INativeHostService,
|
||||
@ILogService logService: ILogService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IViewDescriptorService viewsDescriptorService: IViewDescriptorService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
) {
|
||||
|
||||
timerService.whenReady().then(() => {
|
||||
|
||||
// SLOW threshold
|
||||
const slowThreshold = (timerService.startupMetrics.timers.ellapsedRequire / 2) | 0;
|
||||
|
||||
// Keep a record of the last events
|
||||
const eventHistory = new RingBuffer<{ command: string; timestamp: number }>(5);
|
||||
this._disposables.add(commandService.onWillExecuteCommand(e => eventHistory.push({ command: e.commandId, timestamp: Date.now() })));
|
||||
|
||||
const sessionDisposables = this._disposables.add(new DisposableStore());
|
||||
|
||||
const obs = new PerformanceObserver(list => {
|
||||
|
||||
let maxDuration = 0;
|
||||
for (const entry of list.getEntries()) {
|
||||
maxDuration = Math.max(maxDuration, entry.duration);
|
||||
}
|
||||
obs.takeRecords();
|
||||
|
||||
if (maxDuration < slowThreshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
// pause observation, we'll take a detailed look
|
||||
obs.disconnect();
|
||||
|
||||
const sessionId = generateUuid();
|
||||
logService.warn(`[perf] Renderer reported VERY LONG TASK (${maxDuration}ms), starting auto profiling session '${sessionId}'`);
|
||||
|
||||
// all visible views
|
||||
const views = viewsDescriptorService.viewContainers.map(container => {
|
||||
const model = viewsDescriptorService.getViewContainerModel(container);
|
||||
return model.visibleViewDescriptors.map(view => view.id);
|
||||
});
|
||||
|
||||
const editors = editorService.visibleEditors.map(editor => editor.typeId);
|
||||
|
||||
// send telemetry event
|
||||
telemetryService.publicLog2<TelemetryEventData, TelemetryEventClassification>('perf.freeze.events', {
|
||||
sessionId: sessionId,
|
||||
timestamp: Date.now() - maxDuration,
|
||||
recentCommands: JSON.stringify(eventHistory.values()),
|
||||
views: JSON.stringify(views.flat()),
|
||||
editors: JSON.stringify(editors),
|
||||
});
|
||||
|
||||
// start heartbeat monitoring
|
||||
nativeHostService.startHeartbeat(sessionId).then(success => {
|
||||
if (!success) {
|
||||
logService.warn('[perf] FAILED to start heartbeat sending');
|
||||
return;
|
||||
}
|
||||
|
||||
// start sending a repeated heartbeat which is expected to be received by the main side
|
||||
const handle1 = setInterval(() => nativeHostService.sendHeartbeat(sessionId), 500);
|
||||
|
||||
// stop heartbeat after 20s
|
||||
const handle2 = setTimeout(() => sessionDisposables.clear(), 20 * 1000);
|
||||
|
||||
// cleanup
|
||||
// - stop heartbeat
|
||||
// - reconnect perf observer
|
||||
sessionDisposables.add(toDisposable(() => {
|
||||
clearInterval(handle1);
|
||||
clearTimeout(handle2);
|
||||
nativeHostService.stopHeartbeat(sessionId);
|
||||
logService.warn(`[perf] STOPPING to send heartbeat`);
|
||||
|
||||
obs.observe({ entryTypes: ['longtask'] });
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
this._disposables.add(toDisposable(() => obs.disconnect()));
|
||||
obs.observe({ entryTypes: ['longtask'] });
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
type TelemetryEventData = {
|
||||
sessionId: string;
|
||||
timestamp: number;
|
||||
recentCommands: string;
|
||||
views: string;
|
||||
editors: string;
|
||||
};
|
||||
|
||||
type TelemetryEventClassification = {
|
||||
owner: 'jrieken';
|
||||
comment: 'Insight about what happened before/while a long task was reported';
|
||||
sessionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Session identifier that allows to correlate CPU samples and events' };
|
||||
timestamp: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Unix time at which the long task approximately happened' };
|
||||
recentCommands: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Events prior to the long task' };
|
||||
views: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Visible views' };
|
||||
editors: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Visible editor' };
|
||||
};
|
||||
|
||||
class RingBuffer<T> {
|
||||
|
||||
private static _value = {};
|
||||
|
||||
private readonly _data: any[];
|
||||
|
||||
private _index: number = 0;
|
||||
private _size: number = 0;
|
||||
|
||||
constructor(size: number) {
|
||||
this._size = size;
|
||||
this._data = new Array(size);
|
||||
this._data.fill(RingBuffer._value, 0, size);
|
||||
}
|
||||
|
||||
push(value: T): void {
|
||||
this._data[this._index] = value;
|
||||
this._index = (this._index + 1) % this._size;
|
||||
}
|
||||
|
||||
values(): T[] {
|
||||
return [...this._data.slice(this._index), ...this._data.slice(0, this._index)].filter(a => a !== RingBuffer._value);
|
||||
}
|
||||
}
|
|
@ -511,7 +511,7 @@ registerAction2(class ViewAsTreeAction extends Action2 {
|
|||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
group: 'navigation',
|
||||
order: 3,
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey.toNegated()),
|
||||
}]
|
||||
});
|
||||
|
@ -540,7 +540,7 @@ registerAction2(class ViewAsListAction extends Action2 {
|
|||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
group: 'navigation',
|
||||
order: 3,
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey),
|
||||
}]
|
||||
});
|
||||
|
|
|
@ -98,7 +98,7 @@ __vsc_update_prompt() {
|
|||
# means the user re-exported the PS1 so we should re-wrap it
|
||||
if [[ "$__vsc_custom_PS1" == "" || "$__vsc_custom_PS1" != "$PS1" ]]; then
|
||||
__vsc_original_PS1=$PS1
|
||||
__vsc_custom_PS1="\[$(__vsc_prompt_start)\]$PREFIX$__vsc_original_PS1\[$(__vsc_prompt_end)\]"
|
||||
__vsc_custom_PS1="\[$(__vsc_prompt_start)\]$__vsc_original_PS1\[$(__vsc_prompt_end)\]"
|
||||
PS1="$__vsc_custom_PS1"
|
||||
fi
|
||||
if [[ "$__vsc_custom_PS2" == "" || "$__vsc_custom_PS2" != "$PS2" ]]; then
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# ---------------------------------------------------------------------------------------------
|
||||
if [[ $options[norcs] = off && -o "login" && -f $USER_ZDOTDIR/.zlogin ]]; then
|
||||
VSCODE_ZDOTDIR=$ZDOTDIR
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
. $USER_ZDOTDIR/.zlogin
|
||||
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
if [[ $options[norcs] = off && -o "login" && -f $ZDOTDIR/.zlogin ]]; then
|
||||
. $ZDOTDIR/.zlogin
|
||||
fi
|
||||
|
|
|
@ -84,14 +84,11 @@ if [[ -o NOUNSET ]]; then
|
|||
if [ -z "${RPROMPT-}" ]; then
|
||||
RPROMPT=""
|
||||
fi
|
||||
if [ -z "${PREFIX-}" ]; then
|
||||
PREFIX=""
|
||||
fi
|
||||
fi
|
||||
__vsc_update_prompt() {
|
||||
__vsc_prior_prompt="$PS1"
|
||||
__vsc_in_command_execution=""
|
||||
PS1="%{$(__vsc_prompt_start)%}$PREFIX$PS1%{$(__vsc_prompt_end)%}"
|
||||
PS1="%{$(__vsc_prompt_start)%}$PS1%{$(__vsc_prompt_end)%}"
|
||||
PS2="%{$(__vsc_continuation_start)%}$PS2%{$(__vsc_continuation_end)%}"
|
||||
if [ -n "$RPROMPT" ]; then
|
||||
__vsc_prior_rprompt="$RPROMPT"
|
||||
|
|
|
@ -242,7 +242,7 @@ export interface ITerminalEditorService extends ITerminalInstanceHost {
|
|||
detachActiveEditorInstance(): ITerminalInstance;
|
||||
detachInstance(instance: ITerminalInstance): void;
|
||||
splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig?: IShellLaunchConfig): ITerminalInstance;
|
||||
revealActiveEditor(preserveFocus?: boolean): void;
|
||||
revealActiveEditor(preserveFocus?: boolean): Promise<void>;
|
||||
resolveResource(instance: ITerminalInstance | URI): URI;
|
||||
reviveInput(deserializedInput: IDeserializedTerminalEditorInput): EditorInput;
|
||||
getInputFromResource(resource: URI): EditorInput;
|
||||
|
@ -935,11 +935,27 @@ export interface ICommandAction extends IAction {
|
|||
addNewLine?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A matcher that runs on a sub-section of a terminal command's output
|
||||
*/
|
||||
export interface ITerminalOutputMatcher {
|
||||
/**
|
||||
* A string or regex to match against the unwrapped line.
|
||||
*/
|
||||
lineMatcher: string | RegExp;
|
||||
anchor?: 'top' | 'bottom';
|
||||
offset?: number;
|
||||
length?: number;
|
||||
/**
|
||||
* Which side of the output to anchor the {@link offset} and {@link length} against.
|
||||
*/
|
||||
anchor: 'top' | 'bottom';
|
||||
/**
|
||||
* How far from either the top or the bottom of the butter to start matching against.
|
||||
*/
|
||||
offset: number;
|
||||
/**
|
||||
* The number of rows to match against, this should be as small as possible for performance
|
||||
* reasons.
|
||||
*/
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface IXtermTerminal {
|
||||
|
|
|
@ -342,9 +342,9 @@ export function registerTerminalActions() {
|
|||
if (instance) {
|
||||
await instance.runRecent('command');
|
||||
if (instance?.target === TerminalLocation.Editor) {
|
||||
terminalEditorService.revealActiveEditor();
|
||||
await terminalEditorService.revealActiveEditor();
|
||||
} else {
|
||||
terminalGroupService.showPanel(false);
|
||||
await terminalGroupService.showPanel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -392,9 +392,9 @@ export function registerTerminalActions() {
|
|||
if (instance) {
|
||||
await instance.runRecent('cwd');
|
||||
if (instance?.target === TerminalLocation.Editor) {
|
||||
terminalEditorService.revealActiveEditor();
|
||||
await terminalEditorService.revealActiveEditor();
|
||||
} else {
|
||||
terminalGroupService.showPanel(false);
|
||||
await terminalGroupService.showPanel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -596,9 +596,9 @@ export function registerTerminalActions() {
|
|||
}
|
||||
instance.sendText(text, true);
|
||||
if (instance.target === TerminalLocation.Editor) {
|
||||
terminalEditorService.revealActiveEditor();
|
||||
await terminalEditorService.revealActiveEditor();
|
||||
} else {
|
||||
terminalGroupService.showPanel();
|
||||
await terminalGroupService.showPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,14 +14,19 @@ export const GitCommandLineRegex = /git/;
|
|||
export const GitPushCommandLineRegex = /git\s+push/;
|
||||
export const AnyCommandLineRegex = /.{4,}/;
|
||||
export const GitSimilarOutputRegex = /most similar command is\s+([^\s]{3,})/;
|
||||
export const FreePortOutputRegex = /address already in use \d\.\d\.\d\.\d:(\d\d\d\d)\s+|Unable to bind [^ ]*:(\d+)|can't listen on port (\d+)|listen EADDRINUSE [^ ]*:(\d+)/;
|
||||
export const GitPushOutputRegex = /git push --set-upstream origin ([^\s]+)\s+/;
|
||||
export const GitCreatePrOutputRegex = /Create a pull request for \'([^\s]+)\' on GitHub by visiting:\s+remote:\s+(https:.+pull.+)\s+/;
|
||||
export const FreePortOutputRegex = /address already in use \d\.\d\.\d\.\d:(\d\d\d\d)|Unable to bind [^ ]*:(\d+)|can't listen on port (\d+)|listen EADDRINUSE [^ ]*:(\d+)/;
|
||||
export const GitPushOutputRegex = /git push --set-upstream origin ([^\s]+)/;
|
||||
export const GitCreatePrOutputRegex = /Create a pull request for \'([^\s]+)\' on GitHub by visiting:\s+remote:\s+(https:.+pull.+)/;
|
||||
|
||||
export function gitSimilarCommand(): ITerminalContextualActionOptions {
|
||||
return {
|
||||
commandLineMatcher: GitCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitSimilarOutputRegex, anchor: 'bottom' },
|
||||
outputMatcher: {
|
||||
lineMatcher: GitSimilarOutputRegex,
|
||||
anchor: 'bottom',
|
||||
offset: 0,
|
||||
length: 3
|
||||
},
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Run git ${matchResult.outputMatch[1]}` : ``,
|
||||
exitStatus: false,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
|
@ -45,7 +50,14 @@ export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITermin
|
|||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Free port ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: AnyCommandLineRegex,
|
||||
outputMatcher: !isWindows ? { lineMatcher: FreePortOutputRegex, anchor: 'bottom' } : undefined,
|
||||
// TODO: Support free port on Windows https://github.com/microsoft/vscode/issues/161775
|
||||
outputMatcher: isWindows ? undefined : {
|
||||
lineMatcher: FreePortOutputRegex,
|
||||
anchor: 'bottom',
|
||||
offset: 0,
|
||||
length: 20
|
||||
},
|
||||
exitStatus: false,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
const port = matchResult?.outputMatch?.[1];
|
||||
if (!port) {
|
||||
|
@ -69,7 +81,12 @@ export function gitPushSetUpstream(): ITerminalContextualActionOptions {
|
|||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Git push ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: GitPushCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitPushOutputRegex, anchor: 'bottom' },
|
||||
outputMatcher: {
|
||||
lineMatcher: GitPushOutputRegex,
|
||||
anchor: 'bottom',
|
||||
offset: 0,
|
||||
length: 5
|
||||
},
|
||||
exitStatus: false,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
const branch = matchResult?.outputMatch?.[1];
|
||||
|
@ -94,7 +111,12 @@ export function gitCreatePr(openerService: IOpenerService): ITerminalContextualA
|
|||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Create PR for ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: GitPushCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitCreatePrOutputRegex, anchor: 'bottom' },
|
||||
outputMatcher: {
|
||||
lineMatcher: GitCreatePrOutputRegex,
|
||||
anchor: 'bottom',
|
||||
offset: 0,
|
||||
length: 5
|
||||
},
|
||||
exitStatus: true,
|
||||
getActions: (matchResult: ContextualMatchResult, command?: ITerminalCommand) => {
|
||||
if (!command) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
|
|||
import { EditorActivation } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IShellLaunchConfig, TerminalLocation } from 'vs/platform/terminal/common/terminal';
|
||||
import { IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { IDeserializedTerminalEditorInput, ITerminalEditorService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
|
||||
|
@ -26,6 +27,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
instances: ITerminalInstance[] = [];
|
||||
private _activeInstanceIndex: number = -1;
|
||||
private _isShuttingDown = false;
|
||||
private _activeOpenEditorRequest?: { instanceId: number; promise: Promise<IEditorPane | undefined> };
|
||||
|
||||
private _terminalEditorActive: IContextKey<boolean>;
|
||||
|
||||
|
@ -140,16 +142,21 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
async openEditor(instance: ITerminalInstance, editorOptions?: TerminalEditorLocation): Promise<void> {
|
||||
const resource = this.resolveResource(instance);
|
||||
if (resource) {
|
||||
await this._editorService.openEditor({
|
||||
resource,
|
||||
description: instance.description || instance.shellLaunchConfig.type,
|
||||
options:
|
||||
{
|
||||
pinned: true,
|
||||
forceReload: true,
|
||||
preserveFocus: editorOptions?.preserveFocus
|
||||
}
|
||||
}, editorOptions?.viewColumn || ACTIVE_GROUP);
|
||||
await this._activeOpenEditorRequest?.promise;
|
||||
this._activeOpenEditorRequest = {
|
||||
instanceId: instance.instanceId,
|
||||
promise: this._editorService.openEditor({
|
||||
resource,
|
||||
description: instance.description || instance.shellLaunchConfig.type,
|
||||
options: {
|
||||
pinned: true,
|
||||
forceReload: true,
|
||||
preserveFocus: editorOptions?.preserveFocus
|
||||
}
|
||||
}, editorOptions?.viewColumn || ACTIVE_GROUP)
|
||||
};
|
||||
await this._activeOpenEditorRequest?.promise;
|
||||
this._activeOpenEditorRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,9 +180,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
this._editorService.openEditor(input, {
|
||||
pinned: true,
|
||||
forceReload: true
|
||||
},
|
||||
input.group
|
||||
);
|
||||
}, input.group);
|
||||
this._registerInstance(inputKey, input, instance);
|
||||
return instanceOrUri;
|
||||
});
|
||||
|
@ -232,13 +237,11 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
this._editorService.openEditor({
|
||||
resource: URI.revive(resource),
|
||||
description: instance.description,
|
||||
options:
|
||||
{
|
||||
options: {
|
||||
pinned: true,
|
||||
forceReload: true
|
||||
}
|
||||
},
|
||||
SIDE_GROUP);
|
||||
}, SIDE_GROUP);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
@ -294,12 +297,17 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
this._onDidChangeInstances.fire();
|
||||
}
|
||||
|
||||
revealActiveEditor(preserveFocus?: boolean): void {
|
||||
async revealActiveEditor(preserveFocus?: boolean): Promise<void> {
|
||||
const instance = this.activeInstance;
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is an active openEditor call for this instance it will be revealed by that
|
||||
if (this._activeOpenEditorRequest?.instanceId === instance.instanceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorInput = this._editorInputs.get(instance.resource.path)!;
|
||||
this._editorService.openEditor(
|
||||
editorInput,
|
||||
|
@ -308,8 +316,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
|
|||
forceReload: true,
|
||||
preserveFocus,
|
||||
activation: EditorActivation.PRESERVE
|
||||
},
|
||||
editorInput.group
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
|||
import { ICommandAction, ITerminalContextualActionOptions } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { DecorationSelector, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal, IDecoration } from 'xterm';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
||||
export interface IContextualAction {
|
||||
|
@ -47,6 +47,8 @@ export class ContextualActionAddon extends Disposable implements ITerminalAddon,
|
|||
|
||||
private _matchActions: ICommandAction[] | undefined;
|
||||
|
||||
private _decoration: IDecoration | undefined;
|
||||
|
||||
constructor(private readonly _capabilities: ITerminalCapabilityStore,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService) {
|
||||
|
@ -83,7 +85,13 @@ export class ContextualActionAddon extends Disposable implements ITerminalAddon,
|
|||
if (!terminal || !commandDetection) {
|
||||
return;
|
||||
}
|
||||
this._register(commandDetection.onCommandExecuted(() => {
|
||||
this._decoration?.dispose();
|
||||
this._decoration = undefined;
|
||||
}));
|
||||
this._register(commandDetection.onCommandFinished(async command => {
|
||||
this._decoration?.dispose();
|
||||
this._decoration = undefined;
|
||||
this._matchActions = getMatchActions(command, this._commandListeners, this._onDidRequestRerunCommand);
|
||||
}));
|
||||
// The buffer is not ready by the time command finish
|
||||
|
@ -107,6 +115,7 @@ export class ContextualActionAddon extends Disposable implements ITerminalAddon,
|
|||
}
|
||||
const actions = this._matchActions;
|
||||
const decoration = this._terminal.registerDecoration({ marker, layer: 'top' });
|
||||
this._decoration = decoration;
|
||||
decoration?.onRender((e: HTMLElement) => {
|
||||
if (!this._decorationMarkerIds.has(decoration.marker.id)) {
|
||||
this._currentQuickFixElement = e;
|
||||
|
@ -143,23 +152,22 @@ export function getMatchActions(command: ITerminalCommand, actionOptions: Map<st
|
|||
outputMatch = command.getOutputMatch(outputMatcher);
|
||||
}
|
||||
const actions = actionOption.getActions({ commandLineMatch, outputMatch }, command);
|
||||
if (!actions) {
|
||||
return matchActions.length === 0 ? undefined : matchActions;
|
||||
}
|
||||
for (const a of actions) {
|
||||
matchActions.push({
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
class: a.class,
|
||||
enabled: a.enabled,
|
||||
run: async () => {
|
||||
await a.run();
|
||||
if (a.commandToRunInTerminal) {
|
||||
onDidRequestRerunCommand?.fire({ command: a.commandToRunInTerminal, addNewLine: a.addNewLine });
|
||||
}
|
||||
},
|
||||
tooltip: a.tooltip
|
||||
});
|
||||
if (actions) {
|
||||
for (const a of actions) {
|
||||
matchActions.push({
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
class: a.class,
|
||||
enabled: a.enabled,
|
||||
run: async () => {
|
||||
await a.run();
|
||||
if (a.commandToRunInTerminal) {
|
||||
onDidRequestRerunCommand?.fire({ command: a.commandToRunInTerminal, addNewLine: a.addNewLine });
|
||||
}
|
||||
},
|
||||
tooltip: a.tooltip
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ suite('ContextualActionAddon', () => {
|
|||
strictEqual(getMatchActions(createCommand(portCommand, `invalid output`, FreePortOutputRegex), expected), undefined);
|
||||
});
|
||||
});
|
||||
test.skip('returns actions', () => {
|
||||
test('returns actions', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(portCommand, output, FreePortOutputRegex), expected), actionOptions);
|
||||
});
|
||||
});
|
||||
|
@ -127,7 +127,7 @@ suite('ContextualActionAddon', () => {
|
|||
const output = `fatal: The current branch test22 has no upstream branch.
|
||||
To push the current branch and set the remote as upstream, use
|
||||
|
||||
git push --set-upstream origin test22 `;
|
||||
git push --set-upstream origin test22`;
|
||||
const exitCode = 128;
|
||||
const actions = [
|
||||
{
|
||||
|
@ -151,7 +151,7 @@ suite('ContextualActionAddon', () => {
|
|||
strictEqual(getMatchActions(createCommand(`git status`, output, GitPushOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
suite('returns actions when', () => {
|
||||
test('expected unix exit code', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitPushOutputRegex, exitCode), expectedMap), actions);
|
||||
});
|
||||
|
@ -204,6 +204,47 @@ suite('ContextualActionAddon', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
suite('gitPush - multiple providers', () => {
|
||||
const expectedMap = new Map();
|
||||
const command = `git push`;
|
||||
const output = `fatal: The current branch test22 has no upstream branch.
|
||||
To push the current branch and set the remote as upstream, use
|
||||
|
||||
git push --set-upstream origin test22`;
|
||||
const exitCode = 128;
|
||||
const actions = [
|
||||
{
|
||||
id: 'terminal.gitPush',
|
||||
label: 'Git push test22',
|
||||
run: true,
|
||||
tooltip: 'Git push test22',
|
||||
enabled: true
|
||||
}
|
||||
];
|
||||
setup(() => {
|
||||
const pushCommand = gitPushSetUpstream();
|
||||
const prCommand = gitCreatePr(openerService);
|
||||
contextualActionAddon.registerCommandFinishedListener(pushCommand);
|
||||
contextualActionAddon.registerCommandFinishedListener(prCommand);
|
||||
expectedMap.set(pushCommand.commandLineMatcher.toString(), [pushCommand, prCommand]);
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
test('output does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, `invalid output`, GitPushOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('command does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(`git status`, output, GitPushOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
});
|
||||
suite('returns actions when', () => {
|
||||
test('expected unix exit code', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitPushOutputRegex, exitCode), expectedMap), actions);
|
||||
});
|
||||
test('matching exit status', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitPushOutputRegex, 2), expectedMap), actions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createCommand(command: string, output: string, outputMatcher?: RegExp | string, exitCode?: number): ITerminalCommand {
|
||||
|
|
|
@ -45,7 +45,7 @@ suite('ShellIntegrationAddon', () => {
|
|||
xterm = new Terminal({ allowProposedApi: true, cols: 80, rows: 30 });
|
||||
const instantiationService = new TestInstantiationService();
|
||||
instantiationService.stub(ILogService, NullLogService);
|
||||
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon);
|
||||
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon, true, undefined);
|
||||
xterm.loadAddon(shellIntegrationAddon);
|
||||
capabilities = shellIntegrationAddon.capabilities;
|
||||
});
|
||||
|
|
|
@ -162,7 +162,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
|
|||
super({
|
||||
id: `workbench.profiles.actions.updateCurrentProfileShortName`,
|
||||
title: {
|
||||
value: localize('change short name profile', "Change Short Name ({0})...", that.userDataProfileService.currentProfile.shortName),
|
||||
value: localize('change short name profile', "Change Short Name ({0})...", that.userDataProfileService.getShortName(that.userDataProfileService.currentProfile)),
|
||||
original: `Change Short Name (${that.userDataProfileService.currentProfile.shortName})...`
|
||||
},
|
||||
menu: [
|
||||
|
|
|
@ -345,7 +345,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv
|
|||
description: fromNow(syncResourceHandle.created, true),
|
||||
themeIcon: FolderThemeIcon,
|
||||
syncResourceHandle,
|
||||
contextValue: `sync-resource-${syncResourceHandle.syncResource}`
|
||||
contextValue: `sync-resource-${syncResourceHandle.syncResource.syncResource}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -669,7 +669,16 @@ export class NativeWindow extends Disposable {
|
|||
|
||||
// Installation Dir Warning
|
||||
if (this.environmentService.isBuilt) {
|
||||
const installLocationUri = URI.file(this.environmentService.appRoot);
|
||||
let installLocationUri: URI;
|
||||
if (isMacintosh) {
|
||||
// appRoot = /Applications/Visual Studio Code - Insiders.app/Contents/Resources/app
|
||||
installLocationUri = dirname(dirname(dirname(URI.file(this.environmentService.appRoot))));
|
||||
} else {
|
||||
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app
|
||||
// appRoot = /usr/share/code-insiders/resources/app
|
||||
installLocationUri = dirname(dirname(URI.file(this.environmentService.appRoot)));
|
||||
}
|
||||
|
||||
for (const folder of this.contextService.getWorkspace().folders) {
|
||||
if (this.uriIdentityService.extUri.isEqualOrParent(folder.uri, installLocationUri)) {
|
||||
this.bannerService.show({
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue