Merge from vscode 3a6dcb42008d509900b3a3b2d695564eeb4dbdac (#5098)

This commit is contained in:
Alan Ren
2019-04-17 23:38:44 -07:00
committed by GitHub
parent 1fec26c6b3
commit b852f032d3
63 changed files with 676 additions and 413 deletions

View File

@@ -184,6 +184,12 @@ export class MainThreadCommentThread implements modes.CommentThread2 {
private _onDidChangeCollasibleState = new Emitter<modes.CommentThreadCollapsibleState | undefined>();
public onDidChangeCollasibleState = this._onDidChangeCollasibleState.event;
private _isDisposed: boolean;
get isDisposed(): boolean {
return this._isDisposed;
}
constructor(
public commentThreadHandle: number,
public controller: MainThreadCommentController,
@@ -191,7 +197,9 @@ export class MainThreadCommentThread implements modes.CommentThread2 {
public threadId: string,
public resource: string,
private _range: IRange
) { }
) {
this._isDisposed = false;
}
batchUpdate(
range: IRange,
@@ -210,7 +218,16 @@ export class MainThreadCommentThread implements modes.CommentThread2 {
this._collapsibleState = collapsibleState;
}
dispose() { }
dispose() {
this._isDisposed = true;
this._onDidChangeAcceptInputCommand.dispose();
this._onDidChangeAdditionalCommands.dispose();
this._onDidChangeCollasibleState.dispose();
this._onDidChangeComments.dispose();
this._onDidChangeInput.dispose();
this._onDidChangeLabel.dispose();
this._onDidChangeRange.dispose();
}
toJSON(): any {
return {
@@ -493,6 +510,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments
return undefined;
}
console.log('createCommentThread', commentThreadHandle);
return provider.createCommentThread(commentThreadHandle, threadId, resource, range);
}

View File

@@ -46,23 +46,20 @@ export class MainThreadWindow implements MainThreadWindowShape {
}
async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise<boolean> {
const uri = URI.revive(uriComponent);
let uri = URI.revive(uriComponent);
if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) {
if (uri.scheme === 'http' || uri.scheme === 'https') {
const port = this.getLocalhostPort(uri);
if (typeof port === 'number') {
const tunnel = await this.getOrCreateTunnel(port);
if (tunnel) {
const tunneledUrl = uri.toString().replace(
new RegExp(`^${uri.scheme}://localhost:${port}/`),
`${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`);
return this.windowsService.openExternal(tunneledUrl);
uri = uri.with({ authority: `localhost:${tunnel.tunnelLocalPort}` });
}
}
}
}
return this.windowsService.openExternal(uri.toString(true));
return this.windowsService.openExternal(encodeURI(uri.toString(true)));
}
private getLocalhostPort(uri: URI): number | undefined {

View File

@@ -22,6 +22,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isEqualOrParent } from 'vs/base/common/resources';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@@ -40,7 +42,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@IStatusbarService private readonly _statusbarService: IStatusbarService,
@IWindowService private readonly _windowService: IWindowService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService
@ILabelService private readonly _labelService: ILabelService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace)));
@@ -110,6 +113,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
}
return {
configuration: workspace.configuration || undefined,
isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false,
folders: workspace.folders,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)

View File

@@ -15,6 +15,7 @@ import { IWindowsService, IOpenSettings, IURIToOpen } from 'vs/platform/windows/
import { IDownloadService } from 'vs/platform/download/common/download';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IRecent } from 'vs/platform/history/common/history';
import { Schemas } from 'vs/base/common/network';
// -----------------------------------------------------------------
// The following commands are registered on both sides separately.
@@ -51,7 +52,7 @@ export class OpenFolderAPICommand {
}
const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow, noRecentEntry: arg.noRecentEntry };
uri = URI.revive(uri);
const uriToOpen: IURIToOpen = hasWorkspaceFileExtension(uri.path) ? { workspaceUri: uri } : { folderUri: uri };
const uriToOpen: IURIToOpen = (hasWorkspaceFileExtension(uri.path) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri };
return executor.executeCommand('_files.windowOpen', [uriToOpen], options);
}
}

View File

@@ -68,6 +68,7 @@ export interface IStaticWorkspaceData {
id: string;
name: string;
configuration?: UriComponents | null;
isUntitled?: boolean | null;
}
export interface IWorkspaceData extends IStaticWorkspaceData {

View File

@@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { Counter } from 'vs/base/common/numbers';
import { isLinux } from 'vs/base/common/platform';
import { basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources';
import { basenameOrAuthority, dirname, isEqual, relativePath, basename } from 'vs/base/common/resources';
import { compare } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
@@ -24,6 +24,7 @@ import * as vscode from 'vscode';
import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from './extHost.protocol';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Barrier } from 'vs/base/common/async';
import { Schemas } from 'vs/base/common/network';
export interface IExtHostWorkspaceProvider {
getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise<vscode.WorkspaceFolder | undefined>;
@@ -67,7 +68,7 @@ class ExtHostWorkspaceImpl extends Workspace {
return { workspace: null, added: [], removed: [] };
}
const { id, name, folders } = data;
const { id, name, folders, configuration, isUntitled } = data;
const newWorkspaceFolders: vscode.WorkspaceFolder[] = [];
// If we have an existing workspace, we try to find the folders that match our
@@ -95,7 +96,7 @@ class ExtHostWorkspaceImpl extends Workspace {
// make sure to restore sort order based on index
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders);
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled);
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
return { workspace, added, removed };
@@ -115,8 +116,8 @@ class ExtHostWorkspaceImpl extends Workspace {
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) {
super(id, folders.map(f => new WorkspaceFolder(f)));
constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean) {
super(id, folders.map(f => new WorkspaceFolder(f)), configuration);
// setup the workspace folder data structure
folders.forEach(folder => {
@@ -129,6 +130,10 @@ class ExtHostWorkspaceImpl extends Workspace {
return this._name;
}
get isUntitled(): boolean {
return this._isUntitled;
}
get workspaceFolders(): vscode.WorkspaceFolder[] {
return this._workspaceFolders.slice(0);
}
@@ -175,7 +180,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace);
this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService);
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, []) : undefined;
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled) : undefined;
}
$initializeWorkspace(data: IWorkspaceData): void {
@@ -197,6 +202,20 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
return this._actualWorkspace ? this._actualWorkspace.name : undefined;
}
get workspaceFile(): vscode.Uri | undefined {
if (this._actualWorkspace) {
if (this._actualWorkspace.configuration) {
if (this._actualWorkspace.isUntitled) {
return URI.from({ scheme: Schemas.untitled, path: basename(dirname(this._actualWorkspace.configuration)) }); // Untitled Worspace: return untitled URI
}
return this._actualWorkspace.configuration; // Workspace: return the configuration location
}
}
return undefined;
}
private get _actualWorkspace(): ExtHostWorkspaceImpl | undefined {
return this._unconfirmedWorkspace || this._confirmedWorkspace;
}
@@ -365,7 +384,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
id: this._actualWorkspace.id,
name: this._actualWorkspace.name,
configuration: this._actualWorkspace.configuration,
folders
folders,
isUntitled: this._actualWorkspace.isUntitled
} as IWorkspaceData, this._actualWorkspace).workspace || undefined;
}
}

View File

@@ -116,7 +116,7 @@ export function createApiFactory(
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors));
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService));
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostWorkspace, extHostDocumentsAndEditors, extHostLogService));
// {{SQL CARBON EDIT}}
// const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands));
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
@@ -539,6 +539,12 @@ export function createApiFactory(
set name(value) {
throw errors.readonly();
},
get workspaceFile() {
return extHostWorkspace.workspaceFile;
},
set workspaceFile(value) {
throw errors.readonly();
},
updateWorkspaceFolders: (index, deleteCount, ...workspaceFoldersToAdd) => {
return extHostWorkspace.updateWorkspaceFolders(extension, index, deleteCount || 0, ...workspaceFoldersToAdd);
},

View File

@@ -65,6 +65,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape;
private readonly _almostReadyToRunExtensions: Barrier;
private readonly _readyToStartExtensionHost: Barrier;
private readonly _readyToRunExtensions: Barrier;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _storage: ExtHostStorage;
@@ -101,6 +102,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService);
this._almostReadyToRunExtensions = new Barrier();
this._readyToStartExtensionHost = new Barrier();
this._readyToRunExtensions = new Barrier();
this._registry = new ExtensionDescriptionRegistry(initData.extensions);
this._storage = new ExtHostStorage(this._extHostContext);
@@ -171,7 +173,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
this._almostReadyToRunExtensions.open();
await this._extHostWorkspace.waitForInitializeCall();
this._readyToRunExtensions.open();
this._readyToStartExtensionHost.open();
} catch (err) {
errors.onUnexpectedError(err);
}
@@ -581,7 +583,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
}
this._started = true;
return this._readyToRunExtensions.wait()
return this._readyToStartExtensionHost.wait()
.then(() => this._readyToRunExtensions.open())
.then(() => this._handleEagerExtensions())
.then(() => this._handleExtensionTests())
.then(() => {

View File

@@ -13,10 +13,14 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
import { timeout } from 'vs/base/common/async';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
// {{SQL CARBON EDIT}}
// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
const RENDERER_NO_PROCESS_ID = -1;
@@ -288,6 +292,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
constructor(
mainContext: IMainContext,
private _extHostConfiguration: ExtHostConfiguration,
private _extHostWorkspace: ExtHostWorkspace,
private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
private _logService: ILogService,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService);
@@ -436,6 +442,16 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
}
private _apiInspectConfigToPlain<T>(
config: { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined
): { user: T | undefined, value: T | undefined, default: T | undefined } {
return {
user: config ? config.globalValue : undefined,
value: config ? config.workspaceValue : undefined,
default: config ? config.defaultValue : undefined,
};
}
public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
const shellLaunchConfig: IShellLaunchConfig = {
name: shellLaunchConfigDto.name,
@@ -447,57 +463,54 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Merge in shell and args from settings
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const configProvider = await this._extHostConfiguration.getConfigProvider();
if (!shellLaunchConfig.executable) {
const fetchSetting = (key: string) => {
const setting = configProvider
.getConfiguration(key.substr(0, key.lastIndexOf('.')))
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
return {
user: setting ? setting.globalValue : undefined,
value: setting ? setting.workspaceValue : undefined,
default: setting ? setting.defaultValue : undefined,
};
return this._apiInspectConfigToPlain<string | string[]>(setting);
};
terminalEnvironment.mergeDefaultShellPathAndArgs(shellLaunchConfig, fetchSetting, isWorkspaceShellAllowed || false);
}
// Get the initial cwd
const configProvider = await this._extHostConfiguration.getConfigProvider();
const terminalConfig = configProvider.getConfiguration('terminal.integrated');
const activeWorkspaceRootUri = URI.revive(activeWorkspaceRootUriComponents);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), activeWorkspaceRootUri, terminalConfig.cwd);
// TODO: Pull in and resolve config settings
// // Resolve env vars from config and shell
// const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri);
// const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...terminalConfig.env[platformKey] }, lastActiveWorkspaceRoot);
const envFromConfig = { ...terminalConfig.env[platformKey] };
// const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
// Merge process env with the env from config
const env = { ...process.env };
Object.keys(env).filter(k => env[k] === undefined).forEach(k => {
delete env[k];
});
const castedEnv = env as platform.IProcessEnvironment;
terminalEnvironment.mergeEnvironments(castedEnv, envFromConfig);
terminalEnvironment.mergeEnvironments(castedEnv, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
sanitizeProcessEnvironment(castedEnv, 'VSCODE_IPC_HOOK_CLI');
// Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process
terminalEnvironment.addTerminalEnvironmentKeys(castedEnv, pkg.version, platform.locale, terminalConfig.get('setLocaleVariables') as boolean);
// Get the environment
const apiLastActiveWorkspace = await this._extHostWorkspace.getWorkspaceFolder(activeWorkspaceRootUri);
const lastActiveWorkspace = apiLastActiveWorkspace ? {
uri: apiLastActiveWorkspace.uri,
name: apiLastActiveWorkspace.name,
index: apiLastActiveWorkspace.index,
toResource: () => {
throw new Error('Not implemented');
}
} as IWorkspaceFolder : null;
const envFromConfig = this._apiInspectConfigToPlain(configProvider.getConfiguration('terminal.integrated').inspect<ITerminalEnvironment>(`env.${platformKey}`));
const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2();
// {{SQL CARBON EDIT}}
// const variableResolver = workspaceFolders ? new ExtHostVariableResolverService(workspaceFolders, this._extHostDocumentsAndEditors, configProvider) : undefined;
const variableResolver = undefined;
const env = terminalEnvironment.createTerminalEnvironment(
shellLaunchConfig,
lastActiveWorkspace,
envFromConfig,
variableResolver,
isWorkspaceShellAllowed,
pkg.version,
terminalConfig.get<boolean>('setLocaleVariables', false)
);
// Fork the process and listen for messages
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, castedEnv);
const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, castedEnv, terminalConfig.get('windowsEnableConpty') as boolean);
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env);
const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, terminalConfig.get('windowsEnableConpty') as boolean);
p.onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid));
p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
p.onProcessData(data => this._proxy.$sendProcessData(id, data));
p.onProcessExit((exitCode) => this._onProcessExit(id, exitCode));
p.onProcessExit(exitCode => this._onProcessExit(id, exitCode));
this._terminalProcesses[id] = p;
}
@@ -541,7 +554,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Send exit event to main side
this._proxy.$sendProcessExit(id, exitCode);
}
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal> {

View File

@@ -145,12 +145,13 @@ export class NoTabsTitleControl extends TitleControl {
this.redraw();
}
updateEditorLabel(editor?: IEditorInput): void {
if (!editor) {
editor = withNullAsUndefined(this.group.activeEditor);
}
if (editor) {
this.ifEditorIsActive(editor, () => this.redraw());
updateEditorLabel(editor: IEditorInput): void {
this.ifEditorIsActive(editor, () => this.redraw());
}
updateEditorLabels(): void {
if (this.group.activeEditor) {
this.updateEditorLabel(this.group.activeEditor); // we only have the active one to update
}
}

View File

@@ -371,6 +371,12 @@ export class TabsTitleControl extends TitleControl {
updateEditorLabel(editor: IEditorInput): void {
// Update all labels to account for changes to tab labels
this.updateEditorLabels();
}
updateEditorLabels(): void {
// A change to a label requires to recompute all labels
this.computeTabLabels();

View File

@@ -93,7 +93,7 @@ export abstract class TitleControl extends Themable {
private registerListeners(): void {
this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabel()));
this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabels()));
}
protected abstract create(parent: HTMLElement): void;
@@ -343,7 +343,9 @@ export abstract class TitleControl extends Themable {
abstract setActive(isActive: boolean): void;
abstract updateEditorLabel(editor?: IEditorInput): void;
abstract updateEditorLabel(editor: IEditorInput): void;
abstract updateEditorLabels(): void;
abstract updateEditorDirty(editor: IEditorInput): void;

View File

@@ -153,7 +153,7 @@ class NotificationMessageRenderer {
const anchor = document.createElement('a');
anchor.textContent = link.name;
anchor.title = link.href;
anchor.title = link.title;
anchor.href = link.href;
if (actionHandler) {

View File

@@ -10,6 +10,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { Action } from 'vs/base/common/actions';
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
import { startsWith } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
export interface INotificationsModel {
@@ -306,8 +308,9 @@ export class NotificationViewItemProgress extends Disposable implements INotific
}
export interface IMessageLink {
name: string;
href: string;
name: string;
title: string;
offset: number;
length: number;
}
@@ -325,7 +328,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
// Example link: "Some message with [link text](http://link.href)."
// RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace)
private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)\)/gi;
private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi;
private _expanded: boolean;
@@ -392,8 +395,17 @@ export class NotificationViewItem extends Disposable implements INotificationVie
// Parse Links
const links: IMessageLink[] = [];
message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, offset: number) => {
links.push({ name, href, offset, length: matchString.length });
message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, title: string, offset: number) => {
let massagedTitle: string;
if (title && title.length > 0) {
massagedTitle = title;
} else if (startsWith(href, 'command:')) {
massagedTitle = localize('executeCommand', "Click to execute command '{0}'", href.substr('command:'.length));
} else {
massagedTitle = href;
}
links.push({ name, href, title: massagedTitle, offset, length: matchString.length });
return matchString;
});

View File

@@ -738,6 +738,7 @@ export class ReviewController implements IEditorContribution {
this._commentInfos.forEach(info => {
let providerCacheStore = this._pendingCommentCache[info.owner];
info.threads = info.threads.filter(thread => !thread.isDisposed);
info.threads.forEach(thread => {
let pendingComment: string | null = null;
if (providerCacheStore) {

View File

@@ -18,7 +18,7 @@ import {
ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource,
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State
} from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { Source, UNKNOWN_SOURCE_LABEL } from 'vs/workbench/contrib/debug/common/debugSource';
import { commonSuffixLength } from 'vs/base/common/strings';
import { posix } from 'vs/base/common/path';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -381,7 +381,10 @@ export class StackFrame implements IStackFrame {
}
toString(): string {
return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`;
const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';
const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;
return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;
}
openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {

View File

@@ -13,7 +13,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/
import { Schemas } from 'vs/base/common/network';
import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
/**
* Debug URI format

View File

@@ -394,6 +394,22 @@ suite('Debug - Model', () => {
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
const session = createMockSession(model);
const thread = new Thread(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId');
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
assert.equal(stackFrame2.toString(), 'module');
});
test('debug child sessions are added in correct order', () => {
const session = createMockSession(model);
model.addSession(session);

View File

@@ -252,35 +252,6 @@ 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."),
default: false
}
}
});

View File

@@ -18,7 +18,7 @@ import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IE
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -176,14 +176,17 @@ export class InstallAction extends ExtensionAction {
}
update(): void {
if (!this.extension || this.extension.type === ExtensionType.System) {
if (!this.extension || this.extension.type === ExtensionType.System || this.extension.state === ExtensionState.Installed) {
this.enabled = false;
this.class = InstallAction.Class;
this.label = InstallAction.INSTALL_LABEL;
return;
}
this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && !this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, this.extension.identifier));
this.enabled = false;
if (this.extensionsWorkbenchService.canInstall(this.extension)) {
const local = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0];
this.enabled = !local || (!!local.local && isLanguagePackExtension(local.local.manifest));
}
this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class;
this.updateLabel();
}
@@ -2590,6 +2593,9 @@ export class DisabledLabelAction extends ExtensionAction {
this.class = `${DisabledLabelAction.Class} hide`;
this.label = '';
this.enabled = false;
if (this.extension && this.extension.local && isLanguagePackExtension(this.extension.local.manifest)) {
return;
}
if (this.warningAction.enabled) {
this.enabled = true;
this.class = DisabledLabelAction.Class;
@@ -2649,6 +2655,9 @@ export class SystemDisabledWarningAction extends ExtensionAction {
this.enabled = false;
this.class = `${SystemDisabledWarningAction.Class} hide`;
this.tooltip = '';
if (this.extension && this.extension.local && isLanguagePackExtension(this.extension.local.manifest)) {
return;
}
if (this.extension && this.extension.local && this.extension.server && this._runningExtensions && this.workbenchEnvironmentService.configuration.remoteAuthority && this.extensionManagementServerService.remoteExtensionManagementServer) {
const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0];
const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null;

View File

@@ -19,6 +19,7 @@ import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteB
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
export interface IExtensionsViewState {
onFocus: Event<IExtension>;
@@ -154,7 +155,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const updateEnablement = async () => {
const runningExtensions = await this.extensionService.getExtensions();
if (extension.local) {
if (extension.local && !isLanguagePackExtension(extension.local.manifest)) {
const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, extension.identifier))[0];
const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation);
toggleClass(data.root, 'disabled', !isSameExtensionRunning);

View File

@@ -54,6 +54,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { RemoteAuthorityContext } from 'vs/workbench/common/contextkeys';
interface SearchInputEvent extends Event {
target: HTMLInputElement;
@@ -139,7 +140,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: EnabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.not('config.extensions.showInstalledExtensionsByDefault')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')),
weight: 40,
canToggleVisibility: true,
order: 1
@@ -154,7 +155,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: DisabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.not('config.extensions.showInstalledExtensionsByDefault')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')),
weight: 10,
canToggleVisibility: true,
order: 3,
@@ -195,7 +196,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id: `extensions.${server.authority}.default`,
name: localize('installed', "Installed"),
ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server] },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.has('config.extensions.showInstalledExtensionsByDefault')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.notEqualsTo('')),
weight: 40,
order: 1
}];
@@ -512,7 +513,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
private doSearch(): Promise<void> {
const value = this.normalizedQuery();
this.defaultViewsContextKey.set(!value);
const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);
this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));
this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value));
@@ -522,6 +522,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery);
this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
this.defaultViewsContextKey.set(!value);
return this.progress(Promise.all(this.panels.map(view =>
(<ExtensionsListView>view).show(this.normalizedQuery())

View File

@@ -41,7 +41,7 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IAction } from 'vs/base/common/actions';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import product from 'vs/platform/product/node/product';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
@@ -343,6 +343,11 @@ export class ExtensionsListView extends ViewletPanel {
if ((isE1Running && isE2Running) || (!isE1Running && !isE2Running)) {
return e1.displayName.localeCompare(e2.displayName);
}
const isE1LanguagePackExtension = e1.local && isLanguagePackExtension(e1.local.manifest);
const isE2LanguagePackExtension = e2.local && isLanguagePackExtension(e2.local.manifest);
if ((isE1Running && isE2LanguagePackExtension) || (isE2Running && isE1LanguagePackExtension)) {
return e1.displayName.localeCompare(e2.displayName);
}
return isE1Running ? -1 : 1;
});
}

View File

@@ -17,7 +17,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { toResource, ITextEditor, SideBySideEditor } from 'vs/workbench/common/editor';
import { toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
@@ -356,64 +356,6 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean
return directories.length > 0 && files.length > 0;
}
let pasteShouldMove = false;
// Paste File/Folder
class PasteFileAction extends Action {
public static readonly ID = 'filesExplorer.paste';
constructor(
private element: ExplorerItem,
@IFileService private fileService: IFileService,
@INotificationService private notificationService: INotificationService,
@IEditorService private readonly editorService: IEditorService,
@IExplorerService private readonly explorerService: IExplorerService
) {
super(PasteFileAction.ID, PASTE_FILE_LABEL);
if (!this.element) {
this.element = this.explorerService.roots[0];
}
}
public run(fileToPaste: URI): Promise<any> {
// Check if target is ancestor of pasted folder
if (this.element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(this.element.resource, fileToPaste, !isLinux /* ignorecase */)) {
throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder"));
}
return this.fileService.resolve(fileToPaste).then(fileToPasteStat => {
// Find target
let target: ExplorerItem;
if (this.element.resource.toString() === fileToPaste.toString()) {
target = this.element.parent!;
} else {
target = this.element.isDirectory ? this.element : this.element.parent!;
}
const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove });
// Copy File
const promise = pasteShouldMove ? this.fileService.move(fileToPaste, targetFile) : this.fileService.copy(fileToPaste, targetFile);
return promise.then<ITextEditor | undefined>(stat => {
if (pasteShouldMove) {
// Cut is done. Make sure to clear cut state.
this.explorerService.setToCopy([], false);
}
if (!stat.isDirectory) {
return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } })
.then(types.withNullAsUndefined);
}
return undefined;
}, e => onError(this.notificationService, e));
}, error => {
onError(this.notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile")));
});
}
}
export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwirte: boolean }): URI {
let name = resources.basenameOrAuthority(fileToPaste.resource);
@@ -1083,6 +1025,7 @@ export const deleteFileHandler = (accessor: ServicesAccessor) => {
return deleteFiles(accessor, stats, false);
};
let pasteShouldMove = false;
export const copyFileHandler = (accessor: ServicesAccessor) => {
const listService = accessor.get(IListService);
if (!listService.lastFocusedList) {
@@ -1112,16 +1055,50 @@ export const cutFileHandler = (accessor: ServicesAccessor) => {
};
export const pasteFileHandler = (accessor: ServicesAccessor) => {
const instantiationService = accessor.get(IInstantiationService);
const listService = accessor.get(IListService);
const clipboardService = accessor.get(IClipboardService);
if (!listService.lastFocusedList) {
return Promise.resolve();
}
const explorerContext = getContext(listService.lastFocusedList);
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
const notificationService = accessor.get(INotificationService);
const editorService = accessor.get(IEditorService);
return sequence(resources.distinctParents(clipboardService.readResources(), r => r).map(toCopy => {
const pasteFileAction = instantiationService.createInstance(PasteFileAction, explorerContext.stat);
return () => pasteFileAction.run(toCopy);
}));
if (listService.lastFocusedList) {
const explorerContext = getContext(listService.lastFocusedList);
const toPaste = resources.distinctParents(clipboardService.readResources(), r => r);
const element = explorerContext.stat || explorerService.roots[0];
// Check if target is ancestor of pasted folder
sequence(toPaste.map(fileToPaste => () => {
if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste, !isLinux /* ignorecase */)) {
throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder"));
}
return fileService.resolve(fileToPaste).then(fileToPasteStat => {
// Find target
let target: ExplorerItem;
if (element.resource.toString() === fileToPaste.toString()) {
target = element.parent!;
} else {
target = element.isDirectory ? element : element.parent!;
}
const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove });
// Copy File
return pasteShouldMove ? fileService.move(fileToPaste, targetFile) : fileService.copy(fileToPaste, targetFile);
}, error => {
onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile")));
});
})).then((stat) => {
if (pasteShouldMove) {
// Cut is done. Make sure to clear cut state.
explorerService.setToCopy([], false);
}
if (stat.length === 1 && !stat[0].isDirectory) {
editorService.openEditor({ resource: stat[0].resource, options: { pinned: true, preserveFocus: true } }).then(undefined, onUnexpectedError);
}
});
}
};

View File

@@ -5,9 +5,9 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
// {{SQL CARBON EDIT}} - Import EditorInput
import { toResource, IEditorCommandsContext, EditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions } from 'vs/platform/windows/common/windows';
// {{SQL CARBON EDIT}} import EditorInput
import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor';
import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -35,14 +35,15 @@ import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/edito
import { Schemas } from 'vs/base/common/network';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
// {{SQL CARBON EDIT}} - Import EditorInput
import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ILabelService } from 'vs/platform/label/common/label';
import { onUnexpectedError } from 'vs/base/common/errors';
import { basename, toLocalResource } from 'vs/base/common/resources';
import { basename, toLocalResource, joinPath } from 'vs/base/common/resources';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces';
// {{SQL CARBON EDIT}}
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
@@ -86,6 +87,19 @@ export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace'
export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURIToOpen[], options?: IOpenSettings) => {
if (Array.isArray(urisToOpen)) {
const windowService = accessor.get(IWindowService);
const environmentService = accessor.get(IEnvironmentService);
// rewrite untitled: workspace URIs to the absolute path on disk
urisToOpen = urisToOpen.map(uriToOpen => {
if (isWorkspaceToOpen(uriToOpen) && uriToOpen.workspaceUri.scheme === Schemas.untitled) {
return {
workspaceUri: joinPath(environmentService.untitledWorkspacesHome, uriToOpen.workspaceUri.path, UNTITLED_WORKSPACE_NAME)
};
}
return uriToOpen;
});
windowService.openWindow(urisToOpen, options);
}
};

View File

@@ -228,6 +228,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement {
return this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE;
}
if (configTarget === ConfigurationTarget.USER_REMOTE) {
return this.setting.scope === ConfigurationScope.MACHINE || this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE;
}
return true;
}

View File

@@ -16,13 +16,11 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME, getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
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;
@@ -173,53 +171,20 @@ 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);
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, 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);
}
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) {
// 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
terminalEnvironment.mergeNonNullKeys(env, process.env);
// 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<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user) };
// 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
// variables
sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
// Adding other env keys necessary to create the process
terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables);
}
return env;
}
public setDimensions(cols: number, rows: number): void {
if (!this._process) {
return;

View File

@@ -9,6 +9,7 @@ import { URI as Uri } from 'vs/base/common/uri';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
/**
* This module contains utility functions related to the environment, cwd and paths.
@@ -59,7 +60,7 @@ export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, ve
}
}
export function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) {
function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) {
if (!other) {
return;
}
@@ -71,7 +72,7 @@ export function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerm
}
}
export function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment {
function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment {
Object.keys(env).forEach((key) => {
const value = env[key];
if (typeof value === 'string' && lastActiveWorkspaceRoot !== null) {
@@ -189,3 +190,49 @@ export function mergeDefaultShellPathAndArgs(
shell.executable = shell.executable.replace(/\//g, '\\');
}
}
export function createTerminalEnvironment(
shellLaunchConfig: IShellLaunchConfig,
lastActiveWorkspace: IWorkspaceFolder | null,
envFromConfig: { user: ITerminalEnvironment | undefined, value: ITerminalEnvironment | undefined, default: ITerminalEnvironment | undefined },
configurationResolverService: IConfigurationResolverService | undefined,
isWorkspaceShellAllowed: boolean,
version: string | undefined,
setLocaleVariables: boolean
): platform.IProcessEnvironment {
// Create a terminal environment based on settings, launch config and permissions
let env: platform.IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
// strictEnv is true, only use the requested env (ignoring null entries)
mergeNonNullKeys(env, shellLaunchConfig.env);
} else {
// Merge process env with the env from config and from shellLaunchConfig
mergeNonNullKeys(env, process.env);
// const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
// const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.user) };
// Resolve env vars from config and shell
if (configurationResolverService) {
if (allowedEnvFromConfig) {
resolveConfigurationVariables(configurationResolverService, allowedEnvFromConfig, lastActiveWorkspace);
}
if (shellLaunchConfig.env) {
resolveConfigurationVariables(configurationResolverService, shellLaunchConfig.env, lastActiveWorkspace);
}
}
// Merge config (settings) and ShellLaunchConfig environments
mergeEnvironments(env, allowedEnvFromConfig);
mergeEnvironments(env, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
// Adding other env keys necessary to create the process
addTerminalEnvironmentKeys(env, version, platform.locale, setLocaleVariables);
}
return env;
}

View File

@@ -38,6 +38,7 @@ import { parseExtensionDevOptions } from '../common/extensionDevOptions';
import { VSBuffer } from 'vs/base/common/buffer';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { isEqualOrParent } from 'vs/base/common/resources';
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
@@ -400,7 +401,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
configuration: withNullAsUndefined(workspace.configuration),
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
name: this._labelService.getWorkspaceLabel(workspace),
isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false
},
resolvedExtensions: [],
hostExtensions: [],

View File

@@ -8,7 +8,6 @@ import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
import { ExtensionType, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
@@ -46,8 +45,10 @@ export class MultiExtensionManagementService extends Disposable implements IExte
}
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)))
.then(result => flatten(result));
const installedExtensions: ILocalExtension[] = [];
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type).then(extensions => installedExtensions.push(...extensions))))
.then(_ => installedExtensions)
.catch(e => installedExtensions);
}
async uninstall(extension: ILocalExtension, force?: boolean): Promise<void> {

View File

@@ -17,6 +17,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream } from 'vs/base/common/buffer';
import { Queue } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
export class FileService extends Disposable implements IFileService {
@@ -101,7 +102,7 @@ export class FileService extends Disposable implements IFileService {
// Assert path is absolute
if (!isAbsolutePath(resource)) {
throw new FileOperationError(localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)), FileOperationResult.FILE_INVALID_PATH);
throw new FileOperationError(localize('invalidPath', "The path of resource '{0}' must be absolute", this.resourceForError(resource)), FileOperationResult.FILE_INVALID_PATH);
}
// Activate provider
@@ -110,11 +111,11 @@ export class FileService extends Disposable implements IFileService {
// Assert provider
const provider = this.provider.get(resource.scheme);
if (!provider) {
const err = new Error();
err.name = 'ENOPRO';
err.message = `No provider found for ${resource.toString()}`;
const error = new Error();
error.name = 'ENOPRO';
error.message = localize('noProviderFound', "No file system provider found for {0}", resource.toString());
throw err;
throw error;
}
return provider;
@@ -150,7 +151,7 @@ export class FileService extends Disposable implements IFileService {
// Specially handle file not found case as file operation result
if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) {
throw new FileOperationError(
localize('fileNotFoundError', "File not found ({0})", resource.toString(true)),
localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)),
FileOperationResult.FILE_NOT_FOUND
);
}
@@ -270,7 +271,7 @@ export class FileService extends Disposable implements IFileService {
// validate overwrite
const overwrite = !!(options && options.overwrite);
if (!overwrite && await this.exists(resource)) {
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", resource.toString(true)), FileOperationResult.FILE_MODIFIED_SINCE, options);
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", this.resourceForError(resource)), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
// do write into file (this will create it too)
@@ -305,7 +306,7 @@ export class FileService extends Disposable implements IFileService {
await this.doWriteUnbuffered(provider, resource, bufferOrReadable);
}
} catch (error) {
throw new FileOperationError(localize('err.write', "Failed to write file {0}", resource.toString(false)), toFileOperationResult(error), options);
throw new FileOperationError(localize('err.write', "Unable to write file ({0})", error.toString()), toFileOperationResult(error), options);
}
return this.resolve(resource, { resolveMetadata: true });
@@ -321,7 +322,7 @@ export class FileService extends Disposable implements IFileService {
// file cannot be directory
if ((stat.type & FileType.Directory) !== 0) {
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", resource.toString()), FileOperationResult.FILE_IS_DIRECTORY, options);
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
}
// Dirty write prevention: if the file on disk has been changed and does not match our expected
@@ -397,7 +398,7 @@ export class FileService extends Disposable implements IFileService {
value: fileStream
};
} catch (error) {
throw new FileOperationError(localize('err.read', "Failed to read file {0}", resource.toString(false)), toFileOperationResult(error), options);
throw new FileOperationError(localize('err.read', "Unable to read file ({0})", error.toString()), toFileOperationResult(error), options);
}
}
@@ -488,7 +489,7 @@ export class FileService extends Disposable implements IFileService {
// Return early if resource is a directory
if (stat.isDirectory) {
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", resource.toString()), FileOperationResult.FILE_IS_DIRECTORY, options);
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
}
// Return early if file not modified since
@@ -692,7 +693,7 @@ export class FileService extends Disposable implements IFileService {
try {
const stat = await provider.stat(directory);
if ((stat.type & FileType.Directory) === 0) {
throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", directory.toString()));
throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", this.resourceForError(directory)));
}
break; // we have hit a directory that exists -> good
@@ -732,7 +733,7 @@ export class FileService extends Disposable implements IFileService {
if (!recursive && await this.exists(resource)) {
const stat = await this.resolve(resource);
if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) {
throw new Error(localize('deleteFailed', "Failed to delete non-empty folder '{0}'.", resource.toString()));
throw new Error(localize('deleteFailed', "Unable to delete non-empty folder '{0}'.", this.resourceForError(resource)));
}
}
@@ -1006,5 +1007,13 @@ export class FileService extends Disposable implements IFileService {
return true;
}
private resourceForError(resource: URI): string {
if (resource.scheme === Schemas.file) {
return resource.fsPath;
}
return resource.toString(true);
}
//#endregion
}

View File

@@ -13,7 +13,7 @@ import { FileChangeType } from 'vs/platform/files/common/files';
import { ThrottledDelayer } from 'vs/base/common/async';
import { normalizeNFC } from 'vs/base/common/normalization';
import { realcaseSync } from 'vs/base/node/extpath';
import { isMacintosh } from 'vs/base/common/platform';
import { isMacintosh, isLinux } from 'vs/base/common/platform';
import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher';
import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { Emitter, Event } from 'vs/base/common/event';
@@ -114,12 +114,21 @@ export class ChokidarWatcherService implements IWatcherService {
disableGlobbing: true // fix https://github.com/Microsoft/vscode/issues/4586
};
const excludes: string[] = [];
// if there's only one request, use the built-in ignore-filterering
const isSingleFolder = requests.length === 1;
if (isSingleFolder) {
watcherOpts.ignored = requests[0].excludes;
excludes.push(...requests[0].excludes);
}
if ((isMacintosh || isLinux) && (basePath.length === 0 || basePath === '/')) {
excludes.push('/dev/**');
if (isLinux) {
excludes.push('/proc/**', '/sys/**');
}
}
watcherOpts.ignored = excludes;
// Chokidar fails when the basePath does not match case-identical to the path on disk
// so we have to find the real casing of the path and do some path massaging to fix this
// see https://github.com/paulmillr/chokidar/issues/418

View File

@@ -1382,6 +1382,10 @@ suite('Disk File Service', () => {
});
test('watch - file - multiple writes', done => {
if (isWindows) {
return done(); // not happy
}
const toWatch = URI.file(join(testDir, 'index-watch1.html'));
writeFileSync(toWatch.fsPath, 'Init');
@@ -1487,7 +1491,7 @@ suite('Disk File Service', () => {
setTimeout(() => mkdirSync(folder.fsPath), 50);
});
test('watch - folder (non recursive) - delete folder', done => {
test.skip('watch - folder (non recursive) - delete folder', done => {
const watchDir = URI.file(join(testDir, 'watch7'));
mkdirSync(watchDir.fsPath);

View File

@@ -160,7 +160,7 @@ export class LabelService implements ILabelService {
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
if (!isWorkspaceIdentifier(workspace) && !isSingleFolderWorkspaceIdentifier(workspace)) {
if (IWorkspace.isIWorkspace(workspace)) {
const identifier = toWorkspaceIdentifier(workspace);
if (!identifier) {
return '';
@@ -176,23 +176,27 @@ export class LabelService implements ILabelService {
return this.appendWorkspaceSuffix(label, workspace);
}
// Workspace: Untitled
if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
if (isWorkspaceIdentifier(workspace)) {
// Workspace: Untitled
if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
let filename = basename(workspace.configPath);
if (endsWith(filename, WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
// Workspace: Saved
let filename = basename(workspace.configPath);
if (endsWith(filename, WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspace.configPath);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspace.configPath);
return '';
}
getSeparator(scheme: string, authority?: string): '/' | '\\' {

View File

@@ -154,7 +154,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Model does not exist
else {
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined);
model = newModel;
modelPromise = model.load(options);
// Install state change listener
@@ -192,24 +191,24 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
try {
const model = await modelPromise;
const resolvedModel = await modelPromise;
// Make known to manager (if not already known)
this.add(resource, model);
this.add(resource, resolvedModel);
// Model can be dirty if a backup was restored, so we make sure to have this event delivered
if (model.isDirty()) {
this._onModelDirty.fire(new TextFileModelChangeEvent(model, StateChange.DIRTY));
if (resolvedModel.isDirty()) {
this._onModelDirty.fire(new TextFileModelChangeEvent(resolvedModel, StateChange.DIRTY));
}
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
return model;
return resolvedModel;
} catch (error) {
// Free resources of this invalid model
if (model && typeof model.dispose === 'function') { // workaround for https://github.com/Microsoft/vscode/issues/72404
if (model) {
model.dispose();
}

View File

@@ -39,6 +39,7 @@ import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { VSBuffer } from 'vs/base/common/buffer';
import { ITextSnapshot } from 'vs/editor/common/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
/**
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
@@ -85,7 +86,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
@IContextKeyService contextKeyService: IContextKeyService,
@IDialogService private readonly dialogService: IDialogService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService
) {
super();

View File

@@ -17,7 +17,7 @@ import { isMacintosh, isLinux } from 'vs/base/common/platform';
import product from 'vs/platform/product/node/product';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, IDetectedEncodingResult, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer } from 'vs/base/node/encoding';
import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer } from 'vs/base/node/encoding';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources';
import { Disposable } from 'vs/base/common/lifecycle';
@@ -70,8 +70,8 @@ export class NodeTextFileService extends TextFileService {
// read through encoding library
const decoder = await toDecodeStream(this.streamToNodeReadable(bufferStream.value), {
guessEncoding: options && options.autoGuessEncoding,
overwriteEncoding: detected => this.encoding.getReadEncoding(resource, options, { encoding: detected, seemsBinary: false })
guessEncoding: (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'),
overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding)
});
// validate binary
@@ -417,7 +417,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
const overwriteEncoding = options && options.overwriteEncoding;
if (!overwriteEncoding && encoding === UTF8) {
try {
const buffer = (await this.fileService.readFile(resource, { length: 3 })).value;
const buffer = (await this.fileService.readFile(resource, { length: UTF8_BOM.length })).value;
if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8) {
return { encoding, addBOM: true };
}
@@ -438,12 +438,12 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
};
}
getReadEncoding(resource: URI, options: IReadTextFileOptions | undefined, detected: IDetectedEncodingResult): string {
getReadEncoding(resource: URI, options: IReadTextFileOptions | undefined, detectedEncoding: string | null): string {
let preferredEncoding: string | undefined;
// Encoding passed in as option
if (options && options.encoding) {
if (detected.encoding === UTF8 && options.encoding === UTF8) {
if (detectedEncoding === UTF8 && options.encoding === UTF8) {
preferredEncoding = UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8
} else {
preferredEncoding = options.encoding; // give passed in encoding highest priority
@@ -451,11 +451,11 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
}
// Encoding detected
else if (detected.encoding) {
if (detected.encoding === UTF8) {
else if (detectedEncoding) {
if (detectedEncoding === UTF8) {
preferredEncoding = UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM
} else {
preferredEncoding = detected.encoding;
preferredEncoding = detectedEncoding;
}
}

View File

@@ -570,6 +570,13 @@ suite('Files - TextFileService i/o', () => {
assert.equal(result.encoding, 'utf16be');
});
test('readStream - autoguessEncoding', async () => {
const resource = URI.file(join(testDir, 'some_cp1252.txt'));
const result = await service.readStream(resource, { autoGuessEncoding: true });
assert.equal(result.encoding, 'windows1252');
});
test('readStream - FILE_IS_BINARY', async () => {
const resource = URI.file(join(testDir, 'binary.txt'));
@@ -586,4 +593,21 @@ suite('Files - TextFileService i/o', () => {
const result = await service.readStream(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true });
assert.equal(result.name, 'small.txt');
});
test('read - FILE_IS_BINARY', async () => {
const resource = URI.file(join(testDir, 'binary.txt'));
let error: TextFileOperationError | undefined = undefined;
try {
await service.read(resource, { acceptTextOnly: true });
} catch (err) {
error = err;
}
assert.ok(error);
assert.equal(error!.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY);
const result = await service.read(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true });
assert.equal(result.name, 'small.txt');
});
});

View File

@@ -6,19 +6,19 @@
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Registry } from 'vs/platform/registry/common/platform';
import * as errors from 'vs/base/common/errors';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { ColorThemeData } from './colorThemeData';
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore';
import { ColorThemeStore } from 'vs/workbench/services/themes/common/colorThemeStore';
import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
import { removeClasses, addClasses } from 'vs/base/browser/dom';
@@ -64,10 +64,6 @@ function validateThemeId(theme: string): string {
return theme;
}
export interface IColorCustomizations {
[colorIdOrThemeSettingsId: string]: string | IColorCustomizations;
}
export class WorkbenchThemeService implements IWorkbenchThemeService {
_serviceBrand: any;

View File

@@ -6,8 +6,8 @@
import { basename } from 'vs/base/common/path';
import * as Json from 'vs/base/common/json';
import { Color } from 'vs/base/common/color';
import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { convertSettings } from 'vs/workbench/services/themes/browser/themeCompatibility';
import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility';
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
@@ -15,7 +15,6 @@ import * as resources from 'vs/base/common/resources';
import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { ThemeType } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IColorCustomizations } from 'vs/workbench/services/themes/browser/workbenchThemeService';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';

View File

@@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types';
import * as resources from 'vs/base/common/resources';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ColorThemeData } from 'vs/workbench/services/themes/browser/colorThemeData';
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';

View File

@@ -64,6 +64,10 @@ export interface IWorkbenchThemeService extends IThemeService {
onDidFileIconThemeChange: Event<IFileIconTheme>;
}
export interface IColorCustomizations {
[colorIdOrThemeSettingsId: string]: string | IColorCustomizations;
}
export interface ITokenColorCustomizations {
comments?: string | ITokenColorizationSetting;
strings?: string | ITokenColorizationSetting;

View File

@@ -106,19 +106,27 @@ suite('Notifications', () => {
assert.equal(item6.actions.primary!.length, 1);
// Links
let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](https://link2.com) and [Invalid Link3](ftp://link3.com)' })!;
let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!;
const links = item7.message.links;
assert.equal(links.length, 2);
assert.equal(links.length, 3);
assert.equal(links[0].name, 'Link 1');
assert.equal(links[0].href, 'http://link1.com');
assert.equal(links[0].title, 'http://link1.com');
assert.equal(links[0].length, '[Link 1](http://link1.com)'.length);
assert.equal(links[0].offset, 'Unable to '.length);
assert.equal(links[1].name, 'Link 2');
assert.equal(links[1].href, 'https://link2.com');
assert.equal(links[1].length, '[Link 2](https://link2.com)'.length);
assert.equal(links[1].href, 'command:open.me');
assert.equal(links[1].title, 'Open This');
assert.equal(links[1].length, '[Link 2](command:open.me "Open This")'.length);
assert.equal(links[1].offset, 'Unable to [Link 1](http://link1.com) open '.length);
assert.equal(links[2].name, 'Link 3');
assert.equal(links[2].href, 'command:without.title');
assert.equal(links[2].title, 'Click to execute command \'without.title\'');
assert.equal(links[2].length, '[Link 3](command:without.title)'.length);
assert.equal(links[2].offset, 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and '.length);
});
test('Model', () => {

View File

@@ -200,7 +200,8 @@ export class TestTextFileService extends BrowserTextFileService {
@IContextKeyService contextKeyService: IContextKeyService,
@IDialogService dialogService: IDialogService,
@IFileDialogService fileDialogService: IFileDialogService,
@IEditorService editorService: IEditorService
@IEditorService editorService: IEditorService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
) {
super(
contextService,
@@ -219,7 +220,8 @@ export class TestTextFileService extends BrowserTextFileService {
contextKeyService,
dialogService,
fileDialogService,
editorService
editorService,
textResourceConfigurationService
);
}