Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 (#5587)

* Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3

* pipeline errors

* fix build
This commit is contained in:
Anthony Dresser
2019-05-23 11:16:03 -07:00
committed by GitHub
parent ca36f20c6b
commit cf8f8907ee
141 changed files with 6450 additions and 1228 deletions

View File

@@ -24,7 +24,7 @@ import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExte
import { Registry } from 'vs/platform/registry/common/platform';
import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal';
export const VIEWLET_ID = 'workbench.view.debug';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
@@ -111,7 +111,7 @@ export interface IExpression extends IReplElement, IExpressionContainer {
}
export interface IDebugger {
createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter>;
createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter>;
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined>;
getCustomTelemetryService(): Promise<TelemetryService | undefined>;
}
@@ -573,13 +573,7 @@ export interface ITerminalSettings {
osxExec: string,
linuxExec: string
};
integrated: {
shell: {
osx: string,
windows: string,
linux: string
}
};
integrated: ITerminalConfiguration;
}
export interface IConfigurationManager {
@@ -609,7 +603,6 @@ export interface IConfigurationManager {
activateDebuggers(activationEvent: string, debugType?: string): Promise<void>;
needsToRunInExtHost(debugType: string): boolean;
hasDebugConfigurationProvider(debugType: string): boolean;
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable;
@@ -618,9 +611,6 @@ export interface IConfigurationManager {
registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable;
unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void;
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable;
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void;
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise<any>;
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined>;

View File

@@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/node/debugger';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -52,7 +52,6 @@ export class ConfigurationManager implements IConfigurationManager {
private _onDidSelectConfigurationName = new Emitter<void>();
private configProviders: IDebugConfigurationProvider[];
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private adapterTrackerFactories: IDebugAdapterTrackerFactory[];
private debugAdapterFactories: Map<string, IDebugAdapterFactory>;
private terminalLauncher: ITerminalLauncher;
private debugConfigurationTypeContext: IContextKey<string>;
@@ -72,7 +71,6 @@ export class ConfigurationManager implements IConfigurationManager {
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
this.adapterTrackerFactories = [];
this.debuggers = [];
this.toDispose = [];
this.registerListeners(lifecycleService);
@@ -164,24 +162,6 @@ export class ConfigurationManager implements IConfigurationManager {
return Promise.resolve(undefined);
}
// debug adapter trackers
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable {
this.adapterTrackerFactories.push(debugAdapterTrackerFactory);
return {
dispose: () => {
this.unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory);
}
};
}
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void {
const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory);
if (ix >= 0) {
this.adapterTrackerFactories.splice(ix, 1);
}
}
// debug configurations
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
@@ -206,13 +186,6 @@ export class ConfigurationManager implements IConfigurationManager {
return providers.length > 0;
}
needsToRunInExtHost(debugType: string): boolean {
// if the given debugType matches any registered tracker factory we need to run the DA in the EH
const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*');
return providers.length > 0;
}
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise<IConfig | null | undefined> {
return this.activateDebuggers('onDebugResolve', type).then(() => {
// pipe the config through the promises sequentially. Append at the end the '*' types

View File

@@ -25,7 +25,6 @@ import { generateUuid } from 'vs/base/common/uuid';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { Range } from 'vs/editor/common/core/range';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -62,7 +61,6 @@ export class DebugSession implements IDebugSession {
private _parentSession: IDebugSession | undefined,
@IDebugService private readonly debugService: IDebugService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IOutputService private readonly outputService: IOutputService,
@IWindowService private readonly windowService: IWindowService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IViewletService private readonly viewletService: IViewletService,
@@ -167,7 +165,7 @@ export class DebugSession implements IDebugSession {
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
return dbgr.createDebugAdapter(this, this.outputService).then(debugAdapter => {
return dbgr.createDebugAdapter(this).then(debugAdapter => {
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.environmentService);

View File

@@ -11,11 +11,8 @@ import { isObject } from 'vs/base/common/types';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
@@ -38,7 +35,6 @@ export class Debugger implements IDebugger {
constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,
@ICommandService private readonly commandService: ICommandService,
@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
@@ -97,97 +93,25 @@ export class Debugger implements IDebugger {
}
}
public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter> {
public createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {
return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => {
if (this.inExtHost()) {
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
return Promise.resolve(da);
}
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
} else {
return this.getAdapterDescriptor(session).then(adapterDescriptor => {
switch (adapterDescriptor.type) {
case 'executable':
return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService);
case 'server':
return new SocketDebugAdapter(adapterDescriptor);
case 'implementation':
// TODO@AW: this.inExtHost() should now return true
return Promise.resolve(this.configurationManager.createDebugAdapter(session));
default:
throw new Error('unknown descriptor type');
}
}).catch(err => {
if (err && err.message) {
throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message));
} else {
throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter."));
}
});
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
return Promise.resolve(da);
}
});
}
private getAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor> {
// a "debugServer" attribute in the launch config takes precedence
if (typeof session.configuration.debugServer === 'number') {
return Promise.resolve(<IDebugAdapterServer>{
type: 'server',
port: session.configuration.debugServer
});
}
// try the new "createDebugAdapterDescriptor" and the deprecated "provideDebugAdapter" API
return this.configurationManager.getDebugAdapterDescriptor(session).then(adapter => {
if (adapter) {
return adapter;
}
// try deprecated command based extension API "adapterExecutableCommand" to determine the executable
if (this.debuggerContribution.adapterExecutableCommand) {
console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
const rootFolder = session.root ? session.root.uri.toString() : undefined;
return this.commandService.executeCommand<IDebugAdapterExecutable>(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => {
if (ae) {
return <IAdapterDescriptor>{
type: 'executable',
command: ae.command,
args: ae.args || []
};
}
throw new Error('command adapterExecutableCommand did not return proper command.');
});
}
// fallback: use executable information from package.json
const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type);
if (ae === undefined) {
throw new Error('no executable specified in package.json');
}
return ae;
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
});
}
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
if (this.inExtHost()) {
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
});
} else {
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
}
});
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
const config = this.configurationService.getValue<ITerminalSettings>('terminal');
return this.configurationManager.runInTerminal(this.inExtHost() ? this.type : '*', args, config);
}
private inExtHost(): boolean {
return true;
return this.configurationManager.runInTerminal(this.type, args, config);
}
get label(): string {

View File

@@ -10,6 +10,7 @@ import * as pfs from 'vs/base/node/pfs';
import { assign } from 'vs/base/common/objects';
import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal';
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
@@ -314,13 +315,13 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments
let shell: string;
const shell_config = config.integrated.shell;
if (env.isWindows) {
shell = shell_config.windows;
shell = shell_config.windows || getDefaultShell(env.Platform.Windows);
shellType = ShellType.cmd;
} else if (env.isLinux) {
shell = shell_config.linux;
shell = shell_config.linux || getDefaultShell(env.Platform.Linux);
shellType = ShellType.bash;
} else if (env.isMacintosh) {
shell = shell_config.osx;
shell = shell_config.osx || getDefaultShell(env.Platform.Mac);
shellType = ShellType.bash;
} else {
throw new Error('Unknown platform');

View File

@@ -14,7 +14,7 @@ import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugS
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
}
suite('Debug - Model', () => {
@@ -436,7 +436,7 @@ suite('Debug - Model', () => {
// Repl output
test('repl output', () => {
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
const repl = new ReplModel(session);
repl.appendToRepl('first line\n', severity.Error);
repl.appendToRepl('second line ', severity.Error);

View File

@@ -130,7 +130,7 @@ suite('Debug - Debugger', () => {
const testResourcePropertiesService = new TestTextResourcePropertiesService(configurationService);
setup(() => {
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!);
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!);
});
teardown(() => {

View File

@@ -19,6 +19,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isNonEmptyArray } from 'vs/base/common/arrays';
export interface IExtensionTemplateData {
icon: HTMLImageElement;
@@ -217,4 +219,46 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
}
}));
}
}
export class ExtensionData implements IExtensionData {
readonly extension: IExtension;
readonly parent: IExtensionData | null;
private readonly getChildrenExtensionIds: (extension: IExtension) => string[];
private readonly childrenExtensionIds: string[];
private readonly extensionsWorkbenchService: IExtensionsWorkbenchService;
constructor(extension: IExtension, parent: IExtensionData | null, getChildrenExtensionIds: (extension: IExtension) => string[], extensionsWorkbenchService: IExtensionsWorkbenchService) {
this.extension = extension;
this.parent = parent;
this.getChildrenExtensionIds = getChildrenExtensionIds;
this.extensionsWorkbenchService = extensionsWorkbenchService;
this.childrenExtensionIds = this.getChildrenExtensionIds(extension);
}
get hasChildren(): boolean {
return isNonEmptyArray(this.childrenExtensionIds);
}
async getChildren(): Promise<IExtensionData[] | null> {
if (this.hasChildren) {
const localById = this.extensionsWorkbenchService.local.reduce((result, e) => { result.set(e.identifier.id.toLowerCase(), e); return result; }, new Map<string, IExtension>());
const result: IExtension[] = [];
const toQuery: string[] = [];
for (const extensionId of this.childrenExtensionIds) {
const id = extensionId.toLowerCase();
const local = localById.get(id);
if (local) {
result.push(local);
} else {
toQuery.push(id);
}
}
const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: this.childrenExtensionIds, pageSize: this.childrenExtensionIds.length }, CancellationToken.None);
result.push(...galleryResult.firstPage);
return result.map(extension => new ExtensionData(extension, this, this.getChildrenExtensionIds, this.extensionsWorkbenchService));
}
return null;
}
}

View File

@@ -69,14 +69,6 @@ export interface IExtension {
readonly isMalicious: boolean;
}
export interface IExtensionDependencies {
dependencies: IExtensionDependencies[];
hasDependencies: boolean;
identifier: string;
extension: IExtension;
dependent: IExtensionDependencies | null;
}
export const SERVICE_ID = 'extensionsWorkbenchService';
export const IExtensionsWorkbenchService = createDecorator<IExtensionsWorkbenchService>(SERVICE_ID);
@@ -97,7 +89,6 @@ export interface IExtensionsWorkbenchService {
installVersion(extension: IExtension, version: string): Promise<IExtension>;
reinstall(extension: IExtension): Promise<IExtension>;
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null>;
open(extension: IExtension, sideByside?: boolean): Promise<any>;
checkForUpdates(): Promise<void>;
allowedBadgeProviders: string[];

View File

@@ -24,7 +24,7 @@ import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/ex
import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -43,13 +43,13 @@ import { Color } from 'vs/base/common/color';
import { assign } from 'vs/base/common/objects';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionsTree, IExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
import { ExtensionsTree, ExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry';
import { isUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { isUndefined } from 'vs/base/common/types';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
// {{SQL CARBON EDIT}}
@@ -181,7 +181,6 @@ export class ExtensionEditor extends BaseEditor {
private extensionReadme: Cache<string> | null;
private extensionChangelog: Cache<string> | null;
private extensionManifest: Cache<IExtensionManifest | null> | null;
private extensionDependencies: Cache<IExtensionDependencies | null> | null;
private layoutParticipants: ILayoutParticipant[] = [];
private contentDisposables: IDisposable[] = [];
@@ -210,7 +209,6 @@ export class ExtensionEditor extends BaseEditor {
this.extensionReadme = null;
this.extensionChangelog = null;
this.extensionManifest = null;
this.extensionDependencies = null;
}
createEditor(parent: HTMLElement): void {
@@ -298,7 +296,6 @@ export class ExtensionEditor extends BaseEditor {
this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token)));
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token)));
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true);
const onError = Event.once(domEvent(this.icon, 'error'));
@@ -643,69 +640,28 @@ export class ExtensionEditor extends BaseEditor {
}
private openDependencies(extension: IExtension): Promise<IActiveElement> {
if (extension.dependencies.length === 0) {
if (arrays.isFalsyOrEmpty(extension.dependencies)) {
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(this.content);
}
return this.loadContents(() => this.extensionDependencies!.get())
.then<IActiveElement, IActiveElement>(extensionDependencies => {
if (extensionDependencies) {
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const dependenciesTree = this.renderDependencies(content, extensionDependencies);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
dependenciesTree.layout(scrollDimensions.height);
};
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
dependenciesTree.layout(scrollDimensions.height);
};
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
this.contentDisposables.push(dependenciesTree);
scrollableContent.scanDomNode();
return { focus() { dependenciesTree.domFocus(); } };
} else {
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(this.content);
}
}, error => {
append(this.content, $('p.nocontent')).textContent = error;
this.notificationService.error(error);
return this.content;
});
}
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): ExtensionsTree {
class ExtensionData implements IExtensionData {
private readonly extensionDependencies: IExtensionDependencies;
constructor(extensionDependencies: IExtensionDependencies) {
this.extensionDependencies = extensionDependencies;
}
get extension(): IExtension {
return this.extensionDependencies.extension;
}
get parent(): IExtensionData | null {
return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
}
get hasChildren(): boolean {
return this.extensionDependencies.hasDependencies;
}
getChildren(): Promise<IExtensionData[] | null> {
return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null);
}
}
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extensionDependencies), container);
this.contentDisposables.push(dependenciesTree);
scrollableContent.scanDomNode();
return Promise.resolve({ focus() { dependenciesTree.domFocus(); } });
}
private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
@@ -714,7 +670,7 @@ export class ExtensionEditor extends BaseEditor {
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const extensionsPackTree = this.renderExtensionPack(content, extension);
const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
@@ -728,35 +684,6 @@ export class ExtensionEditor extends BaseEditor {
return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
}
private renderExtensionPack(container: HTMLElement, extension: IExtension): ExtensionsTree {
const extensionsWorkbenchService = this.extensionsWorkbenchService;
class ExtensionData implements IExtensionData {
readonly extension: IExtension;
readonly parent: IExtensionData | null;
constructor(extension: IExtension, parent?: IExtensionData) {
this.extension = extension;
this.parent = withUndefinedAsNull(parent);
}
get hasChildren(): boolean {
return this.extension.extensionPack.length > 0;
}
getChildren(): Promise<IExtensionData[] | null> {
if (this.hasChildren) {
const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase());
return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length }, CancellationToken.None)
.then(result => result.firstPage.map(extension => new ExtensionData(extension, this)));
}
return Promise.resolve(null);
}
}
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension), container);
}
private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
const contributes = manifest.contributes;
const configuration = contributes && contributes.configuration;

View File

@@ -1558,7 +1558,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction {
export class ShowEnabledExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showEnabledExtensions';
static LABEL = localize('showEnabledExtensions', 'Show Enabled Extensions');
static LABEL = localize('showEnabledExtensions', "Show Enabled Extensions");
constructor(
id: string,

View File

@@ -23,8 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IWindowService } from 'vs/platform/windows/common/windows';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
// {{SQL CARBON EDIT}}
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
@@ -323,53 +322,6 @@ ${this.description}
}
}
class ExtensionDependencies implements IExtensionDependencies {
private _hasDependencies: boolean | null = null;
constructor(private _extension: IExtension, private _identifier: string, private _map: Map<string, IExtension>, private _dependent: IExtensionDependencies | null = null) { }
get hasDependencies(): boolean {
if (this._hasDependencies === null) {
this._hasDependencies = this.computeHasDependencies();
}
return this._hasDependencies;
}
get extension(): IExtension {
return this._extension;
}
get identifier(): string {
return this._identifier;
}
get dependent(): IExtensionDependencies | null {
return this._dependent;
}
get dependencies(): IExtensionDependencies[] {
if (!this.hasDependencies) {
return [];
}
return this._extension.dependencies.map(id => new ExtensionDependencies(this._map.get(id)!, id, this._map, this));
}
private computeHasDependencies(): boolean {
if (this._extension && this._extension.dependencies.length > 0) {
let dependent = this._dependent;
while (dependent !== null) {
if (dependent.identifier === this.identifier) {
return false;
}
dependent = dependent.dependent;
}
return true;
}
return false;
}
}
class Extensions extends Disposable {
private readonly _onChange: Emitter<Extension | undefined> = new Emitter<Extension | undefined>();
@@ -666,27 +618,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
});
}
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null> {
if (!extension.dependencies.length) {
return Promise.resolve(null);
}
return this.extensionService.getExtensionsReport()
.then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => ({ id })), token)
.then(galleryExtensions => {
const extensions: IExtension[] = [...this.local, ...galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet))];
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.identifier.id, extension);
}
return new ExtensionDependencies(extension, extension.identifier.id, map);
});
});
}
open(extension: IExtension, sideByside: boolean = false): Promise<any> {
return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP));
}

View File

@@ -480,248 +480,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
assert.ok(target.calledOnce);
});
test('test extension dependencies when empty', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
return testObject.queryGallery(CancellationToken.None).then(page => {
return testObject.loadDependencies(page.firstPage[0], CancellationToken.None).then(dependencies => {
assert.equal(null, dependencies);
});
});
});
test('test one level extension dependencies without cycle', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c', 'pub.d'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('c'), aGalleryExtension('d')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(3, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0];
assert.ok(!actual.hasDependencies);
assert.equal('pub.b', actual.extension.identifier.id);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1];
assert.ok(!actual.hasDependencies);
assert.equal('pub.c', actual.extension.identifier.id);
assert.equal('pub.c', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[2];
assert.ok(!actual.hasDependencies);
assert.equal('pub.d', actual.extension.identifier.id);
assert.equal('pub.d', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with cycle', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.b', actual.extension.identifier.id);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with missing dependencies', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal(null, actual.extension);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with in built dependencies', async () => {
const local = aLocalExtension('inbuilt', {}, { type: ExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.inbuilt', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.inbuilt', actual.extension.identifier.id);
assert.equal('pub.inbuilt', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test more than one level of extension dependencies', async () => {
const local = aLocalExtension('c', { extensionDependencies: ['pub.d'] }, { type: ExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [
aGalleryExtension('b', {}, { dependencies: ['pub.d', 'pub.e'] }),
aGalleryExtension('d', {}, { dependencies: ['pub.f', 'pub.c'] }),
aGalleryExtension('e')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(a => {
assert.ok(a!.hasDependencies);
assert.equal(extension, a!.extension);
assert.equal(null, a!.dependent);
assert.equal(2, a!.dependencies.length);
assert.equal('pub.a', a!.identifier);
let b = a!.dependencies[0];
assert.ok(b.hasDependencies);
assert.equal('pub.b', b.extension.identifier.id);
assert.equal('pub.b', b.identifier);
assert.equal(a, b.dependent);
assert.equal(2, b.dependencies.length);
let c = a!.dependencies[1];
assert.ok(c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(a, c.dependent);
assert.equal(1, c.dependencies.length);
let d = b.dependencies[0];
assert.ok(d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(b, d.dependent);
assert.equal(2, d.dependencies.length);
let e = b.dependencies[1];
assert.ok(!e.hasDependencies);
assert.equal('pub.e', e.extension.identifier.id);
assert.equal('pub.e', e.identifier);
assert.equal(b, e.dependent);
assert.equal(0, e.dependencies.length);
let f = d.dependencies[0];
assert.ok(!f.hasDependencies);
assert.equal(null, f.extension);
assert.equal('pub.f', f.identifier);
assert.equal(d, f.dependent);
assert.equal(0, f.dependencies.length);
c = d.dependencies[1];
assert.ok(c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(d, c.dependent);
assert.equal(1, c.dependencies.length);
d = c.dependencies[0];
assert.ok(!d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(c, d.dependent);
assert.equal(0, d.dependencies.length);
c = a!.dependencies[1];
d = c.dependencies[0];
assert.ok(d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(c, d.dependent);
assert.equal(2, d.dependencies.length);
f = d.dependencies[0];
assert.ok(!f.hasDependencies);
assert.equal(null, f.extension);
assert.equal('pub.f', f.identifier);
assert.equal(d, f.dependent);
assert.equal(0, f.dependencies.length);
c = d.dependencies[1];
assert.ok(!c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(d, c.dependent);
assert.equal(0, c.dependencies.length);
});
});
});
test('test uninstalled extensions are always enabled', async () => {
return instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('b')], EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('c')], EnablementState.WorkspaceDisabled))

View File

@@ -94,7 +94,7 @@ CommandsRegistry.registerCommand({
const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath)));
return directoriesToOpen.map(dir => {
if (configurationService.getValue<IExternalTerminalConfiguration>().terminal.explorerKind === 'integrated') {
const instance = integratedTerminalService.createTerminal({ cwd: dir }, true);
const instance = integratedTerminalService.createTerminal({ cwd: dir });
if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) {
integratedTerminalService.setActiveInstance(instance);
integratedTerminalService.showPanel(true);

View File

@@ -196,7 +196,7 @@ export const tocData: ITOCEntry = {
]
};
export const knownAcronyms = new Set();
export const knownAcronyms = new Set<string>();
[
'css',
'html',
@@ -209,3 +209,7 @@ export const knownAcronyms = new Set();
'id',
'php',
].forEach(str => knownAcronyms.add(str));
export const knownTermMappings = new Map<string, string>();
knownTermMappings.set('power shell', 'PowerShell');
knownTermMappings.set('powershell', 'PowerShell');

View File

@@ -11,7 +11,7 @@ import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
@@ -404,8 +404,8 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category
}
function wordifyKey(key: string): string {
return key
.replace(/\.([a-z0-9])/g, (match, p1) => ` ${p1.toUpperCase()}`) // Replace dot with spaced '>'
key = key
.replace(/\.([a-z0-9])/g, (_, p1) => ` ${p1.toUpperCase()}`) // Replace dot with spaced '>'
.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar
.replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo
.replace(/\b\w+\b/g, match => { // Upper casing known acronyms
@@ -413,6 +413,12 @@ function wordifyKey(key: string): string {
match.toUpperCase() :
match;
});
for (let [k, v] of knownTermMappings) {
key = key.replace(new RegExp(`\\b${k}\\b`, 'gi'), v);
}
return key;
}
function trimCategoryForGroup(category: string, groupId: string): string {

View File

@@ -1225,8 +1225,14 @@ export class SettingsEditor2 extends BaseEditor {
return;
}
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
? 'initial'
: 'none';
if (!this.searchResultModel) {
this.countElement.style.display = 'none';
DOM.removeClass(this.rootElement, 'no-results');
return;
}
if (this.tocTreeModel && this.tocTreeModel.settingsTreeRoot) {
@@ -1239,9 +1245,6 @@ export class SettingsEditor2 extends BaseEditor {
this.countElement.style.display = 'block';
DOM.toggleClass(this.rootElement, 'no-results', count === 0);
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
? 'initial'
: 'none';
}
}

View File

@@ -35,6 +35,20 @@ suite('SettingsTree', () => {
category: '',
label: 'Foo'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1leading.number'),
{
category: 'Foo 1leading',
label: 'Number'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1Leading.number'),
{
category: 'Foo 1 Leading',
label: 'Number'
});
});
test('settingKeyToDisplayFormat - with category', () => {
@@ -101,19 +115,21 @@ suite('SettingsTree', () => {
category: 'Something Else',
label: 'Etc'
});
});
test('settingKeyToDisplayFormat - known acronym/term', () => {
assert.deepEqual(
settingKeyToDisplayFormat('foo.1leading.number'),
settingKeyToDisplayFormat('css.someCssSetting'),
{
category: 'Foo 1leading',
label: 'Number'
category: 'CSS',
label: 'Some CSS Setting'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1Leading.number'),
settingKeyToDisplayFormat('powershell.somePowerShellSetting'),
{
category: 'Foo 1 Leading',
label: 'Number'
category: 'PowerShell',
label: 'Some PowerShell Setting'
});
});

View File

@@ -0,0 +1,428 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { RemoteExtensionLogFileName, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { MenuId, IMenuService, MenuItemAction, IMenu } from 'vs/platform/actions/common/actions';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output';
import * as resources from 'vs/base/common/resources';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { ILabelService } from 'vs/platform/label/common/label';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { ILogService } from 'vs/platform/log/common/log';
import { IFileService } from 'vs/platform/files/common/files';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc';
import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc';
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
import { ipcRenderer as ipc } from 'electron';
import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IProgressService2, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import Severity from 'vs/base/common/severity';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions/windowActions';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions';
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
private windowCommandMenu: IMenu;
private hasWindowActions: boolean = false;
private remoteAuthority: string | undefined;
private disconnected: boolean = false;
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ILabelService private readonly labelService: ILabelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IMenuService private menuService: IMenuService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ICommandService private readonly commandService: ICommandService,
@IExtensionService extensionService: IExtensionService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
this._register(this.windowCommandMenu);
this._register(CommandsRegistry.registerCommand(WINDOW_ACTIONS_COMMAND_ID, _ => this.showIndicatorActions(this.windowCommandMenu)));
this.remoteAuthority = environmentService.configuration.remoteAuthority;
if (this.remoteAuthority) {
// Pending entry until extensions are ready
this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."));
}
extensionService.whenInstalledExtensionsRegistered().then(_ => {
if (this.remoteAuthority) {
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
}
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
this.updateWindowIndicator();
});
const connection = remoteAgentService.getConnection();
if (connection) {
this._register(connection.onDidStateChange((e) => {
switch (e.type) {
case PersistenConnectionEventType.ConnectionLost:
case PersistenConnectionEventType.ReconnectionPermanentFailure:
case PersistenConnectionEventType.ReconnectionRunning:
case PersistenConnectionEventType.ReconnectionWait:
this.setDisconnected(true);
break;
case PersistenConnectionEventType.ConnectionGain:
this.setDisconnected(false);
break;
}
}));
}
}
private setDisconnected(isDisconnected: boolean): void {
if (this.disconnected !== isDisconnected) {
this.disconnected = isDisconnected;
this.updateWindowIndicator();
}
}
private updateWindowIndicator(): void {
const windowActionCommand = this.windowCommandMenu.getActions().length ? WINDOW_ACTIONS_COMMAND_ID : undefined;
if (this.remoteAuthority) {
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
if (!this.disconnected) {
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
} else {
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel));
}
} else {
if (windowActionCommand) {
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
} else if (this.windowIndicatorEntry) {
this.windowIndicatorEntry.dispose();
this.windowIndicatorEntry = undefined;
}
}
}
private updateWindowActions() {
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
if (newHasWindowActions !== this.hasWindowActions) {
this.hasWindowActions = newHasWindowActions;
this.updateWindowIndicator();
}
}
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
const properties: IStatusbarEntry = {
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
};
if (this.windowIndicatorEntry) {
this.windowIndicatorEntry.update(properties);
} else {
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
}
}
private showIndicatorActions(menu: IMenu) {
const actions = menu.getActions();
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
for (let actionGroup of actions) {
if (items.length) {
items.push({ type: 'separator' });
}
for (let action of actionGroup[1]) {
if (action instanceof MenuItemAction) {
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
if (action.item.category) {
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
label = nls.localize('cat.title', "{0}: {1}", category, label);
}
items.push({
type: 'item',
id: action.item.id,
label
});
}
}
}
const quickPick = this.quickInputService.createQuickPick();
quickPick.items = items;
quickPick.canSelectMany = false;
quickPick.onDidAccept(_ => {
const selectedItems = quickPick.selectedItems;
if (selectedItems.length === 1) {
this.commandService.executeCommand(selectedItems[0].id!);
}
quickPick.hide();
});
quickPick.show();
}
}
class LogOutputChannels extends Disposable implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
super();
remoteAgentService.getEnvironment().then(remoteEnv => {
if (remoteEnv) {
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels);
outputChannelRegistry.registerChannel({ id: 'remoteExtensionLog', label: nls.localize('remoteExtensionLog', "Remote Server"), file: resources.joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true });
}
});
}
}
class RemoteChannelsContribution implements IWorkbenchContribution {
constructor(
@ILogService logService: ILogService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IFileService fileService: IFileService,
@IDialogService dialogService: IDialogService
) {
const connection = remoteAgentService.getConnection();
if (connection) {
connection.registerChannel('dialog', new DialogChannel(dialogService));
connection.registerChannel('download', new DownloadServiceChannel());
connection.registerChannel('loglevel', new LogLevelSetterChannel(logService));
}
}
}
class RemoteAgentDiagnosticListener implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ILabelService labelService: ILabelService
) {
ipc.on('vscode:getDiagnosticInfo', (event: Event, request: { replyChannel: string, args: IDiagnosticInfoOptions }): void => {
const connection = remoteAgentService.getConnection();
if (connection) {
const hostName = labelService.getHostLabel(REMOTE_HOST_SCHEME, connection.remoteAuthority);
remoteAgentService.getDiagnosticInfo(request.args)
.then(info => {
if (info) {
(info as IRemoteDiagnosticInfo).hostName = hostName;
}
ipc.send(request.replyChannel, info);
})
.catch(e => {
const errorMessage = e && e.message ? `Fetching remote diagnostics for '${hostName}' failed: ${e.message}` : `Fetching remote diagnostics for '${hostName}' failed.`;
ipc.send(request.replyChannel, { hostName, errorMessage });
});
} else {
ipc.send(request.replyChannel);
}
});
}
}
class ProgressReporter {
private _currentProgress: IProgress<IProgressStep> | null = null;
private lastReport: string | null = null;
constructor(currentProgress: IProgress<IProgressStep> | null) {
this._currentProgress = currentProgress;
}
set currentProgress(progress: IProgress<IProgressStep>) {
this._currentProgress = progress;
}
report(message?: string) {
if (message) {
this.lastReport = message;
}
if (this.lastReport && this._currentProgress) {
this._currentProgress.report({ message: this.lastReport });
}
}
}
class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IProgressService2 progressService: IProgressService2,
@IDialogService dialogService: IDialogService,
@ICommandService commandService: ICommandService
) {
const connection = remoteAgentService.getConnection();
if (connection) {
let currentProgressPromiseResolve: (() => void) | null = null;
let progressReporter: ProgressReporter | null = null;
let currentTimer: ReconnectionTimer | null = null;
connection.onDidStateChange((e) => {
if (currentTimer) {
currentTimer.dispose();
currentTimer = null;
}
switch (e.type) {
case PersistenConnectionEventType.ConnectionLost:
if (!currentProgressPromiseResolve) {
let promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
progressService!.withProgress(
{ location: ProgressLocation.Dialog },
(progress: IProgress<IProgressStep> | null) => { progressReporter = new ProgressReporter(progress!); return promise; },
() => {
currentProgressPromiseResolve!();
promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
progressService!.withProgress({ location: ProgressLocation.Notification }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; });
progressReporter!.report();
}
);
}
progressReporter!.report(nls.localize('connectionLost', "Connection Lost"));
break;
case PersistenConnectionEventType.ReconnectionWait:
currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds);
break;
case PersistenConnectionEventType.ReconnectionRunning:
progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));
break;
case PersistenConnectionEventType.ReconnectionPermanentFailure:
currentProgressPromiseResolve!();
currentProgressPromiseResolve = null;
progressReporter = null;
dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(choice => {
// Reload the window
if (choice === 0) {
commandService.executeCommand(ReloadWindowAction.ID);
}
});
break;
case PersistenConnectionEventType.ConnectionGain:
currentProgressPromiseResolve!();
currentProgressPromiseResolve = null;
progressReporter = null;
break;
}
});
}
}
}
class ReconnectionTimer implements IDisposable {
private readonly _progressReporter: ProgressReporter;
private readonly _completionTime: number;
private readonly _token: NodeJS.Timeout;
constructor(progressReporter: ProgressReporter, completionTime: number) {
this._progressReporter = progressReporter;
this._completionTime = completionTime;
this._token = setInterval(() => this._render(), 1000);
this._render();
}
public dispose(): void {
clearInterval(this._token);
}
private _render() {
const remainingTimeMs = this._completionTime - Date.now();
if (remainingTimeMs < 0) {
return;
}
const remainingTime = Math.ceil(remainingTimeMs / 1000);
if (remainingTime === 1) {
this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime));
} else {
this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime));
}
}
}
class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchContribution {
constructor(
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.updateRemoteTelemetryEnablement();
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('telemetry.enableTelemetry')) {
this.updateRemoteTelemetryEnablement();
}
}));
}
private updateRemoteTelemetryEnablement(): Promise<void> {
if (!this.configurationService.getValue('telemetry.enableTelemetry')) {
return this.remoteAgentService.disableTelemetry();
}
return Promise.resolve();
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchContributionsExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
id: 'remote',
title: nls.localize('remote', "Remote"),
type: 'object',
properties: {
'remote.extensionKind': {
type: 'object',
markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."),
patternProperties: {
'([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': {
type: 'string',
enum: [
'ui',
'workspace'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
],
default: 'ui'
},
},
default: {
'pub.name': 'ui'
}
}
}
});

View File

@@ -342,6 +342,9 @@ export class SearchView extends ViewletPanel {
return this.inputPatternExcludes;
}
/**
* Warning: a bit expensive due to updating the view title
*/
protected updateActions(): void {
for (const action of this.actions) {
action.update();
@@ -1386,6 +1389,8 @@ export class SearchView extends ViewletPanel {
let visibleMatches = 0;
let updatedActionsForFileCount = false;
// Handle UI updates in an interval to show frequent progress and results
const uiRefreshHandle: any = setInterval(() => {
if (!this.searching) {
@@ -1399,7 +1404,9 @@ export class SearchView extends ViewletPanel {
visibleMatches = fileCount;
this.refreshAndUpdateCount();
}
if (fileCount > 0) {
if (fileCount > 0 && !updatedActionsForFileCount) {
updatedActionsForFileCount = true;
this.updateActions();
}
}, 100);

View File

@@ -274,7 +274,7 @@ export class QueryBuilder {
* Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths
*/
private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] {
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || !searchPaths || !searchPaths.length) {
if (!searchPaths || !searchPaths.length) {
// No workspace => ignore search paths
return [];
}

View File

@@ -35,6 +35,7 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const TERMINAL_PICKER_PREFIX = 'term ';
@@ -85,7 +86,7 @@ export class ToggleTerminalAction extends TogglePanelAction {
if (this.terminalService.terminalInstances.length === 0) {
// If there is not yet an instance attempt to create it here so that we can suggest a
// new shell on Windows (and not do so when the panel is restored on reload).
const newTerminalInstance = this.terminalService.createTerminal(undefined, true);
const newTerminalInstance = this.terminalService.createTerminal(undefined);
const toDispose = newTerminalInstance.onProcessIdReady(() => {
newTerminalInstance.focus();
toDispose.dispose();
@@ -329,7 +330,7 @@ export class CreateNewTerminalAction extends Action {
if (folders.length <= 1) {
// Allow terminal service to handle the path when there is only a
// single root
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined, true));
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined));
} else {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
@@ -339,7 +340,7 @@ export class CreateNewTerminalAction extends Action {
// Don't create the instance if the workspace picker was canceled
return null;
}
return this.terminalService.createTerminal({ cwd: workspace.uri }, true);
return this.terminalService.createTerminal({ cwd: workspace.uri });
});
}
@@ -366,7 +367,7 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action {
}
public run(event?: any): Promise<any> {
const instance = this.terminalService.createTerminal(undefined, true);
const instance = this.terminalService.createTerminal(undefined);
if (!instance) {
return Promise.resolve(undefined);
}
@@ -720,6 +721,14 @@ export class SwitchTerminalAction extends Action {
if (!item || !item.split) {
return Promise.resolve(null);
}
if (item === SwitchTerminalActionViewItem.SEPARATOR) {
this.terminalService.refreshActiveTab();
return Promise.resolve(null);
}
if (item === SelectDefaultShellWindowsTerminalAction.LABEL) {
this.terminalService.refreshActiveTab();
return this.terminalService.selectDefaultWindowsShell();
}
const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1;
this.terminalService.setActiveTabByIndex(selectedTabIndex);
return this.terminalService.showPanel(true);
@@ -728,11 +737,14 @@ export class SwitchTerminalAction extends Action {
export class SwitchTerminalActionViewItem extends SelectActionViewItem {
public static readonly SEPARATOR = '─────────';
constructor(
action: IAction,
@ITerminalService private readonly terminalService: ITerminalService,
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService
@IContextViewService contextViewService: IContextViewService,
@IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService
) {
super(null, action, terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') });
@@ -743,7 +755,13 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
}
private _updateItems(): void {
this.setOptions(this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), this.terminalService.activeTabIndex);
const items = this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows;
if (enableSelectDefaultShell) {
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
}
this.setOptions(items, this.terminalService.activeTabIndex);
}
}

View File

@@ -635,7 +635,7 @@ export class TerminalInstance implements ITerminalInstance {
private _measureRenderTime(): void {
const frameTimes: number[] = [];
const textRenderLayer = this._xterm._core.renderer._renderLayers[0];
const textRenderLayer = this._xterm._core._renderCoordinator._renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
const evaluateCanvasRenderer = () => {
@@ -647,11 +647,7 @@ export class TerminalInstance implements ITerminalInstance {
const promptChoices: IPromptChoice[] = [
{
label: nls.localize('yes', "Yes"),
run: () => {
this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER).then(() => {
this._notificationService.info(nls.localize('terminal.rendererInAllNewTerminals', "The terminal is now using the fallback renderer."));
});
}
run: () => this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER)
} as IPromptChoice,
{
label: nls.localize('no', "No"),
@@ -1264,7 +1260,7 @@ export class TerminalInstance implements ITerminalInstance {
// maximize on Windows/Linux would fire an event saying that the terminal was not
// visible.
if (this._xterm.getOption('rendererType') === 'canvas') {
this._xterm._core.renderer.onIntersectionChange({ intersectionRatio: 1 });
this._xterm._core._renderCoordinator._onIntersectionChange({ intersectionRatio: 1 });
// HACK: Force a refresh of the screen to ensure links are refresh corrected.
// This can probably be removed when the above hack is fixed in Chromium.
this._xterm.refresh(0, this._xterm.rows - 1);

View File

@@ -88,6 +88,9 @@ export class TerminalLinkHandler {
this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/;
this._tooltipCallback = (e: MouseEvent) => {
if (!this._widgetManager) {
return;
}
if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') {
const target = (e.target as HTMLElement);
this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString());
@@ -115,7 +118,11 @@ export class TerminalLinkHandler {
const options: ILinkMatcherOptions = {
matchIndex,
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: CUSTOM_LINK_PRIORITY
};
@@ -132,7 +139,11 @@ export class TerminalLinkHandler {
this._xterm.webLinksInit(wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
});
}
@@ -144,7 +155,11 @@ export class TerminalLinkHandler {
this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
});
@@ -158,7 +173,11 @@ export class TerminalLinkHandler {
matchIndex: 1,
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
};
@@ -242,12 +261,16 @@ export class TerminalLinkHandler {
private _getLinkHoverString(): string {
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
if (editorConf.multiCursorModifier === 'ctrlCmd') {
return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link');
if (platform.isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkAlt.mac', "Option + click to follow link");
} else {
return nls.localize('terminalLinkHandler.followLinkAlt', "Alt + click to follow link");
}
}
if (platform.isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkCmd', 'Cmd + click to follow link');
return nls.localize('terminalLinkHandler.followLinkCmd', "Cmd + click to follow link");
}
return nls.localize('terminalLinkHandler.followLinkCtrl', 'Ctrl + click to follow link');
return nls.localize('terminalLinkHandler.followLinkCtrl', "Ctrl + click to follow link");
}
private get osPath(): IPath {

View File

@@ -3,21 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
@@ -36,7 +34,6 @@ export abstract class TerminalService extends CommonTerminalService implements I
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
@@ -52,7 +49,7 @@ export abstract class TerminalService extends CommonTerminalService implements I
return instance;
}
public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance {
public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance {
const terminalTab = this._instantiationService.createInstance(TerminalTab,
this._terminalFocusContextKey,
this.configHelper,
@@ -68,74 +65,9 @@ export abstract class TerminalService extends CommonTerminalService implements I
this.setActiveInstanceByIndex(0);
}
this._onInstancesChanged.fire();
this._suggestShellChange(wasNewTerminalAction);
return instance;
}
private _suggestShellChange(wasNewTerminalAction?: boolean): void {
// Only suggest on Windows since $SHELL works great for macOS/Linux
if (!platform.isWindows) {
return;
}
if (this._environmentService.configuration.remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Only suggest when the terminal instance is being created by an explicit user action to
// launch a terminal, as opposed to something like tasks, debug, panel restore, etc.
if (!wasNewTerminalAction) {
return;
}
if (this._environmentService.configuration.remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Don't suggest if the user has explicitly opted out
const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false);
if (neverSuggest) {
return;
}
// Never suggest if the setting is non-default already (ie. they set the setting manually)
if (this.configHelper.config.shell.windows !== this.getDefaultShell(platform.Platform.Windows)) {
this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL);
return;
}
this._notificationService.prompt(
Severity.Info,
nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."),
[{
label: nls.localize('customize', "Customize"),
run: () => {
this.selectDefaultWindowsShell().then(shell => {
if (!shell) {
return Promise.resolve(null);
}
// Launch a new instance with the newly selected shell
const instance = this.createTerminal({
executable: shell,
args: this.configHelper.config.shellArgs.windows
});
if (instance) {
this.setActiveInstance(instance);
}
return Promise.resolve(null);
});
}
},
{
label: nls.localize('never again', "Don't Show Again"),
isSecondary: true,
run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL)
}]
);
}
public focusFindWidget(): Promise<void> {
return this.showPanel(false).then(() => {
const panel = this._panelService.getActivePanel() as TerminalPanel;

View File

@@ -37,7 +37,6 @@ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey
export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated();
export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed';
export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell';
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
@@ -64,9 +63,9 @@ export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '50
export interface ITerminalConfiguration {
shell: {
linux: string;
osx: string;
windows: string;
linux: string | null;
osx: string | null;
windows: string | null;
};
shellArgs: {
linux: string[];
@@ -216,10 +215,8 @@ export interface ITerminalService {
/**
* Creates a terminal.
* @param shell The shell launch configuration to use.
* @param wasNewTerminalAction Whether this was triggered by a new terminal action, if so a
* default shell selection dialog may display.
*/
createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
/**
* Creates a terminal renderer.
@@ -245,6 +242,12 @@ export interface ITerminalService {
setActiveTabToPrevious(): void;
setActiveTabByIndex(tabIndex: number): void;
/**
* Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated,
* among other things.
*/
refreshActiveTab(): void;
showPanel(focus?: boolean): Promise<void>;
hidePanel(): void;
focusFindWidget(): Promise<void>;

View File

@@ -98,6 +98,7 @@ function _getLangEnvVariable(locale?: string) {
es: 'ES',
fi: 'FI',
fr: 'FR',
hu: 'HU',
it: 'IT',
ja: 'JP',
ko: 'KR',

View File

@@ -41,7 +41,7 @@ export abstract class TerminalService implements ITerminalService {
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; }
private readonly _onActiveTabChanged = new Emitter<void>();
protected readonly _onActiveTabChanged = new Emitter<void>();
public get onActiveTabChanged(): Event<void> { return this._onActiveTabChanged.event; }
protected readonly _onInstanceCreated = new Emitter<ITerminalInstance>();
public get onInstanceCreated(): Event<ITerminalInstance> { return this._onInstanceCreated.event; }
@@ -104,6 +104,7 @@ export abstract class TerminalService implements ITerminalService {
protected abstract _getWslPath(path: string): Promise<string>;
protected abstract _getWindowsBuildNumber(): number;
public abstract refreshActiveTab(): void;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
public abstract getDefaultShell(platform: Platform): string;

View File

@@ -20,7 +20,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ipcRenderer as ipc } from 'electron';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { coalesce } from 'vs/base/common/arrays';
@@ -45,11 +44,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IExtensionService extensionService: IExtensionService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService, remoteAgentService);
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, extensionService, fileService, remoteAgentService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro);
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => {
@@ -102,6 +100,11 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
return getDefaultShell(p);
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public selectDefaultWindowsShell(): Promise<string | undefined> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {

View File

@@ -62,13 +62,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
!is32ProcessOn64Windows &&
getWindowsBuildNumber() >= 18309;
const options: pty.IPtyForkOptions = {
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
name: shellName,
cwd,
env,
cols,
rows,
experimentalUseConpty: useConpty
experimentalUseConpty: useConpty,
conptyInheritCursor: true
};
// TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break
@@ -91,14 +92,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
this._processStartupComplete = new Promise<void>(c => {
this.onProcessIdReady(() => c());
});
ptyProcess.on('data', (data) => {
ptyProcess.on('data', data => {
this._onProcessData.fire(data);
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
this._queueProcessExit();
}
});
ptyProcess.on('exit', (code) => {
ptyProcess.on('exit', code => {
this._exitCode = code;
this._queueProcessExit();
});
@@ -126,12 +127,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
setTimeout(() => {
this._sendProcessTitle(ptyProcess);
}, 0);
// Setup polling
this._titleInterval = setInterval(() => {
if (this._currentTitle !== ptyProcess.process) {
this._sendProcessTitle(ptyProcess);
}
}, 200);
// Setup polling for non-Windows, for Windows `process` doesn't change
if (!platform.isWindows) {
this._titleInterval = setInterval(() => {
if (this._currentTitle !== ptyProcess.process) {
this._sendProcessTitle(ptyProcess);
}
}, 200);
}
}
// Allow any trailing data events to be sent before the exit event is sent.
@@ -189,7 +192,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
if (this._isDisposed || !this._ptyProcess) {
return;
}
this._logService.trace('IPty#write', data);
this._logService.trace('IPty#write', `${data.length} characters`);
this._ptyProcess.write(data);
}

View File

@@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
@@ -118,7 +118,7 @@ class WebviewProtocolProvider extends Disposable {
private readonly _extensionLocation: URI | undefined,
private readonly _getLocalResourceRoots: () => ReadonlyArray<URI>,
private readonly _environmentService: IEnvironmentService,
private readonly _textFileService: ITextFileService,
private readonly _fileService: IFileService,
) {
super();
@@ -137,11 +137,11 @@ class WebviewProtocolProvider extends Disposable {
const appRootUri = URI.file(this._environmentService.appRoot);
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._textFileService, undefined, () => [
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [
appRootUri
]);
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._textFileService, this._extensionLocation, () =>
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
this._getLocalResourceRoots()
);
}
@@ -165,7 +165,8 @@ class WebviewPortMappingProvider extends Disposable {
session.onBeforeRequest(async (details) => {
const uri = URI.parse(details.url);
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
const allowedSchemes = ['http', 'https', 'ws', 'wss'];
if (allowedSchemes.indexOf(uri.scheme) === -1) {
return undefined;
}
@@ -379,7 +380,7 @@ export class WebviewElement extends Disposable implements Webview {
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IEnvironmentService environmentService: IEnvironmentService,
@ITextFileService textFileService: ITextFileService,
@IFileService fileService: IFileService,
@ITunnelService tunnelService: ITunnelService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -422,7 +423,7 @@ export class WebviewElement extends Disposable implements Webview {
this._options.extension ? this._options.extension.location : undefined,
() => (this.content.options.localResourceRoots || []),
environmentService,
textFileService));
fileService));
this._register(new WebviewPortMappingProvider(
session,

View File

@@ -7,17 +7,20 @@ import { extname, sep } from 'vs/base/common/path';
import { startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import * as electron from 'electron';
type BufferProtocolCallback = (buffer?: Buffer | electron.MimeTypedBuffer | { error: number }) => void;
export const enum WebviewProtocol {
CoreResource = 'vscode-core-resource',
VsCodeResource = 'vscode-resource',
}
function resolveContent(textFileService: ITextFileService, resource: URI, mime: string, callback: any): void {
textFileService.read(resource, { encoding: 'binary' }).then(contents => {
function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: BufferProtocolCallback): void {
fileService.readFile(resource).then(contents => {
callback({
data: Buffer.from(contents.value, contents.encoding),
data: Buffer.from(contents.value.buffer),
mimeType: mime
});
}, (err) => {
@@ -27,9 +30,9 @@ function resolveContent(textFileService: ITextFileService, resource: URI, mime:
}
export function registerFileProtocol(
contents: Electron.WebContents,
contents: electron.WebContents,
protocol: WebviewProtocol,
textFileService: ITextFileService,
fileService: IFileService,
extensionLocation: URI | undefined,
getRoots: () => ReadonlyArray<URI>
) {
@@ -51,10 +54,10 @@ export function registerFileProtocol(
requestResourcePath: requestUri.path
})
});
resolveContent(textFileService, redirectedUri, getMimeType(requestUri), callback);
resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback);
return;
} else {
resolveContent(textFileService, normalizedPath, getMimeType(normalizedPath), callback);
resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback);
return;
}
}

View File

@@ -44,6 +44,7 @@ export default () => `
<li><a href="command:workbench.action.openDocumentationUrl">${escape(localize('welcomePage.productDocumentation', "Product documentation"))}</a></li>
<li><a href="https://github.com/Microsoft/vscode">${escape(localize('welcomePage.gitHubRepository', "GitHub repository"))}</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/vscode?sort=votes&pageSize=50">${escape(localize('welcomePage.stackOverflow', "Stack Overflow"))}</a></li>
<li><a href="command:workbench.action.openNewsletterSignupUrl">${escape(localize('welcomePage.newsletterSignup', "Join our Newsletter"))}</a></li>
</ul>
</div>
<p class="showOnStartup"><input type="checkbox" id="showOnStartup" class="checkbox"> <label class="caption" for="showOnStartup">${escape(localize('welcomePage.showOnStartup', "Show welcome page on startup"))}</label></p>