Merge branch 'main' into build-integrated-cli

This commit is contained in:
Martin Aeschlimann 2022-09-26 18:47:52 +02:00
commit 081133f8e7
No known key found for this signature in database
GPG key ID: 4B615CF8ED6ADC96
109 changed files with 2078 additions and 795 deletions

View file

@ -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,

View file

@ -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,

View file

@ -14,6 +14,7 @@
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string"] }
{ "open": "/*", "close": "*/", "notIn": ["string", "comment"] },
],
"surroundingPairs": [
["{", "}"],

View file

@ -27,9 +27,9 @@
{
"id": "properties",
"extensions": [
".conf",
".properties",
".cfg",
".conf",
".directory",
".gitattributes",
".gitconfig",

View file

@ -61,7 +61,8 @@
"filenames": [
"babel.config.json",
".babelrc.json",
".ember-cli"
".ember-cli",
"typedoc.json"
],
"configuration": "./language-configuration.json"
}

View file

@ -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]];
}
}

View file

@ -6,7 +6,7 @@
"git": {
"name": "seti-ui",
"repositoryUrl": "https://github.com/jesseweed/seti-ui",
"commitHash": "2d10473b7575ec00c47eda751ea9caeec6b0b606"
"commitHash": "fd20793e5a75b350eab8d489165fb9b420df3f62"
}
},
"version": "0.1.0"

View file

@ -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"
}

View file

@ -139,6 +139,10 @@
{
"fileMatch": "jsconfig.*.json",
"url": "./schemas/jsconfig.schema.json"
},
{
"fileMatch": "typedoc.json",
"url": "https://typedoc.org/schema.json"
}
],
"configuration": {

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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);
});

View file

@ -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)));
});

View file

@ -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> {

View file

@ -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,
) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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"
},

View file

@ -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"
},

View file

@ -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"
}
}

View file

@ -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==

View file

@ -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"

View file

@ -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)

View file

@ -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)

View file

@ -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%
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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));
}

View file

@ -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 {

View file

@ -83,7 +83,7 @@
}
.monaco-tree-type-filter.disabled {
top: -40px;
top: -40px !important;
}
.monaco-tree-type-filter-grab {

View file

@ -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() { }

View file

@ -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;

View file

@ -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;

View file

@ -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)));
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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(

View file

@ -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' },

View file

@ -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[] = [];

View file

@ -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>;

View file

@ -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)

View file

@ -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));
}
}

View 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);
}
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
});
}

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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."),
}
}
};

View file

@ -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
)

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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#`')
},
}
});

View file

@ -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');
}
});

View file

@ -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();
}
});

View file

@ -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>;

View file

@ -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 () {

View file

@ -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]);

View file

@ -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]);

View file

@ -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);

View file

@ -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),
});
}

View file

@ -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',

View file

@ -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);

View file

@ -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);

View file

@ -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') });

View file

@ -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());
}

View file

@ -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);
}

View file

@ -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>;

View file

@ -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};
}
`);
}
});

View file

@ -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) {

View file

@ -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() {

View file

@ -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) {

View file

@ -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

View file

@ -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);
}
}

View file

@ -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),
}]
});

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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 {

View file

@ -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();
}
}
});

View file

@ -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) {

View file

@ -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
}
);
}
}

View file

@ -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
});
}
}
}
}

View file

@ -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 {

View file

@ -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;
});

View file

@ -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: [

View file

@ -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}`
};
});
}

View file

@ -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