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

@@ -2,5 +2,5 @@
setlocal
set VSCODE_DEV=
set ELECTRON_RUN_AS_NODE=1
call "%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %*
"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %*
endlocal

View File

@@ -18,15 +18,20 @@ export const UTF16be_BOM = [0xFE, 0xFF];
export const UTF16le_BOM = [0xFF, 0xFE];
export const UTF8_BOM = [0xEF, 0xBB, 0xBF];
const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not
const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding, small number of bytes are enough
const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing
export interface IDecodeStreamOptions {
guessEncoding?: boolean;
guessEncoding: boolean;
minBytesRequiredForDetection?: number;
overwriteEncoding?(detectedEncoding: string | null): string;
overwriteEncoding(detectedEncoding: string | null): string;
}
export interface IDecodeStreamResult {
detected: IDetectedEncodingResult;
stream: NodeJS.ReadableStream;
detected: IDetectedEncodingResult;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
@@ -34,78 +39,82 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN;
}
if (!options.overwriteEncoding) {
options.overwriteEncoding = detected => detected || UTF8;
}
return new Promise<IDecodeStreamResult>((resolve, reject) => {
const writer = new class extends Writable {
private decodeStream: NodeJS.ReadWriteStream;
private decodeStreamConstruction: Promise<void>;
private buffer: Buffer[] = [];
private decodeStreamPromise: Promise<void>;
private bufferedChunks: Buffer[] = [];
private bytesBuffered = 0;
_write(chunk: any, encoding: string, callback: Function): void {
_write(chunk: Buffer, encoding: string, callback: (error: Error | null) => void): void {
if (!Buffer.isBuffer(chunk)) {
callback(new Error('data must be a buffer'));
return callback(new Error('toDecodeStream(): data must be a buffer'));
}
// if the decode stream is ready, we just write directly
if (this.decodeStream) {
this.decodeStream.write(chunk, callback); // just a forwarder now
this.decodeStream.write(chunk, callback);
return;
}
this.buffer.push(chunk);
this.bytesBuffered += chunk.length;
// otherwise we need to buffer the data until the stream is ready
this.bufferedChunks.push(chunk);
this.bytesBuffered += chunk.byteLength;
// waiting for the decoder to be ready
if (this.decodeStreamConstruction) {
this.decodeStreamConstruction.then(() => callback(), err => callback(err));
if (this.decodeStreamPromise) {
this.decodeStreamPromise.then(() => callback(null), error => callback(error));
}
// buffered enough data, create stream and forward data
// buffered enough data for encoding detection, create stream and forward data
else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) {
this._startDecodeStream(callback);
}
// only buffering
// only buffering until enough data for encoding detection is there
else {
callback();
callback(null);
}
}
_startDecodeStream(callback: Function): void {
this.decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this.buffer),
_startDecodeStream(callback: (error: Error | null) => void): void {
// detect encoding from buffer
this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this.bufferedChunks),
bytesRead: this.bytesBuffered
}, options.guessEncoding)).then(detected => {
if (options.overwriteEncoding) {
detected.encoding = options.overwriteEncoding(detected.encoding);
}
// ensure to respect overwrite of encoding
detected.encoding = options.overwriteEncoding(detected.encoding);
// decode and write buffer
this.decodeStream = decodeStream(detected.encoding);
this.decodeStream.write(Buffer.concat(this.bufferedChunks), callback);
this.bufferedChunks.length = 0;
for (const buffer of this.buffer) {
this.decodeStream.write(buffer);
}
callback();
// signal to the outside our detected encoding
// and final decoder stream
resolve({ detected, stream: this.decodeStream });
}, err => {
this.emit('error', err);
callback(err);
}, error => {
this.emit('error', error);
callback(error);
});
}
_final(callback: (err?: any) => any) {
_final(callback: (error: Error | null) => void) {
// normal finish
if (this.decodeStream) {
this.decodeStream.end(callback);
}
// we were still waiting for data...
// we were still waiting for data to do the encoding
// detection. thus, wrap up starting the stream even
// without all the data to get things going
else {
this._startDecodeStream(() => this.decodeStream.end(callback));
}
@@ -149,7 +158,7 @@ function toNodeEncoding(enc: string | null): string {
}
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null {
if (!buffer || bytesRead < 2) {
if (!buffer || bytesRead < UTF16be_BOM.length) {
return null;
}
@@ -166,7 +175,7 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null,
return UTF16le;
}
if (bytesRead < 3) {
if (bytesRead < UTF8_BOM.length) {
return null;
}
@@ -256,10 +265,6 @@ export function toCanonicalName(enc: string): string {
}
}
const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not
const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding, small number of bytes are enough
const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing
export interface IDetectedEncodingResult {
encoding: string | null;
seemsBinary: boolean;

View File

@@ -16,7 +16,9 @@ export function registerContextMenuListener(): void {
y: options ? options.y : undefined,
positioningItem: options ? options.positioningItem : undefined,
callback: () => {
event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId);
if (menu) {
event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId);
}
}
});
});

View File

@@ -126,7 +126,8 @@ const enum ProtocolMessageType {
Regular = 1,
Control = 2,
Ack = 3,
KeepAlive = 4
KeepAlive = 4,
Disconnect = 5
}
export const enum ProtocolConstants {
@@ -373,6 +374,10 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
return this._socket;
}
sendDisconnect(): void {
// Nothing to do...
}
send(buffer: VSBuffer): void {
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.Regular, 0, 0, buffer));
}
@@ -393,6 +398,7 @@ export class Client<TContext = string> extends IPCClient<TContext> {
dispose(): void {
super.dispose();
const socket = this.protocol.getSocket();
this.protocol.sendDisconnect();
this.protocol.dispose();
socket.end();
}
@@ -572,7 +578,6 @@ export class PersistentProtocol {
this._socketDisposables.push(this._socketReader);
this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire()));
this._socketDisposables.push(this._socket.onEnd(() => this._onClose.fire()));
if (initialChunk) {
this._socketReader.acceptChunk(initialChunk);
}
@@ -601,6 +606,12 @@ export class PersistentProtocol {
this._socketDisposables = dispose(this._socketDisposables);
}
sendDisconnect(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
this._socketWriter.flush();
}
private _sendKeepAliveCheck(): void {
if (this._outgoingKeepAliveTimeout) {
// there will be a check in the near future
@@ -659,7 +670,6 @@ export class PersistentProtocol {
this._socketDisposables.push(this._socketReader);
this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire()));
this._socketDisposables.push(this._socket.onEnd(() => this._onClose.fire()));
this._socketReader.acceptChunk(initialDataChunk);
}
@@ -703,6 +713,8 @@ export class PersistentProtocol {
}
} else if (msg.type === ProtocolMessageType.Control) {
this._onControlMessage.fire(msg.data);
} else if (msg.type === ProtocolMessageType.Disconnect) {
this._onClose.fire();
}
}

View File

@@ -240,7 +240,7 @@ suite('Encoding', () => {
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4 });
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -260,7 +260,7 @@ suite('Encoding', () => {
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64 });
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -277,7 +277,7 @@ suite('Encoding', () => {
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512 });
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -292,7 +292,7 @@ suite('Encoding', () => {
let path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64 });
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
assert.equal(detected.encoding, 'utf16be');
assert.equal(detected.seemsBinary, false);
@@ -307,7 +307,7 @@ suite('Encoding', () => {
let path = getPathFromAmdModule(require, './fixtures/empty.txt');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, {});
let { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
let expected = await readAndDecodeFromDisk(path, detected.encoding);
let actual = await readAllAsString(stream);

View File

@@ -40,7 +40,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
import { Button } from 'vs/base/browser/ui/button/button';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService';
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
@@ -940,15 +940,24 @@ export class IssueReporter extends Disposable {
</table>`;
systemInfo.remoteData.forEach(remote => {
renderedData += `
<hr>
<table>
<tr><td>Remote</td><td>${remote.hostName}</td></tr>
<tr><td>OS</td><td>${remote.machineInfo.os}</td></tr>
<tr><td>CPUs</td><td>${remote.machineInfo.cpus}</td></tr>
<tr><td>Memory (System)</td><td>${remote.machineInfo.memory}</td></tr>
<tr><td>VM</td><td>${remote.machineInfo.vmHint}</td></tr>
</table>`;
if (isRemoteDiagnosticError(remote)) {
renderedData += `
<hr>
<table>
<tr><td>Remote</td><td>${remote.hostName}</td></tr>
<tr><td></td><td>${remote.errorMessage}</td></tr>
</table>`;
} else {
renderedData += `
<hr>
<table>
<tr><td>Remote</td><td>${remote.hostName}</td></tr>
<tr><td>OS</td><td>${remote.machineInfo.os}</td></tr>
<tr><td>CPUs</td><td>${remote.machineInfo.cpus}</td></tr>
<tr><td>Memory (System)</td><td>${remote.machineInfo.memory}</td></tr>
<tr><td>VM</td><td>${remote.machineInfo.vmHint}</td></tr>
</table>`;
}
});
target.innerHTML = renderedData;

View File

@@ -5,7 +5,7 @@
import { assign } from 'vs/base/common/objects';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService';
export interface IssueReporterData {
issueType: IssueType;
@@ -76,7 +76,8 @@ ${this.getInfos()}
private getRemoteOSes(): string {
if (this._data.systemInfo && this._data.systemInfo.remoteData.length) {
return this._data.systemInfo.remoteData.map(remote => `Remote OS version: ${remote.machineInfo.os}`).join('\n') + '\n';
return this._data.systemInfo.remoteData
.map(remote => isRemoteDiagnosticError(remote) ? remote.errorMessage : `Remote OS version: ${remote.machineInfo.os}`).join('\n') + '\n';
}
return '';
@@ -169,7 +170,10 @@ ${this.getInfos()}
|VM|${this._data.systemInfo.vmHint}|`;
this._data.systemInfo.remoteData.forEach(remote => {
md += `
if (isRemoteDiagnosticError(remote)) {
md += `\n\n${remote.errorMessage}`;
} else {
md += `
|Item|Value|
|---|---|
@@ -178,6 +182,7 @@ ${this.getInfos()}
|CPUs|${remote.machineInfo.cpus}|
|Memory (System)|${remote.machineInfo.memory}|
|VM|${remote.machineInfo.vmHint}|`;
}
});
}

View File

@@ -3,9 +3,7 @@
<html>
<head>
<meta charset="utf-8" />
<!-- // {{SQL CARBON EDIT}}
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote:;">
</head>
<body class="vs-dark" aria-label="">
</body>

View File

@@ -1190,46 +1190,62 @@ export class WindowsManager implements IWindowsMainService {
}
}
// Make sure we are not asked to open a workspace or folder that is already opened
if (cliArgs.length && cliArgs.some(path => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, URI.file(path)))) {
cliArgs = [];
if (!Array.isArray(extensionDevelopmentPath)) {
extensionDevelopmentPath = [extensionDevelopmentPath];
}
if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
folderUris = [];
let authority = '';
for (let p of extensionDevelopmentPath) {
if (p.match(/^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/)) {
const url = URI.parse(p);
if (url.scheme === Schemas.vscodeRemote) {
if (authority) {
if (url.authority !== authority) {
this.logService.error('more than one extension development path authority');
}
} else {
authority = url.authority;
}
}
}
}
if (fileUris.length && fileUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
fileUris = [];
}
// Make sure that we do not try to open:
// - a workspace or folder that is already opened
// - a workspace or file that has a different authority as the extension development.
cliArgs = cliArgs.filter(path => {
const uri = URI.file(path);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, uri)) {
return false;
}
return uri.authority === authority;
});
folderUris = folderUris.filter(uri => {
const u = this.argToUri(uri);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) {
return false;
}
return u ? u.authority === authority : false;
});
fileUris = fileUris.filter(uri => {
const u = this.argToUri(uri);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) {
return false;
}
return u ? u.authority === authority : false;
});
openConfig.cli._ = cliArgs;
openConfig.cli['folder-uri'] = folderUris;
openConfig.cli['file-uri'] = fileUris;
if (Array.isArray(extensionDevelopmentPath)) {
let authority: string | undefined = undefined;
for (let p of extensionDevelopmentPath) {
const match = p.match(/^vscode-remote:\/\/([^\/]+)/);
if (match) {
const auth = URI.parse(p).authority;
if (authority) {
if (auth !== authority) {
console.log('more than one authority');
}
} else {
authority = auth;
}
}
}
// if there are no files or folders cli args left, use the "remote" cli argument
if (!cliArgs.length && !folderUris.length && !fileUris.length) {
if (authority) {
openConfig.cli['remote'] = authority;
}
} else {
const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/);
if (match) {
openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority;
openConfig.cli.remote = authority;
}
}
@@ -1581,7 +1597,8 @@ export class WindowsManager implements IWindowsMainService {
cli = { ...cli, remote };
}
const forceReuseWindow = options && options.reuseWindow;
return this.open({ context, cli, forceEmpty: true, forceReuseWindow });
const forceNewWindow = !forceReuseWindow;
return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow });
}
openNewTabbedWindow(context: OpenContext): ICodeWindow[] {

View File

@@ -55,7 +55,7 @@ export class OpenerService implements IOpenerService {
if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) {
// open http or default mail application
dom.windowOpenNoOpener(resource.toString(true));
dom.windowOpenNoOpener(encodeURI(resource.toString(true)));
return Promise.resolve(true);
} else if (equalsIgnoreCase(scheme, Schemas.command)) {

View File

@@ -1288,6 +1288,7 @@ export interface CommentThread2 {
onDidChangeRange: Event<IRange>;
onDidChangeLabel: Event<string>;
onDidChangeCollasibleState: Event<CommentThreadCollapsibleState | undefined>;
isDisposed: boolean;
}
/**
@@ -1312,6 +1313,7 @@ export interface CommentThread {
comments: Comment[] | undefined;
collapsibleState?: CommentThreadCollapsibleState;
reply?: Command;
isDisposed?: boolean;
}
/**

View File

@@ -399,7 +399,7 @@ export abstract class BaseEditorSimpleWorker {
// ---- BEGIN minimal edits ---------------------------------------------------------------
private static readonly _diffLimit = 10000;
private static readonly _diffLimit = 100000;
public computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise<TextEdit[]> {
const model = this._getModel(modelUrl);
@@ -432,7 +432,7 @@ export abstract class BaseEditorSimpleWorker {
}
const original = model.getValueInRange(range);
text = text!.replace(/\r\n|\n|\r/g, model.eol);
text = text.replace(/\r\n|\n|\r/g, model.eol);
if (original === text) {
// noop

View File

@@ -21,6 +21,8 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { regExpFlags } from 'vs/base/common/strings';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { StopWatch } from 'vs/base/common/stopwatch';
/**
* Stop syncing a model to the worker if it was not needed for 1 min.
@@ -48,14 +50,16 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
private readonly _modelService: IModelService;
private readonly _workerManager: WorkerManager;
private readonly _logService: ILogService;
constructor(
@IModelService modelService: IModelService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@ILogService logService: ILogService
) {
super();
this._modelService = modelService;
this._workerManager = this._register(new WorkerManager(this._modelService));
this._logService = logService;
// todo@joh make sure this happens only once
this._register(modes.LinkProviderRegistry.register('*', {
@@ -96,7 +100,10 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
if (!canSyncModel(this._modelService, resource)) {
return Promise.resolve(edits); // File too large
}
return this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits));
const sw = StopWatch.create(true);
const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits));
result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
return result;
} else {
return Promise.resolve(undefined);

View File

@@ -145,8 +145,6 @@ export module StaticServices {
export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o)));
export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o)));
export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl());
export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o)));
@@ -157,6 +155,8 @@ export module StaticServices {
export const logService = define(ILogService, () => new NullLogService());
export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o)));
export const suggestMemoryService = define(ISuggestMemoryService, (o) => new SuggestMemoryService(storageService.get(o), configurationService.get(o)));
}

View File

@@ -22,7 +22,7 @@ export interface SystemInfo extends IMachineInfo {
processArgs: string;
gpuStatus: any;
screenReader: string;
remoteData: IRemoteDiagnosticInfo[];
remoteData: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[];
load?: string;
}

View File

@@ -11,14 +11,8 @@ import product from 'vs/platform/product/node/product';
export function isUIExtension(manifest: IExtensionManifest, uiContributions: string[], configurationService: IConfigurationService): boolean {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const { ui, workspace } = configurationService.getValue<{ ui: string[], workspace: string[] }>('extensions.extensionKind') || { ui: [], workspace: [] };
if (isNonEmptyArray(workspace) && workspace.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return false;
}
if (isNonEmptyArray(ui) && ui.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
switch (manifest.extensionKind) {
const extensionKind = getExtensionKind(manifest, configurationService);
switch (extensionKind) {
case 'ui': return true;
case 'workspace': return false;
default: {
@@ -38,3 +32,14 @@ export function isUIExtension(manifest: IExtensionManifest, uiContributions: str
}
}
}
function getExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): string | undefined {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const configuredExtensionKinds = configurationService.getValue<{ [key: string]: string }>('remote.extensionKind') || {};
for (const id of Object.keys(configuredExtensionKinds)) {
if (areSameExtensions({ id: extensionId }, { id })) {
return configuredExtensionKinds[id];
}
}
return manifest.extensionKind;
}

View File

@@ -34,8 +34,9 @@ export function resolveCommonProperties(commit: string | undefined, version: str
result['common.nodePlatform'] = process.platform;
// __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.nodeArch'] = process.arch;
// __GDPR__COMMON__ "common.product" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
// {{SQL CARBON EDIT}}
result['common.product'] = product.nameShort || 'desktop';
result['common.application.name'] = product.nameLong;
// dynamic properties which value differs on each call

View File

@@ -1371,4 +1371,43 @@ declare module 'vscode' {
group?: string;
}
//#endregion
//#region Workspace URI Ben
export namespace workspace {
/**
* The location of the workspace file, for example:
*
* `file:///Users/name/Development/myProject.code-workspace`
*
* or
*
* `untitled:1555503116870`
*
* for a workspace that is untitled and not yet saved.
*
* Depending on the workspace that is opened, the value will be:
* * `undefined` when no workspace or a single folder is opened
* * the path of the workspace file as `Uri` otherwise. if the workspace
* is untitled, the returned URI will use the `untitled:` scheme
*
* The location can e.g. be used with the `vscode.openFolder` command to
* open the workspace again after it has been closed.
*
* **Example:**
* ```typescript
* vscode.commands.executeCommand('vscode.openFolder', uriOfWorkspace);
* ```
*
* **Note:** it is not advised to use `workspace.workspaceFile` to write
* configuration data into the file. You can use `workspace.getConfiguration().update()`
* for that purpose which will work both when a single folder is opened as
* well as an untitled or saved workspace.
*/
export const workspaceFile: Uri | undefined;
}
//#endregion
}

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