Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd (#5074)

* Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd

* fix tests
This commit is contained in:
Anthony Dresser
2019-04-16 22:11:30 -07:00
committed by GitHub
parent 2f8519cb6b
commit 8956b591f7
217 changed files with 5120 additions and 3926 deletions

View File

@@ -97,7 +97,7 @@ export class LanguageConfigurationFileHandler {
}
private _handleConfigFile(languageIdentifier: LanguageIdentifier, configFileLocation: URI): void {
this._fileService.resolveContent(configFileLocation, { encoding: 'utf8' }).then((contents) => {
this._fileService.readFile(configFileLocation).then((contents) => {
const errors: ParseError[] = [];
const configuration = <ILanguageConfiguration>parse(contents.value.toString(), errors);
if (errors.length) {

View File

@@ -559,7 +559,7 @@ class Launch extends AbstractLaunch implements ILaunch {
const resource = this.uri;
let created = false;
return this.fileService.resolveContent(resource).then(content => content.value, err => {
return this.fileService.readFile(resource).then(content => content.value, err => {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
return this.configurationManager.guessDebugger(type).then(adapter => {
if (adapter) {
@@ -576,19 +576,17 @@ class Launch extends AbstractLaunch implements ILaunch {
}
created = true; // pin only if config file is created #8727
return this.textFileService.write(resource, content).then(() => {
// convert string into IContent; see #32135
return content;
});
return this.textFileService.write(resource, content).then(() => content);
});
}).then(content => {
if (!content) {
return { editor: null, created: false };
}
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
const contentValue = content.toString();
const index = contentValue.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.charAt(i) === '\n') {
if (contentValue.charAt(i) === '\n') {
startLineNumber++;
}
}

View File

@@ -29,35 +29,37 @@ export function getTerminalLauncher() {
}
let _DEFAULT_TERMINAL_LINUX_READY: Promise<string> | null = null;
export function getDefaultTerminalLinuxReady(): Promise<string> {
if (!_DEFAULT_TERMINAL_LINUX_READY) {
_DEFAULT_TERMINAL_LINUX_READY = new Promise<string>(c => {
_DEFAULT_TERMINAL_LINUX_READY = new Promise<string>(resolve => {
if (env.isLinux) {
Promise.all<any>([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => {
if (isDebian) {
c('x-terminal-emulator');
resolve('x-terminal-emulator');
} else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {
c('gnome-terminal');
resolve('gnome-terminal');
} else if (process.env.DESKTOP_SESSION === 'kde-plasma') {
c('konsole');
resolve('konsole');
} else if (process.env.COLORTERM) {
c(process.env.COLORTERM);
resolve(process.env.COLORTERM);
} else if (process.env.TERM) {
c(process.env.TERM);
resolve(process.env.TERM);
} else {
c('xterm');
resolve('xterm');
}
});
return;
}
c('xterm');
resolve('xterm');
});
}
return _DEFAULT_TERMINAL_LINUX_READY;
}
let _DEFAULT_TERMINAL_WINDOWS: string | null = null;
export function getDefaultTerminalWindows(): string {
if (!_DEFAULT_TERMINAL_WINDOWS) {
const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
@@ -82,7 +84,7 @@ class WinTerminalService extends TerminalLauncher {
const exec = configuration.external.windowsExec || getDefaultTerminalWindows();
return new Promise<number | undefined>((c, e) => {
return new Promise<number | undefined>((resolve, reject) => {
const title = `"${dir} - ${TERMINAL_TITLE}"`;
const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code
@@ -104,9 +106,11 @@ class WinTerminalService extends TerminalLauncher {
};
const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options);
cmd.on('error', e);
cmd.on('error', err => {
reject(improveError(err));
});
c(undefined);
resolve(undefined);
});
}
}
@@ -120,7 +124,7 @@ class MacTerminalService extends TerminalLauncher {
const terminalApp = configuration.external.osxExec || MacTerminalService.DEFAULT_TERMINAL_OSX;
return new Promise<number | undefined>((c, e) => {
return new Promise<number | undefined>((resolve, reject) => {
if (terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') {
@@ -156,24 +160,26 @@ class MacTerminalService extends TerminalLauncher {
let stderr = '';
const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs);
osa.on('error', e);
osa.on('error', err => {
reject(improveError(err));
});
osa.stderr.on('data', (data) => {
stderr += data.toString();
});
osa.on('exit', (code: number) => {
if (code === 0) { // OK
c(undefined);
resolve(undefined);
} else {
if (stderr) {
const lines = stderr.split('\n', 1);
e(new Error(lines[0]));
reject(new Error(lines[0]));
} else {
e(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code)));
reject(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code)));
}
}
});
} else {
e(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp)));
reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp)));
}
});
}
@@ -188,7 +194,7 @@ class LinuxTerminalService extends TerminalLauncher {
const terminalConfig = configuration.external;
const execThenable: Promise<string> = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady();
return new Promise<number | undefined>((c, e) => {
return new Promise<number | undefined>((resolve, reject) => {
let termArgs: string[] = [];
//termArgs.push('--title');
@@ -218,19 +224,21 @@ class LinuxTerminalService extends TerminalLauncher {
let stderr = '';
const cmd = cp.spawn(exec, termArgs, options);
cmd.on('error', e);
cmd.on('error', err => {
reject(improveError(err));
});
cmd.stderr.on('data', (data) => {
stderr += data.toString();
});
cmd.on('exit', (code: number) => {
if (code === 0) { // OK
c(undefined);
resolve(undefined);
} else {
if (stderr) {
const lines = stderr.split('\n', 1);
e(new Error(lines[0]));
reject(new Error(lines[0]));
} else {
e(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code)));
reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code)));
}
}
});
@@ -239,6 +247,16 @@ class LinuxTerminalService extends TerminalLauncher {
}
}
/**
* tries to turn OS errors into more meaningful error messages
*/
function improveError(err: Error): Error {
if (err['errno'] === 'ENOENT' && err['path']) {
return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err['path']));
}
return err;
}
/**
* Quote args if necessary and combine into a space separated string.
*/

View File

@@ -46,6 +46,7 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { extname } from 'vs/base/common/resources';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
@@ -111,6 +112,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@IExperimentService private readonly experimentService: IExperimentService,
@ITextFileService private readonly textFileService: ITextFileService
) {
super();
@@ -339,8 +341,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return Promise.resolve(null);
}
return Promise.resolve(this.fileService.resolveContent(workspace.configuration)
.then(content => <IExtensionsConfigContent>(json.parse(content.value)['extensions']), err => null));
return Promise.resolve(this.fileService.readFile(workspace.configuration)
.then(content => <IExtensionsConfigContent>(json.parse(content.value.toString())['extensions']), err => null));
}
/**
@@ -350,8 +352,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const extensionsJsonUri = workspaceFolder.toResource(EXTENSIONS_CONFIG);
return Promise.resolve(this.fileService.resolve(extensionsJsonUri)
.then(() => this.fileService.resolveContent(extensionsJsonUri))
.then(content => <IExtensionsConfigContent>json.parse(content.value), err => null));
.then(() => this.fileService.readFile(extensionsJsonUri))
.then(content => <IExtensionsConfigContent>json.parse(content.value.toString()), err => null));
}
/**
@@ -968,7 +970,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations';
const workspaceUri = this.contextService.getWorkspace().folders[0].uri;
return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => {
return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => {
const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []);
if (!hashedRemotes.length) {
return undefined;

View File

@@ -252,6 +252,30 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
scope: ConfigurationScope.APPLICATION,
default: ExtensionsPolicy.allowAll
},
'extensions.extensionKind': {
type: 'object',
description: localize('extensions.extensionKind', "Configure ui or workspace extensions and allow them to run locally or remotely in a remote window."),
properties: {
'ui': {
type: 'array',
items: {
type: 'string',
pattern: '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$',
}
},
'workspace': {
type: 'array',
items: {
type: 'string',
pattern: '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$',
}
}
},
default: {
ui: [],
workspace: []
}
},
'extensions.showInstalledExtensionsByDefault': {
type: 'boolean',
description: localize('extensions.showInstalledExtensionsByDefault', "When enabled, extensions view shows installed extensions view by default."),

View File

@@ -23,7 +23,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
import { IFileService, IContent } from 'vs/platform/files/common/files';
import { IFileService, IFileContent } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -168,8 +168,7 @@ export class InstallAction extends ExtensionAction {
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILabelService private readonly labelService: ILabelService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@ILabelService private readonly labelService: ILabelService
) {
super(`extensions.install`, InstallAction.INSTALL_LABEL, InstallAction.Class, false);
this.update();
@@ -196,12 +195,12 @@ export class InstallAction extends ExtensionAction {
} else {
if (this._manifest && this.workbenchEnvironmentService.configuration.remoteAuthority) {
if (isUIExtension(this._manifest, this.configurationService)) {
this.label = `${InstallAction.INSTALL_LABEL} (${this.extensionManagementServerService.localExtensionManagementServer.label})`;
this.tooltip = `${InstallAction.INSTALL_LABEL} (${this.extensionManagementServerService.localExtensionManagementServer.label})`;
this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`;
this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`;
} else {
const host = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.workbenchEnvironmentService.configuration.remoteAuthority) || localize('remote', "Remote");
this.label = `${InstallAction.INSTALL_LABEL} (${host})`;
this.tooltip = `${InstallAction.INSTALL_LABEL} (${host})`;
this.label = `${InstallAction.INSTALL_LABEL} on ${host}`;
this.tooltip = `${InstallAction.INSTALL_LABEL} on ${host}`;
}
} else {
this.label = InstallAction.INSTALL_LABEL;
@@ -319,7 +318,7 @@ export class RemoteInstallAction extends ExtensionAction {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
const host = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority) || localize('remote', "Remote");
this.label = `${RemoteInstallAction.INSTALL_LABEL} (${host})`;
this.label = `${RemoteInstallAction.INSTALL_LABEL} on ${host}`;
return;
}
}
@@ -1233,13 +1232,21 @@ export class ReloadAction extends ExtensionAction {
const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local);
if (runningExtension) {
// Extension is running
const isSameVersionRunning = isSameExtensionRunning && this.extension.version === runningExtension.version;
if (isEnabled) {
if (!isSameVersionRunning && !this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) {
this.enabled = true;
this.label = localize('reloadRequired', "Reload Required");
// {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio
this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension.");
if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) {
if (isSameExtensionRunning) {
if (this.extension.version !== runningExtension.version) {
this.enabled = true;
this.label = localize('reloadRequired', "Reload Required");
// {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio
this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension.");
}
} else {
this.enabled = true;
this.label = localize('reloadRequired', "Reload Required");
// {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio
this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension.");
}
}
} else {
if (isSameExtensionRunning) {
@@ -2057,7 +2064,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
protected openWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise<any> {
return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile)
.then(content => this.getSelectionPosition(content.value, content.resource, ['extensions', 'recommendations']))
.then(content => this.getSelectionPosition(content.value.toString(), content.resource, ['extensions', 'recommendations']))
.then(selection => this.editorService.openEditor({
resource: workspaceConfigurationFile,
options: {
@@ -2071,7 +2078,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile)
.then(content => {
const extensionIdLowerCase = extensionId.toLowerCase();
const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value) || {})['extensions'] || {};
const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value.toString()) || {})['extensions'] || {};
let insertInto = shouldRecommend ? workspaceExtensionsConfigContent.recommendations || [] : workspaceExtensionsConfigContent.unwantedRecommendations || [];
let removeFrom = shouldRecommend ? workspaceExtensionsConfigContent.unwantedRecommendations || [] : workspaceExtensionsConfigContent.recommendations || [];
@@ -2131,26 +2138,26 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
}
protected getWorkspaceExtensionsConfigContent(extensionsFileResource: URI): Promise<IExtensionsConfigContent> {
return Promise.resolve(this.fileService.resolveContent(extensionsFileResource))
return Promise.resolve(this.fileService.readFile(extensionsFileResource))
.then(content => {
return (json.parse(content.value) || {})['extensions'] || {};
return (json.parse(content.value.toString()) || {})['extensions'] || {};
}, err => ({ recommendations: [], unwantedRecommendations: [] }));
}
protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): Promise<IExtensionsConfigContent> {
return Promise.resolve(this.fileService.resolveContent(extensionsFileResource))
return Promise.resolve(this.fileService.readFile(extensionsFileResource))
.then(content => {
return (<IExtensionsConfigContent>json.parse(content.value));
return (<IExtensionsConfigContent>json.parse(content.value.toString()));
}, err => ({ recommendations: [], unwantedRecommendations: [] }));
}
private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise<IContent> {
return Promise.resolve(this.fileService.resolveContent(workspaceConfigurationFile))
private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise<IFileContent> {
return Promise.resolve(this.fileService.readFile(workspaceConfigurationFile))
.then(content => {
const workspaceRecommendations = <IExtensionsConfigContent>json.parse(content.value)['extensions'];
const workspaceRecommendations = <IExtensionsConfigContent>json.parse(content.value.toString())['extensions'];
if (!workspaceRecommendations || !workspaceRecommendations.recommendations) {
return this.jsonEditingService.write(workspaceConfigurationFile, { key: 'extensions', value: { recommendations: [] } }, true)
.then(() => this.fileService.resolveContent(workspaceConfigurationFile));
.then(() => this.fileService.readFile(workspaceConfigurationFile));
}
return content;
});
@@ -2179,8 +2186,8 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
}
private getOrCreateExtensionsFile(extensionsFileResource: URI): Promise<{ created: boolean, extensionsFileResource: URI, content: string }> {
return Promise.resolve(this.fileService.resolveContent(extensionsFileResource)).then(content => {
return { created: false, extensionsFileResource, content: content.value };
return Promise.resolve(this.fileService.readFile(extensionsFileResource)).then(content => {
return { created: false, extensionsFileResource, content: content.value.toString() };
}, err => {
return this.textFileService.write(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => {
return { created: true, extensionsFileResource, content: ExtensionsConfigurationInitialContent };
@@ -2562,29 +2569,42 @@ export class DisabledLabelAction extends ExtensionAction {
updateWhenCounterExtensionChanges: boolean = true;
private disposables: IDisposable[] = [];
private _runningExtensions: IExtensionDescription[] | null = null;
constructor(
private readonly warningAction: SystemDisabledWarningAction,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IExtensionService private readonly extensionService: IExtensionService,
) {
super('extensions.disabledLabel', warningAction.tooltip, `${DisabledLabelAction.Class} hide`, false);
warningAction.onDidChange(() => this.update(), this, this.disposables);
this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables);
this.updateRunningExtensions();
}
private updateRunningExtensions(): void {
this.extensionService.getExtensions().then(runningExtensions => { this._runningExtensions = runningExtensions; this.update(); });
}
update(): void {
this.class = `${DisabledLabelAction.Class} hide`;
this.label = '';
this.enabled = false;
if (this.warningAction.enabled) {
this.enabled = true;
this.class = DisabledLabelAction.Class;
this.label = this.warningAction.tooltip;
return;
}
if (this.extension && this.extension.local && !this.extensionEnablementService.isEnabled(this.extension.local)) {
this.enabled = true;
this.class = DisabledLabelAction.Class;
this.label = localize('disabled by user', "This extension is disabled by the user.");
return;
if (this.extension && this.extension.local && this._runningExtensions) {
const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local);
const isExtensionRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier));
if (!isExtensionRunning && !isEnabled) {
this.enabled = true;
this.class = DisabledLabelAction.Class;
this.label = localize('disabled by user', "This extension is disabled by the user.");
return;
}
}
}
@@ -2630,31 +2650,37 @@ export class SystemDisabledWarningAction extends ExtensionAction {
this.class = `${SystemDisabledWarningAction.Class} hide`;
this.tooltip = '';
if (this.extension && this.extension.local && this.extension.server && this._runningExtensions && this.workbenchEnvironmentService.configuration.remoteAuthority && this.extensionManagementServerService.remoteExtensionManagementServer) {
if (
// Local Workspace Extension
this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService)
) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('disabled workspace Extension', "This extension from {0} server is disabled because it cannot run in a window connected to the remote server.", this.getServerLabel(this.extensionManagementServerService.localExtensionManagementServer));
if (!this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)
&& this.extensionsWorkbenchService.canInstall(this.extension)
) {
// Extension does not exist in remote
this.tooltip = `${this.tooltip} ${localize('Install in remote server', "Install it in {0} server to enable.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer))}`;
}
return;
}
const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0];
const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null;
if (
// Not same as running extension
runningExtensionServer && this.extension.server !== runningExtensionServer
) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('disabled because running in another server', "This extension from {0} server is disabled because another instance of same extension from {1} server is enabled.", this.getServerLabel(this.extension.server), this.getServerLabel(runningExtensionServer));
return;
const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0];
const localExtensionServer = localExtension ? localExtension.server : null;
if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService)) {
if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer));
return;
}
if (localExtensionServer !== this.extensionManagementServerService.remoteExtensionManagementServer) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('Install in remote server', "Install the extension on '{0}' to enable.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer));
return;
}
}
if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.configurationService)) {
if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer));
return;
}
if (localExtensionServer !== this.extensionManagementServerService.localExtensionManagementServer) {
this.enabled = true;
this.class = `${SystemDisabledWarningAction.Class}`;
this.tooltip = localize('Install in local server', "Install the extension locally to enable.");
return;
}
}
}
}
@@ -2878,7 +2904,7 @@ export class InstallVSIXAction extends Action {
this.extensionsWorkbenchService.install(vsix).then(extension => {
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
: localize('InstallVSIXAction.success', "Installing the extension {0} is completed.", extension.identifier.id);
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
const actions = requireReload ? [{
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
run: () => this.windowService.reloadWindow()
@@ -2911,7 +2937,7 @@ export class InstallVSIXAction extends Action {
this.extensionsWorkbenchService.install(vsix).then(extension => {
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
: localize('InstallVSIXAction.success', "Installing the extension {0} is completed.", extension.identifier.id);
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
const actions = requireReload ? [{
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
run: () => this.windowService.reloadWindow()

View File

@@ -66,7 +66,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const element = append(root, $('.extension'));
const iconContainer = append(element, $('.icon-container'));
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
const badgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer);
const iconRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer);
const details = append(element, $('.details'));
const headerContainer = append(details, $('.header-container'));
const header = append(headerContainer, $('.header'));
@@ -74,6 +74,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const version = append(header, $('span.version'));
const installCount = append(header, $('span.install-count'));
const ratings = append(header, $('span.ratings'));
const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header);
const description = append(details, $('.description.ellipsis'));
const footer = append(details, $('.footer'));
const author = append(footer, $('.author.ellipsis'));
@@ -89,10 +90,11 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction);
const reloadAction = this.instantiationService.createInstance(ReloadAction);
const actions = [
this.instantiationService.createInstance(StatusLabelAction),
this.instantiationService.createInstance(UpdateAction),
this.instantiationService.createInstance(ReloadAction),
reloadAction,
this.instantiationService.createInstance(InstallAction),
this.instantiationService.createInstance(RemoteInstallAction),
this.instantiationService.createInstance(MaliciousStatusLabelAction, false),
@@ -100,10 +102,11 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
this.instantiationService.createInstance(ManageExtensionAction)
];
const disabledLabelAction = this.instantiationService.createInstance(DisabledLabelAction, systemDisabledWarningAction);
const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, disabledLabelAction, recommendationWidget);
const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, disabledLabelAction, recommendationWidget, reloadAction);
const widgets = [
recommendationWidget,
badgeWidget,
iconRemoteBadgeWidget,
headerRemoteBadgeWidget,
tooltipWidget,
this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version),
this.instantiationService.createInstance(InstallCountWidget, installCount, true),

View File

@@ -11,13 +11,14 @@ import * as platform from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction, ReloadAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension;
@@ -148,11 +149,15 @@ export class TooltipWidget extends ExtensionWidget {
constructor(
private readonly parent: HTMLElement,
private readonly extensionLabelAction: DisabledLabelAction,
private readonly recommendationWidget: RecommendationWidget
private readonly recommendationWidget: RecommendationWidget,
private readonly reloadAction: ReloadAction
) {
super();
this._register(this.extensionLabelAction.onDidChange(() => this.render()));
this._register(this.recommendationWidget.onDidChangeTooltip(() => this.render()));
this._register(Event.any<any>(
this.extensionLabelAction.onDidChange,
this.reloadAction.onDidChange,
this.recommendationWidget.onDidChangeTooltip
)(() => this.render()));
}
render(): void {
@@ -166,6 +171,9 @@ export class TooltipWidget extends ExtensionWidget {
}
private getTitle(): string {
if (this.reloadAction.enabled) {
return this.reloadAction.tooltip;
}
if (this.extensionLabelAction.enabled) {
return this.extensionLabelAction.label;
}
@@ -235,30 +243,30 @@ export class RecommendationWidget extends ExtensionWidget {
}
export class RemoteBadgeWidget extends ExtensionWidget {
private element: HTMLElement | null;
private remoteBadge: RemoteBadge | null;
private disposables: IDisposable[] = [];
private element: HTMLElement;
constructor(
private parent: HTMLElement,
@ILabelService private readonly labelService: ILabelService,
@IThemeService private readonly themeService: IThemeService,
parent: HTMLElement,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
this.element = append(parent, $('.extension-remote-badge-container'));
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
if (this.element) {
this.parent.removeChild(this.element);
if (this.remoteBadge) {
this.element.removeChild(this.remoteBadge.element);
this.remoteBadge.dispose();
}
this.element = null;
this.remoteBadge = null;
this.disposables = dispose(this.disposables);
}
@@ -268,30 +276,56 @@ export class RemoteBadgeWidget extends ExtensionWidget {
return;
}
if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
this.element = append(this.parent, $('div.extension-remote-badge'));
append(this.element, $('span.octicon.octicon-remote'));
const applyBadgeStyle = () => {
if (!this.element) {
return;
}
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.element.style.color = fgColor ? fgColor.toString() : '';
};
applyBadgeStyle();
this.themeService.onThemeChange(applyBadgeStyle, this, this.disposables);
this.workspaceContextService.onDidChangeWorkbenchState(applyBadgeStyle, this, this.disposables);
const updateTitle = () => {
if (this.element) {
this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority));
}
};
this.labelService.onDidChangeFormatters(() => updateTitle(), this, this.disposables);
updateTitle();
this.remoteBadge = this.instantiationService.createInstance(RemoteBadge);
append(this.element, this.remoteBadge.element);
}
}
dispose(): void {
if (this.remoteBadge) {
this.remoteBadge.dispose();
}
super.dispose();
}
}
class RemoteBadge extends Disposable {
readonly element: HTMLElement;
constructor(
@ILabelService private readonly labelService: ILabelService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
this.element = $('div.extension-remote-badge');
this.render();
}
private render(): void {
append(this.element, $('span.octicon.octicon-remote'));
const applyBadgeStyle = () => {
if (!this.element) {
return;
}
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.element.style.color = fgColor ? fgColor.toString() : '';
};
applyBadgeStyle();
this._register(this.themeService.onThemeChange(() => applyBadgeStyle()));
this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => applyBadgeStyle()));
const updateTitle = () => {
if (this.element) {
this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority));
}
};
this._register(this.labelService.onDidChangeFormatters(() => updateTitle()));
updateTitle();
}
}

View File

@@ -35,7 +35,17 @@
.extension-editor > .header > .icon-container .extension-remote-badge {
position: absolute;
right: 0px;
top: 94px;
top: 88px;
width: 38px;
height: 38px;
line-height: 38px;
border-radius: 20px;
text-align: center;
}
.extension-editor > .header > .icon-container .extension-remote-badge .octicon {
font-size: 32px;
vertical-align: middle;
}
.extension-editor > .header > .details {

View File

@@ -102,10 +102,32 @@
object-fit: contain;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container > .extension-remote-badge {
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge {
position: absolute;
right: 5px;
bottom: 5px;
width: 22px;
height: 22px;
line-height: 22px;
border-radius: 20px;
text-align: center;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header > .extension-remote-badge-container {
margin-left: 6px;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge {
width: 14px;
height: 14px;
line-height: 14px;
border-radius: 20px;
text-align: center;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .octicon {
font-size: 13px;
vertical-align: middle;
}
.extensions-viewlet.narrow > .extensions .extension > .icon-container,
@@ -146,10 +168,13 @@
opacity: 0.85;
font-size: 80%;
padding-left: 6px;
flex: 1;
min-width: fit-content;
}
.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .version {
flex: 1;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) {
font-size: 80%;
margin: 0 6px;
@@ -164,6 +189,7 @@
text-align: right;
}
.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .extension-remote-badge-container,
.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .ratings,
.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .install-count {
display: none;

View File

@@ -46,12 +46,4 @@
.extension-ratings.small > .count {
margin-left: 2px;
}
.extension-remote-badge {
width: 22px;
height: 22px;
line-height: 22px;
border-radius: 20px;
text-align: center;
}

View File

@@ -254,7 +254,7 @@ class Extension implements IExtension {
}
if (this.local && this.local.readmeUrl) {
return this.fileService.resolveContent(this.local.readmeUrl, { encoding: 'utf8' }).then(content => content.value);
return this.fileService.readFile(this.local.readmeUrl).then(content => content.value.toString());
}
if (this.type === ExtensionType.System) {
@@ -297,7 +297,7 @@ ${this.description}
return Promise.reject(new Error('not available'));
}
return this.fileService.resolveContent(changelogUrl, { encoding: 'utf8' }).then(content => content.value);
return this.fileService.readFile(changelogUrl).then(content => content.value.toString());
}
get dependencies(): string[] {

View File

@@ -21,12 +21,11 @@ import { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService, TestStorageService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices';
import { TestContextService, TestLifecycleService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IPager } from 'vs/base/common/paging';
import { assign } from 'vs/base/common/objects';
@@ -47,6 +46,11 @@ import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/ele
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { FileService } from 'vs/workbench/services/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {

View File

@@ -12,9 +12,9 @@ import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { URI } from 'vs/base/common/uri';
import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
/**
* An implementation of editor for binary files like images.
@@ -26,10 +26,10 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IFileService fileService: IFileService,
@IWindowsService private readonly windowsService: IWindowsService,
@IEditorService private readonly editorService: IEditorService,
@IStorageService storageService: IStorageService
@IStorageService storageService: IStorageService,
@ITextFileService textFileService: ITextFileService
) {
super(
BinaryFileEditor.ID,
@@ -39,7 +39,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
},
telemetryService,
themeService,
fileService,
textFileService,
storageService
);
}

View File

@@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath';
import { basename } from 'vs/base/common/resources';
import { Action } from 'vs/base/common/actions';
import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -170,7 +170,7 @@ export class TextFileEditor extends BaseTextEditor {
// In case we tried to open a file inside the text editor and the response
// indicates that this is not a text file, reopen the file through the binary
// editor.
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY) {
if ((<TextFileOperationError>error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) {
return this.openAsBinary(input, options);
}

View File

@@ -13,7 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { AutoSaveConfiguration, HotExitConfiguration, SUPPORTED_ENCODINGS } from 'vs/platform/files/common/files';
import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker';
import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler';
@@ -37,6 +37,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
// Viewlet Action
export class OpenExplorerViewletAction extends ShowViewletAction {

View File

@@ -9,7 +9,7 @@ import { basename } from 'vs/base/common/resources';
import { Action } from 'vs/base/common/actions';
import { URI } from 'vs/base/common/uri';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel, IWriteTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -134,7 +134,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I
// Any other save error
else {
const isReadonly = fileOperationError.fileOperationResult === FileOperationResult.FILE_READ_ONLY;
const triedToMakeWriteable = isReadonly && fileOperationError.options && fileOperationError.options.overwriteReadonly;
const triedToMakeWriteable = isReadonly && fileOperationError.options && (fileOperationError.options as IWriteTextFileOptions).overwriteReadonly;
const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED;
// Save Elevated (TODO@remote cannot write elevated https://github.com/Microsoft/vscode/issues/48659)

View File

@@ -12,7 +12,7 @@ import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditor
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IReference } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -269,7 +269,10 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
}, error => {
// In case of an error that indicates that the file is binary or too large, just return with the binary editor model
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
if (
(<TextFileOperationError>error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY ||
(<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE
) {
return this.doResolveAsBinary();
}

View File

@@ -176,7 +176,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> {
const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority);
return this.textFileService.resolve(savedFileResource).then(content => {
return this.textFileService.readStream(savedFileResource).then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (codeEditorModel) {
this.modelService.updateModel(codeEditorModel, content.value);

View File

@@ -11,7 +11,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EncodingMode, Verbosity } from 'vs/workbench/common/editor';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -155,7 +155,7 @@ suite('Files - FileEditorInput', () => {
test('resolve handles binary files', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_IS_BINARY));
accessor.textFileService.setResolveTextContentErrorOnce(new TextFileOperationError('error', TextFileOperationResult.FILE_IS_BINARY));
return input.resolve().then(resolved => {
assert.ok(resolved);

View File

@@ -9,8 +9,8 @@ import { toResource } from 'vs/base/test/common/utils';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextFileService, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { FileChangesEvent, FileChangeType, IFileService, snapshotToString } from 'vs/platform/files/common/files';
import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { timeout } from 'vs/base/common/async';

View File

@@ -511,20 +511,20 @@ export class SettingsTargetsWidget extends Widget {
actionItemProvider: (action: Action) => action.id === 'folderSettings' ? this.folderSettings : undefined
}));
this.userLocalSettings = new Action('userSettings', localize('userSettings', "User Settings"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL));
this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL));
this.userLocalSettings.tooltip = this.userLocalSettings.label;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority);
const remoteSettingsLabel = localize('userSettingsRemote', "Remote Settings") +
const remoteSettingsLabel = localize('userSettingsRemote', "Remote") +
(hostLabel ? ` (${hostLabel})` : '');
this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE));
this.userRemoteSettings.tooltip = this.userRemoteSettings.label;
this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace Settings"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
this.workspaceSettings.tooltip = this.workspaceSettings.label;
const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder Settings"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder.uri));
const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder.uri));
this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionItem, folderSettingsAction);
this.update();
@@ -551,14 +551,14 @@ export class SettingsTargetsWidget extends Widget {
setResultCount(settingsTarget: SettingsTarget, count: number): void {
if (settingsTarget === ConfigurationTarget.WORKSPACE) {
let label = localize('workspaceSettings', "Workspace Settings");
let label = localize('workspaceSettings', "Workspace");
if (count) {
label += ` (${count})`;
}
this.workspaceSettings.label = label;
} else if (settingsTarget === ConfigurationTarget.USER_LOCAL) {
let label = localize('userSettings', "User Settings");
let label = localize('userSettings', "User");
if (count) {
label += ` (${count})`;
}

View File

@@ -11,7 +11,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search';
import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
@@ -24,6 +24,7 @@ suite('QueryBuilder', () => {
const PATTERN_INFO: IPatternInfo = { pattern: 'a' };
const ROOT_1 = fixPath('/foo/root1');
const ROOT_1_URI = getUri(ROOT_1);
const WS_CONFIG_PATH = getUri('/bar/test.code-workspace'); // location of the workspace file (not important except that it is a file URI)
let instantiationService: TestInstantiationService;
let queryBuilder: QueryBuilder;
@@ -40,7 +41,7 @@ suite('QueryBuilder', () => {
instantiationService.stub(IConfigurationService, mockConfigService);
mockContextService = new TestContextService();
mockWorkspace = new Workspace('workspace', toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }]));
mockWorkspace = new Workspace('workspace', [toWorkspaceFolder(ROOT_1_URI)]);
mockContextService.setWorkspace(mockWorkspace);
instantiationService.stub(IWorkspaceContextService, mockContextService);
@@ -277,7 +278,7 @@ suite('QueryBuilder', () => {
const ROOT_2_URI = getUri(ROOT_2);
const ROOT_3 = fixPath('/project/root3');
const ROOT_3_URI = getUri(ROOT_3);
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: ROOT_2_URI.fsPath }, { path: ROOT_3_URI.fsPath }]);
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: ROOT_2_URI.fsPath }, { path: ROOT_3_URI.fsPath }], WS_CONFIG_PATH);
mockWorkspace.configuration = uri.file(fixPath('/config'));
mockConfigService.setUserConfiguration('search', {
@@ -689,7 +690,7 @@ suite('QueryBuilder', () => {
test('relative includes w/two root folders', () => {
const ROOT_2 = '/project/root2';
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }]);
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH);
mockWorkspace.configuration = uri.file(fixPath('config'));
const cases: [string, ISearchPathsInfo][] = [
@@ -730,7 +731,7 @@ suite('QueryBuilder', () => {
test('include ./foldername', () => {
const ROOT_2 = '/project/root2';
const ROOT_1_FOLDERNAME = 'foldername';
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }]);
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH);
mockWorkspace.configuration = uri.file(fixPath('config'));
const cases: [string, ISearchPathsInfo][] = [
@@ -758,7 +759,7 @@ suite('QueryBuilder', () => {
test('relative includes w/multiple ambiguous root folders', () => {
const ROOT_2 = '/project/rootB';
const ROOT_3 = '/otherproject/rootB';
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }]);
mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }], WS_CONFIG_PATH);
mockWorkspace.configuration = uri.file(fixPath('/config'));
const cases: [string, ISearchPathsInfo][] = [

View File

@@ -198,7 +198,7 @@ export class SnippetFile {
load(): Promise<this> {
if (!this._loadPromise) {
this._loadPromise = Promise.resolve(this._fileService.resolveContent(this.location, { encoding: 'utf8' })).then(content => {
this._loadPromise = Promise.resolve(this._fileService.readFile(this.location)).then(content => {
const data = <JsonSerializedSnippets>jsonParse(content.value.toString());
if (typeof data === 'object') {
forEach(data, entry => {

View File

@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import * as crypto from 'crypto';
import { onUnexpectedError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { IFileService, IFileStat, IResolveFileResult, IContent } from 'vs/platform/files/common/files';
import { IFileService, IFileStat, IResolveFileResult } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -20,6 +20,7 @@ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspa
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { joinPath } from 'vs/base/common/resources';
import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles';
const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/;
const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/;
@@ -193,14 +194,14 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool
});
}
export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, stripEndingDotGit: boolean = false): Promise<string[]> {
export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, textFileService: ITextFileService, stripEndingDotGit: boolean = false): Promise<string[]> {
const path = workspaceUri.path;
const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` });
return fileService.exists(uri).then(exists => {
if (!exists) {
return [];
}
return fileService.resolveContent(uri, { acceptTextOnly: true }).then(
return textFileService.read(uri, { acceptTextOnly: true }).then(
content => getHashedRemotesFromConfig(content.value, stripEndingDotGit),
err => [] // ignore missing or binary file
);
@@ -221,7 +222,8 @@ export class WorkspaceStats implements IWorkbenchContribution {
@IWindowService private readonly windowService: IWindowService,
@INotificationService private readonly notificationService: INotificationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@ITextFileService private readonly textFileService: ITextFileService
) {
this.report();
}
@@ -434,7 +436,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
tags['workspace.android.cpp'] = true;
}
function getFilePromises(filename: string, fileService: IFileService, contentHandler: (content: IContent) => void): Promise<void>[] {
function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise<void>[] {
return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => {
const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` });
return fileService.exists(uri).then(exists => {
@@ -442,7 +444,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
return undefined;
}
return fileService.resolveContent(uri, { acceptTextOnly: true }).then(contentHandler);
return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler);
}, err => {
// Ignore missing file
});
@@ -468,7 +470,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
}
}
const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, content => {
const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => {
const dependencies: string[] = content.value.split(/\r\n|\r|\n/);
for (let dependency of dependencies) {
// Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo`
@@ -479,7 +481,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
}
});
const pipfilePromises = getFilePromises('pipfile', this.fileService, content => {
const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => {
let dependencies: string[] = content.value.split(/\r\n|\r|\n/);
// We're only interested in the '[packages]' section of the Pipfile
@@ -499,7 +501,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
});
const packageJsonPromises = getFilePromises('package.json', this.fileService, content => {
const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => {
try {
const packageJsonContents = JSON.parse(content.value);
if (packageJsonContents['dependencies']) {
@@ -624,7 +626,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
if (!exists) {
return [];
}
return this.fileService.resolveContent(uri, { acceptTextOnly: true }).then(
return this.textFileService.read(uri, { acceptTextOnly: true }).then(
content => getDomainsOfRemotes(content.value, SecondLevelDomainWhitelist),
err => [] // ignore missing or binary file
);
@@ -644,7 +646,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
private reportRemotes(workspaceUris: URI[]): void {
Promise.all<string[]>(workspaceUris.map(workspaceUri => {
return getHashedRemotesFromUri(workspaceUri, this.fileService, true);
return getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true);
})).then(hashedRemotes => {
/* __GDPR__
"workspace.hashedRemotes" : {
@@ -693,7 +695,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
if (!exists) {
return false;
}
return this.fileService.resolveContent(uri, { acceptTextOnly: true }).then(
return this.textFileService.read(uri, { acceptTextOnly: true }).then(
content => !!content.value.match(/azure/i),
err => false
);

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -14,6 +13,7 @@ import Severity from 'vs/base/common/severity';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
import { mergeDefaultShellPathAndArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
@@ -167,9 +167,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, defaultValue);
}
public checkWorkspaceShellPermissions(platformOverride: platform.Platform = platform.platform): boolean {
public checkWorkspaceShellPermissions(osOverride: platform.OperatingSystem = platform.OS): boolean {
// Check whether there is a workspace setting
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const platformKey = osOverride === platform.OperatingSystem.Windows ? 'windows' : osOverride === platform.OperatingSystem.Macintosh ? 'osx' : 'linux';
const shellConfigValue = this._workspaceConfigurationService.inspect<string>(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
const envConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.env.${platformKey}`);
@@ -228,28 +228,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
}
public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride: platform.Platform = platform.platform): void {
const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride);
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellConfigValue = this._workspaceConfigurationService.inspect<string>(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || shellConfigValue.default;
shell.args = (isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default;
// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
// safe to assume that this was used by accident as Sysnative does not
// exist and will break the terminal in non-WoW64 environments.
if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) {
const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase();
if (shell.executable.toLowerCase().indexOf(sysnativePath) === 0) {
shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length));
}
}
// Convert / to \ on Windows for convenience
if (platformOverride === platform.Platform.Windows) {
shell.executable = shell.executable.replace(/\//g, '\\');
}
const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride === platform.Platform.Windows ? platform.OperatingSystem.Windows : (platformOverride === platform.Platform.Mac ? platform.OperatingSystem.Macintosh : platform.OperatingSystem.Linux));
mergeDefaultShellPathAndArgs(shell, (key) => this._workspaceConfigurationService.inspect(key), isWorkspaceShellAllowed);
}
private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number {

View File

@@ -6,7 +6,7 @@
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
@@ -22,6 +22,7 @@ import { IProductService } from 'vs/platform/product/common/product';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { URI } from 'vs/base/common/uri';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
@@ -133,16 +134,13 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = this._launchProcess(shellLaunchConfig, cols, rows);
}
this.processState = ProcessState.LAUNCHING;
// The process is non-null, but TS isn't clever enough to know
const p = this._process!;
p.onProcessData(data => {
this._process.onProcessData(data => {
const beforeProcessDataEvent: IBeforeProcessDataEvent = { data };
this._onBeforeProcessData.fire(beforeProcessDataEvent);
if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) {
@@ -150,19 +148,19 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
});
p.onProcessIdReady(pid => {
this._process.onProcessIdReady(pid => {
this.shellProcessId = pid;
this._onProcessReady.fire();
// Send any queued data that's waiting
if (this._preLaunchInputQueue.length > 0) {
p.input(this._preLaunchInputQueue.join(''));
if (this._preLaunchInputQueue.length > 0 && this._process) {
this._process.input(this._preLaunchInputQueue.join(''));
this._preLaunchInputQueue.length = 0;
}
});
p.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
p.onProcessExit(exitCode => this._onExit(exitCode));
this._process.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
this._process.onProcessExit(exitCode => this._onExit(exitCode));
setTimeout(() => {
if (this.processState === ProcessState.LAUNCHING) {
@@ -175,32 +173,41 @@ export class TerminalProcessManager implements ITerminalProcessManager {
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd);
const env = this._createEnvironment(shellLaunchConfig, activeWorkspaceRootUri);
// Compel type system as process.env should not have any undefined entries
this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env);
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
}
private _createEnvironment(shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined): platform.IProcessEnvironment {
// Create a terminal environment based on settings, launch config and permissions
let env: platform.IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
// Only base the terminal process environment on this environment and add the
// various mixins when strictEnv is false
env = { ...shellLaunchConfig.env } as any;
// strictEnv is true, only use the requested env (ignoring null entries)
terminalEnvironment.mergeNonNullKeys(env, shellLaunchConfig.env);
} else {
// Merge process env with the env from config and from shellLaunchConfig
env = { ...process.env } as any;
terminalEnvironment.mergeNonNullKeys(env, process.env);
// Resolve env vars from config and shell
// Determine config env based on workspace shell permissions
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const envFromConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = (isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user);
const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...allowedEnvFromConfig }, lastActiveWorkspaceRoot);
const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
shellLaunchConfig.env = envFromShell;
const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user) };
terminalEnvironment.mergeEnvironments(env, envFromConfig);
// Resolve env vars from config and shell
if (allowedEnvFromConfig) {
terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, allowedEnvFromConfig, lastActiveWorkspaceRoot);
}
if (shellLaunchConfig.env) {
terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, shellLaunchConfig.env, lastActiveWorkspaceRoot);
}
// Merge config (settings) and ShellLaunchConfig environments
terminalEnvironment.mergeEnvironments(env, allowedEnvFromConfig);
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
@@ -210,9 +217,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
// Adding other env keys necessary to create the process
terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables);
}
this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env);
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
return env;
}
public setDimensions(cols: number, rows: number): void {

View File

@@ -115,7 +115,7 @@ export interface ITerminalConfigHelper {
mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride?: platform.Platform): void;
/** Sets whether a workspace shell configuration is allowed or not */
setWorkspaceShellAllowed(isAllowed: boolean): void;
checkWorkspaceShellPermissions(platformOverride?: platform.Platform): boolean;
checkWorkspaceShellPermissions(osOverride?: platform.OperatingSystem): boolean;
}
export interface ITerminalFont {
@@ -268,7 +268,7 @@ export interface ITerminalService {
preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise<string>;
extHostReady(remoteAuthority: string): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
}
export const enum Direction {
@@ -714,6 +714,7 @@ export interface ITerminalProcessExtHostRequest {
activeWorkspaceRootUri: URI;
cols: number;
rows: number;
isWorkspaceShellAllowed: boolean;
}
export enum LinuxDistro {

View File

@@ -14,7 +14,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
* This module contains utility functions related to the environment, cwd and paths.
*/
export function mergeEnvironments(parent: platform.IProcessEnvironment, other?: ITerminalEnvironment): void {
export function mergeEnvironments(parent: platform.IProcessEnvironment, other: ITerminalEnvironment | undefined): void {
if (!other) {
return;
}
@@ -49,14 +49,28 @@ function _mergeEnvironmentValue(env: ITerminalEnvironment, key: string, value: s
}
}
export function addTerminalEnvironmentKeys(env: ITerminalEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void {
export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void {
env['TERM_PROGRAM'] = 'vscode';
env['TERM_PROGRAM_VERSION'] = version ? version : null;
if (version) {
env['TERM_PROGRAM_VERSION'] = version;
}
if (setLocaleVariables) {
env['LANG'] = _getLangEnvVariable(locale);
}
}
export function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) {
if (!other) {
return;
}
for (const key of Object.keys(other)) {
const value = other[key];
if (value) {
env[key] = value;
}
}
}
export function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment {
Object.keys(env).forEach((key) => {
const value = env[key];
@@ -144,3 +158,34 @@ export function escapeNonWindowsPath(path: string): string {
}
return newPath;
}
export function mergeDefaultShellPathAndArgs(
shell: IShellLaunchConfig,
fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
isWorkspaceShellAllowed: boolean,
platformOverride: platform.Platform = platform.platform
): void {
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`);
// const shellConfigValue = this._workspaceConfigurationService.inspect<string>(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
// const shellArgsConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
shell.executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || <string>shellConfigValue.default;
shell.args = (isWorkspaceShellAllowed ? <string[]>shellArgsConfigValue.value : <string[]>shellArgsConfigValue.user) || <string[]>shellArgsConfigValue.default;
// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
// safe to assume that this was used by accident as Sysnative does not
// exist and will break the terminal in non-WoW64 environments.
if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) {
const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase();
if (shell.executable && shell.executable.toLowerCase().indexOf(sysnativePath) === 0) {
shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length));
}
}
// Convert / to \ on Windows for convenience
if (shell.executable && platformOverride === platform.Platform.Windows) {
shell.executable = shell.executable.replace(/\//g, '\\');
}
}

View File

@@ -4,9 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import * as nls from 'vs/nls';
let hasReceivedResponse: boolean = false;
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
private _disposables: IDisposable[] = [];
@@ -43,10 +47,19 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
activeWorkspaceRootUri: URI,
cols: number,
rows: number,
@ITerminalService private readonly _terminalService: ITerminalService
configHelper: ITerminalConfigHelper,
@ITerminalService private readonly _terminalService: ITerminalService,
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService
) {
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
setTimeout(() => this._onProcessTitleChanged.fire('Starting...'), 0);
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
public dispose(): void {
@@ -59,6 +72,7 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
}
public emitTitle(title: string): void {
// hasReceivedResponse = true;
this._onProcessTitleChanged.fire(title);
}

View File

@@ -118,7 +118,7 @@ export abstract class TerminalService implements ITerminalService {
return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction);
}
public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void {
public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void {
this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
// Wait for the remoteAuthority to be ready (and listening for events) before proceeding
const conn = this._remoteAgentService.getConnection();
@@ -127,7 +127,7 @@ export abstract class TerminalService implements ITerminalService {
while (!this._extHostsReady[remoteAuthority] && ++retries < 50) {
await timeout(100);
}
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows });
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed });
});
}

View File

@@ -234,8 +234,8 @@ CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (acc
let fileName = basename(resource);
let snapper = accessor.get(IInstantiationService).createInstance(Snapper);
return fileService.resolveContent(resource).then(content => {
return snapper.captureSyntaxTokens(fileName, content.value);
return fileService.readFile(resource).then(content => {
return snapper.captureSyntaxTokens(fileName, content.value.toString());
});
};

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 { IFileService } from 'vs/platform/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
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 _fileService: IFileService,
private readonly _textFileService: ITextFileService,
) {
super();
@@ -137,11 +137,11 @@ class WebviewProtocolProvider extends Disposable {
const appRootUri = URI.file(this._environmentService.appRoot);
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._textFileService, undefined, () => [
appRootUri
]);
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._textFileService, this._extensionLocation, () =>
this._getLocalResourceRoots()
);
}
@@ -374,7 +374,7 @@ export class WebviewElement extends Disposable implements Webview {
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@ITextFileService textFileService: ITextFileService,
@ITunnelService tunnelService: ITunnelService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -412,7 +412,7 @@ export class WebviewElement extends Disposable implements Webview {
this._options.extension ? this._options.extension.location : undefined,
() => (this._contentOptions.localResourceRoots || []),
environmentService,
fileService));
textFileService));
this._register(new WebviewPortMappingProvider(
session,

View File

@@ -6,16 +6,16 @@ import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime';
import { extname, sep } from 'vs/base/common/path';
import { startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
export const enum WebviewProtocol {
CoreResource = 'vscode-core-resource',
VsCodeResource = 'vscode-resource',
}
function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: any): void {
fileService.resolveContent(resource, { encoding: 'binary' }).then(contents => {
function resolveContent(textFileService: ITextFileService, resource: URI, mime: string, callback: any): void {
textFileService.read(resource, { encoding: 'binary' }).then(contents => {
callback({
data: Buffer.from(contents.value, contents.encoding),
mimeType: mime
@@ -29,7 +29,7 @@ function resolveContent(fileService: IFileService, resource: URI, mime: string,
export function registerFileProtocol(
contents: Electron.WebContents,
protocol: WebviewProtocol,
fileService: IFileService,
textFileService: ITextFileService,
extensionLocation: URI | undefined,
getRoots: () => ReadonlyArray<URI>
) {
@@ -44,7 +44,7 @@ export function registerFileProtocol(
requestResourcePath: requestUri.path
})
});
resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback);
resolveContent(textFileService, redirectedUri, getMimeType(requestUri), callback);
return;
}
@@ -52,7 +52,7 @@ export function registerFileProtocol(
const normalizedPath = URI.file(requestPath);
for (const root of getRoots()) {
if (startsWith(normalizedPath.fsPath, root.fsPath + sep)) {
resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback);
resolveContent(textFileService, normalizedPath, getMimeType(normalizedPath), callback);
return;
}
}

View File

@@ -35,7 +35,7 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW
reject(err);
}
});
}) : this.textFileService.resolve(URI.file(resource.fsPath)).then(content => content.value));
}) : this.textFileService.readStream(URI.file(resource.fsPath)).then(content => content.value));
return content.then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) {
@@ -61,7 +61,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi
}
public provideTextContent(resource: URI): Promise<ITextModel> {
return this.textFileService.resolve(URI.file(resource.fsPath)).then(content => {
return this.textFileService.readStream(URI.file(resource.fsPath)).then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) {
const j = parseInt(resource.fragment);