Merge branch 'master' into joao/submenus

This commit is contained in:
João Moreno 2020-07-23 16:46:41 +02:00
commit 187e9ddf5e
138 changed files with 2961 additions and 1121 deletions

View file

@ -35,7 +35,7 @@ jobs:
upvotesRequired: 20
numCommentsOverride: 20
initComment: "This feature request is now a candidate for our backlog. The community has 60 days to [upvote](https://github.com/microsoft/vscode/wiki/Issues-Triaging#up-voting-a-feature-request) the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!"
warnComment: "This feature request has not yet received the 20 community [upvotes](https://github.com/microsoft/vscode/wiki/Issues-Triaging#up-voting-a-feature-request) it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding"
warnComment: "This feature request has not yet received the 20 community [upvotes](https://github.com/microsoft/vscode/wiki/Issues-Triaging#up-voting-a-feature-request) it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!"
acceptComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!"
rejectComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!"
warnDays: 10

View file

@ -44,7 +44,7 @@
"minimist": "^1.2.3",
"request": "^2.85.0",
"terser": "4.3.8",
"typescript": "^4.0.0-dev.20200715",
"typescript": "^4.0.0-dev.20200722",
"vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.6.0",
"xml2js": "^0.4.17"

View file

@ -2530,10 +2530,10 @@ typescript@^3.0.1:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^4.0.0-dev.20200715:
version "4.0.0-dev.20200715"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200715.tgz#d65961a5a6f13fde95a6f4db5f5946f15e4c59bc"
integrity sha512-gmPXoWktfXeutmWTM6el9U4vIn5kqOHGI1OESSOhPtLWrxodKqLfFuMygQtOUTtGjKLFQRFAJhHEwUhHZNOURA==
typescript@^4.0.0-dev.20200722:
version "4.0.0-dev.20200722"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200722.tgz#b59dd5a3cd84a98d5aae0e4f3a3c58f0c81a3b9b"
integrity sha512-MmJ1YyPNK3JYeKLiTg5sQXdeZaMgt99Fg4BMRZhJmhoq1/x2V1cpXMYvE1rtIYl9K7NvmTDdU3WDW7ZOD6ybaw==
typical@^4.0.0:
version "4.0.0"

View file

@ -54,7 +54,7 @@
"url": "vscode://schemas/keybindings"
},
{
"fileMatch": "vscode://defaultsettings/*/*.json",
"fileMatch": "vscode://defaultsettings/defaultSettings.json",
"url": "vscode://schemas/settings/default"
},
{

View file

@ -1801,7 +1801,7 @@
"Ignore",
"ignore"
],
"filenames": [
"extensions": [
".gitignore_global",
".gitignore"
],

View file

@ -74,7 +74,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
new GitTimelineProvider(model)
);
await checkGitVersion(info);
checkGitVersion(info);
return model;
}

View file

@ -40,13 +40,13 @@
"languages": [
{
"id": "ignore",
"filenames": [
"extensions": [
".npmignore"
]
},
{
"id": "properties",
"filenames": [
"extensions": [
".npmrc"
]
}

View file

@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Dependencies shared by all extensions",
"dependencies": {
"typescript": "3.9.6"
"typescript": "3.9.7"
},
"scripts": {
"postinstall": "node ./postinstall"

View file

@ -7,6 +7,8 @@
'use strict';
const CopyPlugin = require('copy-webpack-plugin');
const { lchmod } = require('graceful-fs');
const Terser = require('terser');
const withBrowserDefaults = require('../shared.webpack.config').browser;
@ -19,7 +21,22 @@ module.exports = withBrowserDefaults({
// @ts-ignore
new CopyPlugin({
patterns: [
{ from: 'node_modules/typescript-web-server', to: 'typescript-web' }
{
from: 'node_modules/typescript-web-server',
to: 'typescript-web',
transform: (content, absoluteFrom) => {
if (absoluteFrom.endsWith('tsserver.js')) {
return Terser.minify(content.toString()).code;
}
return content;
},
transformPath: (targetPath) => {
if (targetPath.endsWith('tsserver.js')) {
return targetPath.replace('tsserver.js', 'tsserver.web.js');
}
return targetPath;
}
}
],
}),
],

View file

@ -28,6 +28,7 @@
"@types/rimraf": "2.0.2",
"@types/semver": "^5.5.0",
"copy-webpack-plugin": "^6.0.3",
"terser": "^4.8.0",
"typescript-web-server": "git://github.com/mjbvz/ts-server-web-build",
"vscode": "^1.1.36"
},
@ -685,6 +686,22 @@
"description": "%typescript.preferences.importModuleSpecifierEnding%",
"scope": "resource"
},
"typescript.preferences.includePackageJsonAutoImports": {
"type": "string",
"enum": [
"all",
"exclude-dev",
"none"
],
"enumDescriptions": [
"%typescript.preferences.includePackageJsonAutoImports.all%",
"%typescript.preferences.includePackageJsonAutoImports.excludeDev%",
"%typescript.preferences.includePackageJsonAutoImports.none%"
],
"default": "exclude-dev",
"markdownDescription": "%typescript.preferences.includePackageJsonAutoImports%",
"scope": "window"
},
"javascript.preferences.renameShorthandProperties": {
"type": "boolean",
"default": true,

View file

@ -74,8 +74,12 @@
"typescript.preferences.importModuleSpecifierEnding": "Preferred path ending for auto imports.",
"typescript.preferences.importModuleSpecifierEnding.auto": "Use project settings to select a default.",
"typescript.preferences.importModuleSpecifierEnding.minimal": "Shorten `./component/index.js` to `./component`.",
"typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`",
"typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`.",
"typescript.preferences.importModuleSpecifierEnding.js": "Do not shorten path endings; include the `.js` extension.",
"typescript.preferences.includePackageJsonAutoImports": "Enable/disable processing `package.json` dependencies for available auto imports.",
"typescript.preferences.includePackageJsonAutoImports.all": "Include all listed dependencies.",
"typescript.preferences.includePackageJsonAutoImports.excludeDev": "Exclude devDependencies.",
"typescript.preferences.includePackageJsonAutoImports.none": "Disable package.json dependency processing.",
"typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code. Requires using TypeScript 2.9 or newer in the workspace.",
"typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.",
"typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.",

View file

@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Command } from '../utils/commandManager';
import { PluginManager } from '../utils/plugins';
import { Command } from './commandManager';
export class ConfigurePluginCommand implements Command {
public readonly id = '_typescript.configurePlugin';

View file

@ -5,9 +5,9 @@
import * as vscode from 'vscode';
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { Command } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { openProjectConfigForFile, ProjectType } from '../utils/tsconfig';
import { Command } from './commandManager';
export class TypeScriptGoToProjectConfigCommand implements Command {
public readonly id = 'typescript.goToProjectConfig';

View file

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { CommandManager } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { PluginManager } from '../utils/plugins';
import { CommandManager } from './commandManager';
import { ConfigurePluginCommand } from './configurePlugin';
import { JavaScriptGoToProjectConfigCommand, TypeScriptGoToProjectConfigCommand } from './goToProjectConfiguration';
import { LearnMoreAboutRefactoringsCommand } from './learnMoreAboutRefactorings';

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Command } from '../utils/commandManager';
import { isTypeScriptDocument } from '../utils/languageModeIds';
import { Command } from './commandManager';
export class LearnMoreAboutRefactoringsCommand implements Command {
public static readonly id = '_typescript.learnMoreAboutRefactorings';

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { Command } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { Command } from './commandManager';
export class OpenTsServerLogCommand implements Command {
public readonly id = 'typescript.openTsServerLog';

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { Command } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { Command } from './commandManager';
export class ReloadTypeScriptProjectsCommand implements Command {
public readonly id = 'typescript.reloadProjects';

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { Command } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { Command } from './commandManager';
export class RestartTsServerCommand implements Command {
public readonly id = 'typescript.restartTsServer';

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
import { Command } from '../utils/commandManager';
import { Lazy } from '../utils/lazy';
import { Command } from './commandManager';
export class SelectTypeScriptVersionCommand implements Command {
public readonly id = 'typescript.selectTypeScriptVersion';

View file

@ -11,9 +11,9 @@ import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './tsServer/versionProvider';
import { WorkerServerProcess } from './tsServer/workerServerProcess';
import { WorkerServerProcess } from './tsServer/serverProcess.browser';
import API from './utils/api';
import { CommandManager } from './utils/commandManager';
import { CommandManager } from './commands/commandManager';
import { TypeScriptServiceConfiguration } from './utils/configuration';
import { PluginManager } from './utils/plugins';
@ -52,7 +52,7 @@ export function activate(
const versionProvider = new StaticVersionProvider(
new TypeScriptVersion(
TypeScriptVersionSource.Bundled,
'/builtin-extension/typescript-language-features/dist/browser/typescript-web/tsserver.js',
'/builtin-extension/typescript-language-features/dist/browser/typescript-web/tsserver.web.js',
API.v400));
const lazyClientHost = createLazyClientHost(context, false, {

View file

@ -11,12 +11,12 @@ import { LanguageConfigurationManager } from './languageFeatures/languageConfigu
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron';
import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron';
import { ChildServerProcess } from './tsServer/serverProcess';
import { ChildServerProcess } from './tsServer/serverProcess.electron';
import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron';
import { CommandManager } from './utils/commandManager';
import * as electron from './utils/electron';
import { onCaseInsenitiveFileSystem } from './utils/fileSystem';
import { CommandManager } from './commands/commandManager';
import { onCaseInsenitiveFileSystem } from './utils/fileSystem.electron';
import { PluginManager } from './utils/plugins';
import * as temp from './utils/temp.electron';
export function activate(
context: vscode.ExtensionContext
@ -62,5 +62,5 @@ export function activate(
}
export function deactivate() {
rimraf.sync(electron.getInstanceDir());
rimraf.sync(temp.getInstanceTempDir());
}

View file

@ -5,14 +5,14 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Command, CommandManager } from '../commands/commandManager';
import type * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { ClientCapability, ITypeScriptServiceClient, ServerResponse } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { applyCodeAction } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { conditionalRegistration, requireSomeCapability, requireConfiguration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import { parseKindModifier } from '../utils/modifiers';
import * as Previewer from '../utils/previewer';
@ -330,10 +330,25 @@ class CompletionAcceptedCommand implements Command {
public constructor(
private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void,
private readonly telemetryReporter: TelemetryReporter,
) { }
public execute(item: vscode.CompletionItem) {
this.onCompletionAccepted(item);
if (item instanceof MyCompletionItem) {
/* __GDPR__
"completions.accept" : {
"isPackageJsonImport" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.telemetryReporter.logTelemetry('completions.accept', {
// @ts-expect-error - remove after TS 4.0 protocol update
isPackageJsonImport: item.tsEntry.isPackageJsonImport ? 'true' : undefined,
});
}
}
}
@ -422,7 +437,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider<
) {
commandManager.register(new ApplyCompletionCodeActionCommand(this.client));
commandManager.register(new CompositeCommand());
commandManager.register(new CompletionAcceptedCommand(onCompletionAccepted));
commandManager.register(new CompletionAcceptedCommand(onCompletionAccepted, this.telemetryReporter));
}
public async provideCompletionItems(
@ -471,34 +486,18 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider<
let dotAccessorContext: DotAccessorContext | undefined;
let entries: ReadonlyArray<Proto.CompletionEntry>;
let metadata: any | undefined;
let response: ServerResponse.Response<Proto.CompletionInfoResponse> | undefined;
let duration: number | undefined;
if (this.client.apiVersion.gte(API.v300)) {
const startTime = Date.now();
let response: ServerResponse.Response<Proto.CompletionInfoResponse> | undefined;
try {
response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token));
} finally {
const duration: number = Date.now() - startTime;
/* __GDPR__
"completions.execute" : {
"duration" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"type" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"count" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"updateGraphDurationMs" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.telemetryReporter.logTelemetry('completions.execute', {
duration: duration,
type: response?.type ?? 'unknown',
count: response?.type === 'response' && response.body ? response.body.entries.length : 0,
updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined,
});
duration = Date.now() - startTime;
}
if (response.type !== 'response' || !response.body) {
this.logCompletionsTelemetry(duration, response);
return null;
}
isNewIdentifierLocation = response.body.isNewIdentifierLocation;
@ -536,15 +535,47 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider<
useFuzzyWordRangeLogic: this.client.apiVersion.lt(API.v390),
};
let includesPackageJsonImport = false;
const items: MyCompletionItem[] = [];
for (let entry of entries) {
if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) {
items.push(new MyCompletionItem(position, document, entry, completionContext, metadata));
// @ts-expect-error - remove after TS 4.0 protocol update
includesPackageJsonImport = !!entry.isPackageJsonImport;
}
}
if (duration !== undefined) {
this.logCompletionsTelemetry(duration, response, includesPackageJsonImport);
}
return new vscode.CompletionList(items, isIncomplete);
}
private logCompletionsTelemetry(
duration: number,
response: ServerResponse.Response<Proto.CompletionInfoResponse> | undefined,
includesPackageJsonImport?: boolean
) {
/* __GDPR__
"completions.execute" : {
"duration" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"type" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"count" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"updateGraphDurationMs" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"includesPackageJsonImport" : { "classification": "SystemMetadata", "purpose": "FeatureInsight" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.telemetryReporter.logTelemetry('completions.execute', {
duration: duration,
type: response?.type ?? 'unknown',
count: response?.type === 'response' && response.body ? response.body.entries.length : 0,
updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined,
includesPackageJsonImport: includesPackageJsonImport ? 'true' : undefined,
});
}
private getTsTriggerCharacter(context: vscode.CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
switch (context.triggerCharacter) {
case '@': // Workaround for https://github.com/Microsoft/TypeScript/issues/27321

View file

@ -9,7 +9,7 @@ import type * as Proto from '../protocol';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { Command, CommandManager } from '../utils/commandManager';
import { Command, CommandManager } from '../commands/commandManager';
import { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import { TelemetryReporter } from '../utils/telemetry';

View file

@ -5,21 +5,21 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Command, CommandManager } from '../commands/commandManager';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient, ClientCapability } from '../typescriptService';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import * as fixNames from '../utils/fixNames';
import { memoize } from '../utils/memoize';
import { equals } from '../utils/objects';
import { TelemetryReporter } from '../utils/telemetry';
import * as typeConverters from '../utils/typeConverters';
import { DiagnosticsManager } from './diagnostics';
import FileConfigurationManager from './fileConfigurationManager';
import { equals } from '../utils/objects';
import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
const localize = nls.loadMessageBundle();

View file

@ -5,13 +5,13 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Command, CommandManager } from '../commands/commandManager';
import { LearnMoreAboutRefactoringsCommand } from '../commands/learnMoreAboutRefactorings';
import type * as Proto from '../protocol';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { Command, CommandManager } from '../utils/commandManager';
import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import * as fileSchemes from '../utils/fileSchemes';
import { TelemetryReporter } from '../utils/telemetry';

View file

@ -9,7 +9,7 @@ import { DiagnosticKind } from './languageFeatures/diagnostics';
import FileConfigurationManager from './languageFeatures/fileConfigurationManager';
import { CachedResponse } from './tsServer/cachedResponse';
import TypeScriptServiceClient from './typescriptServiceClient';
import { CommandManager } from './utils/commandManager';
import { CommandManager } from './commands/commandManager';
import { Disposable } from './utils/dispose';
import { DocumentSelector } from './utils/documentSelector';
import * as fileSchemes from './utils/fileSchemes';

View file

@ -4,15 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { CommandManager } from './commands/commandManager';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { TsServerProcessFactory } from './tsServer/server';
import { ITypeScriptVersionProvider } from './tsServer/versionProvider';
import TypeScriptServiceClientHost from './typeScriptServiceClientHost';
import { flatten } from './utils/arrays';
import { CommandManager } from './utils/commandManager';
import { standardLanguageDescriptions } from './utils/languageDescription';
import * as ProjectStatus from './utils/largeProjectStatus';
import { lazy, Lazy } from './utils/lazy';
import ManagedFileContextManager from './utils/managedFileContext';
import { PluginManager } from './utils/plugins';
@ -40,13 +39,6 @@ export function createLazyClientHost(
context.subscriptions.push(clientHost);
clientHost.serviceClient.onReady(() => {
context.subscriptions.push(
ProjectStatus.create(
clientHost.serviceClient,
clientHost.serviceClient.telemetryReporter));
});
return clientHost;
});
}

View file

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { OngoingRequestCanceller, OngoingRequestCancellerFactory } from './cancellation';
import { getTempFile } from '../utils/electron';
import { getTempFile } from '../utils/temp.electron';
import Tracer from '../utils/tracer';
import { OngoingRequestCanceller, OngoingRequestCancellerFactory } from './cancellation';
export class NodeRequestCanceller implements OngoingRequestCanceller {
public readonly cancellationPipeName: string;

View file

@ -3,17 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { ChildProcess } from 'child_process';
import * as child_process from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import type { Readable } from 'stream';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { TsServerProcess, TsServerProcessKind } from './server';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { fork } from '../utils/electron';
import { TypeScriptVersionManager } from './versionManager';
import { Disposable } from '../utils/dispose';
import { TsServerProcess, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
const localize = nls.loadMessageBundle();
@ -149,20 +149,36 @@ export class ChildServerProcess extends Disposable implements TsServerProcess {
versionManager.reset();
tsServerPath = versionManager.currentVersion.tsServerPath;
}
const childProcess = fork(tsServerPath, args, this.getForkOptions(kind, configuration));
const childProcess = child_process.fork(tsServerPath, args, {
silent: true,
cwd: undefined,
env: this.generatePatchedEnv(process.env, tsServerPath),
execArgv: this.getExecArgv(kind, configuration),
});
return new ChildServerProcess(childProcess);
}
private static getForkOptions(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration) {
private static generatePatchedEnv(env: any, modulePath: string): any {
const newEnv = Object.assign({}, env);
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
// Ensure we always have a PATH set
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
return newEnv;
}
private static getExecArgv(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration): string[] {
const debugPort = this.getDebugPort(kind);
const inspectFlag = process.env['TSS_DEBUG_BRK'] ? '--inspect-brk' : '--inspect';
const tsServerForkOptions: any = {
execArgv: [
...(debugPort ? [`${inspectFlag}=${debugPort}`] : []),
...(configuration.maxTsServerMemory ? [`--max-old-space-size=${configuration.maxTsServerMemory}`] : [])
]
};
return tsServerForkOptions;
return [
...(debugPort ? [`${inspectFlag}=${debugPort}`] : []),
...(configuration.maxTsServerMemory ? [`--max-old-space-size=${configuration.maxTsServerMemory}`] : [])
];
}
private static getDebugPort(kind: TsServerProcessKind): number | undefined {
@ -181,7 +197,7 @@ export class ChildServerProcess extends Disposable implements TsServerProcess {
}
private constructor(
private readonly _process: ChildProcess,
private readonly _process: child_process.ChildProcess,
) {
super();
this._reader = this._register(new Reader<Proto.Response>(this._process.stdout!));

View file

@ -5,9 +5,9 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Command, CommandManager } from '../commands/commandManager';
import { ITypeScriptServiceClient } from '../typescriptService';
import { coalesce } from '../utils/arrays';
import { Command, CommandManager } from '../utils/commandManager';
import { Disposable } from '../utils/dispose';
import { isTypeScriptDocument } from '../utils/languageModeIds';
import { isImplicitProjectConfigFile, openOrCreateConfig, openProjectConfigForFile, openProjectConfigOrPromptToCreate, ProjectType } from '../utils/tsconfig';

View file

@ -21,13 +21,14 @@ import { ITypeScriptVersionProvider } from './tsServer/versionProvider';
import VersionStatus from './tsServer/versionStatus';
import TypeScriptServiceClient from './typescriptServiceClient';
import { coalesce, flatten } from './utils/arrays';
import { CommandManager } from './utils/commandManager';
import { CommandManager } from './commands/commandManager';
import { Disposable } from './utils/dispose';
import * as errorCodes from './utils/errorCodes';
import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription';
import { PluginManager } from './utils/plugins';
import * as typeConverters from './utils/typeConverters';
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
import * as ProjectStatus from './utils/largeProjectStatus';
namespace Experimental {
export interface Diagnostic extends Proto.Diagnostic {
@ -95,6 +96,8 @@ export default class TypeScriptServiceClientHost extends Disposable {
this._register(new VersionStatus(this.client, services.commandManager));
this._register(new AtaProgressReporter(this.client));
this.typingsStatus = this._register(new TypingsStatus(this.client));
this._register(ProjectStatus.create(this.client));
this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsenitiveFileSystem));
for (const description of descriptions) {

View file

@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import BufferSyncSupport from './languageFeatures/bufferSyncSupport';
import * as Proto from './protocol';
import BufferSyncSupport from './tsServer/bufferSyncSupport';
import { ExectuionTarget } from './tsServer/server';
import { TypeScriptVersion } from './tsServer/versionProvider';
import API from './utils/api';
import { TypeScriptServiceConfiguration } from './utils/configuration';
import { PluginManager } from './utils/plugins';
import { TelemetryReporter } from './utils/telemetry';
export namespace ServerResponse {
@ -159,9 +160,11 @@ export interface ITypeScriptServiceClient {
showVersionPicker(): void;
readonly apiVersion: API;
readonly pluginManager: PluginManager;
readonly configuration: TypeScriptServiceConfiguration;
readonly bufferSyncSupport: BufferSyncSupport;
readonly telemetryReporter: TelemetryReporter;
execute<K extends keyof StandardTsServerRequests>(
command: K,

View file

@ -6,10 +6,10 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import BufferSyncSupport from './languageFeatures/bufferSyncSupport';
import { DiagnosticKind, DiagnosticsManager } from './languageFeatures/diagnostics';
import * as Proto from './protocol';
import { EventName } from './protocol.const';
import BufferSyncSupport from './tsServer/bufferSyncSupport';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { ITypeScriptServer, TsServerProcessFactory } from './tsServer/server';
@ -115,11 +115,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
private readonly loadingIndicator = new ServerInitializingIndicator();
public readonly telemetryReporter: TelemetryReporter;
public readonly bufferSyncSupport: BufferSyncSupport;
public readonly diagnosticsManager: DiagnosticsManager;
public readonly pluginManager: PluginManager;
private readonly logDirectoryProvider: ILogDirectoryProvider;
private readonly cancellerFactory: OngoingRequestCancellerFactory;
private readonly versionProvider: ITypeScriptVersionProvider;
@ -527,6 +526,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
preferences: {
providePrefixAndSuffixTextForRename: true,
allowRenameOfImportPath: true,
// @ts-expect-error, remove after 4.0 protocol update
includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports,
},
watchOptions
};

View file

@ -66,6 +66,7 @@ export class TypeScriptServiceConfiguration {
public readonly maxTsServerMemory: number;
public readonly enablePromptUseWorkspaceTsdk: boolean;
public readonly watchOptions: protocol.WatchOptions | undefined;
public readonly includePackageJsonAutoImports: string | undefined;
public static loadFromWorkspace(): TypeScriptServiceConfiguration {
return new TypeScriptServiceConfiguration();
@ -88,6 +89,7 @@ export class TypeScriptServiceConfiguration {
this.maxTsServerMemory = TypeScriptServiceConfiguration.readMaxTsServerMemory(configuration);
this.enablePromptUseWorkspaceTsdk = TypeScriptServiceConfiguration.readEnablePromptUseWorkspaceTsdk(configuration);
this.watchOptions = TypeScriptServiceConfiguration.readWatchOptions(configuration);
this.includePackageJsonAutoImports = TypeScriptServiceConfiguration.readIncludePackageJsonAutoImports(configuration);
}
public isEqualTo(other: TypeScriptServiceConfiguration): boolean {
@ -104,7 +106,8 @@ export class TypeScriptServiceConfiguration {
&& this.enableProjectDiagnostics === other.enableProjectDiagnostics
&& this.maxTsServerMemory === other.maxTsServerMemory
&& objects.equals(this.watchOptions, other.watchOptions)
&& this.enablePromptUseWorkspaceTsdk === other.enablePromptUseWorkspaceTsdk;
&& this.enablePromptUseWorkspaceTsdk === other.enablePromptUseWorkspaceTsdk
&& this.includePackageJsonAutoImports === other.includePackageJsonAutoImports;
}
private static fixPathPrefixes(inspectValue: string): string {
@ -178,6 +181,10 @@ export class TypeScriptServiceConfiguration {
return configuration.get<protocol.WatchOptions>('typescript.tsserver.watchOptions');
}
private static readIncludePackageJsonAutoImports(configuration: vscode.WorkspaceConfiguration): string | undefined {
return configuration.get<string>('typescript.preferences.includePackageJsonAutoImports');
}
private static readMaxTsServerMemory(configuration: vscode.WorkspaceConfiguration): number {
const defaultMaxMemory = 3072;
const minimumMaxMemory = 128;

View file

@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as temp from './temp';
import path = require('path');
import fs = require('fs');
import cp = require('child_process');
import process = require('process');
const getRootTempDir = (() => {
let dir: string | undefined;
return () => {
if (!dir) {
dir = temp.getTempFile(`vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`);
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return dir;
};
})();
export const getInstanceDir = (() => {
let dir: string | undefined;
return () => {
if (!dir) {
dir = path.join(getRootTempDir(), temp.makeRandomHexString(20));
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return dir;
};
})();
export function getTempFile(prefix: string): string {
return path.join(getInstanceDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`);
}
function generatePatchedEnv(env: any, modulePath: string): any {
const newEnv = Object.assign({}, env);
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
// Ensure we always have a PATH set
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
return newEnv;
}
export interface ForkOptions {
readonly cwd?: string;
readonly execArgv?: string[];
}
export function fork(
modulePath: string,
args: readonly string[],
options: ForkOptions,
): cp.ChildProcess {
const newEnv = generatePatchedEnv(process.env, modulePath);
return cp.fork(modulePath, args, {
silent: true,
cwd: options.cwd,
env: newEnv,
execArgv: options.execArgv
});
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { getTempFile } from '../utils/temp';
import { getTempFile } from './temp.electron';
export const onCaseInsenitiveFileSystem = (() => {
let value: boolean | undefined;

View file

@ -111,16 +111,15 @@ function onConfigureExcludesSelected(
export function create(
client: ITypeScriptServiceClient,
telemetryReporter: TelemetryReporter
) {
): vscode.Disposable {
const toDispose: vscode.Disposable[] = [];
const item = new ExcludeHintItem(telemetryReporter);
const item = new ExcludeHintItem(client.telemetryReporter);
toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
if (item.configFileName) {
onConfigureExcludesSelected(client, item.configFileName);
}
let { message } = item.getCurrentHint();
const { message } = item.getCurrentHint();
return vscode.window.showInformationMessage(message);
}));
@ -128,4 +127,3 @@ export function create(
return vscode.Disposable.from(...toDispose);
}

View file

@ -3,6 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function isWeb(): boolean {
return typeof process === 'undefined';
// @ts-expect-error
return typeof navigator !== 'undefined' && vscode.env.uiKind === vscode.UIKind.Web;
}

View file

@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
function makeRandomHexString(length: number): string {
const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let result = '';
for (let i = 0; i < length; i++) {
const idx = Math.floor(chars.length * Math.random());
result += chars[idx];
}
return result;
}
const getRootTempDir = (() => {
let dir: string | undefined;
return () => {
if (!dir) {
const filename = `vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`;
dir = path.join(os.tmpdir(), filename);
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return dir;
};
})();
export const getInstanceTempDir = (() => {
let dir: string | undefined;
return () => {
if (!dir) {
dir = path.join(getRootTempDir(), makeRandomHexString(20));
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return dir;
};
})();
export function getTempFile(prefix: string): string {
return path.join(getInstanceTempDir(), `${prefix}-${makeRandomHexString(20)}.tmp`);
}

View file

@ -1,21 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import path = require('path');
import os = require('os');
export function makeRandomHexString(length: number): string {
const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let result = '';
for (let i = 0; i < length; i++) {
const idx = Math.floor(chars.length * Math.random());
result += chars[idx];
}
return result;
}
export function getTempFile(name: string): string {
return path.join(os.tmpdir(), name);
}

View file

@ -250,6 +250,11 @@ commander@2.15.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -1051,6 +1056,14 @@ source-map-support@^0.5.0:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@~0.5.12:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@ -1097,6 +1110,15 @@ tar@^6.0.2:
mkdirp "^1.0.3"
yallist "^4.0.0"
terser@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
source-map-support "~0.5.12"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@ -1131,7 +1153,7 @@ typescript-vscode-sh-plugin@^0.6.14:
"typescript-web-server@git://github.com/mjbvz/ts-server-web-build":
version "0.0.0"
resolved "git://github.com/mjbvz/ts-server-web-build#6018bbd0049444cccee29c57ddabf394564fbf35"
resolved "git://github.com/mjbvz/ts-server-web-build#1d85be25043f9b5e36a531941ea345dd5a2ca007"
unique-filename@^1.1.1:
version "1.1.1"

View file

@ -6,7 +6,7 @@
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
import { join } from 'path';
import { createRandomFile } from './utils';
export function timeoutAsync(n: number): Promise<void> {
return new Promise(resolve => {
@ -56,6 +56,42 @@ async function splitEditor() {
await once;
}
async function saveFileAndCloseAll(resource: vscode.Uri) {
const documentClosed = new Promise((resolve, _reject) => {
const d = vscode.notebook.onDidCloseNotebookDocument(e => {
if (e.uri.toString() === resource.toString()) {
d.dispose();
resolve();
}
});
});
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await documentClosed;
}
async function saveAllFilesAndCloseAll(resource: vscode.Uri) {
const documentClosed = new Promise((resolve, _reject) => {
const d = vscode.notebook.onDidCloseNotebookDocument(e => {
if (e.uri.toString() === resource.toString()) {
d.dispose();
resolve();
}
});
});
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await documentClosed;
}
function assertInitalState() {
// no-op unless we figure out why some documents are opened after the editor is closed
// assert.equal(vscode.notebook.activeNotebookEditor, undefined);
// assert.equal(vscode.notebook.notebookDocuments.length, 0);
// assert.equal(vscode.notebook.visibleNotebookEditors.length, 0);
}
suite('Notebook API tests', () => {
// test.only('crash', async function () {
// for (let i = 0; i < 200; i++) {
@ -83,7 +119,9 @@ suite('Notebook API tests', () => {
// });
test('document open/close event', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await firstDocumentOpen;
@ -94,7 +132,9 @@ suite('Notebook API tests', () => {
});
test('shared document in notebook editors', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
let counter = 0;
const disposables: vscode.Disposable[] = [];
disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => {
@ -115,7 +155,9 @@ suite('Notebook API tests', () => {
});
test('editor open/close event', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await firstEditorOpen;
@ -126,7 +168,9 @@ suite('Notebook API tests', () => {
});
test('editor open/close event 2', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
let count = 0;
const disposables: vscode.Disposable[] = [];
disposables.push(vscode.notebook.onDidChangeVisibleNotebookEditors(() => {
@ -144,7 +188,9 @@ suite('Notebook API tests', () => {
});
test('editor editing event 2', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@ -216,7 +262,8 @@ suite('Notebook API tests', () => {
});
test('editor move cell event', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
@ -257,7 +304,8 @@ suite('Notebook API tests', () => {
});
test('notebook editor active/visible', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstEditor = vscode.notebook.activeNotebookEditor;
assert.equal(firstEditor?.active, true);
@ -292,7 +340,8 @@ suite('Notebook API tests', () => {
});
test('notebook active editor change', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await firstEditorOpen;
@ -301,12 +350,12 @@ suite('Notebook API tests', () => {
await vscode.commands.executeCommand('workbench.action.splitEditor');
await firstEditorDeactivate;
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
test('edit API', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@ -321,12 +370,12 @@ suite('Notebook API tests', () => {
assert.deepEqual(cellChangeEventRet.changes[0].deletedCount, 0);
assert.equal(cellChangeEventRet.changes[0].items[0], vscode.notebook.activeNotebookEditor!.document.cells[1]);
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
test('initialzation should not emit cell change events.', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
let count = 0;
const disposables: vscode.Disposable[] = [];
@ -338,14 +387,15 @@ suite('Notebook API tests', () => {
assert.equal(count, 0);
disposables.forEach(d => d.dispose());
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await saveFileAndCloseAll(resource);
});
});
suite('notebook workflow', () => {
test('notebook open', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test');
@ -366,7 +416,8 @@ suite('notebook workflow', () => {
});
test('notebook cell actions', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test');
@ -439,7 +490,8 @@ suite('notebook workflow', () => {
});
test('notebook join cells', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test');
@ -447,7 +499,9 @@ suite('notebook workflow', () => {
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
await vscode.commands.executeCommand('notebook.cell.joinAbove');
@ -460,7 +514,8 @@ suite('notebook workflow', () => {
});
test('move cells will not recreate cells in ExtHost', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
@ -473,15 +528,14 @@ suite('notebook workflow', () => {
const newActiveCell = vscode.notebook.activeNotebookEditor!.selection;
assert.deepEqual(activeCell, newActiveCell);
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
// TODO@rebornix, there are still some events order issue.
// assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2);
});
// test.only('document metadata is respected', async function () {
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
@ -505,7 +559,8 @@ suite('notebook workflow', () => {
// });
test('cell runnable metadata is respected', async () => {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.notebook.activeNotebookEditor!;
@ -526,7 +581,8 @@ suite('notebook workflow', () => {
});
test('document runnable metadata is respected', async () => {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.notebook.activeNotebookEditor!;
@ -548,7 +604,8 @@ suite('notebook workflow', () => {
suite('notebook dirty state', () => {
test('notebook open', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test');
@ -564,25 +621,22 @@ suite('notebook dirty state', () => {
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3);
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1);
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const edit = new vscode.WorkspaceEdit();
edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true);
assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true);
assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection);
assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;');
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await saveFileAndCloseAll(resource);
});
});
suite('notebook undo redo', () => {
test('notebook open', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test');
@ -600,7 +654,9 @@ suite('notebook undo redo', () => {
// modify the second cell, delete it
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
await vscode.commands.executeCommand('notebook.cell.delete');
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 2);
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1);
@ -618,12 +674,12 @@ suite('notebook undo redo', () => {
// assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1);
// assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'test');
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await saveFileAndCloseAll(resource);
});
test.skip('execute and then undo redo', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@ -681,15 +737,14 @@ suite('notebook undo redo', () => {
});
assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0);
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
});
suite('notebook working copy', () => {
// test('notebook revert on close', async function () {
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
// assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
@ -710,7 +765,7 @@ suite('notebook working copy', () => {
// });
// test('notebook revert', async function () {
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
// assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
@ -730,15 +785,18 @@ suite('notebook working copy', () => {
// });
test('multiple tabs: dirty + clean', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
@ -749,20 +807,22 @@ suite('notebook working copy', () => {
assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 3);
assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;');
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await saveFileAndCloseAll(resource);
});
test('multiple tabs: two dirty tabs and switching', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
@ -783,13 +843,15 @@ suite('notebook working copy', () => {
assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 2);
assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), '');
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveAllFilesAndCloseAll(secondResource);
// await vscode.commands.executeCommand('workbench.action.files.saveAll');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('multiple tabs: different editors with same document', async function () {
assertInitalState();
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstNotebookEditor = vscode.notebook.activeNotebookEditor;
assert.equal(firstNotebookEditor !== undefined, true, 'notebook first');
@ -806,28 +868,31 @@ suite('notebook working copy', () => {
assert.equal(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document');
assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')));
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveAllFilesAndCloseAll(resource);
// await vscode.commands.executeCommand('workbench.action.files.saveAll');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
});
suite('metadata', () => {
test('custom metadata should be supported', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123);
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript');
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
// TODO@rebornix skip as it crashes the process all the time
test.skip('custom metadata should be supported', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
test.skip('custom metadata should be supported 2', async function () {
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
@ -840,27 +905,29 @@ suite('metadata', () => {
// assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1);
// assert.equal(activeCell?.metadata.custom!['testCellMetadata'] as number, 123);
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
});
suite('regression', () => {
test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript');
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await saveFileAndCloseAll(resource);
});
test('#97830, #97764. Support switch to other editor types', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;');
@ -869,33 +936,37 @@ suite('regression', () => {
await vscode.commands.executeCommand('vscode.openWith', resource, 'default');
assert.equal(vscode.window.activeTextEditor?.document.uri.path, resource.path);
await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
// open text editor, pin, and then open a notebook
test('#96105 - dirty editors', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'default');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
// now it's dirty, open the resource with notebook editor should open a new one
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'notebook first');
assert.notEqual(vscode.window.activeTextEditor, undefined);
await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('#102411 - untitled notebook creation failed', async function () {
assertInitalState();
await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' });
assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('#102423 - copy/paste shares the same text buffer', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
assertInitalState();
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
let activeCell = vscode.notebook.activeNotebookEditor!.selection;
@ -907,10 +978,14 @@ suite('regression', () => {
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1);
assert.equal(activeCell?.document.getText(), 'test');
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
const edit = new vscode.WorkspaceEdit();
edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit);
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 2);
assert.notEqual(vscode.notebook.activeNotebookEditor!.document.cells[0].document.getText(), vscode.notebook.activeNotebookEditor!.document.cells[1].document.getText());
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
});
@ -921,11 +996,11 @@ suite('webview', () => {
// return;
// }
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
// const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png'));
// assert.equal(uri.scheme, 'vscode-resource');
// assert.equal(uri.scheme, 'vscode-webview-resource');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
// });

View file

@ -15,7 +15,7 @@ export function activate(context: vscode.ExtensionContext): any {
context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', {
onDidChangeNotebook: _onDidChangeNotebook.event,
openNotebook: async (_resource: vscode.Uri) => {
if (_resource.path.endsWith('empty.vsctestnb')) {
if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) {
return {
languages: ['typescript'],
metadata: {},

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 * as path from 'path';
import * as vscode from 'vscode';
class File implements vscode.FileStat {
type: vscode.FileType;
ctime: number;
mtime: number;
size: number;
name: string;
data?: Uint8Array;
constructor(name: string) {
this.type = vscode.FileType.File;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
}
}
class Directory implements vscode.FileStat {
type: vscode.FileType;
ctime: number;
mtime: number;
size: number;
name: string;
entries: Map<string, File | Directory>;
constructor(name: string) {
this.type = vscode.FileType.Directory;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
this.entries = new Map();
}
}
export type Entry = File | Directory;
export class TestFS implements vscode.FileSystemProvider {
constructor(
readonly scheme: string,
readonly isCaseSensitive: boolean
) { }
readonly root = new Directory('');
// --- manage file metadata
stat(uri: vscode.Uri): vscode.FileStat {
return this._lookup(uri, false);
}
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
const entry = this._lookupAsDirectory(uri, false);
let result: [string, vscode.FileType][] = [];
for (const [name, child] of entry.entries) {
result.push([name, child.type]);
}
return result;
}
// --- manage file contents
readFile(uri: vscode.Uri): Uint8Array {
const data = this._lookupAsFile(uri, false).data;
if (data) {
return data;
}
throw vscode.FileSystemError.FileNotFound();
}
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void {
let basename = path.posix.basename(uri.path);
let parent = this._lookupParentDirectory(uri);
let entry = parent.entries.get(basename);
if (entry instanceof Directory) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}
if (!entry && !options.create) {
throw vscode.FileSystemError.FileNotFound(uri);
}
if (entry && options.create && !options.overwrite) {
throw vscode.FileSystemError.FileExists(uri);
}
if (!entry) {
entry = new File(basename);
parent.entries.set(basename, entry);
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
}
entry.mtime = Date.now();
entry.size = content.byteLength;
entry.data = content;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
}
// --- manage files/folders
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
if (!options.overwrite && this._lookup(newUri, true)) {
throw vscode.FileSystemError.FileExists(newUri);
}
let entry = this._lookup(oldUri, false);
let oldParent = this._lookupParentDirectory(oldUri);
let newParent = this._lookupParentDirectory(newUri);
let newName = path.posix.basename(newUri.path);
oldParent.entries.delete(entry.name);
entry.name = newName;
newParent.entries.set(newName, entry);
this._fireSoon(
{ type: vscode.FileChangeType.Deleted, uri: oldUri },
{ type: vscode.FileChangeType.Created, uri: newUri }
);
}
delete(uri: vscode.Uri): void {
let dirname = uri.with({ path: path.posix.dirname(uri.path) });
let basename = path.posix.basename(uri.path);
let parent = this._lookupAsDirectory(dirname, false);
if (!parent.entries.has(basename)) {
throw vscode.FileSystemError.FileNotFound(uri);
}
parent.entries.delete(basename);
parent.mtime = Date.now();
parent.size -= 1;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
}
createDirectory(uri: vscode.Uri): void {
let basename = path.posix.basename(uri.path);
let dirname = uri.with({ path: path.posix.dirname(uri.path) });
let parent = this._lookupAsDirectory(dirname, false);
let entry = new Directory(basename);
parent.entries.set(entry.name, entry);
parent.mtime = Date.now();
parent.size += 1;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
}
// --- lookup
private _lookup(uri: vscode.Uri, silent: false): Entry;
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
let parts = uri.path.split('/');
let entry: Entry = this.root;
for (const part of parts) {
const partLow = part.toLowerCase();
if (!part) {
continue;
}
let child: Entry | undefined;
if (entry instanceof Directory) {
if (this.isCaseSensitive) {
child = entry.entries.get(part);
} else {
for (let [key, value] of entry.entries) {
if (key.toLowerCase() === partLow) {
child = value;
break;
}
}
}
}
if (!child) {
if (!silent) {
throw vscode.FileSystemError.FileNotFound(uri);
} else {
return undefined;
}
}
entry = child;
}
return entry;
}
private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
let entry = this._lookup(uri, silent);
if (entry instanceof Directory) {
return entry;
}
throw vscode.FileSystemError.FileNotADirectory(uri);
}
private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
let entry = this._lookup(uri, silent);
if (entry instanceof File) {
return entry;
}
throw vscode.FileSystemError.FileIsADirectory(uri);
}
private _lookupParentDirectory(uri: vscode.Uri): Directory {
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
return this._lookupAsDirectory(dirname, false);
}
// --- manage file events
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
private _bufferedEvents: vscode.FileChangeEvent[] = [];
private _fireSoonHandle?: NodeJS.Timer;
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;
watch(_resource: vscode.Uri): vscode.Disposable {
// ignore, fires for all changes...
return new vscode.Disposable(() => { });
}
private _fireSoon(...events: vscode.FileChangeEvent[]): void {
this._bufferedEvents.push(...events);
if (this._fireSoonHandle) {
clearTimeout(this._fireSoonHandle);
}
this._fireSoonHandle = setTimeout(() => {
this._emitter.fire(this._bufferedEvents);
this._bufferedEvents.length = 0;
}, 5);
}
}
export function rndName() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
}
export const testFs = new TestFS('fake-fs', true);
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive });
export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, prefix = '', ext = ''): Promise<vscode.Uri> {
let fakeFile: vscode.Uri;
if (dir) {
fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext });
} else {
fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${prefix}-${rndName() + ext}`);
}
await testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true });
return fakeFile;
}

View file

@ -76,10 +76,10 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
typescript@3.9.6:
version "3.9.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
typescript@3.9.7:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
wrappy@1:
version "1.0.2"

View file

@ -163,7 +163,7 @@
"source-map": "^0.4.4",
"style-loader": "^1.0.0",
"ts-loader": "^4.4.2",
"typescript": "^4.0.0-dev.20200715",
"typescript": "^4.0.0-dev.20200722",
"typescript-formatter": "7.1.0",
"underscore": "^1.8.2",
"vinyl": "^2.0.0",

View file

@ -44,9 +44,6 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
:: Tests in the extension host
REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
REM if %errorlevel% neq 0 exit /b %errorlevel%
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
if %errorlevel% neq 0 exit /b %errorlevel%
@ -65,6 +62,9 @@ if %errorlevel% neq 0 exit /b %errorlevel%
call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% .
if %errorlevel% neq 0 exit /b %errorlevel%
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
if %errorlevel% neq 0 exit /b %errorlevel%
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%

View file

@ -56,7 +56,7 @@ fi
#"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
# Tests in commonJS
cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js

View file

@ -26,7 +26,7 @@ export interface IContextMenuDelegate {
actionRunner?: IActionRunner;
autoSelectFirstItem?: boolean;
anchorAlignment?: AnchorAlignment;
anchorAsContainer?: boolean;
domForShadowRoot?: HTMLElement;
}
export interface IContextMenuProvider {

View file

@ -5,7 +5,6 @@
.monaco-action-bar {
text-align: right;
overflow: hidden;
white-space: nowrap;
}

View file

@ -28,7 +28,7 @@ function initialize() {
delayer.schedule();
}
function formatRule(c: Codicon) {
export function formatRule(c: Codicon) {
let def = c.definition;
while (def instanceof Codicon) {
def = def.definition;

View file

@ -10,6 +10,12 @@ import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/
import { Range } from 'vs/base/common/range';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
export const enum ContextViewDOMPosition {
ABSOLUTE = 1,
FIXED,
FIXED_SHADOW
}
export interface IAnchor {
x: number;
y: number;
@ -105,32 +111,62 @@ export class ContextView extends Disposable {
private container: HTMLElement | null = null;
private view: HTMLElement;
private useFixedPosition: boolean;
private useShadowDOM: boolean;
private delegate: IDelegate | null = null;
private toDisposeOnClean: IDisposable = Disposable.None;
private toDisposeOnSetContainer: IDisposable = Disposable.None;
private shadowRoot: ShadowRoot | null = null;
private shadowRootHostElement: HTMLElement | null = null;
constructor(container: HTMLElement, useFixedPosition: boolean) {
constructor(container: HTMLElement, domPosition: ContextViewDOMPosition) {
super();
this.view = DOM.$('.context-view');
this.useFixedPosition = false;
this.useShadowDOM = false;
DOM.hide(this.view);
this.setContainer(container, useFixedPosition);
this.setContainer(container, domPosition);
this._register(toDisposable(() => this.setContainer(null, false)));
this._register(toDisposable(() => this.setContainer(null, ContextViewDOMPosition.ABSOLUTE)));
}
setContainer(container: HTMLElement | null, useFixedPosition: boolean): void {
setContainer(container: HTMLElement | null, domPosition: ContextViewDOMPosition): void {
if (this.container) {
this.toDisposeOnSetContainer.dispose();
this.container.removeChild(this.view);
if (this.shadowRoot) {
this.shadowRoot.removeChild(this.view);
this.shadowRoot = null;
DOM.removeNode(this.shadowRootHostElement!);
this.shadowRootHostElement = null;
} else {
this.container.removeChild(this.view);
}
this.container = null;
}
if (container) {
this.container = container;
this.container.appendChild(this.view);
this.useFixedPosition = domPosition !== ContextViewDOMPosition.ABSOLUTE;
this.useShadowDOM = domPosition === ContextViewDOMPosition.FIXED_SHADOW;
if (this.useShadowDOM) {
this.shadowRootHostElement = DOM.$('.shadow-root-host');
this.container.appendChild(this.shadowRootHostElement);
this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'closed' });
this.shadowRoot.innerHTML = `
<style>
${SHADOW_ROOT_CSS}
</style>
`;
this.shadowRoot.appendChild(this.view);
this.shadowRoot.appendChild(DOM.$('slot'));
} else {
this.container.appendChild(this.view);
}
const toDisposeOnSetContainer = new DisposableStore();
@ -148,8 +184,6 @@ export class ContextView extends Disposable {
this.toDisposeOnSetContainer = toDisposeOnSetContainer;
}
this.useFixedPosition = useFixedPosition;
}
show(delegate: IDelegate): void {
@ -162,6 +196,7 @@ export class ContextView extends Disposable {
this.view.className = 'context-view';
this.view.style.top = '0px';
this.view.style.left = '0px';
this.view.style.zIndex = '2500';
this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute';
DOM.show(this.view);
@ -300,3 +335,45 @@ export class ContextView extends Disposable {
super.dispose();
}
}
let SHADOW_ROOT_CSS = /* css */ `
:host {
all: initial; /* 1st rule so subsequent properties are reset. */
}
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype");
}
.codicon[class*='codicon-'] {
font: normal normal normal 16px/1 codicon;
display: inline-block;
text-decoration: none;
text-rendering: auto;
text-align: center;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
:host-context(.mac) { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
:host-context(.mac:lang(zh-Hans)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
:host-context(.mac:lang(zh-Hant)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
:host-context(.mac:lang(ja)) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
:host-context(.mac:lang(ko)) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
:host-context(.windows) { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
:host-context(.windows:lang(zh-Hans)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
:host-context(.windows:lang(zh-Hant)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
:host-context(.windows:lang(ja)) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
:host-context(.windows:lang(ko)) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
:host-context(.mac).linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
:host-context(.mac).linux:lang(zh-Hans)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(zh-Hant)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(ja)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(ko)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
`;

View file

@ -265,7 +265,7 @@ export class DropdownMenu extends BaseDropdown {
onHide: () => this.onHide(),
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT,
anchorAsContainer: this.menuAsChild
domForShadowRoot: this.menuAsChild ? this.element : undefined
});
}

View file

@ -1,225 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-menu .monaco-action-bar.vertical {
margin-left: 0;
overflow: visible;
}
.monaco-menu .monaco-action-bar.vertical .actions-container {
display: block;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
padding: 0;
transform: none;
display: flex;
}
.monaco-menu .monaco-action-bar.vertical .action-item.active {
transform: none;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
flex: 1 1 auto;
display: flex;
height: 2em;
align-items: center;
position: relative;
}
.monaco-menu .monaco-action-bar.vertical .action-label {
flex: 1 1 auto;
text-decoration: none;
padding: 0 1em;
background: none;
font-size: 12px;
line-height: 1;
}
.monaco-menu .monaco-action-bar.vertical .keybinding,
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
display: inline-block;
flex: 2 1 auto;
padding: 0 1em;
text-align: right;
font-size: 12px;
line-height: 1;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
height: 100%;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon {
font-size: 16px !important;
display: flex;
align-items: center;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before {
margin-left: auto;
margin-right: -20px;
}
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator {
opacity: 0.4;
}
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
display: inline-block;
box-sizing: border-box;
margin: 0;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
position: static;
overflow: visible;
}
.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
position: absolute;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
padding: 0.5em 0 0 0;
margin-bottom: 0.5em;
width: 100%;
height: 0px !important;
margin-left: .8em !important;
margin-right: .8em !important;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
padding: 0.7em 1em 0.1em 1em;
font-weight: bold;
opacity: 1;
}
.monaco-menu .monaco-action-bar.vertical .action-label:hover {
color: inherit;
}
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
position: absolute;
visibility: hidden;
width: 1em;
height: 100%;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check {
visibility: visible;
display: flex;
align-items: center;
justify-content: center;
}
/* Context Menu */
.context-view.monaco-menu-container {
outline: 0;
border: none;
animation: fadeIn 0.083s linear;
}
.context-view.monaco-menu-container :focus,
.context-view.monaco-menu-container .monaco-action-bar.vertical:focus,
.context-view.monaco-menu-container .monaco-action-bar.vertical :focus {
outline: 0;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
border: thin solid transparent; /* prevents jumping behaviour on hover or focus */
}
/* High Contrast Theming */
.hc-black .context-view.monaco-menu-container {
box-shadow: none;
}
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused {
background: none;
}
/* Menubar styles */
.menubar {
display: flex;
flex-shrink: 1;
box-sizing: border-box;
height: 30px;
overflow: hidden;
flex-wrap: wrap;
}
.fullscreen .menubar:not(.compact) {
margin: 0px;
padding: 0px 5px;
}
.menubar > .menubar-menu-button {
align-items: center;
box-sizing: border-box;
padding: 0px 8px;
cursor: default;
-webkit-app-region: no-drag;
zoom: 1;
white-space: nowrap;
outline: 0;
}
.menubar.compact {
flex-shrink: 0;
overflow: visible; /* to avoid the compact menu to be repositioned when clicking */
}
.menubar.compact > .menubar-menu-button {
width: 100%;
height: 100%;
padding: 0px;
}
.menubar .menubar-menu-items-holder {
position: absolute;
left: 0px;
opacity: 1;
z-index: 2000;
}
.menubar .menubar-menu-items-holder.monaco-menu-container {
outline: 0;
border: none;
}
.menubar .menubar-menu-items-holder.monaco-menu-container :focus {
outline: 0;
}
.menubar .toolbar-toggle-more {
width: 20px;
height: 100%;
}
.menubar.compact .toolbar-toggle-more {
position: relative;
left: 0px;
top: 0px;
cursor: pointer;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.menubar .toolbar-toggle-more {
padding: 0;
vertical-align: sub;
}
.menubar.compact .toolbar-toggle-more::before {
content: "\eb94" !important;
}

View file

@ -3,13 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./menu';
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode } from 'vs/base/browser/dom';
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { RunOnceScheduler } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
@ -21,6 +20,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons';
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&amp;)?(&amp;)([^\s&])/g;
@ -67,6 +67,8 @@ export class Menu extends ActionBar {
private readonly menuDisposables: DisposableStore;
private scrollableElement: DomScrollableElement;
private menuElement: HTMLElement;
static globalStyleSheet: HTMLStyleElement;
protected styleSheet: HTMLStyleElement | undefined;
constructor(container: HTMLElement, actions: ReadonlyArray<IAction>, options: IMenuOptions = {}) {
addClass(container, 'monaco-menu-container');
@ -92,6 +94,8 @@ export class Menu extends ActionBar {
this.menuDisposables = this._register(new DisposableStore());
this.initializeStyleSheet(container);
addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
const event = new StandardKeyboardEvent(e);
@ -220,6 +224,20 @@ export class Menu extends ActionBar {
});
}
private initializeStyleSheet(container: HTMLElement): void {
if (isInShadowDOM(container)) {
this.styleSheet = createStyleSheet(container);
this.styleSheet.innerHTML = MENU_WIDGET_CSS;
} else {
if (!Menu.globalStyleSheet) {
Menu.globalStyleSheet = createStyleSheet();
Menu.globalStyleSheet.innerHTML = MENU_WIDGET_CSS;
}
this.styleSheet = Menu.globalStyleSheet;
}
}
style(style: IMenuStyles): void {
const container = this.getContainer();
@ -883,3 +901,298 @@ export function cleanMnemonic(label: string): string {
return label.replace(regex, mnemonicInText ? '$2$3' : '').trim();
}
let MENU_WIDGET_CSS: string = /* css */`
.monaco-menu {
font-size: 13px;
}
${formatRule(menuSelectionIcon)}
${formatRule(menuSubmenuIcon)}
.monaco-action-bar {
text-align: right;
overflow: hidden;
white-space: nowrap;
}
.monaco-action-bar .actions-container {
display: flex;
margin: 0 auto;
padding: 0;
width: 100%;
justify-content: flex-end;
}
.monaco-action-bar.vertical .actions-container {
display: inline-block;
}
.monaco-action-bar.reverse .actions-container {
flex-direction: row-reverse;
}
.monaco-action-bar .action-item {
cursor: pointer;
display: inline-block;
transition: transform 50ms ease;
position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */
}
.monaco-action-bar .action-item.disabled {
cursor: default;
}
.monaco-action-bar.animated .action-item.active {
transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */
}
.monaco-action-bar .action-item .icon,
.monaco-action-bar .action-item .codicon {
display: inline-block;
}
.monaco-action-bar .action-item .codicon {
display: flex;
align-items: center;
}
.monaco-action-bar .action-label {
font-size: 11px;
margin-right: 4px;
}
.monaco-action-bar .action-item.disabled .action-label,
.monaco-action-bar .action-item.disabled .action-label:hover {
opacity: 0.4;
}
/* Vertical actions */
.monaco-action-bar.vertical {
text-align: left;
}
.monaco-action-bar.vertical .action-item {
display: block;
}
.monaco-action-bar.vertical .action-label.separator {
display: block;
border-bottom: 1px solid #bbb;
padding-top: 1px;
margin-left: .8em;
margin-right: .8em;
}
.monaco-action-bar.animated.vertical .action-item.active {
transform: translate(5px, 0);
}
.secondary-actions .monaco-action-bar .action-label {
margin-left: 6px;
}
/* Action Items */
.monaco-action-bar .action-item.select-container {
overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */
flex: 1;
max-width: 170px;
min-width: 60px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.monaco-menu .monaco-action-bar.vertical {
margin-left: 0;
overflow: visible;
}
.monaco-menu .monaco-action-bar.vertical .actions-container {
display: block;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
padding: 0;
transform: none;
display: flex;
}
.monaco-menu .monaco-action-bar.vertical .action-item.active {
transform: none;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
flex: 1 1 auto;
display: flex;
height: 2em;
align-items: center;
position: relative;
}
.monaco-menu .monaco-action-bar.vertical .action-label {
flex: 1 1 auto;
text-decoration: none;
padding: 0 1em;
background: none;
font-size: 12px;
line-height: 1;
}
.monaco-menu .monaco-action-bar.vertical .keybinding,
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
display: inline-block;
flex: 2 1 auto;
padding: 0 1em;
text-align: right;
font-size: 12px;
line-height: 1;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
height: 100%;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon {
font-size: 16px !important;
display: flex;
align-items: center;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before {
margin-left: auto;
margin-right: -20px;
}
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator {
opacity: 0.4;
}
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
display: inline-block;
box-sizing: border-box;
margin: 0;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
position: static;
overflow: visible;
}
.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
position: absolute;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
padding: 0.5em 0 0 0;
margin-bottom: 0.5em;
width: 100%;
height: 0px !important;
margin-left: .8em !important;
margin-right: .8em !important;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
padding: 0.7em 1em 0.1em 1em;
font-weight: bold;
opacity: 1;
}
.monaco-menu .monaco-action-bar.vertical .action-label:hover {
color: inherit;
}
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
position: absolute;
visibility: hidden;
width: 1em;
height: 100%;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check {
visibility: visible;
display: flex;
align-items: center;
justify-content: center;
}
/* Context Menu */
.context-view.monaco-menu-container {
outline: 0;
border: none;
animation: fadeIn 0.083s linear;
}
.context-view.monaco-menu-container :focus,
.context-view.monaco-menu-container .monaco-action-bar.vertical:focus,
.context-view.monaco-menu-container .monaco-action-bar.vertical :focus {
outline: 0;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
border: thin solid transparent; /* prevents jumping behaviour on hover or focus */
}
/* High Contrast Theming */
.hc-black .context-view.monaco-menu-container {
box-shadow: none;
}
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused {
background: none;
}
/* Vertical Action Bar Styles */
.monaco-menu .monaco-action-bar.vertical {
padding: .5em 0;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
height: 1.8em;
}
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator),
.monaco-menu .monaco-action-bar.vertical .keybinding {
font-size: inherit;
padding: 0 2em;
}
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
font-size: inherit;
width: 2em;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
font-size: inherit;
padding: 0.2em 0 0 0;
margin-bottom: 0.2em;
}
linux .monaco-menu .monaco-action-bar.vertical .action-label.separator {
margin-left: 0;
margin-right: 0;
}
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
font-size: 60%;
padding: 0 1.8em;
}
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
height: 100%;
mask-size: 10px 10px;
-webkit-mask-size: 10px 10px;
}
.monaco-menu .action-item {
cursor: default;
}
`;

View file

@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Menubar styles */
.menubar {
display: flex;
flex-shrink: 1;
box-sizing: border-box;
height: 30px;
overflow: hidden;
flex-wrap: wrap;
}
.fullscreen .menubar:not(.compact) {
margin: 0px;
padding: 0px 5px;
}
.menubar > .menubar-menu-button {
align-items: center;
box-sizing: border-box;
padding: 0px 8px;
cursor: default;
-webkit-app-region: no-drag;
zoom: 1;
white-space: nowrap;
outline: 0;
}
.menubar.compact {
flex-shrink: 0;
overflow: visible; /* to avoid the compact menu to be repositioned when clicking */
}
.menubar.compact > .menubar-menu-button {
width: 100%;
height: 100%;
padding: 0px;
}
.menubar .menubar-menu-items-holder {
position: absolute;
left: 0px;
opacity: 1;
z-index: 2000;
}
.menubar .menubar-menu-items-holder.monaco-menu-container {
outline: 0;
border: none;
}
.menubar .menubar-menu-items-holder.monaco-menu-container :focus {
outline: 0;
}
.menubar .toolbar-toggle-more {
width: 20px;
height: 100%;
}
.menubar.compact .toolbar-toggle-more {
position: relative;
left: 0px;
top: 0px;
cursor: pointer;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.menubar .toolbar-toggle-more {
padding: 0;
vertical-align: sub;
}
.menubar.compact .toolbar-toggle-more::before {
content: "\eb94" !important;
}

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./menubar';
import * as browser from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import * as strings from 'vs/base/common/strings';

View file

@ -3,8 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { VSBuffer } from 'vs/base/common/buffer';
import { regExpFlags } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
export function stringify(obj: any): string {
return JSON.stringify(obj, replacer);
@ -44,10 +45,23 @@ export function revive(obj: any, depth = 0): any {
case 2: return new RegExp(obj.source, obj.flags);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
obj[key] = revive(obj[key], depth + 1);
if (
obj instanceof VSBuffer
|| obj instanceof Uint8Array
) {
return obj;
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
obj[i] = revive(obj[i], depth + 1);
}
} else {
// walk object
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
obj[key] = revive(obj[key], depth + 1);
}
}
}
}

View file

@ -16,6 +16,7 @@ export interface ISocket extends IDisposable {
onEnd(listener: () => void): IDisposable;
write(buffer: VSBuffer): void;
end(): void;
drain(): Promise<void>;
}
let emptyBuffer: VSBuffer | null = null;
@ -277,6 +278,11 @@ class ProtocolWriter {
this._isDisposed = true;
}
public drain(): Promise<void> {
this.flush();
return this._socket.drain();
}
public flush(): void {
// flush
this._writeNow();
@ -372,6 +378,10 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
this._register(this._socket.onClose(() => this._onClose.fire()));
}
drain(): Promise<void> {
return this._socketWriter.drain();
}
getSocket(): ISocket {
return this._socket;
}
@ -619,6 +629,10 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketDisposables = dispose(this._socketDisposables);
}
drain(): Promise<void> {
return this._socketWriter.drain();
}
sendDisconnect(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);

View file

@ -70,6 +70,10 @@ interface IHandler {
export interface IMessagePassingProtocol {
send(buffer: VSBuffer): void;
onMessage: Event<VSBuffer>;
/**
* Wait for the write buffer (if applicable) to become empty.
*/
drain?(): Promise<void>;
}
enum State {
@ -482,10 +486,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
return e(errors.canceled());
}
let uninitializedPromise: CancelablePromise<void> | null = createCancelablePromise(_ => this.whenInitialized());
uninitializedPromise.then(() => {
uninitializedPromise = null;
const doRequest = () => {
const handler: IHandler = response => {
switch (response.type) {
case ResponseType.PromiseSuccess:
@ -510,7 +511,18 @@ export class ChannelClient implements IChannelClient, IDisposable {
this.handlers.set(id, handler);
this.sendRequest(request);
});
};
let uninitializedPromise: CancelablePromise<void> | null = null;
if (this.state === State.Idle) {
doRequest();
} else {
uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
uninitializedPromise.then(() => {
uninitializedPromise = null;
doRequest();
});
}
const cancel = () => {
if (uninitializedPromise) {

View file

@ -12,6 +12,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import { ISocket, Protocol, Client, ChunkStream } from 'vs/base/parts/ipc/common/ipc.net';
import { onUnexpectedError } from 'vs/base/common/errors';
export class NodeSocket implements ISocket {
public readonly socket: Socket;
@ -57,12 +58,47 @@ export class NodeSocket implements ISocket {
// > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback
// > However, the false return value is only advisory and the writable stream will unconditionally
// > accept and buffer chunk even if it has not been allowed to drain.
this.socket.write(<Buffer>buffer.buffer);
try {
this.socket.write(<Buffer>buffer.buffer);
} catch (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
}
public end(): void {
this.socket.end();
}
public drain(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this.socket.bufferSize === 0) {
resolve();
return;
}
const finished = () => {
this.socket.off('close', finished);
this.socket.off('end', finished);
this.socket.off('error', finished);
this.socket.off('timeout', finished);
this.socket.off('drain', finished);
resolve();
};
this.socket.on('close', finished);
this.socket.on('end', finished);
this.socket.on('error', finished);
this.socket.on('timeout', finished);
this.socket.on('drain', finished);
});
}
}
const enum Constants {
@ -229,6 +265,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
}
}
}
public drain(): Promise<void> {
return this.socket.drain();
}
}
function unmask(buffer: VSBuffer, mask: number): void {

View file

@ -89,6 +89,7 @@ export class CodeActionMenu extends Disposable {
const resolver = this._keybindingResolver.getResolver();
this._contextMenuService.showContextMenu({
domForShadowRoot: this._editor.getDomNode()!,
getAnchor: () => anchor,
getActions: () => menuActions,
onHide: () => {

View file

@ -204,6 +204,8 @@ export class ContextMenuController implements IEditorContribution {
// Show menu
this._contextMenuIsBeingShownCount++;
this._contextMenuService.showContextMenu({
domForShadowRoot: this._editor.getDomNode(),
getAnchor: () => anchor!,
getActions: () => actions,

View file

@ -54,6 +54,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur()));
this._dndDecorationIds = [];
this._mouseDown = false;
this._modifierPressed = false;

View file

@ -6,7 +6,7 @@
/* Find widget */
.monaco-editor .find-widget {
position: absolute;
z-index: 10;
z-index: 20;
height: 33px;
overflow: hidden;
line-height: 19px;

View file

@ -50,7 +50,7 @@ export class ContextMenuHandler {
let menu: Menu | undefined;
const anchor = delegate.getAnchor();
let shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined;
this.contextViewService.showContextView({
getAnchor: () => delegate.getAnchor(),
canRelayout: false,
@ -133,7 +133,7 @@ export class ContextMenuHandler {
this.focusToReturn.focus();
}
}
}, !!delegate.anchorAsContainer && isHTMLElement(anchor) ? anchor : undefined);
}, shadowRootElement, !!shadowRootElement);
}
private onActionRun(e: IRunEvent): void {

View file

@ -15,7 +15,7 @@ export interface IContextViewService extends IContextViewProvider {
readonly _serviceBrand: undefined;
showContextView(delegate: IContextViewDelegate, container?: HTMLElement): IDisposable;
showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable;
hideContextView(data?: any): void;
layout(): void;
anchorAlignment?: AnchorAlignment;

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IContextViewService, IContextViewDelegate } from './contextView';
import { ContextView } from 'vs/base/browser/ui/contextview/contextview';
import { ContextView, ContextViewDOMPosition } from 'vs/base/browser/ui/contextview/contextview';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
@ -21,7 +21,7 @@ export class ContextViewService extends Disposable implements IContextViewServic
super();
this.container = layoutService.container;
this.contextView = this._register(new ContextView(this.container, false));
this.contextView = this._register(new ContextView(this.container, ContextViewDOMPosition.ABSOLUTE));
this.layout();
this._register(layoutService.onLayout(() => this.layout()));
@ -29,20 +29,20 @@ export class ContextViewService extends Disposable implements IContextViewServic
// ContextView
setContainer(container: HTMLElement, useFixedPosition?: boolean): void {
this.contextView.setContainer(container, !!useFixedPosition);
setContainer(container: HTMLElement, domPosition?: ContextViewDOMPosition): void {
this.contextView.setContainer(container, domPosition || ContextViewDOMPosition.ABSOLUTE);
}
showContextView(delegate: IContextViewDelegate, container?: HTMLElement): IDisposable {
showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable {
if (container) {
if (container !== this.container) {
this.container = container;
this.setContainer(container, true);
this.setContainer(container, shadowRoot ? ContextViewDOMPosition.FIXED_SHADOW : ContextViewDOMPosition.FIXED);
}
} else {
if (this.container !== this.layoutService.container) {
this.container = this.layoutService.container;
this.setContainer(this.container, false);
this.setContainer(this.container, ContextViewDOMPosition.ABSOLUTE);
}
}

View file

@ -142,8 +142,25 @@ export interface IExtensionIdentifier {
uuid?: string;
}
export const EXTENSION_CATEGORIES = ['Programming Languages', 'Snippets', 'Linters', 'Themes', 'Debuggers', 'Other', 'Keymaps', 'Formatters', 'Extension Packs',
'SCM Providers', 'Azure', 'Language Packs', 'Data Science', 'Machine Learning', 'Visualization', 'Testing', 'Notebooks'];
export const EXTENSION_CATEGORIES = [
'Azure',
'Data Science',
'Debuggers',
'Extension Packs',
'Formatters',
'Keymaps',
'Language Packs',
'Linters',
'Machine Learning',
'Notebooks',
'Programming Languages',
'SCM Providers',
'Snippets',
'Themes',
'Testing',
'Visualization',
'Other',
];
export interface IExtensionManifest {
readonly name: string;

View file

@ -194,6 +194,9 @@ class BrowserSocket implements ISocket {
this.socket.close();
}
public drain(): Promise<void> {
return Promise.resolve();
}
}

View file

@ -390,7 +390,7 @@ export const menuSeparatorBackground = registerColor('menu.separatorBackground',
export const snippetTabstopHighlightBackground = registerColor('editor.snippetTabstopHighlightBackground', { dark: new Color(new RGBA(124, 124, 124, 0.3)), light: new Color(new RGBA(10, 50, 100, 0.2)), hc: new Color(new RGBA(124, 124, 124, 0.3)) }, nls.localize('snippetTabstopHighlightBackground', "Highlight background color of a snippet tabstop."));
export const snippetTabstopHighlightBorder = registerColor('editor.snippetTabstopHighlightBorder', { dark: null, light: null, hc: null }, nls.localize('snippetTabstopHighlightBorder', "Highlight border color of a snippet tabstop."));
export const snippetFinalTabstopHighlightBackground = registerColor('editor.snippetFinalTabstopHighlightBackground', { dark: null, light: null, hc: null }, nls.localize('snippetFinalTabstopHighlightBackground', "Highlight background color of the final tabstop of a snippet."));
export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', { dark: '#525252', light: new Color(new RGBA(10, 50, 100, 0.5)), hc: '#525252' }, nls.localize('snippetFinalTabstopHighlightBorder', "Highlight border color of the final stabstop of a snippet."));
export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', { dark: '#525252', light: new Color(new RGBA(10, 50, 100, 0.5)), hc: '#525252' }, nls.localize('snippetFinalTabstopHighlightBorder', "Highlight border color of the final tabstop of a snippet."));
/**
* Breadcrumb colors

View file

@ -82,10 +82,19 @@ class RemovedResources {
let messages: string[] = [];
if (externalRemoval.length > 0) {
messages.push(nls.localize('externalRemoval', "The following files have been closed and modified on disk: {0}.", externalRemoval.join(', ')));
messages.push(
nls.localize(
{ key: 'externalRemoval', comment: ['{0} is a list of filenames'] },
"The following files have been closed and modified on disk: {0}.", externalRemoval.join(', ')
)
);
}
if (noParallelUniverses.length > 0) {
messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', ')));
messages.push(
nls.localize(
{ key: 'noParallelUniverses', comment: ['{0} is a list of filenames'] },
"The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', ')
));
}
return messages.join('\n');
}
@ -771,10 +780,26 @@ export class UndoRedoService implements IUndoRedoService {
private _checkWorkspaceUndo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot, checkInvalidatedResources: boolean): WorkspaceVerificationError | null {
if (element.removedResources) {
return this._tryToSplitAndUndo(strResource, element, element.removedResources, nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()));
return this._tryToSplitAndUndo(
strResource,
element,
element.removedResources,
nls.localize(
{ key: 'cannotWorkspaceUndo', comment: ['{0} is a label for an operation. {1} is another message.'] },
"Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()
)
);
}
if (checkInvalidatedResources && element.invalidatedResources) {
return this._tryToSplitAndUndo(strResource, element, element.invalidatedResources, nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()));
return this._tryToSplitAndUndo(
strResource,
element,
element.invalidatedResources,
nls.localize(
{ key: 'cannotWorkspaceUndo', comment: ['{0} is a label for an operation. {1} is another message.'] },
"Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()
)
);
}
// this must be the last past element in all the impacted resources!
@ -785,7 +810,15 @@ export class UndoRedoService implements IUndoRedoService {
}
}
if (cannotUndoDueToResources.length > 0) {
return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, cannotUndoDueToResources.join(', ')));
return this._tryToSplitAndUndo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceUndoDueToChanges', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not undo '{0}' across all files because changes were made to {1}", element.label, cannotUndoDueToResources.join(', ')
)
);
}
const cannotLockDueToResources: string[] = [];
@ -795,12 +828,28 @@ export class UndoRedoService implements IUndoRedoService {
}
}
if (cannotLockDueToResources.length > 0) {
return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToInProgressUndoRedo', "Could not undo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ')));
return this._tryToSplitAndUndo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceUndoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not undo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ')
)
);
}
// check if new stack elements were added in the meantime...
if (!editStackSnapshot.isValid()) {
return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToInMeantimeUndoRedo', "Could not undo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label));
return this._tryToSplitAndUndo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceUndoDueToInMeantimeUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not undo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label
)
);
}
return null;
@ -881,7 +930,10 @@ export class UndoRedoService implements IUndoRedoService {
return;
}
if (editStack.locked) {
const message = nls.localize('cannotResourceUndoDueToInProgressUndoRedo', "Could not undo '{0}' because there is already an undo or redo operation running.", element.label);
const message = nls.localize(
{ key: 'cannotResourceUndoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] },
"Could not undo '{0}' because there is already an undo or redo operation running.", element.label
);
this._notificationService.info(message);
return;
}
@ -942,10 +994,26 @@ export class UndoRedoService implements IUndoRedoService {
private _checkWorkspaceRedo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot, checkInvalidatedResources: boolean): WorkspaceVerificationError | null {
if (element.removedResources) {
return this._tryToSplitAndRedo(strResource, element, element.removedResources, nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()));
return this._tryToSplitAndRedo(
strResource,
element,
element.removedResources,
nls.localize(
{ key: 'cannotWorkspaceRedo', comment: ['{0} is a label for an operation. {1} is another message.'] },
"Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()
)
);
}
if (checkInvalidatedResources && element.invalidatedResources) {
return this._tryToSplitAndRedo(strResource, element, element.invalidatedResources, nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()));
return this._tryToSplitAndRedo(
strResource,
element,
element.invalidatedResources,
nls.localize(
{ key: 'cannotWorkspaceRedo', comment: ['{0} is a label for an operation. {1} is another message.'] },
"Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()
)
);
}
// this must be the last future element in all the impacted resources!
@ -956,7 +1024,15 @@ export class UndoRedoService implements IUndoRedoService {
}
}
if (cannotRedoDueToResources.length > 0) {
return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, cannotRedoDueToResources.join(', ')));
return this._tryToSplitAndRedo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceRedoDueToChanges', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not redo '{0}' across all files because changes were made to {1}", element.label, cannotRedoDueToResources.join(', ')
)
);
}
const cannotLockDueToResources: string[] = [];
@ -966,12 +1042,28 @@ export class UndoRedoService implements IUndoRedoService {
}
}
if (cannotLockDueToResources.length > 0) {
return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToInProgressUndoRedo', "Could not redo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ')));
return this._tryToSplitAndRedo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceRedoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not redo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ')
)
);
}
// check if new stack elements were added in the meantime...
if (!editStackSnapshot.isValid()) {
return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToInMeantimeUndoRedo', "Could not redo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label));
return this._tryToSplitAndRedo(
strResource,
element,
null,
nls.localize(
{ key: 'cannotWorkspaceRedoDueToInMeantimeUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] },
"Could not redo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label
)
);
}
return null;
@ -1015,7 +1107,10 @@ export class UndoRedoService implements IUndoRedoService {
return;
}
if (editStack.locked) {
const message = nls.localize('cannotResourceRedoDueToInProgressUndoRedo', "Could not redo '{0}' because there is already an undo or redo operation running.", element.label);
const message = nls.localize(
{ key: 'cannotResourceRedoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] },
"Could not redo '{0}' because there is already an undo or redo operation running.", element.label
);
this._notificationService.info(message);
return;
}

View file

@ -32,6 +32,7 @@ type AutoSyncErrorClassification = {
const enablementKey = 'sync.enable';
const disableMachineEventuallyKey = 'sync.disableMachineEventually';
const sessionIdKey = 'sync.sessionId';
const storeUrlKey = 'sync.storeUrl';
export class UserDataAutoSyncEnablementService extends Disposable {
@ -97,15 +98,20 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
if (userDataSyncStoreService.userDataSyncStore) {
storageService.store(storeUrlKey, userDataSyncStoreService.userDataSyncStore.url.toString(), StorageScope.GLOBAL);
if (this.isEnabled()) {
this.logService.info('Auto Sync is enabled.');
} else {
this.logService.info('Auto Sync is disabled.');
}
this.updateAutoSync();
if (this.hasToDisableMachineEventually()) {
this.disableMachineEventually();
}
this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync()));
this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync()));
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));

View file

@ -868,22 +868,6 @@ declare module 'vscode' {
debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
}
export interface DebugSession {
/**
* Terminates the session.
*/
terminate(): Thenable<void>;
}
export interface DebugSession {
/**
* Terminates the session.
*/
terminate(): Thenable<void>;
}
export namespace debug {
/**

View file

@ -262,14 +262,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return Promise.reject(new Error('debug session not found'));
}
public $terminateDebugSession(sessionId: DebugSessionUUID): Promise<void> {
const session = this.debugService.getModel().getSession(sessionId, true);
if (session) {
return session.terminate();
}
return Promise.reject(new Error('debug session not found'));
}
public $stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void> {
if (sessionId) {
const session = this.debugService.getModel().getSession(sessionId, true);

View file

@ -269,6 +269,14 @@ export class MainThreadTextEditor {
}
}));
const isValidCodeEditor = () => {
// Due to event timings, it is possible that there is a model change event not yet delivered to us.
// > e.g. a model change event is emitted to a listener which then decides to update editor options
// > In this case the editor configuration change event reaches us first.
// So simply check that the model is still attached to this code editor
return (this._codeEditor && this._codeEditor.getModel() === this._model);
};
const updateProperties = (selectionChangeSource: string | null) => {
// Some editor events get delivered faster than model content changes. This is
// problematic, as this leads to editor properties reaching the extension host
@ -287,18 +295,30 @@ export class MainThreadTextEditor {
this._codeEditorListeners.add(this._codeEditor.onDidChangeCursorSelection((e) => {
// selection
if (!isValidCodeEditor()) {
return;
}
updateProperties(e.source);
}));
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => {
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration((e) => {
// options
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => {
// visibleRanges
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => {
// visibleRanges
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._updatePropertiesNow(null);

View file

@ -128,7 +128,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
}
}
$onExtensionHostExit(code: number): void {
async $onExtensionHostExit(code: number): Promise<void> {
this._extensionService._onExtensionHostExit(code);
}
}

View file

@ -634,7 +634,7 @@ export class MainThreadTask implements MainThreadTaskShape {
return URI.parse(`${info.scheme}://${info.authority}${path}`);
},
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables> => {
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables | undefined> => {
const vars: string[] = [];
toResolve.variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => {
@ -642,8 +642,12 @@ export class MainThreadTask implements MainThreadTaskShape {
forEach(values.variables, (entry) => {
partiallyResolvedVars.push(entry.value);
});
return new Promise<ResolvedVariables>((resolve, reject) => {
return new Promise<ResolvedVariables | undefined>((resolve, reject) => {
this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => {
if (!resolvedVars) {
resolve(undefined);
}
const result: ResolvedVariables = {
process: undefined,
variables: new Map<string, string>()
@ -677,4 +681,9 @@ export class MainThreadTask implements MainThreadTaskShape {
}
});
}
async $registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise<void> {
return this._taskService.registerSupportedExecutions(custom, shell, process);
}
}

View file

@ -787,6 +787,7 @@ export interface MainThreadTaskShape extends IDisposable {
$terminateTask(id: string): Promise<void>;
$registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void;
$customExecutionComplete(id: string, result?: number): Promise<void>;
$registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise<void>;
}
export interface MainThreadExtensionServiceShape extends IDisposable {
@ -795,7 +796,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
$onExtensionHostExit(code: number): void;
$onExtensionHostExit(code: number): Promise<void>;
}
export interface SCMProviderFeatures {
@ -878,7 +879,6 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void>;
$setDebugSessionName(id: DebugSessionUUID, name: string): void;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$terminateDebugSession(id: DebugSessionUUID): Promise<void>;
$appendDebugConsole(value: string): void;
$startBreakpointEvents(): void;
$registerBreakpoints(breakpoints: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void>;

View file

@ -957,10 +957,6 @@ export class ExtHostDebugSession implements vscode.DebugSession {
public customRequest(command: string, args: any): Promise<any> {
return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args);
}
public terminate(): Promise<void> {
return this._debugServiceProxy.$terminateDebugSession(this._id);
}
}
export class ExtHostDebugConsole implements vscode.DebugConsole {

View file

@ -557,7 +557,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
}
// after tests have run, we shutdown the host
this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
this._testRunnerExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
};
const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback);
@ -567,11 +567,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
runResult
.then(() => {
c();
this._gracefulExit(0);
this._testRunnerExit(0);
})
.catch((err: Error) => {
e(err.toString());
this._gracefulExit(1);
this._testRunnerExit(1);
});
}
});
@ -579,24 +579,20 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
// Otherwise make sure to shutdown anyway even in case of an error
else {
this._gracefulExit(1 /* ERROR */);
this._testRunnerExit(1 /* ERROR */);
}
return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath)));
}
private _gracefulExit(code: number): void {
// to give the PH process a chance to flush any outstanding console
// messages to the main process, we delay the exit() by some time
setTimeout(() => {
// If extension tests are running, give the exit code to the renderer
if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) {
this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
return;
}
private _testRunnerExit(code: number): void {
// wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain
// (this is to ensure all outstanding messages reach the renderer)
const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
const drainPromise = this._extHostContext.drain();
Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => {
this._hostUtils.exit(code);
}, 500);
});
}
private _startExtensionHost(): Promise<void> {

View file

@ -18,12 +18,12 @@ export class ExtHostRpcService implements IExtHostRpcService {
readonly getProxy: <T>(identifier: ProxyIdentifier<T>) => T;
readonly set: <T, R extends T> (identifier: ProxyIdentifier<T>, instance: R) => R;
readonly assertRegistered: (identifiers: ProxyIdentifier<any>[]) => void;
readonly drain: () => Promise<void>;
constructor(rpcProtocol: IRPCProtocol) {
this.getProxy = rpcProtocol.getProxy.bind(rpcProtocol);
this.set = rpcProtocol.set.bind(rpcProtocol);
this.assertRegistered = rpcProtocol.assertRegistered.bind(rpcProtocol);
this.drain = rpcProtocol.drain.bind(rpcProtocol);
}
}

View file

@ -419,6 +419,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
this._activeCustomExecutions2 = new Map<string, types.CustomExecution>();
this._logService = logService;
this._deprecationService = deprecationService;
this._proxy.$registerSupportedExecutions(true);
}
public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable {

View file

@ -47,6 +47,7 @@ export class ExtHostTask extends ExtHostTaskBase {
platform: process.platform
});
}
this._proxy.$registerSupportedExecutions(true, true, true);
}
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {

View file

@ -51,7 +51,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
// fetch JS sources as text and create a new function around it
const source = await response.text();
const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${module.toString(true)}`);
// Here we append #vscode-extension to serve as a marker, such that source maps
// can be adjusted for the extra wrapping function.
const sourceURL = `${module.toString(true)}#vscode-extension`;
const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`);
// define commonjs globals: `module`, `exports`, and `require`
const _exports = {};

View file

@ -32,6 +32,7 @@ import { IAuthenticationService } from 'vs/workbench/services/authentication/bro
import { AuthenticationSession } from 'vs/editor/common/modes';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
export class ViewContainerActivityAction extends ActivityAction {
@ -97,6 +98,8 @@ export class ViewContainerActivityAction extends ActivityAction {
}
}
export const ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts';
export class AccountsActionViewItem extends ActivityActionViewItem {
constructor(
action: ActivityAction,
@ -106,7 +109,8 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
@IMenuService protected menuService: IMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageService private readonly storageService: IStorageService
) {
super(action, { draggable: false, colors, icon: true }, themeService);
}
@ -189,6 +193,15 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
}
});
if (menus.length) {
menus.push(new Separator());
}
menus.push(new Action('hide', nls.localize('hide', "Hide"), undefined, true, _ => {
this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL);
return Promise.resolve();
}));
return menus;
}

View file

@ -5,10 +5,10 @@
import 'vs/css!./media/activitybarpart';
import * as nls from 'vs/nls';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity';
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem, ACCOUNTS_VISIBILITY_PREFERENCE_KEY } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -75,7 +75,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2';
private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets';
private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator';
private static readonly ACCOUNTS_ACTION_INDEX = 0;
//#region IView
readonly minimumWidth: number = 48;
@ -164,6 +164,18 @@ export class ActivitybarPart extends Part implements IActivityBarService {
actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu")));
}
const toggleAccountsVisibilityAction = new Action(
'toggleAccountsVisibility',
nls.localize('accounts', "Accounts"),
undefined,
true,
async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; }
);
toggleAccountsVisibilityAction.checked = !!this.accountsActivityAction;
actions.push(toggleAccountsVisibilityAction);
actions.push(new Separator());
actions.push(new Action(
ToggleActivityBarVisibilityAction.ID,
nls.localize('hideActivitBar', "Hide Activity Bar"),
@ -587,17 +599,35 @@ export class ActivitybarPart extends Part implements IActivityBarService {
cssClass: Codicon.settingsGear.classNames
});
this.accountsActivityAction = new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
});
if (this.accountsVisibilityPreference) {
this.accountsActivityAction = new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
});
this.globalActivityActionBar.push(this.accountsActivityAction);
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
}
this.globalActivityActionBar.push(this.globalActivityAction);
}
private toggleAccountsActivity() {
if (this.globalActivityActionBar) {
if (this.accountsActivityAction) {
this.globalActivityActionBar.pull(ActivitybarPart.ACCOUNTS_ACTION_INDEX);
this.accountsActivityAction = undefined;
} else {
this.accountsActivityAction = new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
});
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
}
}
}
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } {
let compositeActions = this.compositeActions.get(compositeId);
if (!compositeActions) {
@ -827,6 +857,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) {
this.onDidChangeHomeBarVisibility();
}
if (e.key === ACCOUNTS_VISIBILITY_PREFERENCE_KEY && e.scope === StorageScope.GLOBAL) {
this.toggleAccountsActivity();
}
}
private saveCachedViewContainers(): void {
@ -964,6 +998,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL);
}
private get accountsVisibilityPreference(): boolean {
return this.storageService.getBoolean(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.GLOBAL, true);
}
private set accountsVisibilityPreference(value: boolean) {
this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.GLOBAL);
}
private migrateFromOldCachedViewContainersValue(): void {
const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
if (value !== undefined) {

View file

@ -66,18 +66,18 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
}
getSecondaryActions(): IAction[] {
const viewSecondaryActions = this.viewPaneContainer.getViewsVisibilityActions();
const viewVisibilityActions = this.viewPaneContainer.getViewsVisibilityActions();
const secondaryActions = this.viewPaneContainer.getSecondaryActions();
if (viewSecondaryActions.length <= 1) {
if (viewVisibilityActions.length <= 1 || viewVisibilityActions.every(({ enabled }) => !enabled)) {
return secondaryActions;
}
if (secondaryActions.length === 0) {
return viewSecondaryActions;
return viewVisibilityActions;
}
return [
new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewSecondaryActions),
new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewVisibilityActions),
new Separator(),
...secondaryActions
];

View file

@ -689,7 +689,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
toJSON(): any {
const result = super.toJSON();
result.uri = this.uri;
result.uri = this._uri;
result.lineNumber = this._lineNumber;
result.column = this._column;
result.adapterData = this.adapterData;

View file

@ -1763,6 +1763,29 @@ export class ShowPopularExtensionsAction extends Action {
}
}
export class RecentlyPublishedExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.recentlyPublishedExtensions';
static readonly LABEL = localize('recentlyPublishedExtensions', "Recently Published Extensions");
constructor(
id: string,
label: string,
@IViewletService private readonly viewletService: IViewletService
) {
super(id, label, undefined, true);
}
run(): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search('@sort:publishedDate ');
viewlet.focus();
});
}
}
export class ShowRecommendedExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showRecommendedExtensions';
@ -2038,6 +2061,27 @@ export class ShowAzureExtensionsAction extends Action {
}
}
export class SearchCategoryAction extends Action {
constructor(
id: string,
label: string,
private readonly category: string,
@IViewletService private readonly viewletService: IViewletService
) {
super(id, label, undefined, true);
}
run(): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search(`@category:"${this.category.toLowerCase()}"`);
viewlet.focus();
});
}
}
export class ChangeSortAction extends Action {
private query: Query;

View file

@ -10,23 +10,23 @@ import { isPromiseCanceledError } from 'vs/base/common/errors';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Event as EventOf, Emitter } from 'vs/base/common/event';
import { IAction, Action, Separator } from 'vs/base/common/actions';
import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions';
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
ShowRecommendedExtensionsAction, ShowPopularExtensionsAction,
ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, RecentlyPublishedExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, ShowEnabledExtensionsAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews';
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, OutdatedExtensionsView, InstalledExtensionsView, SearchBuiltInExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import Severity from 'vs/base/common/severity';
@ -49,9 +49,8 @@ import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/work
import { alert } from 'vs/base/browser/ui/aria/aria';
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { RemoteNameContext } from 'vs/workbench/browser/contextkeys';
import { ILabelService } from 'vs/platform/label/common/label';
import { MementoObject } from 'vs/workbench/common/memento';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@ -68,24 +67,9 @@ const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdat
const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);
const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
const DefaultRecommendedExtensionsContext = new RawContextKey<boolean>('defaultRecommendedExtensions', false);
const viewIdNameMappings: { [id: string]: string } = {
'extensions.listView': localize('marketPlace', "Marketplace"),
'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"),
'extensions.enabledExtensionList2': localize('enabledExtensions', "Enabled"),
'extensions.disabledExtensionList': localize('disabledExtensions', "Disabled"),
'extensions.disabledExtensionList2': localize('disabledExtensions', "Disabled"),
'extensions.popularExtensionsList': localize('popularExtensions', "Popular"),
'extensions.recommendedList': localize('recommendedExtensions', "Recommended"),
'extensions.otherrecommendedList': localize('otherRecommendedExtensions', "Other Recommendations"),
'extensions.workspaceRecommendedList': localize('workspaceRecommendedExtensions', "Workspace Recommendations"),
'extensions.builtInExtensionsList': localize('builtInExtensions', "Features"),
'extensions.builtInThemesExtensionsList': localize('builtInThemesExtensions', "Themes"),
'extensions.builtInBasicsExtensionsList': localize('builtInBasicsExtensions', "Programming Languages"),
'extensions.syncedExtensionsList': localize('syncedExtensions', "My Account"),
};
export class ExtensionsViewletViewsContribution implements IWorkbenchContribution {
@ -101,220 +85,233 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
}
private registerViews(): void {
let viewDescriptors: IViewDescriptor[] = [];
viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultEnabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultDisabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultPopularExtensionsListViewDescriptor());
viewDescriptors.push(this.createEnabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createDisabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInBasicsExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInThemesExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor());
const viewDescriptors: IViewDescriptor[] = [];
if (this.extensionManagementServerService.localExtensionManagementServer) {
viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer));
}
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer));
}
/* Default views */
viewDescriptors.push(...this.createDefaultExtensionsViewDescriptors());
/* Search views */
viewDescriptors.push(...this.createSearchExtensionsViewDescriptors());
/* Recommendations views */
viewDescriptors.push(...this.createRecommendedExtensionsViewDescriptors());
/* Built-in extensions views */
viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors());
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container);
}
// View used for any kind of searching
private createMarketPlaceExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.listView';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(ExtensionsListView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')),
weight: 100
};
}
private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] {
const viewDescriptors: IViewDescriptor[] = [];
// Separate view for enabled extensions required as we need to show enabled, disabled and recommended sections
// in the default view when there is no search text, but user has installed extensions.
private createDefaultEnabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.enabledExtensionList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
weight: 40,
canToggleVisibility: true,
order: 1
};
}
// Separate view for disabled extensions required as we need to show enabled, disabled and recommended sections
// in the default view when there is no search text, but user has installed extensions.
private createDefaultDisabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.disabledExtensionList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
weight: 10,
canToggleVisibility: true,
order: 3,
collapsed: true
};
}
// Separate view for popular extensions required as we need to show popular and recommended sections
// in the default view when there is no search text, and user has no installed extensions.
private createDefaultPopularExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.popularExtensionsList';
return {
id,
name: viewIdNameMappings[id],
/*
* Default popular extensions view
* Separate view for popular extensions required as we need to show popular and recommended sections
* in the default view when there is no search text, and user has no installed extensions.
*/
viewDescriptors.push({
id: 'workbench.views.extensions.popular',
name: localize('popularExtensions', "Popular"),
ctorDescriptor: new SyncDescriptor(ExtensionsListView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')),
weight: 60,
order: 1
};
}
order: 1,
});
private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] {
/*
* Default installed extensions views - Shows all user installed extensions.
*/
const servers: IExtensionManagementServer[] = [];
if (this.extensionManagementServerService.localExtensionManagementServer) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
}
const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {
const serverLabel = server.label;
if (viewTitle && this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
return `${serverLabel} - ${viewTitle}`;
}
return viewTitle ? viewTitle : serverLabel;
return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle;
};
const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);
const getOutdatedViewName = (): string => getViewName(localize('outdated', "Outdated"), server);
const onDidChangeServerLabel: EventOf<void> = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined);
return [{
id: `extensions.${server.id}.installed`,
get name() { return getInstalledViewName(); },
ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')),
weight: 100
}, {
id: `extensions.${server.id}.outdated`,
get name() { return getOutdatedViewName(); },
ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getOutdatedViewName())]),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')),
weight: 100
}, {
id: `extensions.${server.id}.default`,
get name() { return getInstalledViewName(); },
ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.notEqualsTo('')),
weight: 40,
order: 1
}];
}
for (const server of servers) {
const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);
const onDidChangeServerLabel: EventOf<void> = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined);
viewDescriptors.push({
id: servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`,
get name() { return getInstalledViewName(); },
ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
weight: 100,
order: 2,
/* Installed extensions views shall not be hidden when there are more than one server */
canToggleVisibility: servers.length === 1
});
}
// Separate view for recommended extensions required as we need to show it along with other views when there is no search text.
// When user has installed extensions, this is shown along with the views for enabled & disabled extensions
// When user has no installed extensions, this is shown along with the view for popular extensions
private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.recommendedList';
return {
id,
name: viewIdNameMappings[id],
/*
* Default recommended extensions view
* When user has installed extensions, this is shown along with the views for enabled & disabled extensions
* When user has no installed extensions, this is shown along with the view for popular extensions
*/
viewDescriptors.push({
id: 'extensions.recommendedList',
name: localize('recommendedExtensions', "Recommended"),
ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('defaultRecommendedExtensions')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')),
weight: 40,
order: 2,
order: 3,
canToggleVisibility: true
};
});
/* Installed views shall be default in multi server window */
if (servers.length === 1) {
/*
* Default enabled extensions view - Shows all user installed enabled extensions.
* Hidden by default
*/
viewDescriptors.push({
id: 'workbench.views.extensions.enabled',
name: localize('enabledExtensions', "Enabled"),
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
hideByDefault: true,
weight: 40,
order: 4,
canToggleVisibility: true
});
/*
* Default disabled extensions view - Shows all disabled extensions.
* Hidden by default
*/
viewDescriptors.push({
id: 'workbench.views.extensions.disabled',
name: localize('disabledExtensions', "Disabled"),
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
hideByDefault: true,
weight: 10,
order: 5,
canToggleVisibility: true
});
}
return viewDescriptors;
}
// Separate view for recommedations that are not workspace recommendations.
// Shown along with view for workspace recommendations, when using the command that shows recommendations
private createOtherRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.otherrecommendedList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView),
when: ContextKeyExpr.has('recommendedExtensions'),
weight: 50,
order: 2
};
}
private createSearchExtensionsViewDescriptors(): IViewDescriptor[] {
const viewDescriptors: IViewDescriptor[] = [];
// Separate view for workspace recommendations.
// Shown along with view for other recommendations, when using the command that shows recommendations
private createWorkspaceRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.workspaceRecommendedList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
weight: 50,
order: 1
};
}
/*
* View used for searching Marketplace
*/
viewDescriptors.push({
id: 'workbench.views.extensions.marketplace',
name: localize('marketPlace', "Marketplace"),
ctorDescriptor: new SyncDescriptor(ExtensionsListView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')),
});
private createEnabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.enabledExtensionList2';
return {
id,
name: viewIdNameMappings[id],
/*
* View used for searching all installed extensions
*/
viewDescriptors.push({
id: 'workbench.views.extensions.searchInstalled',
name: localize('installed', "Installed"),
ctorDescriptor: new SyncDescriptor(InstalledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')),
});
/*
* View used for searching enabled extensions
*/
viewDescriptors.push({
id: 'workbench.views.extensions.searchEnabled',
name: localize('enabled', "Enabled"),
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),
weight: 40,
order: 1
};
}
});
private createDisabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.disabledExtensionList2';
return {
id,
name: viewIdNameMappings[id],
/*
* View used for searching disabled extensions
*/
viewDescriptors.push({
id: 'workbench.views.extensions.searchDisabled',
name: localize('disabled', "Disabled"),
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),
weight: 10,
order: 3,
collapsed: true
};
});
/*
* View used for searching outdated extensions
*/
viewDescriptors.push({
id: 'workbench.views.extensions.searchOutdated',
name: localize('outdated', "Outdated"),
ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')),
});
/*
* View used for searching builtin extensions
*/
viewDescriptors.push({
id: 'workbench.views.extensions.searchBuiltin',
name: localize('builtin', "Builtin"),
ctorDescriptor: new SyncDescriptor(SearchBuiltInExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')),
});
return viewDescriptors;
}
private createBuiltInExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(BuiltInExtensionsView),
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100
};
private createRecommendedExtensionsViewDescriptors(): IViewDescriptor[] {
const viewDescriptors: IViewDescriptor[] = [];
viewDescriptors.push({
id: 'workbench.views.extensions.workspaceRecommendations',
name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"),
ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView),
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
order: 1
});
viewDescriptors.push({
id: 'workbench.views.extensions.otherRecommendations',
name: localize('otherRecommendedExtensions', "Other Recommendations"),
ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView),
when: ContextKeyExpr.has('recommendedExtensions'),
order: 2
});
return viewDescriptors;
}
private createBuiltInThemesExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInThemesExtensionsList';
return {
id,
name: viewIdNameMappings[id],
private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] {
const viewDescriptors: IViewDescriptor[] = [];
viewDescriptors.push({
id: 'workbench.views.extensions.builtinFeatureExtensions',
name: localize('builtinFeatureExtensions', "Features"),
ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView),
when: ContextKeyExpr.has('builtInExtensions'),
});
viewDescriptors.push({
id: 'workbench.views.extensions.builtinThemeExtensions',
name: localize('builtInThemesExtensions', "Themes"),
ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView),
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100
};
}
when: ContextKeyExpr.has('builtInExtensions'),
});
private createBuiltInBasicsExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInBasicsExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: new SyncDescriptor(BuiltInBasicsExtensionsView),
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100
};
viewDescriptors.push({
id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions',
name: localize('builtinProgrammingLanguageExtensions', "Programming Languages"),
ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView),
when: ContextKeyExpr.has('builtInExtensions'),
});
return viewDescriptors;
}
}
@ -331,15 +328,13 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
private searchEnabledExtensionsContextKey: IContextKey<boolean>;
private searchDisabledExtensionsContextKey: IContextKey<boolean>;
private hasInstalledExtensionsContextKey: IContextKey<boolean>;
private builtInExtensionsContextKey: IContextKey<boolean>;
private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
private recommendedExtensionsContextKey: IContextKey<boolean>;
private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;
private searchDelayer: Delayer<void>;
private root: HTMLElement | undefined;
private searchBox: SuggestEnabledInput | undefined;
private primaryActions: IAction[] | undefined;
private secondaryActions: IAction[] | null = null;
private readonly searchViewletState: MementoObject;
constructor(
@ -349,6 +344,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
@IInstantiationService instantiationService: IInstantiationService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@INotificationService private readonly notificationService: INotificationService,
@IViewletService private readonly viewletService: IViewletService,
@IThemeService themeService: IThemeService,
@ -359,7 +355,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IPreferencesService private readonly preferencesService: IPreferencesService
@IPreferencesService private readonly preferencesService: IPreferencesService,
) {
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
@ -372,10 +368,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);
this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);
this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService);
this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this));
this.searchViewletState = this.getMemento(StorageScope.WORKSPACE);
@ -385,12 +380,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
this.secondaryActions = null;
this.updateTitleArea();
}
if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) {
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
}
}, this));
}
@ -504,39 +495,60 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
}
getActions(): IAction[] {
if (!this.primaryActions) {
this.primaryActions = [
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '')
];
const filterActions: IAction[] = [];
// Local extensions filters
filterActions.push(...[
this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")),
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")),
this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")),
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")),
]);
if (this.extensionGalleryService.isEnabled()) {
filterActions.splice(0, 0, ...[
this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, localize('most popular filter', "Most Popular")),
this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('recomended filter', "Recommended")),
new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))),
new Separator(),
]);
filterActions.push(...[
new Separator(),
new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), [
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate'),
]),
]);
}
return this.primaryActions;
return [
new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, 'codicon-filter'),
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : ''),
];
}
getSecondaryActions(): IAction[] {
if (!this.secondaryActions) {
this.secondaryActions = [
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
new Separator(),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Sort By: Rating"), this.onSearchChange, 'rating'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Sort By: Name"), this.onSearchChange, 'name'),
new Separator(),
this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
new Separator(),
this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL),
this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)
];
const actions: IAction[] = [];
actions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL));
if (this.configurationService.getValue(AutoUpdateConfigurationKey)) {
actions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL));
} else {
actions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL));
}
return this.secondaryActions;
actions.push(new Separator());
actions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL));
actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL));
actions.push(new Separator());
actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL));
return actions;
}
search(value: string, refresh: boolean = false): void {
@ -574,7 +586,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value));
this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));
this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));
this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value));
this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery);
this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
@ -596,19 +609,20 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
}
private alertSearchResult(count: number, viewId: string): void {
const view = this.viewContainerModel.visibleViewDescriptors.find(view => view.id === viewId);
switch (count) {
case 0:
break;
case 1:
if (viewIdNameMappings[viewId]) {
alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", viewIdNameMappings[viewId]));
if (view) {
alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name));
} else {
alert(localize('extensionFound', "1 extension found."));
}
break;
default:
if (viewIdNameMappings[viewId]) {
alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, viewIdNameMappings[viewId]));
if (view) {
alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name));
} else {
alert(localize('extensionsFound', "{0} extensions found.", count));
}

View file

@ -196,6 +196,7 @@ export class ExtensionsListView extends ViewPane {
case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break;
case 'rating': options = assign(options, { sortBy: SortBy.WeightedRating }); break;
case 'name': options = assign(options, { sortBy: SortBy.Title }); break;
case 'publishedDate': options = assign(options, { sortBy: SortBy.PublishedDate }); break;
}
const successCallback = (model: IPagedModel<IExtension>) => {
@ -807,16 +808,21 @@ export class ExtensionsListView extends ViewPane {
this.list = null;
}
static isBuiltInExtensionsQuery(query: string): boolean {
return /^\s*@builtin\s*$/i.test(query);
}
static isLocalExtensionsQuery(query: string): boolean {
return this.isInstalledExtensionsQuery(query)
|| this.isOutdatedExtensionsQuery(query)
|| this.isEnabledExtensionsQuery(query)
|| this.isDisabledExtensionsQuery(query)
|| this.isBuiltInExtensionsQuery(query);
|| this.isBuiltInExtensionsQuery(query)
|| this.isSearchBuiltInExtensionsQuery(query);
}
static isSearchBuiltInExtensionsQuery(query: string): boolean {
return /@builtin\s.+/i.test(query);
}
static isBuiltInExtensionsQuery(query: string): boolean {
return /@builtin$/i.test(query.trim());
}
static isInstalledExtensionsQuery(query: string): boolean {
@ -897,7 +903,7 @@ export class ServerExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
query = query ? query : '@installed';
if (!ExtensionsListView.isLocalExtensionsQuery(query) && !ExtensionsListView.isBuiltInExtensionsQuery(query)) {
if (!ExtensionsListView.isLocalExtensionsQuery(query)) {
query = query += ' @installed';
}
return super.show(query.trim());
@ -929,7 +935,29 @@ export class DisabledExtensionsView extends ExtensionsListView {
}
}
export class BuiltInExtensionsView extends ExtensionsListView {
export class OutdatedExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
query = query || '@outdated';
return ExtensionsListView.isOutdatedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
}
}
export class InstalledExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
query = query || '@installed';
return ExtensionsListView.isInstalledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
}
}
export class SearchBuiltInExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return ExtensionsListView.isSearchBuiltInExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
}
}
export class BuiltInFeatureExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features');
}
@ -941,7 +969,7 @@ export class BuiltInThemesExtensionsView extends ExtensionsListView {
}
}
export class BuiltInBasicsExtensionsView extends ExtensionsListView {
export class BuiltInProgrammingLanguageExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics');
}

View file

@ -39,6 +39,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { asDomUri } from 'vs/base/browser/dom';
import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isWeb } from 'vs/base/common/platform';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@ -665,14 +666,79 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (extensions.length === 1) {
return extensions[0];
}
const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local));
if (enabledExtensions.length === 0) {
return extensions[0];
}
if (enabledExtensions.length === 1) {
return enabledExtensions[0];
}
return enabledExtensions.find(e => e.server === this.extensionManagementServerService.remoteExtensionManagementServer) || enabledExtensions[0];
const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions;
let extension = extensionsToChoose.find(extension => {
for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) {
switch (extensionKind) {
case 'ui':
/* UI extension is chosen only if it is installed locally */
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
return true;
}
return false;
case 'workspace':
/* Choose remote workspace extension if exists */
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
return true;
}
return false;
case 'web':
/* Choose web extension if exists */
if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {
return true;
}
return false;
}
}
return false;
});
if (!extension && this.extensionManagementServerService.localExtensionManagementServer) {
extension = extensionsToChoose.find(extension => {
for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) {
switch (extensionKind) {
case 'workspace':
/* Choose local workspace extension if exists */
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
return true;
}
return false;
case 'web':
/* Choose local web extension if exists */
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
return true;
}
return false;
}
}
return false;
});
}
if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) {
extension = extensionsToChoose.find(extension => {
for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) {
switch (extensionKind) {
case 'web':
/* Choose remote web extension if exists */
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
return true;
}
return false;
}
}
return false;
});
}
return extension || extensions[0];
}
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): IExtension {

View file

@ -164,16 +164,17 @@ export class RuntimeExtensionsEditor extends BaseEditor {
this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule()));
}
private _updateExtensions(): void {
this._elements = this._resolveExtensions();
private async _updateExtensions(): Promise<void> {
this._elements = await this._resolveExtensions();
if (this._list) {
this._list.splice(0, this._list.length, this._elements);
}
}
private _resolveExtensions(): IRuntimeExtension[] {
private async _resolveExtensions(): Promise<IRuntimeExtension[]> {
let marketplaceMap: { [id: string]: IExtension; } = Object.create(null);
for (let extension of this._extensionsWorkbenchService.local) {
const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal();
for (let extension of marketPlaceExtensions) {
marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension;
}
@ -328,7 +329,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
} else {
data.icon.style.visibility = 'inherit';
}
data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || '';
data.name.textContent = element.marketplaceInfo.displayName;
data.version.textContent = element.description.version;
const activationTimes = element.status.activationTimes!;
@ -462,11 +463,10 @@ export class RuntimeExtensionsEditor extends BaseEditor {
actions.push(new ReportExtensionIssueAction(e.element, this._openerService, this._clipboardService, this._productService));
actions.push(new Separator());
if (e.element.marketplaceInfo) {
actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace)));
actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally)));
actions.push(new Separator());
}
actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace)));
actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally)));
actions.push(new Separator());
const state = this._extensionHostProfileService.state;
if (state === ProfileSessionState.Running) {
actions.push(this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL));

View file

@ -14,7 +14,7 @@ import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
@ -34,7 +34,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { NativeURLService } from 'vs/platform/url/common/urlService';
import { URI } from 'vs/base/common/uri';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, IExtension, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
@ -46,6 +46,8 @@ import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestSer
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
suite('ExtensionsWorkbenchServiceTest', () => {
@ -981,6 +983,384 @@ suite('ExtensionsWorkbenchServiceTest', () => {
assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace);
});
test('test user extension is preferred when the same extension exists as system and user extension', async () => {
testObject = await aWorkbenchService();
const userExtension = aLocalExtension('pub.a');
const systemExtension = aLocalExtension('pub.a', {}, { type: ExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [systemExtension, userExtension]);
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, userExtension);
});
test('test user extension is disabled when the same extension exists as system and user extension and system extension is disabled', async () => {
testObject = await aWorkbenchService();
const systemExtension = aLocalExtension('pub.a', {}, { type: ExtensionType.System });
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([systemExtension], EnablementState.DisabledGlobally);
const userExtension = aLocalExtension('pub.a');
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [systemExtension, userExtension]);
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, userExtension);
assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally);
});
test('Test local ui extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local workspace extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local web extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['web'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local ui,workspace extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local workspace,ui extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace', 'ui'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local ui,workspace,web extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'workspace', 'web'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local ui,web,workspace extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'web', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local web,ui,workspace extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['web', 'ui', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local web,workspace,ui extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['web', 'workspace', 'ui'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local workspace,web,ui extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace', 'web', 'ui'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local workspace,ui,web extension is chosen if it exists only in local server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace', 'ui', 'web'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local UI extension is chosen if it exists in both servers', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test local ui,workspace extension is chosen if it exists in both servers', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test remote workspace extension is chosen if it exists in remote server', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace'];
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, remoteExtension);
});
test('Test remote workspace extension is chosen if it exists in both servers', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, remoteExtension);
});
test('Test remote workspace extension is chosen if it exists in both servers and local is disabled', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally);
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, remoteExtension);
assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally);
});
test('Test remote workspace extension is chosen if it exists in both servers and remote is disabled in workspace', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([remoteExtension], EnablementState.DisabledWorkspace);
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, remoteExtension);
assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace);
});
test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally);
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally);
});
test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled in workspace', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['ui', 'workspace'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledWorkspace);
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace);
});
test('Test local web extension is chosen if it exists in both servers', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['web'];
const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) });
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, localExtension);
});
test('Test remote web extension is chosen if it exists only in remote', async () => {
// multi server setup
const extensionKind: ExtensionKind[] = ['web'];
const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([]), createExtensionManagementService([remoteExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
testObject = await aWorkbenchService();
const actual = await testObject.queryLocal();
assert.equal(actual.length, 1);
assert.equal(actual[0].local, remoteExtension);
});
async function aWorkbenchService(): Promise<ExtensionsWorkbenchService> {
const workbenchService: ExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
await workbenchService.queryLocal();
@ -1031,4 +1411,49 @@ suite('ExtensionsWorkbenchServiceTest', () => {
});
});
}
function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService {
const localExtensionManagementServer: IExtensionManagementServer = {
id: 'vscode-local',
label: 'local',
extensionManagementService: localExtensionManagementService || createExtensionManagementService()
};
const remoteExtensionManagementServer: IExtensionManagementServer = {
id: 'vscode-remote',
label: 'remote',
extensionManagementService: remoteExtensionManagementService || createExtensionManagementService()
};
return {
_serviceBrand: undefined,
localExtensionManagementServer,
remoteExtensionManagementServer,
webExtensionManagementServer: null,
getExtensionManagementServer: (extension: IExtension) => {
if (extension.location.scheme === Schemas.file) {
return localExtensionManagementServer;
}
if (extension.location.scheme === REMOTE_HOST_SCHEME) {
return remoteExtensionManagementServer;
}
throw new Error('');
}
};
}
function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService {
return <IExtensionManagementService>{
onInstallExtension: Event.None,
onDidInstallExtension: Event.None,
onUninstallExtension: Event.None,
onDidUninstallExtension: Event.None,
getInstalled: () => Promise.resolve<ILocalExtension[]>(installed),
installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')),
updateMetadata: async (local: ILocalExtension, metadata: IGalleryMetadata) => {
local.identifier.uuid = metadata.id;
local.publisherDisplayName = metadata.publisherDisplayName;
local.publisherId = metadata.publisherId;
return local;
}
};
}
});

View file

@ -13,7 +13,8 @@ export const CELL_RUN_GUTTER = 28;
export const CODE_CELL_LEFT_MARGIN = 32;
export const EDITOR_TOOLBAR_HEIGHT = 0;
export const BOTTOM_CELL_TOOLBAR_HEIGHT = 28;
export const BOTTOM_CELL_TOOLBAR_HEIGHT = 18;
export const BOTTOM_CELL_TOOLBAR_OFFSET = 12;
export const CELL_STATUSBAR_HEIGHT = 22;
// Margin above editor

View file

@ -74,7 +74,8 @@ const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput';
export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.category', "Notebook"), original: 'Notebook' };
export const CELL_TITLE_GROUP_ID = 'inline';
export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell';
export const CELL_TITLE_OUTPUT_GROUP_ID = 'inline/output';
const EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc
@ -403,6 +404,11 @@ registerAction2(class extends NotebookCellAction {
weight: KeybindingWeight.WorkbenchContrib
},
precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR),
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
group: '2_edit',
}
});
}
@ -421,6 +427,11 @@ registerAction2(class extends NotebookCellAction {
primary: KeyCode.KEY_M,
weight: KeybindingWeight.WorkbenchContrib
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
group: '2_edit',
}
});
}
@ -597,7 +608,7 @@ registerAction2(class extends NotebookCellAction {
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.toNegated(),
NOTEBOOK_CELL_EDITABLE),
order: CellToolbarOrder.EditCell,
group: CELL_TITLE_GROUP_ID
group: CELL_TITLE_CELL_GROUP_ID
},
icon: { id: 'codicon/pencil' }
});
@ -621,7 +632,7 @@ registerAction2(class extends NotebookCellAction {
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
NOTEBOOK_CELL_EDITABLE),
order: CellToolbarOrder.SaveCell,
group: CELL_TITLE_GROUP_ID
group: CELL_TITLE_CELL_GROUP_ID
},
icon: { id: 'codicon/check' },
keybinding: {
@ -655,7 +666,7 @@ registerAction2(class extends NotebookCellAction {
id: MenuId.NotebookCellTitle,
order: CellToolbarOrder.DeleteCell,
when: NOTEBOOK_EDITOR_EDITABLE,
group: CELL_TITLE_GROUP_ID
group: CELL_TITLE_CELL_GROUP_ID
},
keybinding: {
primary: KeyCode.Delete,
@ -754,6 +765,11 @@ registerAction2(class extends NotebookCellAction {
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
weight: EDITOR_WIDGET_ACTION_WEIGHT
},
menu: {
id: MenuId.NotebookCellTitle,
when: NOTEBOOK_EDITOR_FOCUSED,
group: '1_copy',
}
});
}
@ -776,6 +792,11 @@ registerAction2(class extends NotebookCellAction {
primary: KeyMod.CtrlCmd | KeyCode.KEY_X,
weight: EDITOR_WIDGET_ACTION_WEIGHT
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
group: '1_copy',
}
});
}
@ -860,6 +881,11 @@ registerAction2(class extends NotebookAction {
primary: KeyMod.CtrlCmd | KeyCode.KEY_V,
weight: EDITOR_WIDGET_ACTION_WEIGHT
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE),
group: '1_copy',
}
});
}
@ -1172,7 +1198,7 @@ registerAction2(class extends NotebookCellAction {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_RUNNABLE),
order: CellToolbarOrder.ClearCellOutput,
group: CELL_TITLE_GROUP_ID
group: CELL_TITLE_OUTPUT_GROUP_ID
},
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_CELL_HAS_OUTPUTS),
@ -1335,7 +1361,11 @@ registerAction2(class extends NotebookCellAction {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext),
order: CellToolbarOrder.SplitCell,
group: CELL_TITLE_GROUP_ID
group: CELL_TITLE_CELL_GROUP_ID,
// alt: {
// id: JOIN_CELL_BELOW_COMMAND_ID,
// title: localize('notebookActions.joinCellBelow', "Join with Next Cell")
// }
},
icon: { id: 'codicon/split-vertical' },
keybinding: {
@ -1388,6 +1418,11 @@ registerAction2(class extends NotebookCellAction {
when: NOTEBOOK_EDITOR_FOCUSED,
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J,
weight: KeybindingWeight.WorkbenchContrib
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
group: '2_edit',
}
});
}

Some files were not shown because too many files have changed in this diff Show more