Compare commits

..

2 commits

102 changed files with 1226 additions and 2583 deletions

View file

@ -37,6 +37,12 @@ steps:
git config user.name "VSCode"
displayName: Prepare tooling
- script: |
set -e
sudo xcode-select -s /Applications/Xcode_12.2.app
displayName: Switch to Xcode 12
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64'))
- script: |
set -e
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
@ -78,6 +84,7 @@ steps:
set -e
export npm_config_arch=$(VSCODE_ARCH)
export npm_config_node_gyp=$(which node-gyp)
export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
for i in {1..3}; do # try 3 times, for Terrapin
yarn --frozen-lockfile && break

View file

@ -6,7 +6,6 @@
import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode';
import { resolvePath, joinPath } from './requests';
export function getCustomDataSource(toDispose: Disposable[]) {
let pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
let pathsInExtensions = getCustomDataPathsFromAllExtensions();
@ -15,7 +14,7 @@ export function getCustomDataSource(toDispose: Disposable[]) {
toDispose.push(extensions.onDidChange(_ => {
const newPathsInExtensions = getCustomDataPathsFromAllExtensions();
if (pathsInExtensions.size !== newPathsInExtensions.size || ![...pathsInExtensions].every(path => newPathsInExtensions.has(path))) {
if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) {
pathsInExtensions = newPathsInExtensions;
onChange.fire();
}
@ -27,16 +26,9 @@ export function getCustomDataSource(toDispose: Disposable[]) {
}
}));
toDispose.push(workspace.onDidChangeTextDocument(e => {
const path = e.document.uri.toString();
if (pathsInExtensions.has(path) || pathsInWorkspace.has(path)) {
onChange.fire();
}
}));
return {
get uris() {
return [...pathsInWorkspace].concat([...pathsInExtensions]);
return pathsInWorkspace.concat(pathsInExtensions);
},
get onDidChange() {
return onChange.event;
@ -44,31 +36,21 @@ export function getCustomDataSource(toDispose: Disposable[]) {
};
}
function isURI(uriOrPath: string) {
return /^(?<scheme>\w[\w\d+.-]*):/.test(uriOrPath);
}
function getCustomDataPathsInAllWorkspaces(): Set<string> {
function getCustomDataPathsInAllWorkspaces(): string[] {
const workspaceFolders = workspace.workspaceFolders;
const dataPaths = new Set<string>();
const dataPaths: string[] = [];
if (!workspaceFolders) {
return dataPaths;
}
const collect = (uriOrPaths: string[] | undefined, rootFolder: Uri) => {
if (Array.isArray(uriOrPaths)) {
for (const uriOrPath of uriOrPaths) {
if (typeof uriOrPath === 'string') {
if (!isURI(uriOrPath)) {
// path in the workspace
dataPaths.add(resolvePath(rootFolder, uriOrPath).toString());
} else {
// external uri
dataPaths.add(uriOrPath);
}
const collect = (paths: string[] | undefined, rootFolder: Uri) => {
if (Array.isArray(paths)) {
for (const path of paths) {
if (typeof path === 'string') {
dataPaths.push(resolvePath(rootFolder, path).toString());
}
}
}
@ -92,20 +74,13 @@ function getCustomDataPathsInAllWorkspaces(): Set<string> {
return dataPaths;
}
function getCustomDataPathsFromAllExtensions(): Set<string> {
const dataPaths = new Set<string>();
function getCustomDataPathsFromAllExtensions(): string[] {
const dataPaths: string[] = [];
for (const extension of extensions.all) {
const customData = extension.packageJSON?.contributes?.html?.customData;
if (Array.isArray(customData)) {
for (const uriOrPath of customData) {
if (!isURI(uriOrPath)) {
// relative path in an extension
dataPaths.add(joinPath(extension.extensionUri, uriOrPath).toString());
} else {
// external uri
dataPaths.add(uriOrPath);
}
for (const rp of customData) {
dataPaths.push(joinPath(extension.extensionUri, rp).toString());
}
}
}

View file

@ -16,7 +16,7 @@ import {
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient
} from 'vscode-languageclient';
import { activateTagClosing } from './tagClosing';
import { RequestService, serveFileSystemRequests } from './requests';
import { RequestService } from './requests';
import { getCustomDataSource } from './customData';
namespace CustomDataChangedNotification {
@ -120,8 +120,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
toDispose.push(disposable);
client.onReady().then(() => {
toDispose.push(serveFileSystemRequests(client, runtime));
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
customDataSource.onDidChange(() => {
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri, workspace, Disposable } from 'vscode';
import { Uri, workspace } from 'vscode';
import { RequestType, CommonLanguageClient } from 'vscode-languageclient';
import { Runtime } from './htmlClient';
@ -18,9 +18,8 @@ export namespace FsReadDirRequest {
export const type: RequestType<string, [string, FileType][], any> = new RequestType('fs/readDir');
}
export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable {
const disposables = [];
disposables.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) {
client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
const uri = Uri.parse(param.uri);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.getContent(param.uri);
@ -28,22 +27,21 @@ export function serveFileSystemRequests(client: CommonLanguageClient, runtime: R
return workspace.fs.readFile(uri).then(buffer => {
return new runtime.TextDecoder(param.encoding).decode(buffer);
});
}));
disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => {
});
client.onRequest(FsReadDirRequest.type, (uriString: string) => {
const uri = Uri.parse(uriString);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.readDirectory(uriString);
}
return workspace.fs.readDirectory(uri);
}));
disposables.push(client.onRequest(FsStatRequest.type, (uriString: string) => {
});
client.onRequest(FsStatRequest.type, (uriString: string) => {
const uri = Uri.parse(uriString);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.stat(uriString);
}
return workspace.fs.stat(uri);
}));
return Disposable.from(...disposables);
});
}
export enum FileType {

View file

@ -87,12 +87,7 @@
"language": "markdown",
"path": "./snippets/markdown.code-snippets"
}
],
"configurationDefaults": {
"[markdown]": {
"editor.unicodeHighlight.ambiguousCharacters": false
}
}
]
},
"scripts": {
"update-grammar": "node ../node_modules/vscode-grammar-updater/bin microsoft/vscode-markdown-tm-grammar syntaxes/markdown.tmLanguage ./syntaxes/markdown.tmLanguage.json"

View file

@ -73,7 +73,7 @@
"keytar": "7.2.0",
"minimist": "^1.2.5",
"native-is-elevated": "0.4.3",
"native-keymap": "3.0.2",
"native-keymap": "3.0.1",
"native-watchdog": "1.3.0",
"node-pty": "0.11.0-beta11",
"spdlog": "^0.13.0",

View file

@ -43,14 +43,6 @@ export class VSBuffer {
}
}
static fromByteArray(source: number[]): VSBuffer {
const result = VSBuffer.alloc(source.length);
for (let i = 0, len = source.length; i < len; i++) {
result.buffer[i] = source[i];
}
return result;
}
static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
if (typeof totalLength === 'undefined') {
totalLength = 0;
@ -78,12 +70,6 @@ export class VSBuffer {
this.byteLength = this.buffer.byteLength;
}
clone(): VSBuffer {
const result = VSBuffer.alloc(this.byteLength);
result.set(this);
return result;
}
toString(): string {
if (hasBuffer) {
return this.buffer.toString();
@ -104,20 +90,11 @@ export class VSBuffer {
set(array: VSBuffer, offset?: number): void;
set(array: Uint8Array, offset?: number): void;
set(array: ArrayBuffer, offset?: number): void;
set(array: ArrayBufferView, offset?: number): void;
set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void;
set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void {
set(array: VSBuffer | Uint8Array, offset?: number): void {
if (array instanceof VSBuffer) {
this.buffer.set(array.buffer, offset);
} else if (array instanceof Uint8Array) {
this.buffer.set(array, offset);
} else if (array instanceof ArrayBuffer) {
this.buffer.set(new Uint8Array(array), offset);
} else if (ArrayBuffer.isView(array)) {
this.buffer.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), offset);
} else {
throw new Error(`Unkown argument 'array'`);
this.buffer.set(array, offset);
}
}

View file

@ -11,7 +11,6 @@ let _isLinux = false;
let _isLinuxSnap = false;
let _isNative = false;
let _isWeb = false;
let _isElectron = false;
let _isIOS = false;
let _locale: string | undefined = undefined;
let _language: string = LANGUAGE_DEFAULT;
@ -62,8 +61,7 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== '
nodeProcess = process;
}
const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string';
const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer';
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
interface INavigator {
@ -91,7 +89,6 @@ else if (typeof nodeProcess === 'object') {
_isMacintosh = (nodeProcess.platform === 'darwin');
_isLinux = (nodeProcess.platform === 'linux');
_isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
_isElectron = isElectronProcess;
_locale = LANGUAGE_DEFAULT;
_language = LANGUAGE_DEFAULT;
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
@ -143,7 +140,6 @@ export const isMacintosh = _isMacintosh;
export const isLinux = _isLinux;
export const isLinuxSnap = _isLinuxSnap;
export const isNative = _isNative;
export const isElectron = _isElectron;
export const isWeb = _isWeb;
export const isIOS = _isIOS;
export const platform = _platform;

View file

@ -287,7 +287,7 @@ export function NotImplementedProxy<T>(name: string): { new(): T } {
};
}
export function assertNever(value: never, message = 'Unreachable'): never {
export function assertNever(value: never, message = 'Unreachable') {
throw new Error(message);
}

View file

@ -8,89 +8,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IIPCLogger, IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
export const enum SocketDiagnosticsEventType {
Created = 'created',
Read = 'read',
Write = 'write',
Open = 'open',
Error = 'error',
Close = 'close',
BrowserWebSocketBlobReceived = 'browserWebSocketBlobReceived',
NodeEndReceived = 'nodeEndReceived',
NodeEndSent = 'nodeEndSent',
NodeDrainBegin = 'nodeDrainBegin',
NodeDrainEnd = 'nodeDrainEnd',
zlibInflateError = 'zlibInflateError',
zlibInflateData = 'zlibInflateData',
zlibInflateInitialWrite = 'zlibInflateInitialWrite',
zlibInflateInitialFlushFired = 'zlibInflateInitialFlushFired',
zlibInflateWrite = 'zlibInflateWrite',
zlibInflateFlushFired = 'zlibInflateFlushFired',
zlibDeflateError = 'zlibDeflateError',
zlibDeflateData = 'zlibDeflateData',
zlibDeflateWrite = 'zlibDeflateWrite',
zlibDeflateFlushFired = 'zlibDeflateFlushFired',
WebSocketNodeSocketWrite = 'webSocketNodeSocketWrite',
WebSocketNodeSocketPeekedHeader = 'webSocketNodeSocketPeekedHeader',
WebSocketNodeSocketReadHeader = 'webSocketNodeSocketReadHeader',
WebSocketNodeSocketReadData = 'webSocketNodeSocketReadData',
WebSocketNodeSocketUnmaskedData = 'webSocketNodeSocketUnmaskedData',
WebSocketNodeSocketDrainBegin = 'webSocketNodeSocketDrainBegin',
WebSocketNodeSocketDrainEnd = 'webSocketNodeSocketDrainEnd',
ProtocolHeaderRead = 'protocolHeaderRead',
ProtocolMessageRead = 'protocolMessageRead',
ProtocolHeaderWrite = 'protocolHeaderWrite',
ProtocolMessageWrite = 'protocolMessageWrite',
ProtocolWrite = 'protocolWrite',
}
export namespace SocketDiagnostics {
export const enableDiagnostics = false;
export interface IRecord {
timestamp: number;
id: string;
label: string;
type: SocketDiagnosticsEventType;
buff?: VSBuffer;
data?: any;
}
export const records: IRecord[] = [];
const socketIds = new WeakMap<any, string>();
let lastUsedSocketId = 0;
function getSocketId(nativeObject: any, label: string): string {
if (!socketIds.has(nativeObject)) {
const id = String(++lastUsedSocketId);
socketIds.set(nativeObject, id);
}
return socketIds.get(nativeObject)!;
}
export function traceSocketEvent(nativeObject: any, socketDebugLabel: string, type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
if (!enableDiagnostics) {
return;
}
const id = getSocketId(nativeObject, socketDebugLabel);
if (data instanceof VSBuffer || data instanceof Uint8Array || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
const copiedData = VSBuffer.alloc(data.byteLength);
copiedData.set(data);
records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, buff: copiedData });
} else {
// data is a custom object
records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, data: data });
}
}
}
export const enum SocketCloseEventType {
NodeSocketCloseEvent = 0,
WebSocketCloseEvent = 1
@ -143,8 +60,6 @@ export interface ISocket extends IDisposable {
write(buffer: VSBuffer): void;
end(): void;
drain(): Promise<void>;
traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void;
}
let emptyBuffer: VSBuffer | null = null;
@ -253,23 +168,9 @@ const enum ProtocolMessageType {
Regular = 1,
Control = 2,
Ack = 3,
KeepAlive = 4,
Disconnect = 5,
ReplayRequest = 6,
Pause = 7,
Resume = 8
}
function protocolMessageTypeToString(messageType: ProtocolMessageType) {
switch (messageType) {
case ProtocolMessageType.None: return 'None';
case ProtocolMessageType.Regular: return 'Regular';
case ProtocolMessageType.Control: return 'Control';
case ProtocolMessageType.Ack: return 'Ack';
case ProtocolMessageType.Disconnect: return 'Disconnect';
case ProtocolMessageType.ReplayRequest: return 'ReplayRequest';
case ProtocolMessageType.Pause: return 'PauseWriting';
case ProtocolMessageType.Resume: return 'ResumeWriting';
}
ReplayRequest = 6
}
export const enum ProtocolConstants {
@ -279,11 +180,17 @@ export const enum ProtocolConstants {
*/
AcknowledgeTime = 2000, // 2 seconds
/**
* If there is a sent message that has been unacknowledged for 20 seconds,
* and we didn't see any incoming server data in the past 20 seconds,
* then consider the connection has timed out.
* If there is a message that has been unacknowledged for 10 seconds, consider the connection closed...
*/
TimeoutTime = 20000, // 20 seconds
AcknowledgeTimeoutTime = 20000, // 20 seconds
/**
* Send at least a message every 5s for keep alive reasons.
*/
KeepAliveTime = 5000, // 5 seconds
/**
* If there is no message received for 10 seconds, consider the connection closed...
*/
KeepAliveTimeoutTime = 20000, // 20 seconds
/**
* If there is no reconnection within this time-frame, consider the connection permanently closed...
*/
@ -361,9 +268,6 @@ class ProtocolReader extends Disposable {
this._state.messageType = buff.readUInt8(0);
this._state.id = buff.readUInt32BE(1);
this._state.ack = buff.readUInt32BE(5);
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderRead, { messageType: protocolMessageTypeToString(this._state.messageType), id: this._state.id, ack: this._state.ack, messageSize: this._state.readLen });
} else {
// buff is the body
const messageType = this._state.messageType;
@ -377,8 +281,6 @@ class ProtocolReader extends Disposable {
this._state.id = 0;
this._state.ack = 0;
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageRead, buff);
this._onMessage.fire(new ProtocolMessage(messageType, id, ack, buff));
if (this._isDisposed) {
@ -402,7 +304,6 @@ class ProtocolReader extends Disposable {
class ProtocolWriter {
private _isDisposed: boolean;
private _isPaused: boolean;
private readonly _socket: ISocket;
private _data: VSBuffer[];
private _totalLength: number;
@ -410,7 +311,6 @@ class ProtocolWriter {
constructor(socket: ISocket) {
this._isDisposed = false;
this._isPaused = false;
this._socket = socket;
this._data = [];
this._totalLength = 0;
@ -436,15 +336,6 @@ class ProtocolWriter {
this._writeNow();
}
public pause(): void {
this._isPaused = true;
}
public resume(): void {
this._isPaused = false;
this._scheduleWriting();
}
public write(msg: ProtocolMessage) {
if (this._isDisposed) {
// ignore: there could be left-over promises which complete and then
@ -458,10 +349,6 @@ class ProtocolWriter {
header.writeUInt32BE(msg.id, 1);
header.writeUInt32BE(msg.ack, 5);
header.writeUInt32BE(msg.data.byteLength, 9);
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderWrite, { messageType: protocolMessageTypeToString(msg.type), id: msg.id, ack: msg.ack, messageSize: msg.data.byteLength });
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageWrite, msg.data);
this._writeSoon(header, msg.data);
}
@ -481,31 +368,17 @@ class ProtocolWriter {
private _writeSoon(header: VSBuffer, data: VSBuffer): void {
if (this._bufferAdd(header, data)) {
this._scheduleWriting();
setTimeout(() => {
this._writeNow();
});
}
}
private _writeNowTimeout: any = null;
private _scheduleWriting(): void {
if (this._writeNowTimeout) {
return;
}
this._writeNowTimeout = setTimeout(() => {
this._writeNowTimeout = null;
this._writeNow();
});
}
private _writeNow(): void {
if (this._totalLength === 0) {
return;
}
if (this._isPaused) {
return;
}
const data = this._bufferTake();
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolWrite, { byteLength: data.byteLength });
this._socket.write(data);
this._socket.write(this._bufferTake());
}
}
@ -777,6 +650,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
private _incomingMsgLastTime: number;
private _incomingAckTimeout: any | null;
private _outgoingKeepAliveTimeout: any | null;
private _incomingKeepAliveTimeout: any | null;
private _lastReplayRequestTime: number;
private _socket: ISocket;
@ -818,6 +694,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._incomingMsgLastTime = 0;
this._incomingAckTimeout = null;
this._outgoingKeepAliveTimeout = null;
this._incomingKeepAliveTimeout = null;
this._lastReplayRequestTime = 0;
this._socketDisposables = [];
@ -831,6 +710,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
if (initialChunk) {
this._socketReader.acceptChunk(initialChunk);
}
this._sendKeepAliveCheck();
this._recvKeepAliveCheck();
}
dispose(): void {
@ -842,6 +724,14 @@ export class PersistentProtocol implements IMessagePassingProtocol {
clearTimeout(this._incomingAckTimeout);
this._incomingAckTimeout = null;
}
if (this._outgoingKeepAliveTimeout) {
clearTimeout(this._outgoingKeepAliveTimeout);
this._outgoingKeepAliveTimeout = null;
}
if (this._incomingKeepAliveTimeout) {
clearTimeout(this._incomingKeepAliveTimeout);
this._incomingKeepAliveTimeout = null;
}
this._socketDisposables = dispose(this._socketDisposables);
}
@ -855,18 +745,50 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.flush();
}
sendPause(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Pause, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
private _sendKeepAliveCheck(): void {
if (this._outgoingKeepAliveTimeout) {
// there will be a check in the near future
return;
}
const timeSinceLastOutgoingMsg = Date.now() - this._socketWriter.lastWriteTime;
if (timeSinceLastOutgoingMsg >= ProtocolConstants.KeepAliveTime) {
// sufficient time has passed since last message was written,
// and no message from our side needed to be sent in the meantime,
// so we will send a message containing only a keep alive.
const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
this._sendKeepAliveCheck();
return;
}
this._outgoingKeepAliveTimeout = setTimeout(() => {
this._outgoingKeepAliveTimeout = null;
this._sendKeepAliveCheck();
}, ProtocolConstants.KeepAliveTime - timeSinceLastOutgoingMsg + 5);
}
sendResume(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Resume, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
}
private _recvKeepAliveCheck(): void {
if (this._incomingKeepAliveTimeout) {
// there will be a check in the near future
return;
}
pauseSocketWriting() {
this._socketWriter.pause();
const timeSinceLastIncomingMsg = Date.now() - this._socketReader.lastReadTime;
if (timeSinceLastIncomingMsg >= ProtocolConstants.KeepAliveTimeoutTime) {
// It's been a long time since we received a server message
// But this might be caused by the event loop being busy and failing to read messages
if (!this._loadEstimator.hasHighLoad()) {
// Trash the socket
this._onSocketTimeout.fire(undefined);
return;
}
}
this._incomingKeepAliveTimeout = setTimeout(() => {
this._incomingKeepAliveTimeout = null;
this._recvKeepAliveCheck();
}, Math.max(ProtocolConstants.KeepAliveTimeoutTime - timeSinceLastIncomingMsg, 0) + 5);
}
public getSocket(): ISocket {
@ -907,6 +829,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
this._sendKeepAliveCheck();
this._recvKeepAliveCheck();
}
public acceptDisconnect(): void {
@ -927,59 +852,34 @@ export class PersistentProtocol implements IMessagePassingProtocol {
} while (true);
}
switch (msg.type) {
case ProtocolMessageType.None: {
// N/A
break;
}
case ProtocolMessageType.Regular: {
if (msg.id > this._incomingMsgId) {
if (msg.id !== this._incomingMsgId + 1) {
// in case we missed some messages we ask the other party to resend them
const now = Date.now();
if (now - this._lastReplayRequestTime > 10000) {
// send a replay request at most once every 10s
this._lastReplayRequestTime = now;
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer()));
}
} else {
this._incomingMsgId = msg.id;
this._incomingMsgLastTime = Date.now();
this._sendAckCheck();
this._onMessage.fire(msg.data);
if (msg.type === ProtocolMessageType.Regular) {
if (msg.id > this._incomingMsgId) {
if (msg.id !== this._incomingMsgId + 1) {
// in case we missed some messages we ask the other party to resend them
const now = Date.now();
if (now - this._lastReplayRequestTime > 10000) {
// send a replay request at most once every 10s
this._lastReplayRequestTime = now;
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer()));
}
} else {
this._incomingMsgId = msg.id;
this._incomingMsgLastTime = Date.now();
this._sendAckCheck();
this._onMessage.fire(msg.data);
}
break;
}
case ProtocolMessageType.Control: {
this._onControlMessage.fire(msg.data);
break;
}
case ProtocolMessageType.Ack: {
// nothing to do
break;
}
case ProtocolMessageType.Disconnect: {
this._onDidDispose.fire();
break;
}
case ProtocolMessageType.ReplayRequest: {
// Send again all unacknowledged messages
const toSend = this._outgoingUnackMsg.toArray();
for (let i = 0, len = toSend.length; i < len; i++) {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
break;
}
case ProtocolMessageType.Pause: {
this._socketWriter.pause();
break;
}
case ProtocolMessageType.Resume: {
this._socketWriter.resume();
break;
} else if (msg.type === ProtocolMessageType.Control) {
this._onControlMessage.fire(msg.data);
} else if (msg.type === ProtocolMessageType.Disconnect) {
this._onDidDispose.fire();
} else if (msg.type === ProtocolMessageType.ReplayRequest) {
// Send again all unacknowledged messages
const toSend = this._outgoingUnackMsg.toArray();
for (let i = 0, len = toSend.length; i < len; i++) {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
}
}
@ -1056,15 +956,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek()!;
const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime;
const timeSinceLastReceivedSomeData = Date.now() - this._socketReader.lastReadTime;
if (
timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.TimeoutTime
&& timeSinceLastReceivedSomeData >= ProtocolConstants.TimeoutTime
) {
if (timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.AcknowledgeTimeoutTime) {
// It's been a long time since our sent message was acknowledged
// and a long time since we received some data
// But this might be caused by the event loop being busy and failing to read messages
if (!this._loadEstimator.hasHighLoad()) {
// Trash the socket
@ -1076,7 +969,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._outgoingAckTimeout = setTimeout(() => {
this._outgoingAckTimeout = null;
this._recvAckCheck();
}, Math.max(ProtocolConstants.TimeoutTime - timeSinceOldestUnacknowledgedMsg, 500));
}, Math.max(ProtocolConstants.AcknowledgeTimeoutTime - timeSinceOldestUnacknowledgedMsg, 0) + 5);
}
private _sendAck(): void {
@ -1090,39 +983,3 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.write(msg);
}
}
// (() => {
// if (!SocketDiagnostics.enableDiagnostics) {
// return;
// }
// if (typeof require.__$__nodeRequire !== 'function') {
// console.log(`Can only log socket diagnostics on native platforms.`);
// return;
// }
// const type = (
// process.argv.includes('--type=renderer')
// ? 'renderer'
// : (process.argv.includes('--type=extensionHost')
// ? 'extensionHost'
// : (process.argv.some(item => item.includes('server/main'))
// ? 'server'
// : 'unknown'
// )
// )
// );
// setTimeout(() => {
// SocketDiagnostics.records.forEach(r => {
// if (r.buff) {
// r.data = Buffer.from(r.buff.buffer).toString('base64');
// r.buff = undefined;
// }
// });
// const fs = <typeof import('fs')>require.__$__nodeRequire('fs');
// const path = <typeof import('path')>require.__$__nodeRequire('path');
// const logPath = path.join(process.cwd(),`${type}-${process.pid}`);
// console.log(`dumping socket diagnostics at ${logPath}`);
// fs.writeFileSync(logPath, JSON.stringify(SocketDiagnostics.records));
// }, 20000);
// })();

View file

@ -14,25 +14,17 @@ import { join } from 'vs/base/common/path';
import { Platform, platform } from 'vs/base/common/platform';
import { generateUuid } from 'vs/base/common/uuid';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import * as zlib from 'zlib';
export class NodeSocket implements ISocket {
public readonly debugLabel: string;
public readonly socket: Socket;
private readonly _errorListener: (err: any) => void;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data);
}
constructor(socket: Socket, debugLabel: string = '') {
this.debugLabel = debugLabel;
constructor(socket: Socket) {
this.socket = socket;
this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'NodeSocket' });
this._errorListener = (err: any) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Error, { code: err?.code, message: err?.message });
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
@ -55,10 +47,7 @@ export class NodeSocket implements ISocket {
}
public onData(_listener: (e: VSBuffer) => void): IDisposable {
const listener = (buff: Buffer) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff);
_listener(VSBuffer.wrap(buff));
};
const listener = (buff: Buffer) => _listener(VSBuffer.wrap(buff));
this.socket.on('data', listener);
return {
dispose: () => this.socket.off('data', listener)
@ -67,7 +56,6 @@ export class NodeSocket implements ISocket {
public onClose(listener: (e: SocketCloseEvent) => void): IDisposable {
const adapter = (hadError: boolean) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError });
listener({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: hadError,
@ -81,13 +69,9 @@ export class NodeSocket implements ISocket {
}
public onEnd(listener: () => void): IDisposable {
const adapter = () => {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived);
listener();
};
this.socket.on('end', adapter);
this.socket.on('end', listener);
return {
dispose: () => this.socket.off('end', adapter)
dispose: () => this.socket.off('end', listener)
};
}
@ -103,8 +87,7 @@ export class NodeSocket implements ISocket {
// > However, the false return value is only advisory and the writable stream will unconditionally
// > accept and buffer chunk even if it has not been allowed to drain.
try {
this.traceSocketEvent(SocketDiagnosticsEventType.Write, buffer);
this.socket.write(buffer.buffer, (err: any) => {
this.socket.write(<Buffer>buffer.buffer, (err: any) => {
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
@ -133,15 +116,12 @@ export class NodeSocket implements ISocket {
}
public end(): void {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndSent);
this.socket.end();
}
public drain(): Promise<void> {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainBegin);
return new Promise<void>((resolve, reject) => {
if (this.socket.bufferSize === 0) {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd);
resolve();
return;
}
@ -151,7 +131,6 @@ export class NodeSocket implements ISocket {
this.socket.off('error', finished);
this.socket.off('timeout', finished);
this.socket.off('drain', finished);
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd);
resolve();
};
this.socket.on('close', finished);
@ -174,17 +153,25 @@ const enum ReadState {
Fin = 4
}
interface ISocketTracer {
traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void
}
/**
* See https://tools.ietf.org/html/rfc6455#section-5.2
*/
export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketTracer {
export class WebSocketNodeSocket extends Disposable implements ISocket {
public readonly socket: NodeSocket;
private readonly _flowManager: WebSocketFlowManager;
public readonly permessageDeflate: boolean;
private _totalIncomingWireBytes: number;
private _totalIncomingDataBytes: number;
private _totalOutgoingWireBytes: number;
private _totalOutgoingDataBytes: number;
private readonly _zlibInflate: zlib.InflateRaw | null;
private readonly _zlibDeflate: zlib.DeflateRaw | null;
private _zlibDeflateFlushWaitingCount: number;
private readonly _onDidZlibFlush = this._register(new Emitter<void>());
private readonly _recordInflateBytes: boolean;
private readonly _recordedInflateBytes: Buffer[] = [];
private readonly _pendingInflateData: Buffer[] = [];
private readonly _pendingDeflateData: Buffer[] = [];
private readonly _incomingData: ChunkStream;
private readonly _onData = this._register(new Emitter<VSBuffer>());
private readonly _onClose = this._register(new Emitter<SocketCloseEvent>());
@ -199,16 +186,27 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
mask: 0
};
public get permessageDeflate(): boolean {
return this._flowManager.permessageDeflate;
public get totalIncomingWireBytes(): number {
return this._totalIncomingWireBytes;
}
public get totalIncomingDataBytes(): number {
return this._totalIncomingDataBytes;
}
public get totalOutgoingWireBytes(): number {
return this._totalOutgoingWireBytes;
}
public get totalOutgoingDataBytes(): number {
return this._totalOutgoingDataBytes;
}
public get recordedInflateBytes(): VSBuffer {
return this._flowManager.recordedInflateBytes;
}
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
this.socket.traceSocketEvent(type, data);
if (this._recordInflateBytes) {
return VSBuffer.wrap(Buffer.concat(this._recordedInflateBytes));
}
return VSBuffer.alloc(0);
}
/**
@ -226,34 +224,69 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) {
super();
this.socket = socket;
this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes });
this._flowManager = this._register(new WebSocketFlowManager(
this,
permessageDeflate,
inflateBytes,
recordInflateBytes,
this._onData,
(data, compressed) => this._write(data, compressed)
));
this._register(this._flowManager.onError((err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
this._totalIncomingWireBytes = 0;
this._totalIncomingDataBytes = 0;
this._totalOutgoingWireBytes = 0;
this._totalOutgoingDataBytes = 0;
this.permessageDeflate = permessageDeflate;
this._recordInflateBytes = recordInflateBytes;
if (permessageDeflate) {
// See https://tools.ietf.org/html/rfc7692#page-16
// To simplify our logic, we don't negotiate the window size
// and simply dedicate (2^15) / 32kb per web socket
this._zlibInflate = zlib.createInflateRaw({
windowBits: 15
});
}));
this._zlibInflate.on('error', (err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
});
});
this._zlibInflate.on('data', (data: Buffer) => {
this._pendingInflateData.push(data);
});
if (inflateBytes) {
this._zlibInflate.write(inflateBytes.buffer);
this._zlibInflate.flush(() => {
this._pendingInflateData.length = 0;
});
}
this._zlibDeflate = zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
});
});
this._zlibDeflate.on('data', (data: Buffer) => {
this._pendingDeflateData.push(data);
});
} else {
this._zlibInflate = null;
this._zlibDeflate = null;
}
this._zlibDeflateFlushWaitingCount = 0;
this._incomingData = new ChunkStream();
this._register(this.socket.onData(data => this._acceptChunk(data)));
this._register(this.socket.onClose((e) => this._onClose.fire(e)));
}
public override dispose(): void {
if (this._flowManager.isProcessingWriteQueue()) {
if (this._zlibDeflateFlushWaitingCount > 0) {
// Wait for any outstanding writes to finish before disposing
this._register(this._flowManager.onDidFinishProcessingWriteQueue(() => {
this._register(this._onDidZlibFlush.event(() => {
this.dispose();
}));
} else {
@ -275,16 +308,36 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
}
public write(buffer: VSBuffer): void {
this._flowManager.writeMessage(buffer);
this._totalOutgoingDataBytes += buffer.byteLength;
if (this._zlibDeflate) {
this._zlibDeflate.write(<Buffer>buffer.buffer);
this._zlibDeflateFlushWaitingCount++;
// See https://zlib.net/manual.html#Constants
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
this._zlibDeflateFlushWaitingCount--;
let data = Buffer.concat(this._pendingDeflateData);
this._pendingDeflateData.length = 0;
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
data = data.slice(0, data.length - 4);
if (!this._isEnded) {
// Avoid ERR_STREAM_WRITE_AFTER_END
this._write(VSBuffer.wrap(data), true);
}
if (this._zlibDeflateFlushWaitingCount === 0) {
this._onDidZlibFlush.fire();
}
});
} else {
this._write(buffer, false);
}
}
private _write(buffer: VSBuffer, compressed: boolean): void {
if (this._isEnded) {
// Avoid ERR_STREAM_WRITE_AFTER_END
return;
}
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketWrite, buffer);
let headerLen = Constants.MinHeaderByteSize;
if (buffer.byteLength < 126) {
headerLen += 0;
@ -321,6 +374,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
}
this._totalOutgoingWireBytes += header.byteLength + buffer.byteLength;
this.socket.write(VSBuffer.concat([header, buffer]));
}
@ -333,6 +387,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
if (data.byteLength === 0) {
return;
}
this._totalIncomingWireBytes += data.byteLength;
this._incomingData.acceptChunk(data);
@ -358,8 +413,6 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
this._state.firstFrameOfMessage = Boolean(finBit);
this._state.mask = 0;
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin });
} else if (this._state.state === ReadState.ReadHeader) {
// read entire header
const header = this._incomingData.read(this._state.readLen);
@ -400,268 +453,52 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
this._state.readLen = len;
this._state.mask = mask;
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask });
} else if (this._state.state === ReadState.ReadBody) {
// read body
const body = this._incomingData.read(this._state.readLen);
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketReadData, body);
unmask(body, this._state.mask);
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketUnmaskedData, body);
this._state.state = ReadState.PeekHeader;
this._state.readLen = Constants.MinHeaderByteSize;
this._state.mask = 0;
this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin);
if (this._zlibInflate && this._state.compressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
//
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(Buffer.from(<Buffer>body.buffer));
}
this._zlibInflate.write(<Buffer>body.buffer);
if (this._state.fin) {
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff]));
}
this._zlibInflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff]));
}
this._zlibInflate.flush(() => {
const data = Buffer.concat(this._pendingInflateData);
this._pendingInflateData.length = 0;
this._totalIncomingDataBytes += data.length;
this._onData.fire(VSBuffer.wrap(data));
});
} else {
this._totalIncomingDataBytes += body.byteLength;
this._onData.fire(body);
}
}
}
}
public async drain(): Promise<void> {
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin);
if (this._flowManager.isProcessingWriteQueue()) {
await Event.toPromise(this._flowManager.onDidFinishProcessingWriteQueue);
if (this._zlibDeflateFlushWaitingCount > 0) {
await Event.toPromise(this._onDidZlibFlush.event);
}
await this.socket.drain();
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd);
}
}
class WebSocketFlowManager extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibInflateStream: ZlibInflateStream | null;
private readonly _zlibDeflateStream: ZlibDeflateStream | null;
private readonly _writeQueue: VSBuffer[] = [];
private readonly _readQueue: { data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean }[] = [];
private readonly _onDidFinishProcessingWriteQueue = this._register(new Emitter<void>());
public readonly onDidFinishProcessingWriteQueue = this._onDidFinishProcessingWriteQueue.event;
public get permessageDeflate(): boolean {
return Boolean(this._zlibInflateStream && this._zlibDeflateStream);
}
public get recordedInflateBytes(): VSBuffer {
if (this._zlibInflateStream) {
return this._zlibInflateStream.recordedInflateBytes;
}
return VSBuffer.alloc(0);
}
constructor(
private readonly _tracer: ISocketTracer,
permessageDeflate: boolean,
inflateBytes: VSBuffer | null,
recordInflateBytes: boolean,
private readonly _onData: Emitter<VSBuffer>,
private readonly _writeFn: (data: VSBuffer, compressed: boolean) => void
) {
super();
if (permessageDeflate) {
// See https://tools.ietf.org/html/rfc7692#page-16
// To simplify our logic, we don't negotiate the window size
// and simply dedicate (2^15) / 32kb per web socket
this._zlibInflateStream = this._register(new ZlibInflateStream(this._tracer, recordInflateBytes, inflateBytes, { windowBits: 15 }));
this._zlibDeflateStream = this._register(new ZlibDeflateStream(this._tracer, { windowBits: 15 }));
this._register(this._zlibInflateStream.onError((err) => this._onError.fire(err)));
this._register(this._zlibDeflateStream.onError((err) => this._onError.fire(err)));
} else {
this._zlibInflateStream = null;
this._zlibDeflateStream = null;
}
}
public writeMessage(message: VSBuffer): void {
this._writeQueue.push(message);
this._processWriteQueue();
}
private _isProcessingWriteQueue = false;
private async _processWriteQueue(): Promise<void> {
if (this._isProcessingWriteQueue) {
return;
}
this._isProcessingWriteQueue = true;
while (this._writeQueue.length > 0) {
const message = this._writeQueue.shift()!;
if (this._zlibDeflateStream) {
const data = await this._deflateMessage(this._zlibDeflateStream, message);
this._writeFn(data, true);
} else {
this._writeFn(message, false);
}
}
this._isProcessingWriteQueue = false;
this._onDidFinishProcessingWriteQueue.fire();
}
public isProcessingWriteQueue(): boolean {
return (this._isProcessingWriteQueue);
}
/**
* Subsequent calls should wait for the previous `_deflateBuffer` call to complete.
*/
private _deflateMessage(zlibDeflateStream: ZlibDeflateStream, buffer: VSBuffer): Promise<VSBuffer> {
return new Promise<VSBuffer>((resolve, reject) => {
zlibDeflateStream.write(buffer);
zlibDeflateStream.flush(data => resolve(data));
});
}
public acceptFrame(data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean): void {
this._readQueue.push({ data, isCompressed, isLastFrameOfMessage });
this._processReadQueue();
}
private _isProcessingReadQueue = false;
private async _processReadQueue(): Promise<void> {
if (this._isProcessingReadQueue) {
return;
}
this._isProcessingReadQueue = true;
while (this._readQueue.length > 0) {
const frameInfo = this._readQueue.shift()!;
if (this._zlibInflateStream && frameInfo.isCompressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
const data = await this._inflateFrame(this._zlibInflateStream, frameInfo.data, frameInfo.isLastFrameOfMessage);
this._onData.fire(data);
} else {
this._onData.fire(frameInfo.data);
}
}
this._isProcessingReadQueue = false;
}
/**
* Subsequent calls should wait for the previous `transformRead` call to complete.
*/
private _inflateFrame(zlibInflateStream: ZlibInflateStream, buffer: VSBuffer, isLastFrameOfMessage: boolean): Promise<VSBuffer> {
return new Promise<VSBuffer>((resolve, reject) => {
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
zlibInflateStream.write(buffer);
if (isLastFrameOfMessage) {
zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff]));
}
zlibInflateStream.flush(data => resolve(data));
});
}
}
class ZlibInflateStream extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibInflate: zlib.InflateRaw;
private readonly _recordedInflateBytes: VSBuffer[] = [];
private readonly _pendingInflateData: VSBuffer[] = [];
public get recordedInflateBytes(): VSBuffer {
if (this._recordInflateBytes) {
return VSBuffer.concat(this._recordedInflateBytes);
}
return VSBuffer.alloc(0);
}
constructor(
private readonly _tracer: ISocketTracer,
private readonly _recordInflateBytes: boolean,
inflateBytes: VSBuffer | null,
options: zlib.ZlibOptions
) {
super();
this._zlibInflate = zlib.createInflateRaw(options);
this._zlibInflate.on('error', (err) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (<any>err)?.code });
this._onError.fire(err);
});
this._zlibInflate.on('data', (data: Buffer) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateData, data);
this._pendingInflateData.push(VSBuffer.wrap(data));
});
if (inflateBytes) {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialWrite, inflateBytes.buffer);
this._zlibInflate.write(inflateBytes.buffer);
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialFlushFired);
this._pendingInflateData.length = 0;
});
}
}
public write(buffer: VSBuffer): void {
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(buffer.clone());
}
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, buffer);
this._zlibInflate.write(buffer.buffer);
}
public flush(callback: (data: VSBuffer) => void): void {
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired);
const data = VSBuffer.concat(this._pendingInflateData);
this._pendingInflateData.length = 0;
callback(data);
});
}
}
class ZlibDeflateStream extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibDeflate: zlib.DeflateRaw;
private readonly _pendingDeflateData: VSBuffer[] = [];
constructor(
private readonly _tracer: ISocketTracer,
options: zlib.ZlibOptions
) {
super();
this._zlibDeflate = zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (<any>err)?.code });
this._onError.fire(err);
});
this._zlibDeflate.on('data', (data: Buffer) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateData, data);
this._pendingDeflateData.push(VSBuffer.wrap(data));
});
}
public write(buffer: VSBuffer): void {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateWrite, buffer.buffer);
this._zlibDeflate.write(<Buffer>buffer.buffer);
}
public flush(callback: (data: VSBuffer) => void): void {
// See https://zlib.net/manual.html#Constants
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired);
let data = VSBuffer.concat(this._pendingDeflateData);
this._pendingDeflateData.length = 0;
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
data = data.slice(0, data.byteLength - 4);
callback(data);
});
}
}
@ -760,7 +597,7 @@ export class Server extends IPCServer {
const onConnection = Event.fromNodeEventEmitter<Socket>(server, 'connection');
return Event.map(onConnection, socket => ({
protocol: new Protocol(new NodeSocket(socket, 'ipc-server-connection')),
protocol: new Protocol(new NodeSocket(socket)),
onDidClientDisconnect: Event.once(Event.fromNodeEventEmitter<void>(socket, 'close'))
}));
}
@ -802,7 +639,7 @@ export function connect(hook: any, clientId: string): Promise<Client> {
return new Promise<Client>((c, e) => {
const socket = createConnection(hook, () => {
socket.removeListener('error', e);
c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId));
c(Client.fromSocket(new NodeSocket(socket), clientId));
});
socket.once('error', e);

View file

@ -11,7 +11,7 @@ import { Barrier, timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
@ -342,7 +342,7 @@ suite('PersistentProtocol reconnection', () => {
assert.strictEqual(b.unacknowledgedCount, 1);
// wait for scheduled _recvAckCheck() to execute
await timeout(2 * ProtocolConstants.TimeoutTime);
await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime);
assert.strictEqual(a.unacknowledgedCount, 1);
assert.strictEqual(b.unacknowledgedCount, 1);
@ -351,7 +351,7 @@ suite('PersistentProtocol reconnection', () => {
a.endAcceptReconnection();
assert.strictEqual(timeoutListenerCalled, false);
await timeout(2 * ProtocolConstants.TimeoutTime);
await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime);
assert.strictEqual(a.unacknowledgedCount, 0);
assert.strictEqual(b.unacknowledgedCount, 0);
assert.strictEqual(timeoutListenerCalled, false);
@ -364,59 +364,6 @@ suite('PersistentProtocol reconnection', () => {
}
);
});
test('writing can be paused', async () => {
await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => {
const loadEstimator: ILoadEstimator = {
hasHighLoad: () => false
};
const ether = new Ether();
const aSocket = new NodeSocket(ether.a);
const a = new PersistentProtocol(aSocket, null, loadEstimator);
const aMessages = new MessageStream(a);
const bSocket = new NodeSocket(ether.b);
const b = new PersistentProtocol(bSocket, null, loadEstimator);
const bMessages = new MessageStream(b);
// send one message A -> B
a.send(VSBuffer.fromString('a1'));
const a1 = await bMessages.waitForOne();
assert.strictEqual(a1.toString(), 'a1');
// ask A to pause writing
b.sendPause();
// send a message B -> A
b.send(VSBuffer.fromString('b1'));
const b1 = await aMessages.waitForOne();
assert.strictEqual(b1.toString(), 'b1');
// send a message A -> B (this should be blocked at A)
a.send(VSBuffer.fromString('a2'));
// wait a long time and check that not even acks are written
await timeout(2 * ProtocolConstants.AcknowledgeTime);
assert.strictEqual(a.unacknowledgedCount, 1);
assert.strictEqual(b.unacknowledgedCount, 1);
// ask A to resume writing
b.sendResume();
// check that B receives message
const a2 = await bMessages.waitForOne();
assert.strictEqual(a2.toString(), 'a2');
// wait a long time and check that acks are written
await timeout(2 * ProtocolConstants.AcknowledgeTime);
assert.strictEqual(a.unacknowledgedCount, 0);
assert.strictEqual(b.unacknowledgedCount, 0);
aMessages.dispose();
bMessages.dispose();
a.dispose();
b.dispose();
});
});
});
suite('IPC, create handle', () => {
@ -485,9 +432,6 @@ suite('WebSocketNodeSocket', () => {
private readonly _onClose = new Emitter<SocketCloseEvent>();
public readonly onClose = this._onClose.event;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
}
constructor() {
super();
}
@ -578,14 +522,5 @@ suite('WebSocketNodeSocket', () => {
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Hello');
});
test('A single-frame compressed text message followed by a single-frame non-compressed text message', async () => {
const frames = [
[0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00], // contains "Hello"
[0x81, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64] // contains "world"
];
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Helloworld');
});
});
});

View file

@ -41,7 +41,7 @@ import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encry
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter';
@ -1033,7 +1033,7 @@ export class CodeApplication extends Disposable {
private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise<typeof process.env> {
try {
return await getResolvedShellEnv(this.logService, args, env);
return await resolveShellEnv(this.logService, args, env);
} catch (error) {
const errorMessage = toErrorMessage(error);
if (notifyOnError) {

View file

@ -22,7 +22,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { IFileService } from 'vs/platform/files/common/files';
@ -217,8 +217,7 @@ class CliMain extends Disposable {
// Install Extension
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'] };
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], installOptions, !!this.argv['force']);
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']);
}
// Uninstall Extension

View file

@ -899,8 +899,6 @@ export interface ICodeEditor extends editorCommon.IEditor {
* @internal
*/
hasModel(): this is IActiveCodeEditor;
setBanner(bannerDomNode: HTMLElement | null, height: number): void;
}
/**

View file

@ -244,8 +244,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
private _decorationTypeKeysToIds: { [decorationTypeKey: string]: string[] };
private _decorationTypeSubtypes: { [decorationTypeKey: string]: { [subtype: string]: boolean } };
private _bannerDomNode: HTMLElement | null = null;
constructor(
domElement: HTMLElement,
_options: Readonly<editorBrowser.IEditorConstructionOptions>,
@ -1492,19 +1490,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
Configuration.applyFontInfoSlow(target, this._configuration.options.get(EditorOption.fontInfo));
}
public setBanner(domNode: HTMLElement | null, domNodeHeight: number): void {
if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) {
this._domElement.removeChild(this._bannerDomNode);
}
this._bannerDomNode = domNode;
this._configuration.reserveHeight(domNode ? domNodeHeight : 0);
if (this._bannerDomNode) {
this._domElement.prepend(this._bannerDomNode);
}
}
protected _attachModel(model: ITextModel | null): void {
if (!model) {
this._modelData = null;
@ -1718,9 +1703,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (removeDomNode && this._domElement.contains(removeDomNode)) {
this._domElement.removeChild(removeDomNode);
}
if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) {
this._domElement.removeChild(this._bannerDomNode);
}
return model;
}

View file

@ -313,7 +313,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
private _rawOptions: IEditorOptions;
private _readOptions: RawEditorOptions;
protected _validatedOptions: ValidatedEditorOptions;
private _reservedHeight: number = 0;
constructor(isSimpleWidget: boolean, _options: Readonly<IEditorOptions>) {
super();
@ -368,7 +367,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
const env: IEnvironmentalOptions = {
memory: this._computeOptionsMemory,
outerWidth: partialEnv.outerWidth,
outerHeight: partialEnv.outerHeight - this._reservedHeight,
outerHeight: partialEnv.outerHeight,
fontInfo: this.readConfiguration(bareFontInfo),
extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines,
@ -459,10 +458,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
protected abstract readConfiguration(styling: BareFontInfo): FontInfo;
public reserveHeight(height: number) {
this._reservedHeight = height;
this._recomputeOptions();
}
}
export const editorConfigurationBaseNode = Object.freeze<IConfigurationNode>({

View file

@ -162,7 +162,6 @@ export interface IConfiguration extends IDisposable {
observeReferenceElement(dimension?: IDimension): void;
updatePixelRatio(): void;
setIsDominatedByLongLines(isDominatedByLongLines: boolean): void;
reserveHeight(height: number): void;
}
// --- view

View file

@ -760,7 +760,7 @@ export interface ITextModel {
getLineLastNonWhitespaceColumn(lineNumber: number): number;
/**
* Create a valid position.
* Create a valid position,
*/
validatePosition(position: IPosition): Position;
@ -800,7 +800,7 @@ export interface ITextModel {
getPositionAt(offset: number): Position;
/**
* Get a range covering the entire model.
* Get a range covering the entire model
*/
getFullModelRange(): Range;

View file

@ -539,10 +539,11 @@ export interface CompletionItem {
/**
* A string or snippet that should be inserted in a document when selecting
* this completion.
* is used.
*/
insertText: string;
/**
* Additional rules (as bitmask) that should be applied when inserting
* Addition rules (as bitmask) that should be applied when inserting
* this completion.
*/
insertTextRules?: CompletionItemInsertTextRule;

View file

@ -10,7 +10,6 @@ import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
// Define extension point ids
export const Extensions = {
@ -87,12 +86,3 @@ LanguageConfigurationRegistry.register(PLAINTEXT_MODE_ID, {
offSide: true
}
}, 0);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerDefaultConfigurations([{
overrides: {
'[plaintext]': {
'editor.unicodeHighlight.ambiguousCharacters': false
}
}
}]);

View file

@ -6,11 +6,9 @@
import { IRange, Range } from 'vs/editor/common/core/range';
import { Searcher } from 'vs/editor/common/model/textModelSearch';
import * as strings from 'vs/base/common/strings';
import { IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { assertNever } from 'vs/base/common/types';
export class UnicodeTextModelHighlighter {
public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): IUnicodeHighlightsResult {
public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): Range[] {
const startLine = range ? range.startLineNumber : 1;
const endLine = range ? range.endLineNumber : model.getLineCount();
@ -25,15 +23,8 @@ export class UnicodeTextModelHighlighter {
}
const searcher = new Searcher(null, regex);
const ranges: Range[] = [];
let hasMore = false;
const result: Range[] = [];
let m: RegExpExecArray | null;
let ambiguousCharacterCount = 0;
let invisibleCharacterCount = 0;
let nonBasicAsciiCharacterCount = 0;
forLoop:
for (let lineNumber = startLine, lineCount = endLine; lineNumber <= lineCount; lineNumber++) {
const lineContent = model.getLineContent(lineNumber);
const lineLength = lineContent.length;
@ -60,37 +51,19 @@ export class UnicodeTextModelHighlighter {
}
}
const str = lineContent.substring(startIndex, endIndex);
const highlightReason = codePointHighlighter.shouldHighlightNonBasicASCII(str);
if (codePointHighlighter.shouldHighlightNonBasicASCII(str) !== SimpleHighlightReason.None) {
result.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1));
if (highlightReason !== SimpleHighlightReason.None) {
if (highlightReason === SimpleHighlightReason.Ambiguous) {
ambiguousCharacterCount++;
} else if (highlightReason === SimpleHighlightReason.Invisible) {
invisibleCharacterCount++;
} else if (highlightReason === SimpleHighlightReason.NonBasicASCII) {
nonBasicAsciiCharacterCount++;
} else {
assertNever(highlightReason);
const maxResultLength = 1000;
if (result.length > maxResultLength) {
// TODO@hediet a message should be shown in this case
break;
}
const MAX_RESULT_LENGTH = 1000;
if (ranges.length >= MAX_RESULT_LENGTH) {
hasMore = true;
break forLoop;
}
ranges.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1));
}
}
} while (m);
}
return {
ranges,
hasMore,
ambiguousCharacterCount,
invisibleCharacterCount,
nonBasicAsciiCharacterCount
};
return result;
}
public static computeUnicodeHighlightReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null {

View file

@ -18,7 +18,7 @@ import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model
import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes';
import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer';
import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport';
import { IDiffComputationResult, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService';
import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase';
import * as types from 'vs/base/common/types';
import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerServiceImpl';
@ -372,10 +372,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
delete this._models[strURL];
}
public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
const model = this._getModel(url);
if (!model) {
return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 };
return [];
}
return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range);
}

View file

@ -23,7 +23,7 @@ export interface IEditorWorkerService {
readonly _serviceBrand: undefined;
canComputeUnicodeHighlights(uri: URI): boolean;
computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult>;
computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]>;
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null>;
@ -38,11 +38,3 @@ export interface IEditorWorkerService {
canNavigateValueSet(resource: URI): boolean;
navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null>;
}
export interface IUnicodeHighlightsResult {
ranges: IRange[];
hasMore: boolean;
nonBasicAsciiCharacterCount: number;
invisibleCharacterCount: number;
ambiguousCharacterCount: number;
}

View file

@ -15,7 +15,7 @@ import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { regExpFlags } from 'vs/base/common/strings';
@ -86,7 +86,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
return canSyncModel(this._modelService, uri);
}
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
}
@ -475,7 +475,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
});
}
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
return this._withSyncedResources([uri]).then(proxy => {
return proxy.computeUnicodeHighlights(uri.toString(), options, range);
});

View file

@ -1,85 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.editor-banner {
box-sizing: border-box;
cursor: default;
width: 100%;
font-size: 12px;
display: flex;
overflow: visible;
height: 26px;
background: var(--vscode-banner-background);
}
.editor-banner .icon-container {
display: flex;
flex-shrink: 0;
align-items: center;
padding: 0 6px 0 10px;
}
.editor-banner .icon-container.custom-icon {
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
width: 16px;
padding: 0;
margin: 0 6px 0 10px;
}
.editor-banner .message-container {
display: flex;
align-items: center;
line-height: 26px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.editor-banner .message-container p {
margin-block-start: 0;
margin-block-end: 0;
}
.editor-banner .message-actions-container {
flex-grow: 1;
flex-shrink: 0;
line-height: 26px;
margin: 0 4px;
}
.editor-banner .message-actions-container a.monaco-button {
width: inherit;
margin: 2px 8px;
padding: 0px 12px;
}
.editor-banner .message-actions-container a {
padding: 3px;
margin-left: 12px;
text-decoration: underline;
}
.editor-banner .action-container {
padding: 0 10px 0 6px;
}
.editor-banner {
background-color: var(--vscode-banner-background);
}
.editor-banner,
.editor-banner .action-container .codicon,
.editor-banner .message-actions-container .monaco-link {
color: var(--vscode-banner-foreground);
}
.editor-banner .icon-container .codicon {
color: var(--vscode-banner-iconForeground);
}

View file

@ -1,155 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./bannerController';
import { $, append, clearNode } from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { Disposable } from 'vs/base/common/lifecycle';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILinkDescriptor, Link } from 'vs/platform/opener/browser/link';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
const BANNER_ELEMENT_HEIGHT = 26;
export class BannerController extends Disposable {
private readonly banner: Banner;
constructor(
private readonly _editor: ICodeEditor,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.banner = this._register(this.instantiationService.createInstance(Banner));
}
public hide() {
this._editor.setBanner(null, 0);
this.banner.clear();
}
public show(item: IBannerItem) {
this.banner.show({
...item,
onClose: () => {
this.hide();
if (item.onClose) {
item.onClose();
}
}
});
this._editor.setBanner(this.banner.element, BANNER_ELEMENT_HEIGHT);
}
}
// TODO@hediet: Investigate if this can be reused by the workspace banner (bannerPart.ts).
class Banner extends Disposable {
public element: HTMLElement;
private readonly markdownRenderer: MarkdownRenderer;
private messageActionsContainer: HTMLElement | undefined;
private actionBar: ActionBar | undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {});
this.element = $('div.editor-banner');
this.element.tabIndex = 0;
}
private getAriaLabel(item: IBannerItem): string | undefined {
if (item.ariaLabel) {
return item.ariaLabel;
}
if (typeof item.message === 'string') {
return item.message;
}
return undefined;
}
private getBannerMessage(message: MarkdownString | string): HTMLElement {
if (typeof message === 'string') {
const element = $('span');
element.innerText = message;
return element;
}
return this.markdownRenderer.render(message).element;
}
public clear() {
clearNode(this.element);
}
public show(item: IBannerItem) {
// Clear previous item
clearNode(this.element);
// Banner aria label
const ariaLabel = this.getAriaLabel(item);
if (ariaLabel) {
this.element.setAttribute('aria-label', ariaLabel);
}
// Icon
const iconContainer = append(this.element, $('div.icon-container'));
iconContainer.setAttribute('aria-hidden', 'true');
if (item.icon) {
iconContainer.appendChild($(`div${ThemeIcon.asCSSSelector(item.icon)}`));
}
// Message
const messageContainer = append(this.element, $('div.message-container'));
messageContainer.setAttribute('aria-hidden', 'true');
messageContainer.appendChild(this.getBannerMessage(item.message));
// Message Actions
this.messageActionsContainer = append(this.element, $('div.message-actions-container'));
if (item.actions) {
for (const action of item.actions) {
this._register(this.instantiationService.createInstance(Link, this.messageActionsContainer, { ...action, tabIndex: -1 }, {}));
}
}
// Action
const actionBarContainer = append(this.element, $('div.action-container'));
this.actionBar = this._register(new ActionBar(actionBarContainer));
this.actionBar.push(this._register(
new Action(
'banner.close',
'Close Banner',
ThemeIcon.asClassName(widgetClose),
true,
() => {
if (typeof item.onClose === 'function') {
item.onClose();
}
}
)
), { icon: true, label: false });
this.actionBar.setFocusable(false);
}
}
export interface IBannerItem {
readonly id: string;
readonly icon: ThemeIcon | undefined;
readonly message: string | MarkdownString;
readonly actions?: ILinkDescriptor[];
readonly ariaLabel?: string;
readonly onClose?: () => void;
}

View file

@ -5,7 +5,6 @@
import { RunOnceScheduler } from 'vs/base/common/async';
import { CharCode } from 'vs/base/common/charCode';
import { Codicon } from 'vs/base/common/codicons';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { InvisibleCharacters } from 'vs/base/common/strings';
@ -18,44 +17,32 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IModelDecoration, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighterReasonKind, UnicodeTextModelHighlighter } from 'vs/editor/common/modes/unicodeTextModelHighlighter';
import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant';
import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController';
import * as nls from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { minimapFindMatch, minimapUnicodeHighlight, overviewRulerFindMatchForeground, overviewRulerUnicodeHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
export class UnicodeHighlighter extends Disposable implements IEditorContribution {
public static readonly ID = 'editor.contrib.unicodeHighlighter';
private _highlighter: DocumentUnicodeHighlighter | ViewportUnicodeHighlighter | null = null;
private _options: InternalUnicodeHighlightOptions;
private readonly _bannerController: BannerController;
private _bannerClosed: boolean = false;
constructor(
private readonly _editor: ICodeEditor,
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustService: IWorkspaceTrustManagementService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
this._bannerController = this._register(instantiationService.createInstance(BannerController, _editor));
this._register(this._editor.onDidChangeModel(() => {
this._bannerClosed = false;
this._updateHighlighter();
}));
@ -83,57 +70,7 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio
super.dispose();
}
private readonly _updateState = (state: IUnicodeHighlightsResult | null): void => {
if (state && state.hasMore) {
if (this._bannerClosed) {
return;
}
// This document contains many non-basic ASCII characters.
const max = Math.max(state.ambiguousCharacterCount, state.nonBasicAsciiCharacterCount, state.invisibleCharacterCount);
let data;
if (state.nonBasicAsciiCharacterCount >= max) {
data = {
message: nls.localize('unicodeHighlighting.thisDocumentHasManyNonBasicAsciiUnicodeCharacters', 'This document contains many non-basic ASCII unicode characters'),
command: new DisableHighlightingOfNonBasicAsciiCharactersAction(),
};
} else if (state.ambiguousCharacterCount >= max) {
data = {
message: nls.localize('unicodeHighlighting.thisDocumentHasManyAmbiguousUnicodeCharacters', 'This document contains many ambiguous unicode characters'),
command: new DisableHighlightingOfAmbiguousCharactersAction(),
};
} else if (state.invisibleCharacterCount >= max) {
data = {
message: nls.localize('unicodeHighlighting.thisDocumentHasManyInvisibleUnicodeCharacters', 'This document contains many invisible unicode characters'),
command: new DisableHighlightingOfInvisibleCharactersAction(),
};
} else {
throw new Error('Unreachable');
}
this._bannerController.show({
id: 'unicodeHighlightBanner',
message: data.message,
icon: warningIcon,
actions: [
{
label: data.command.shortLabel,
href: `command:${data.command.id}`
}
],
onClose: () => {
this._bannerClosed = true;
},
});
} else {
this._bannerController.hide();
}
};
private _updateHighlighter(): void {
this._updateState(null);
if (this._highlighter) {
this._highlighter.dispose();
this._highlighter = null;
@ -163,9 +100,9 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio
};
if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) {
this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._updateState, this._editorWorkerService);
this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._editorWorkerService);
} else {
this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions, this._updateState);
this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions);
}
}
@ -219,7 +156,6 @@ class DocumentUnicodeHighlighter extends Disposable {
constructor(
private readonly _editor: IActiveCodeEditor,
private readonly _options: UnicodeHighlighterOptions,
private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void,
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
) {
super();
@ -246,20 +182,14 @@ class DocumentUnicodeHighlighter extends Disposable {
const modelVersionId = this._model.getVersionId();
this._editorWorkerService
.computedUnicodeHighlights(this._model.uri, this._options)
.then((info) => {
.then((ranges) => {
if (this._model.getVersionId() !== modelVersionId) {
// model changed in the meantime
return;
}
this._updateState(info);
const decorations: IModelDeltaDecoration[] = [];
if (!info.hasMore) {
// Don't show decoration if there are too many.
// In this case, a banner is shown.
for (const range of info.ranges) {
decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
}
for (const range of ranges) {
decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
}
this._decorationIds = new Set(this._editor.deltaDecorations(
Array.from(this._decorationIds),
@ -288,8 +218,7 @@ class ViewportUnicodeHighlighter extends Disposable {
constructor(
private readonly _editor: IActiveCodeEditor,
private readonly _options: UnicodeHighlighterOptions,
private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void,
private readonly _options: UnicodeHighlighterOptions
) {
super();
@ -324,33 +253,12 @@ class ViewportUnicodeHighlighter extends Disposable {
const ranges = this._editor.getVisibleRanges();
const decorations: IModelDeltaDecoration[] = [];
const totalResult: IUnicodeHighlightsResult = {
ranges: [],
ambiguousCharacterCount: 0,
invisibleCharacterCount: 0,
nonBasicAsciiCharacterCount: 0,
hasMore: false,
};
for (const range of ranges) {
const result = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range);
for (const r of result.ranges) {
totalResult.ranges.push(r);
}
totalResult.ambiguousCharacterCount += totalResult.ambiguousCharacterCount;
totalResult.invisibleCharacterCount += totalResult.invisibleCharacterCount;
totalResult.nonBasicAsciiCharacterCount += totalResult.nonBasicAsciiCharacterCount;
totalResult.hasMore = totalResult.hasMore || result.hasMore;
}
if (!totalResult.hasMore) {
// Don't show decorations if there are too many.
// A banner will be shown instead.
for (const range of totalResult.ranges) {
const ranges = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range);
for (const range of ranges) {
decorations.push({ range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
}
}
this._updateState(totalResult);
this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations));
}
@ -448,7 +356,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa
reason = nls.localize(
'unicodeHighlight.characterIsNonBasicAscii',
'The character {0} is not a basic ASCII character.',
codePointStr
codePoint
);
break;
}
@ -516,82 +424,6 @@ const DECORATION = ModelDecorationOptions.register({
}
});
interface IDisableUnicodeHighlightAction {
shortLabel: string;
}
export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters';
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', '');
constructor() {
super({
id: DisableHighlightingOfAmbiguousCharactersAction.ID,
label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable Ambiguous Highlight'),
alias: 'Disable highlighting of ambiguous characters',
precondition: undefined
});
}
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
let configurationService = accessor?.get(IConfigurationService);
if (configurationService) {
this.runAction(configurationService);
}
}
public async runAction(configurationService: IConfigurationService): Promise<void> {
await configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, ConfigurationTarget.USER);
}
}
export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfInvisibleCharacters';
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters.shortLabel', 'Disable Invisible Highlight');
constructor() {
super({
id: DisableHighlightingOfInvisibleCharactersAction.ID,
label: nls.localize('action.unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'),
alias: 'Disable highlighting of invisible characters',
precondition: undefined
});
}
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
let configurationService = accessor?.get(IConfigurationService);
if (configurationService) {
this.runAction(configurationService);
}
}
public async runAction(configurationService: IConfigurationService): Promise<void> {
await configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, ConfigurationTarget.USER);
}
}
export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters';
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters.shortLabel', 'Disable Non ASCII Highlight');
constructor() {
super({
id: DisableHighlightingOfNonBasicAsciiCharactersAction.ID,
label: nls.localize('action.unicodeHighlight.dhowDisableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'),
alias: 'Disable highlighting of non basic ASCII characters',
precondition: undefined
});
}
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
let configurationService = accessor?.get(IConfigurationService);
if (configurationService) {
this.runAction(configurationService);
}
}
public async runAction(configurationService: IConfigurationService): Promise<void> {
await configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, ConfigurationTarget.USER);
}
}
interface ShowExcludeOptionsArgs {
codePoint: number;
reason: UnicodeHighlighterReason['kind'];
@ -607,7 +439,6 @@ export class ShowExcludeOptions extends EditorAction {
precondition: undefined
});
}
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
const { codePoint, reason } = args as ShowExcludeOptionsArgs;
@ -639,16 +470,28 @@ export class ShowExcludeOptions extends EditorAction {
];
if (reason === UnicodeHighlighterReasonKind.Ambiguous) {
const action = new DisableHighlightingOfAmbiguousCharactersAction();
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
options.push({
label: nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable highlighting of ambiguous characters'),
run: async () => {
await configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, ConfigurationTarget.USER);
}
});
}
else if (reason === UnicodeHighlighterReasonKind.Invisible) {
const action = new DisableHighlightingOfInvisibleCharactersAction();
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
options.push({
label: nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'),
run: async () => {
await configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, ConfigurationTarget.USER);
}
});
}
else if (reason === UnicodeHighlighterReasonKind.NonBasicAscii) {
const action = new DisableHighlightingOfNonBasicAsciiCharactersAction();
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
options.push({
label: nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'),
run: async () => {
await configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, ConfigurationTarget.USER);
}
});
} else {
expectNever(reason);
}
@ -668,8 +511,5 @@ function expectNever(value: never) {
throw new Error(`Unexpected value: ${value}`);
}
registerEditorAction(DisableHighlightingOfAmbiguousCharactersAction);
registerEditorAction(DisableHighlightingOfInvisibleCharactersAction);
registerEditorAction(DisableHighlightingOfNonBasicAsciiCharactersAction);
registerEditorAction(ShowExcludeOptions);
registerEditorContribution(UnicodeHighlighter.ID, UnicodeHighlighter);

8
src/vs/monaco.d.ts vendored
View file

@ -1807,7 +1807,7 @@ declare namespace monaco.editor {
*/
getLineLastNonWhitespaceColumn(lineNumber: number): number;
/**
* Create a valid position.
* Create a valid position,
*/
validatePosition(position: IPosition): Position;
/**
@ -1842,7 +1842,7 @@ declare namespace monaco.editor {
*/
getPositionAt(offset: number): Position;
/**
* Get a range covering the entire model.
* Get a range covering the entire model
*/
getFullModelRange(): Range;
/**
@ -5117,7 +5117,6 @@ declare namespace monaco.editor {
* Apply the same font settings as the editor to `target`.
*/
applyFontInfo(target: HTMLElement): void;
setBanner(bannerDomNode: HTMLElement | null, height: number): void;
}
/**
@ -5899,10 +5898,11 @@ declare namespace monaco.languages {
/**
* A string or snippet that should be inserted in a document when selecting
* this completion.
* is used.
*/
insertText: string;
/**
* Additional rules (as bitmask) that should be applied when inserting
* Addition rules (as bitmask) that should be applied when inserting
* this completion.
*/
insertTextRules?: CompletionItemInsertTextRule;

View file

@ -58,7 +58,7 @@ export interface IConfigurationRegistry {
/**
* Return the registered configuration defaults overrides
*/
getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverride>;
getConfigurationDefaultsOverrides(): IStringDictionary<any>;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
@ -86,12 +86,12 @@ export interface IConfigurationRegistry {
/**
* Returns all configurations settings of all configuration nodes contributed to this registry.
*/
getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;
getConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema };
/**
* Returns all excluded configurations settings of all configuration nodes contributed to this registry.
*/
getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;
getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema };
/**
* Register the identifiers for editor configurations
@ -136,16 +136,8 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
*/
restricted?: boolean;
/**
* When `false` this property is excluded from the registry. Default is to include.
*/
included?: boolean;
/**
* List of tags associated to the property.
* - A tag can be used for filtering
* - Use `experimental` tag for marking the setting as experimental. **Note:** Defaults of experimental settings can be changed by the running experiments.
*/
tags?: string[];
/**
@ -158,9 +150,6 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
*/
disallowSyncIgnore?: boolean;
/**
* Labels for enumeration items
*/
enumItemLabels?: string[];
/**
@ -176,9 +165,9 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
order?: number;
}
export interface IExtensionInfo {
export interface IConfigurationExtensionInfo {
id: string;
displayName?: string;
restrictedConfigurations?: string[];
}
export interface IConfigurationNode {
@ -187,26 +176,17 @@ export interface IConfigurationNode {
type?: string | string[];
title?: string;
description?: string;
properties?: IStringDictionary<IConfigurationPropertySchema>;
properties?: { [path: string]: IConfigurationPropertySchema; };
allOf?: IConfigurationNode[];
scope?: ConfigurationScope;
extensionInfo?: IExtensionInfo;
restrictedProperties?: string[];
extensionInfo?: IConfigurationExtensionInfo;
}
export interface IConfigurationDefaults {
overrides: IStringDictionary<any>;
source?: IExtensionInfo | string;
extensionId?: string;
}
export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & {
defaultDefaultValue?: any,
source?: IExtensionInfo,
defaultValueSource?: IExtensionInfo | string;
};
export type IConfigurationDefaultOverride = { value: any, source?: IExtensionInfo | string };
export const allSettings: { properties: IStringDictionary<IConfigurationPropertySchema>, patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
export const applicationSettings: { properties: IStringDictionary<IConfigurationPropertySchema>, patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
export const machineSettings: { properties: IStringDictionary<IConfigurationPropertySchema>, patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
@ -220,11 +200,11 @@ const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensio
class ConfigurationRegistry implements IConfigurationRegistry {
private readonly configurationDefaultsOverrides: Map<string, IConfigurationDefaultOverride>;
private readonly configurationDefaultsOverrides: IStringDictionary<any>;
private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode;
private readonly configurationContributors: IConfigurationNode[];
private readonly configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;
private readonly excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;
private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly resourceLanguageSettingsSchema: IJSONSchema;
private readonly overrideIdentifiers = new Set<string>();
@ -235,7 +215,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;
constructor() {
this.configurationDefaultsOverrides = new Map<string, IConfigurationDefaultOverride>();
this.configurationDefaultsOverrides = {};
this.defaultLanguageConfigurationOverridesNode = {
id: 'defaultOverrides',
title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),
@ -284,26 +264,23 @@ class ConfigurationRegistry implements IConfigurationRegistry {
const properties: string[] = [];
const overrideIdentifiers: string[] = [];
for (const { overrides, source } of configurationDefaults) {
for (const { overrides } of configurationDefaults) {
for (const key in overrides) {
properties.push(key);
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
const defaultValue = { ...(this.configurationDefaultsOverrides.get(key)?.value || {}), ...overrides[key] };
this.configurationDefaultsOverrides.set(key, { source, value: defaultValue });
const property: IRegisteredConfigurationPropertySchema = {
this.configurationDefaultsOverrides[key] = { ...(this.configurationDefaultsOverrides[key] || {}), ...overrides[key] };
const property: IConfigurationPropertySchema = {
type: 'object',
default: defaultValue,
default: this.configurationDefaultsOverrides[key],
description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key),
$ref: resourceLanguageSettingsSchemaId,
defaultDefaultValue: defaultValue,
source: types.isString(source) ? undefined : source,
$ref: resourceLanguageSettingsSchemaId
};
overrideIdentifiers.push(...overrideIdentifiersFromKey(key));
this.configurationProperties[key] = property;
this.defaultLanguageConfigurationOverridesNode.properties![key] = property;
} else {
this.configurationDefaultsOverrides.set(key, { value: overrides[key], source });
this.configurationDefaultsOverrides[key] = overrides[key];
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
@ -320,16 +297,10 @@ class ConfigurationRegistry implements IConfigurationRegistry {
public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void {
const properties: string[] = [];
for (const { overrides, source } of defaultConfigurations) {
for (const { overrides } of defaultConfigurations) {
for (const key in overrides) {
const configurationDefaultsOverride = this.configurationDefaultsOverrides.get(key);
const id = types.isString(source) ? source : source?.id;
const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.id;
if (id !== configurationDefaultsOverrideSourceId) {
continue;
}
properties.push(key);
this.configurationDefaultsOverrides.delete(key);
delete this.configurationDefaultsOverrides[key];
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
delete this.configurationProperties[key];
delete this.defaultLanguageConfigurationOverridesNode.properties![key];
@ -362,7 +333,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
private doRegisterConfigurations(configurations: IConfigurationNode[], validate: boolean): string[] {
const properties: string[] = [];
configurations.forEach(configuration => {
properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo, configuration.restrictedProperties)); // fills in defaults
properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo)); // fills in defaults
this.configurationContributors.push(configuration);
this.registerJSONConfiguration(configuration);
});
@ -393,7 +364,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
return properties;
}
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo: IExtensionInfo | undefined, restrictedProperties: string[] | undefined, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] {
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo?: IConfigurationExtensionInfo, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] {
scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
let propertyKeys: string[] = [];
let properties = configuration.properties;
@ -404,11 +375,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
continue;
}
const property: IRegisteredConfigurationPropertySchema = properties[key];
property.source = extensionInfo;
const property = properties[key];
// update default value
property.defaultDefaultValue = properties[key].default;
this.updatePropertyDefaultValue(key, property);
// update scope
@ -416,7 +385,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;
property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted;
property.restricted = types.isUndefinedOrNull(property.restricted) ? !!extensionInfo?.restrictedConfigurations?.includes(key) : property.restricted;
}
// Add to properties maps
@ -440,26 +409,25 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let subNodes = configuration.allOf;
if (subNodes) {
for (let node of subNodes) {
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope));
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, extensionInfo, scope));
}
}
return propertyKeys;
}
// TODO: @sandy081 - Remove this method and include required info in getConfigurationProperties
getConfigurations(): IConfigurationNode[] {
return this.configurationContributors;
}
getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {
getConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema } {
return this.configurationProperties;
}
getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {
getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema } {
return this.excludedConfigurationProperties;
}
getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverride> {
getConfigurationDefaultsOverrides(): IStringDictionary<any> {
return this.configurationDefaultsOverrides;
}
@ -563,19 +531,15 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this._onDidSchemaChange.fire();
}
private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void {
const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key);
let defaultValue = configurationdefaultOverride?.value;
let defaultSource = configurationdefaultOverride?.source;
private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void {
let defaultValue = this.configurationDefaultsOverrides[key];
if (types.isUndefined(defaultValue)) {
defaultValue = property.defaultDefaultValue;
defaultSource = undefined;
defaultValue = property.default;
}
if (types.isUndefined(defaultValue)) {
defaultValue = getDefaultValue(property.type);
}
property.default = defaultValue;
property.defaultValueSource = defaultSource;
}
}

View file

@ -58,7 +58,6 @@ export interface NativeParsedArgs {
'show-versions'?: boolean;
'category'?: string;
'install-extension'?: string[]; // undefined or array of 1 or more
'pre-release'?: boolean;
'install-builtin-extension'?: string[]; // undefined or array of 1 or more
'uninstall-extension'?: string[]; // undefined or array of 1 or more
'locate-extension'?: string[]; // undefined or array of 1 or more

View file

@ -56,7 +56,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") },
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' },
'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") },
'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") },
'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },

View file

@ -27,14 +27,15 @@ const MAX_SHELL_RESOLVE_TIME = 10000;
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
/**
* Resolves the shell environment by spawning a shell. This call will cache
* the shell spawning so that subsequent invocations use that cached result.
* We need to get the environment from a user's shell.
* This should only be done when Code itself is not launched
* from within a shell.
*
* Will throw an error if:
* - we hit a timeout of `MAX_SHELL_RESOLVE_TIME`
* - any other error from spawning a shell to figure out the environment
*/
export async function getResolvedShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise<typeof process.env> {
export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise<typeof process.env> {
// Skip if --force-disable-user-env
if (args['force-disable-user-env']) {

View file

@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import {
DidUninstallExtensionEvent, ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOperation, InstallOptions,
InstallVSIXOptions, IExtensionsControlManifest, StatisticType, UninstallOptions, TargetPlatform, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode
InstallVSIXOptions, IReportedExtension, StatisticType, UninstallOptions, TargetPlatform, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
@ -46,7 +46,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
declare readonly _serviceBrand: undefined;
private extensionsControlManifest: Promise<IExtensionsControlManifest> | undefined;
private reportedExtensions: Promise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installingExtensions = new Map<string, IInstallExtensionTask>();
private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>();
@ -120,15 +120,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
await this.installFromGallery(galleryExtension);
}
getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
getExtensionsReport(): Promise<IReportedExtension[]> {
const now = new Date().getTime();
if (!this.extensionsControlManifest || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.extensionsControlManifest = this.updateControlCache();
if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.reportedExtensions = this.updateReportCache();
this.lastReportTimestamp = now;
}
return this.extensionsControlManifest;
return this.reportedExtensions;
}
registerParticipant(participant: IExtensionManagementParticipant): void {
@ -353,15 +353,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension, manifest: IExtensionManifest }> {
const report = await this.getExtensionsControlManifest();
if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) {
if (await this.isMalicious(extension)) {
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious);
}
if (!!report.unsupportedPreReleaseExtensions && !!report.unsupportedPreReleaseExtensions[extension.identifier.id]) {
throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is no longer supported. It is now part of the '{1}' extension as a pre-release version.", extension.identifier.id, report.unsupportedPreReleaseExtensions[extension.identifier.id].displayName), ExtensionManagementErrorCode.UnsupportedPreRelease);
}
if (!await this.canInstall(extension)) {
const targetPlatform = await this.getTargetPlatform();
throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform);
@ -407,6 +402,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return compatibleExtension;
}
private async isMalicious(extension: IGalleryExtension): Promise<boolean> {
const report = await this.getExtensionsReport();
return getMaliciousExtensionsSet(report).has(extension.identifier.id);
}
private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase());
if (uninstallExtensionTask) {
@ -579,15 +579,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return galleryResult.firstPage[0];
}
private async updateControlCache(): Promise<IExtensionsControlManifest> {
private async updateReportCache(): Promise<IReportedExtension[]> {
try {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
const manifest = await this.galleryService.getExtensionsControlManifest();
this.logService.trace(`ExtensionManagementService.refreshControlCache`, manifest);
return manifest;
const result = await this.galleryService.getExtensionsReport();
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
} catch (err) {
this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest');
return { malicious: [] };
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
}
}

View file

@ -5,7 +5,6 @@
import { distinct } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { getOrDefault } from 'vs/base/common/objects';
import { IPager } from 'vs/base/common/paging';
@ -16,7 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { DefaultIconPath, getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isIExtensionIdentifier, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement';
import { DefaultIconPath, getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
@ -439,9 +438,9 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
};
}
interface IRawExtensionsControlManifest {
interface IRawExtensionsReport {
malicious: string[];
unsupported: IStringDictionary<boolean | { preReleaseExtension: { id: string, displayName: string } }>;
slow: string[];
}
abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
@ -943,13 +942,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
return engine;
}
async getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
async getExtensionsReport(): Promise<IReportedExtension[]> {
if (!this.isEnabled()) {
throw new Error('No extension gallery service configured.');
}
if (!this.extensionsControlUrl) {
return { malicious: [] };
return [];
}
const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None);
@ -957,25 +956,18 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
throw new Error('Could not get extensions report.');
}
const result = await asJson<IRawExtensionsControlManifest>(context);
const malicious: IExtensionIdentifier[] = [];
const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string, displayName: string }> = {};
const result = await asJson<IRawExtensionsReport>(context);
const map = new Map<string, IReportedExtension>();
if (result) {
for (const id of result.malicious) {
malicious.push({ id });
}
if (result.unsupported) {
for (const extensionId of Object.keys(result.unsupported)) {
const value = result.unsupported[extensionId];
if (!isBoolean(value)) {
unsupportedPreReleaseExtensions[extensionId.toLowerCase()] = value.preReleaseExtension;
}
}
const ext = map.get(id) || { id: { id }, malicious: true, slow: false };
ext.malicious = true;
map.set(id, ext);
}
}
return { malicious, unsupportedPreReleaseExtensions };
return [...map.values()];
}
}

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { IPager } from 'vs/base/common/paging';
@ -311,9 +310,9 @@ export const enum StatisticType {
Uninstall = 'uninstall'
}
export interface IExtensionsControlManifest {
malicious: IExtensionIdentifier[];
unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string }>;
export interface IReportedExtension {
id: IExtensionIdentifier;
malicious: boolean;
}
export const enum InstallOperation {
@ -339,7 +338,7 @@ export interface IExtensionGalleryService {
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
getExtensionsReport(): Promise<IReportedExtension[]>;
isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean>;
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
getCompatibleExtension(id: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
@ -365,7 +364,6 @@ export interface DidUninstallExtensionEvent {
export enum ExtensionManagementErrorCode {
Unsupported = 'Unsupported',
UnsupportedPreRelease = 'UnsupportedPreRelease',
Malicious = 'Malicious',
Incompatible = 'Incompatible',
IncompatiblePreRelease = 'IncompatiblePreRelease',
@ -414,7 +412,7 @@ export interface IExtensionManagementService {
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
getInstalled(type?: ExtensionType, donotIgnoreInvalidExtensions?: boolean): Promise<ILocalExtension[]>;
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
getExtensionsReport(): Promise<IReportedExtension[]>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
@ -485,7 +483,7 @@ export interface IExtensionManagementCLIService {
readonly _serviceBrand: undefined;
listExtensions(showVersions: boolean, category?: string, output?: CLIOutput): Promise<void>;
installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], installOptions: InstallOptions, force: boolean, output?: CLIOutput): Promise<void>;
installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output?: CLIOutput): Promise<void>;
uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise<void>;
locateExtension(extensions: string[], output?: CLIOutput): Promise<void>;
}

View file

@ -89,7 +89,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
}
}
public async installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], installOptions: InstallOptions, force: boolean, output: CLIOutput = console): Promise<void> {
public async installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output: CLIOutput = console): Promise<void> {
const failed: string[] = [];
const installedExtensionsManifests: IExtensionManifest[] = [];
if (extensions.length) {
@ -119,21 +119,21 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
} else {
const [id, version] = getIdAndVersion(extension);
if (checkIfNotInstalled(id, version)) {
installExtensionInfos.push({ id, version, installOptions: { ...installOptions, isBuiltin: false } });
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } });
}
}
}
for (const extension of builtinExtensionIds) {
const [id, version] = getIdAndVersion(extension);
if (checkIfNotInstalled(id, version)) {
installExtensionInfos.push({ id, version, installOptions: { ...installOptions, isBuiltin: true } });
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } });
}
}
if (vsixs.length) {
await Promise.all(vsixs.map(async vsix => {
try {
const manifest = await this.installVSIX(vsix, { ...installOptions, isBuiltin: false }, force, output);
const manifest = await this.installVSIX(vsix, { isBuiltin: false, isMachineScoped }, force, output);
if (manifest) {
installedExtensionsManifests.push(manifest);
}
@ -200,7 +200,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
const galleryExtensions = new Map<string, IGalleryExtension>();
const result = await this.extensionGalleryService.getExtensions(extensions, extensions.some(e => e.installOptions.installPreReleaseVersion), CancellationToken.None);
const result = await this.extensionGalleryService.getExtensions(extensions, CancellationToken.None);
for (const extension of result) {
galleryExtensions.set(extension.identifier.id.toLowerCase(), extension);
}

View file

@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IExtensionsControlManifest, isTargetPlatformCompatible, TargetPlatform, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IReportedExtension, isTargetPlatformCompatible, TargetPlatform, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
@ -72,7 +72,7 @@ export class ExtensionManagementChannel implements IServerChannel {
case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer)));
case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer));
case 'updateExtensionScope': return this.service.updateExtensionScope(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer));
case 'getExtensionsControlManifest': return this.service.getExtensionsControlManifest();
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
throw new Error('Invalid call');
@ -169,8 +169,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
.then(extension => transformIncomingExtension(extension, null));
}
getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
return Promise.resolve(this.channel.call('getExtensionsControlManifest'));
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
registerParticipant() { throw new Error('Not Supported'); }

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { compareIgnoreCase } from 'vs/base/common/strings';
import { IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, ILocalExtension, IExtensionsControlManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, ILocalExtension, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
@ -117,12 +117,12 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge');
export function getMaliciousExtensionsSet(manifest: IExtensionsControlManifest): Set<string> {
export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<string> {
const result = new Set<string>();
if (manifest.malicious) {
for (const extension of manifest.malicious) {
result.add(extension.id);
for (const extension of report) {
if (extension.malicious) {
result.add(extension.id.id);
}
}

View file

@ -6,7 +6,7 @@
import { IAction } from 'vs/base/common/actions';
import { DeferredPromise } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IProgressService = createDecorator<IProgressService>('progressService');
@ -23,6 +23,12 @@ export interface IProgressService {
task: (progress: IProgress<IProgressStep>) => Promise<R>,
onDidCancel?: (choice?: number) => void
): Promise<R>;
registerProgressLocation(location: string, handle: ICustomProgressLocation): IDisposable;
}
export interface ICustomProgressLocation {
startProgress(): { progress: IProgress<IProgressStep>, token?: CancellationToken, stop(): void };
}
export interface IProgressIndicator {

View file

@ -8,12 +8,12 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ISocket, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
export interface IWebSocketFactory {
create(url: string, debugLabel: string): IWebSocket;
create(url: string): IWebSocket;
}
export interface IWebSocketCloseEvent {
@ -41,7 +41,6 @@ export interface IWebSocket {
readonly onClose: Event<IWebSocketCloseEvent | void>;
readonly onError: Event<any>;
traceSocketEvent?(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void;
send(data: ArrayBuffer | ArrayBufferView): void;
close(): void;
}
@ -51,8 +50,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
private readonly _onData = new Emitter<ArrayBuffer>();
public readonly onData = this._onData.event;
private readonly _onOpen = this._register(new Emitter<void>());
public readonly onOpen = this._onOpen.event;
public readonly onOpen: Event<void>;
private readonly _onClose = this._register(new Emitter<IWebSocketCloseEvent>());
public readonly onClose = this._onClose.event;
@ -60,7 +58,6 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
private readonly _onError = this._register(new Emitter<any>());
public readonly onError = this._onError.event;
private readonly _debugLabel: string;
private readonly _socket: WebSocket;
private readonly _fileReader: FileReader;
private readonly _queue: Blob[];
@ -69,15 +66,9 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
private readonly _socketMessageListener: (ev: MessageEvent) => void;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
SocketDiagnostics.traceSocketEvent(this._socket, this._debugLabel, type, data);
}
constructor(url: string, debugLabel: string) {
constructor(socket: WebSocket) {
super();
this._debugLabel = debugLabel;
this._socket = new WebSocket(url);
this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'BrowserWebSocket', url });
this._socket = socket;
this._fileReader = new FileReader();
this._queue = [];
this._isReading = false;
@ -87,7 +78,6 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
this._isReading = false;
const buff = <ArrayBuffer>(<any>event.target).result;
this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff);
this._onData.fire(buff);
if (this._queue.length > 0) {
@ -105,16 +95,11 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
};
this._socketMessageListener = (ev: MessageEvent) => {
const blob = (<Blob>ev.data);
this.traceSocketEvent(SocketDiagnosticsEventType.BrowserWebSocketBlobReceived, { type: blob.type, size: blob.size });
enqueue(blob);
enqueue(<Blob>ev.data);
};
this._socket.addEventListener('message', this._socketMessageListener);
this._register(dom.addDisposableListener(this._socket, 'open', (e) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Open);
this._onOpen.fire();
}));
this.onOpen = Event.fromDOMEventEmitter(this._socket, 'open');
// WebSockets emit error events that do not contain any real information
// Our only chance of getting to the root cause of an error is to
@ -149,8 +134,6 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
};
this._register(dom.addDisposableListener(this._socket, 'close', (e: CloseEvent) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Close, { code: e.code, reason: e.reason, wasClean: e.wasClean });
this._isClosed = true;
if (pendingErrorEvent) {
@ -174,10 +157,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
this._onClose.fire({ code: e.code, reason: e.reason, wasClean: e.wasClean, event: e });
}));
this._register(dom.addDisposableListener(this._socket, 'error', (err) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Error, { message: err?.message });
sendErrorSoon(err);
}));
this._register(dom.addDisposableListener(this._socket, 'error', sendErrorSoon));
}
send(data: ArrayBuffer | ArrayBufferView): void {
@ -185,13 +165,11 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
// Refuse to write data to closed WebSocket...
return;
}
this.traceSocketEvent(SocketDiagnosticsEventType.Write, data);
this._socket.send(data);
}
close(): void {
this._isClosed = true;
this.traceSocketEvent(SocketDiagnosticsEventType.Close);
this._socket.close();
this._socket.removeEventListener('message', this._socketMessageListener);
this.dispose();
@ -199,27 +177,16 @@ class BrowserWebSocket extends Disposable implements IWebSocket {
}
export const defaultWebSocketFactory = new class implements IWebSocketFactory {
create(url: string, debugLabel: string): IWebSocket {
return new BrowserWebSocket(url, debugLabel);
create(url: string): IWebSocket {
return new BrowserWebSocket(new WebSocket(url));
}
};
class BrowserSocket implements ISocket {
public readonly socket: IWebSocket;
public readonly debugLabel: string;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
if (typeof this.socket.traceSocketEvent === 'function') {
this.socket.traceSocketEvent(type, data);
} else {
SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data);
}
}
constructor(socket: IWebSocket, debugLabel: string) {
constructor(socket: IWebSocket) {
this.socket = socket;
this.debugLabel = debugLabel;
}
public dispose(): void {
@ -272,13 +239,13 @@ export class BrowserSocketFactory implements ISocketFactory {
this._webSocketFactory = webSocketFactory || defaultWebSocketFactory;
}
connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void {
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`, debugLabel);
const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
errorListener.dispose();
callback(undefined, new BrowserSocket(socket, debugLabel));
callback(undefined, new BrowserSocket(socket));
});
}
}

View file

@ -85,7 +85,7 @@ export interface IConnectCallback {
}
export interface ISocketFactory {
connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void;
connect(host: string, port: number, query: string, callback: IConnectCallback): void;
}
function createTimeoutCancellation(millis: number): CancellationToken {
@ -188,9 +188,9 @@ function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellat
return result.promise;
}
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, query: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, query: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
socketFactory.connect(host, port, query, debugLabel, (err: any, socket: ISocket | undefined) => {
socketFactory.connect(host, port, query, (err: any, socket: ISocket | undefined) => {
if (result.didTimeout) {
if (err) {
logService.error(err);
@ -231,7 +231,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
let socket: ISocket;
try {
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken);
} catch (error) {
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
options.logService.error(error);
@ -512,7 +512,7 @@ export class ReconnectionPermanentFailureEvent {
}
export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent;
export abstract class PersistentConnection extends Disposable {
abstract class PersistentConnection extends Disposable {
public static triggerPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void {
this._permanentFailure = true;
@ -521,15 +521,6 @@ export abstract class PersistentConnection extends Disposable {
this._permanentFailureHandled = handled;
this._instances.forEach(instance => instance._gotoPermanentFailure(this._permanentFailureMillisSinceLastIncomingData, this._permanentFailureAttempt, this._permanentFailureHandled));
}
public static debugTriggerReconnection() {
this._instances.forEach(instance => instance._beginReconnecting());
}
public static debugPauseSocketWriting() {
this._instances.forEach(instance => instance._pauseSocketWriting());
}
private static _permanentFailure: boolean = false;
private static _permanentFailureMillisSinceLastIncomingData: number = 0;
private static _permanentFailureAttempt: number = 0;
@ -687,10 +678,6 @@ export abstract class PersistentConnection extends Disposable {
safeDisposeProtocolAndSocket(this.protocol);
}
private _pauseSocketWriting(): void {
this.protocol.pauseSocketWriting();
}
protected abstract _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void>;
}

View file

@ -8,7 +8,7 @@ import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
export const nodeSocketFactory = new class implements ISocketFactory {
connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void {
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
const errorListener = (err: any) => callback(err, undefined);
const socket = net.createConnection({ host: host, port: port }, () => {
@ -34,7 +34,7 @@ export const nodeSocketFactory = new class implements ISocketFactory {
if (strData.indexOf('\r\n\r\n') >= 0) {
// headers received OK
socket.off('data', onData);
callback(undefined, new NodeSocket(socket, debugLabel));
callback(undefined, new NodeSocket(socket));
}
};
socket.on('data', onData);

View file

@ -16,7 +16,7 @@ import { isBoolean, isNumber } from 'vs/base/common/types';
import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { ILogService } from 'vs/platform/log/common/log';
import { IHTTPConfiguration, IRequestService } from 'vs/platform/request/common/request';
import { Agent, getProxyAgent } from 'vs/platform/request/node/proxy';
@ -67,7 +67,7 @@ export class RequestService extends Disposable implements IRequestService {
let shellEnv: typeof process.env | undefined = undefined;
try {
shellEnv = await getResolvedShellEnv(this.logService, this.environmentService.args, process.env);
shellEnv = await resolveShellEnv(this.logService, this.environmentService.args, process.env);
} catch (error) {
this.logService.error('RequestService#request resolving shell environment failed', error);
}

View file

@ -12,7 +12,7 @@ import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { parsePtyHostPort } from 'vs/platform/environment/common/environmentService';
import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { ILogService } from 'vs/platform/log/common/log';
import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -124,7 +124,7 @@ export class PtyHostService extends Disposable implements IPtyService {
}
try {
return await getResolvedShellEnv(this._logService, { _: [] }, process.env);
return await resolveShellEnv(this._logService, { _: [] }, process.env);
} catch (error) {
this._logService.error('ptyHost was unable to resolve shell environment', error);

View file

@ -416,7 +416,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
) {
if (await this.extensionManagementService.canInstall(extension)) {
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* set isMachineScoped value to prevent install and sync dialog in web */);
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* pass options to prevent install and sync dialog in web */);
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
removeFromSkipped.push(extension.identifier);
} else {

View file

@ -13,7 +13,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { IRemoteConsoleLog } from 'vs/base/common/console';
import { Emitter, Event } from 'vs/base/common/event';
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection';
import { IExtHostReadyMessage, IExtHostSocketMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@ -27,7 +27,7 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri
let userShellEnv: typeof process.env | undefined = undefined;
try {
userShellEnv = await getResolvedShellEnv(logService, environmentService.args, process.env);
userShellEnv = await resolveShellEnv(logService, environmentService.args, process.env);
} catch (error) {
logService.error('ExtensionHostConnection#buildUserEnvironment resolving shell environment failed', error);
userShellEnv = {};
@ -110,6 +110,7 @@ export class ExtensionHostConnection {
this._remoteAddress = remoteAddress;
this._extensionHostProcess = null;
this._connectionData = ExtensionHostConnection._toConnectionData(socket, initialDataChunk);
this._connectionData.socket.pause();
this._log(`New connection established.`);
}
@ -155,6 +156,7 @@ export class ExtensionHostConnection {
this._remoteAddress = remoteAddress;
this._log(`The client has reconnected.`);
const connectionData = ExtensionHostConnection._toConnectionData(_socket, initialDataChunk);
connectionData.socket.pause();
if (!this._extensionHostProcess) {
// The extension host didn't even start up yet

View file

@ -27,7 +27,7 @@ import { ProcessItem } from 'vs/base/common/processes';
import { ILog, Translations } from 'vs/workbench/services/extensions/common/extensionPoints';
import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { IBuiltInExtension } from 'vs/base/common/product';
import { IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { cwd } from 'vs/base/common/process';
import { IRemoteTelemetryService } from 'vs/server/remoteTelemetryService';
import { Promises } from 'vs/base/node/pfs';
@ -77,8 +77,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel {
};
if (environmentService.args['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] };
this.whenExtensionsReady = extensionManagementCLIService.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force'])
this.whenExtensionsReady = extensionManagementCLIService.installExtensions([], environmentService.args['install-builtin-extension'], !!environmentService.args['do-not-sync'], !!environmentService.args['force'])
.then(null, error => {
logService.error(error);
});
@ -90,7 +89,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel {
if (extensionsToInstall) {
const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input);
this.whenExtensionsReady
.then(() => extensionManagementCLIService.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force']))
.then(() => extensionManagementCLIService.installExtensions(idsOrVSIX, [], !!environmentService.args['do-not-sync'], !!environmentService.args['force']))
.then(null, error => {
logService.error(error);
});

View file

@ -12,7 +12,7 @@ import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
@ -114,8 +114,7 @@ class CliMain extends Disposable {
// Install Extension
else if (this.args['install-extension'] || this.args['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!this.args['do-not-sync'], installPreReleaseVersion: !!this.args['pre-release'] };
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.args['install-extension'] || []), this.args['install-builtin-extension'] || [], installOptions, !!this.args['force']);
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.args['install-extension'] || []), this.args['install-builtin-extension'] || [], !!this.args['do-not-sync'], !!this.args['force']);
}
// Uninstall Extension

View file

@ -497,9 +497,9 @@ export class RemoteExtensionHostAgentServer extends Disposable {
// Finally!
if (skipWebSocketFrames) {
this._handleWebSocketConnection(new NodeSocket(socket, `server-connection-${reconnectionToken}`), isReconnection, reconnectionToken);
this._handleWebSocketConnection(new NodeSocket(socket), isReconnection, reconnectionToken);
} else {
this._handleWebSocketConnection(new WebSocketNodeSocket(new NodeSocket(socket, `server-connection-${reconnectionToken}`), permessageDeflate, null, true), isReconnection, reconnectionToken);
this._handleWebSocketConnection(new WebSocketNodeSocket(new NodeSocket(socket), permessageDeflate, null, true), isReconnection, reconnectionToken);
}
}
@ -754,7 +754,6 @@ export class RemoteExtensionHostAgentServer extends Disposable {
}
}
protocol.sendPause();
protocol.sendControl(VSBuffer.fromString(JSON.stringify(startParams.port ? { debugPort: startParams.port } : {})));
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();
@ -767,7 +766,6 @@ export class RemoteExtensionHostAgentServer extends Disposable {
return this._rejectWebSocketConnection(logPrefix, protocol, `Duplicate reconnection token`);
}
protocol.sendPause();
protocol.sendControl(VSBuffer.fromString(JSON.stringify(startParams.port ? { debugPort: startParams.port } : {})));
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();

View file

@ -120,7 +120,6 @@ export interface ServerParsedArgs {
force?: boolean; // used by install-extension
'do-not-sync'?: boolean; // used by install-extension
'pre-release'?: boolean; // used by install-extension
'user-data-dir'?: string;
'builtin-extensions-dir'?: string;

View file

@ -71,7 +71,7 @@ CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function (
const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input));
if (Array.isArray(args.install) && args.install.length) {
try {
await cliService.installExtensions(revive(args.install), [], { isMachineScoped: true }, !!args.force, output);
await cliService.installExtensions(revive(args.install), [], true, !!args.force, output);
} catch (e) {
lines.push(e.message);
}

View file

@ -144,7 +144,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<I
});
defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
if (removed.length) {
const removedDefaultConfigurations = removed.map<IConfigurationDefaults>(extension => ({ overrides: objects.deepClone(extension.value), source: { id: extension.description.identifier.value, displayName: extension.description.displayName } }));
const removedDefaultConfigurations = removed.map<IConfigurationDefaults>(extension => ({ overrides: objects.deepClone(extension.value), extensionId: extension.description.identifier.value }));
configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations);
}
if (added.length) {
@ -161,7 +161,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
}
}
}
return { overrides, source: { id: extension.description.identifier.value, displayName: extension.description.displayName } };
return { overrides, extensionId: extension.description.identifier.value };
});
configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations);
}
@ -212,8 +212,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => {
validateProperties(configuration, extension);
configuration.id = node.id || extension.description.identifier.value;
configuration.extensionInfo = { id: extension.description.identifier.value, displayName: extension.description.displayName };
configuration.restrictedProperties = extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined;
configuration.extensionInfo = { id: extension.description.identifier.value, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined };
configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value;
configurations.push(configuration);
return configurations;

View file

@ -98,7 +98,6 @@ export class ExtHostLanguages implements ExtHostLanguagesShape {
command: undefined,
text: '',
detail: '',
busy: false
};
let soonHandle: IDisposable | undefined;
@ -116,8 +115,7 @@ export class ExtHostLanguages implements ExtHostLanguagesShape {
detail: data.detail ?? '',
severity: data.severity === LanguageStatusSeverity.Error ? Severity.Error : data.severity === LanguageStatusSeverity.Warning ? Severity.Warning : Severity.Info,
command: data.command && this._commands.toInternal(data.command, commandDisposables),
accessibilityInfo: data.accessibilityInformation,
busy: data.busy
accessibilityInfo: data.accessibilityInformation
});
}, 0);
};
@ -180,13 +178,6 @@ export class ExtHostLanguages implements ExtHostLanguagesShape {
set command(value) {
data.command = value;
updateAsync();
},
get busy() {
return data.busy;
},
set busy(value: boolean) {
data.busy = value;
updateAsync();
}
};
updateAsync();

View file

@ -15,9 +15,9 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
export class ExtHostProgress implements ExtHostProgressShape {
private _proxy: MainThreadProgressShape;
private readonly _proxy: MainThreadProgressShape;
private readonly _mapHandleToCancellationSource = new Map<number, CancellationTokenSource>();
private _handles: number = 0;
private _mapHandleToCancellationSource: Map<number, CancellationTokenSource> = new Map();
constructor(proxy: MainThreadProgressShape) {
this._proxy = proxy;
@ -42,9 +42,7 @@ export class ExtHostProgress implements ExtHostProgressShape {
const progressEnd = (handle: number): void => {
this._proxy.$progressEnd(handle);
this._mapHandleToCancellationSource.delete(handle);
if (source) {
source.dispose();
}
source?.dispose();
};
let p: Thenable<R>;
@ -56,7 +54,7 @@ export class ExtHostProgress implements ExtHostProgressShape {
throw err;
}
p.then(result => progressEnd(handle), err => progressEnd(handle));
Promise.resolve(p).finally(() => progressEnd(handle));
return p;
}

View file

@ -23,14 +23,14 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions';
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget, ExtensionHoverWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import {
UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction,
RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction,
ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction,
InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SwitchUnsupportedExtensionToPreReleaseExtensionAction
InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@ -68,7 +68,7 @@ import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { errorIcon, infoIcon, preReleaseIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
import { errorIcon, infoIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ViewContainerLocation } from 'vs/workbench/common/views';
@ -150,7 +150,6 @@ interface IExtensionEditorTemplate {
actionsAndStatusContainer: HTMLElement;
extensionActionBar: ActionBar;
status: HTMLElement;
preReleaseText: HTMLElement;
recommendation: HTMLElement;
navbar: NavBar;
content: HTMLElement;
@ -272,7 +271,6 @@ export class ExtensionEditor extends EditorPane {
}));
const status = append(actionsAndStatusContainer, $('.status'));
const preReleaseText = append(details, $('.pre-release-text'));
const recommendation = append(details, $('.recommendation'));
this._register(Event.chain(extensionActionBar.onDidRun)
@ -305,7 +303,6 @@ export class ExtensionEditor extends EditorPane {
rating,
actionsAndStatusContainer,
extensionActionBar,
preReleaseText,
status,
recommendation
};
@ -459,7 +456,6 @@ export class ExtensionEditor extends EditorPane {
]),
this.instantiationService.createInstance(SwitchToPreReleaseVersionAction),
this.instantiationService.createInstance(SwitchToReleasedVersionAction),
this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionAction),
this.instantiationService.createInstance(ToggleSyncExtensionAction),
new ExtensionEditorManageExtensionAction(this.scopedContextKeyService || this.contextKeyService, this.instantiationService),
];
@ -479,7 +475,6 @@ export class ExtensionEditor extends EditorPane {
this.transientDisposables.add(disposable);
}
this.setPreReleaseText(extension, template);
this.setStatus(extension, extensionStatus, template);
this.setRecommendationText(extension, template);
@ -528,30 +523,6 @@ export class ExtensionEditor extends EditorPane {
this.editorLoadComplete = true;
}
private setPreReleaseText(extension: IExtension, template: IExtensionEditorTemplate): void {
let preReleaseText: string | undefined;
reset(template.preReleaseText);
const disposables = this.transientDisposables.add(new DisposableStore());
const updatePreReleaseText = () => {
const newPreReleaseText = ExtensionHoverWidget.getPreReleaseMessage(extension);
if (preReleaseText !== newPreReleaseText) {
preReleaseText = newPreReleaseText;
disposables.clear();
reset(template.preReleaseText);
if (preReleaseText) {
append(template.preReleaseText, $(`span${ThemeIcon.asCSSSelector(preReleaseIcon)}`));
disposables.add(this.renderMarkdownText(preReleaseText, template.preReleaseText));
}
}
};
updatePreReleaseText();
this.transientDisposables.add(this.extensionsWorkbenchService.onChange(e => {
if (e && areSameExtensions(e.identifier, extension.identifier)) {
updatePreReleaseText();
}
}));
}
private setStatus(extension: IExtension, extensionStatus: ExtensionStatusAction, template: IExtensionEditorTemplate): void {
const disposables = new DisposableStore();
this.transientDisposables.add(disposables);
@ -564,7 +535,16 @@ export class ExtensionEditor extends EditorPane {
const statusIconActionBar = disposables.add(new ActionBar(template.status, { animated: false }));
statusIconActionBar.push(extensionStatus, { icon: true, label: false });
}
disposables.add(this.renderMarkdownText(status.message.value, append(template.status, $('.status-text'))));
const rendered = disposables.add(renderMarkdown(new MarkdownString(status.message.value, { isTrusted: true, supportThemeIcons: true }), {
actionHandler: {
callback: (content) => {
this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError);
},
disposables: disposables
}
}));
append(append(template.status, $('.status-text')),
rendered.element);
}
};
updateStatus();
@ -593,20 +573,6 @@ export class ExtensionEditor extends EditorPane {
this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText()));
}
private renderMarkdownText(markdownText: string, parent: HTMLElement): IDisposable {
const disposables = new DisposableStore();
const rendered = disposables.add(renderMarkdown(new MarkdownString(markdownText, { isTrusted: true, supportThemeIcons: true }), {
actionHandler: {
callback: (content) => {
this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError);
},
disposables: disposables
}
}));
append(parent, rendered.element);
return disposables;
}
override clearInput(): void {
this.contentDisposables.clear();
this.transientDisposables.clear();
@ -1802,6 +1768,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
if (link) {
collector.addRule(`.monaco-workbench .extension-editor .content .details .additional-details-container .resources-container a { color: ${link}; }`);
collector.addRule(`.monaco-workbench .extension-editor .content .feature-contributions a { color: ${link}; }`);
collector.addRule(`.monaco-workbench .extension-editor > .header > .details > .actions-status-container > .status > .status-text a { color: ${link}; }`);
}
const activeLink = theme.getColor(textLinkActiveForeground);
@ -1810,6 +1777,9 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
.monaco-workbench .extension-editor .content .details .additional-details-container .resources-container a:active { color: ${activeLink}; }`);
collector.addRule(`.monaco-workbench .extension-editor .content .feature-contributions a:hover,
.monaco-workbench .extension-editor .content .feature-contributions a:active { color: ${activeLink}; }`);
collector.addRule(`.monaco-workbench .extension-editor > .header > .details > .actions-status-container > .status > .status-text a:hover,
.monaco-workbench .extension-editor > .header > .details > actions-status-container > .status > .status-text a:active { color: ${activeLink}; }`);
}
const buttonHoverBackgroundColor = theme.getColor(buttonHoverBackground);

View file

@ -8,7 +8,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Registry } from 'vs/platform/registry/common/platform';
import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
@ -74,7 +74,6 @@ import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensio
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Event } from 'vs/base/common/event';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { UnsupportedPreReleaseExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@ -275,11 +274,6 @@ CommandsRegistry.registerCommand({
'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."),
default: false
},
'installPreReleaseVersion': {
'type': 'boolean',
'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, VS Code installs the pre-release version of the extension if available."),
default: false
},
'donotSync': {
'type': 'boolean',
'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."),
@ -290,18 +284,14 @@ CommandsRegistry.registerCommand({
}
]
},
handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean, installPreReleaseVersion?: boolean, donotSync?: boolean }) => {
handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean, donotSync?: boolean }) => {
const extensionManagementService = accessor.get(IExtensionManagementService);
const extensionGalleryService = accessor.get(IExtensionGalleryService);
try {
if (typeof arg === 'string') {
const [extension] = await extensionGalleryService.getExtensions([{ id: arg }], CancellationToken.None);
if (extension) {
const installOptions: InstallOptions = {
isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */
installPreReleaseVersion: options?.installPreReleaseVersion
};
await extensionManagementService.installFromGallery(extension, installOptions);
await extensionManagementService.installFromGallery(extension, options?.donotSync ? { isMachineScoped: true } : undefined);
} else {
throw new Error(localize('notFound', "Extension '{0}' not found.", arg));
}
@ -1462,7 +1452,6 @@ const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Workbench
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(UnsupportedPreReleaseExtensionsChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);

View file

@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { dispose } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@ -64,7 +64,6 @@ import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { flatten } from 'vs/base/common/arrays';
import { isBoolean } from 'vs/base/common/types';
function getRelativeDateLabel(date: Date): string {
const delta = new Date().getTime() - date.getTime();
@ -137,7 +136,7 @@ export class PromptExtensionInstallFailureAction extends Action {
return;
}
if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.UnsupportedPreRelease].includes(<ExtensionManagementErrorCode>this.error.name)) {
if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious].includes(<ExtensionManagementErrorCode>this.error.name)) {
await this.dialogService.show(Severity.Info, getErrorMessage(this.error));
return;
}
@ -1126,68 +1125,6 @@ export class SwitchToReleasedVersionAction extends ExtensionAction {
}
}
export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends ExtensionAction {
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`;
constructor(
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtension', '', SwitchUnsupportedExtensionToPreReleaseExtensionAction.Class);
}
update(): void {
this.enabled = false;
if (!!this.extension && !!this.extension.local && this.extension.isUnsupported && !isBoolean(this.extension.isUnsupported) && this.extension.state === ExtensionState.Installed) {
this.enabled = true;
this.label = localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}'", this.extension.isUnsupported.preReleaseExtension.displayName);
}
}
override async run(): Promise<any> {
if (!!this.extension && !!this.extension.local && this.extension.isUnsupported && !isBoolean(this.extension.isUnsupported)) {
const gallery = (await this.galleryService.getExtensions([{ id: this.extension.isUnsupported.preReleaseExtension.id }], true, CancellationToken.None))[0];
return this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction, this.extension.local, gallery, false).run();
}
}
}
export class SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction extends Action {
constructor(
private readonly local: ILocalExtension,
private readonly gallery: IGalleryExtension,
private promptReload: boolean,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IProductService private readonly productService: IProductService,
@IHostService private readonly hostService: IHostService,
@IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService,
@INotificationService private readonly notificationService: INotificationService,
) {
super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtensionCommand', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}'", gallery.displayName));
}
override async run(): Promise<any> {
await Promise.all([
this.extensionManagementService.uninstall(this.local),
this.extensionManagementService.installFromGallery(this.gallery, { installPreReleaseVersion: true, isMachineScoped: this.local.isMachineScoped })
.then(local => this.workbenchExtensionEnablementService.setEnablement([this.local], EnablementState.EnabledGlobally)),
]);
if (this.promptReload) {
this.notificationService.prompt(
Severity.Info,
localize('SwitchToAnotherReleaseExtension.successReload', "Please reload {0} to complete switching to the '{1}' extension.", this.productService.nameLong, this.gallery.displayName),
[{
label: localize('reloadNow', "Reload Now"),
run: () => this.hostService.reload()
}],
{ sticky: true }
);
}
}
}
export class InstallAnotherVersionAction extends ExtensionAction {
static readonly ID = 'workbench.extensions.action.install.anotherVersion';
@ -2240,21 +2177,12 @@ export class ExtensionStatusAction extends ExtensionAction {
return;
}
if (this.extension.isMalicious) {
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true);
return;
}
if (this.extension.isUnsupported) {
if (isBoolean(this.extension.isUnsupported)) {
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension no longer supported.")) }, true);
} else {
const link = `[${this.extension.isUnsupported.preReleaseExtension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.isUnsupported.preReleaseExtension.id]))}`)})`;
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is no longer supported and is now part of the {0} extension as a pre-release version. We recommend that you switch to it.", link)) }, true);
}
return;
}
if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) {
if (this.extension.isMalicious) {
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true);
return;
}
if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) {
const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform());
const message = new MarkdownString(`${localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", this.extension.displayName || this.extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform))} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-platform-specific-extensions)`);

View file

@ -825,7 +825,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
}
private checkForMaliciousExtensions(): Promise<void> {
return this.extensionsManagementService.getExtensionsControlManifest().then(report => {
return this.extensionsManagementService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {

View file

@ -176,7 +176,7 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget {
return;
}
if (!this.extension.local?.isPreReleaseVersion && !this.extension.gallery?.properties.isPreReleaseVersion) {
if (!this.extension.local?.isPreReleaseVersion) {
return;
}
@ -482,9 +482,9 @@ export class ExtensionHoverWidget extends ExtensionWidget {
const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true });
markdown.appendMarkdown(`**${this.extension.displayName}**&nbsp;_v${this.extension.version}_`);
if (this.extension.local?.isPreReleaseVersion || this.extension.gallery?.properties.isPreReleaseVersion) {
if (this.extension.state === ExtensionState.Installed && this.extension.local?.isPreReleaseVersion) {
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
markdown.appendMarkdown(`&nbsp;<span style="color:#ffffff;background-color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">&nbsp;${localize('pre-release-label', "Pre-Release")}&nbsp;</span>`);
markdown.appendMarkdown(`&nbsp;<span style="color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">$(${preReleaseIcon.id})</span>`);
}
markdown.appendText(`\n`);
@ -500,13 +500,18 @@ export class ExtensionHoverWidget extends ExtensionWidget {
markdown.appendText(`\n`);
}
const preReleaseMessage = ExtensionHoverWidget.getPreReleaseMessage(this.extension);
const preReleaseMessage = this.getPreReleaseMessage(this.extension);
if (preReleaseMessage) {
markdown.appendMarkdown(preReleaseMessage);
markdown.appendText(`\n`);
}
const extensionRuntimeStatus = this.extensionsWorkbenchService.getExtensionStatus(this.extension);
const extensionStatus = this.extensionStatusAction.status;
const reloadRequiredMessage = this.reloadAction.enabled ? this.reloadAction.tooltip : '';
const recommendationMessage = this.getRecommendationMessage(this.extension);
if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage || preReleaseMessage) {
if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage) {
markdown.appendMarkdown(`---`);
markdown.appendText(`\n`);
@ -549,12 +554,6 @@ export class ExtensionHoverWidget extends ExtensionWidget {
markdown.appendText(`\n`);
}
if (preReleaseMessage) {
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
markdown.appendMarkdown(`<span style="color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">$(${preReleaseIcon.id})</span>&nbsp;${preReleaseMessage}`);
markdown.appendText(`\n`);
}
if (recommendationMessage) {
markdown.appendMarkdown(recommendationMessage);
markdown.appendText(`\n`);
@ -576,15 +575,17 @@ export class ExtensionHoverWidget extends ExtensionWidget {
return `<span style="color:${bgColor ? Color.Format.CSS.formatHex(bgColor) : '#ffffff'};">$(${starEmptyIcon.id})</span>&nbsp;${recommendation.reasonText}`;
}
static getPreReleaseMessage(extension: IExtension): string | undefined {
private getPreReleaseMessage(extension: IExtension): string | undefined {
if (!extension.hasPreReleaseVersion) {
return undefined;
}
if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) {
if (extension.state === ExtensionState.Installed && extension.local?.isPreReleaseVersion) {
return undefined;
}
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`;
return localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink);
const message = localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink);
return `<span style="color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">$(${preReleaseIcon.id})</span>&nbsp;${message}`;
}
}

View file

@ -14,10 +14,10 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier, IExtensionsControlManifest
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IHostService } from 'vs/workbench/services/host/browser/host';
@ -186,7 +186,6 @@ class Extension implements IExtension {
}
public isMalicious: boolean = false;
public isUnsupported: boolean | { preReleaseExtension: { id: string, displayName: string } } = false;
get installCount(): number | undefined {
return this.gallery ? this.gallery.installCount : undefined;
@ -419,43 +418,28 @@ class Extensions extends Disposable {
return this.local;
}
async syncLocalWithGalleryExtension(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): Promise<boolean> {
async syncLocalWithGalleryExtension(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): Promise<boolean> {
const extension = this.getInstalledExtensionMatchingGallery(gallery);
if (!extension?.local) {
if (!extension) {
return false;
}
let hasChanged: boolean = false;
const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier));
if (extension.isMalicious !== isMalicious) {
extension.isMalicious = isMalicious;
hasChanged = true;
if (maliciousExtensionSet.has(extension.identifier.id)) {
extension.isMalicious = true;
}
const compatible = await this.getCompatibleExtension(gallery, !!extension.local?.isPreReleaseVersion);
if (compatible) {
if (!compatible) {
return false;
}
// Sync the local extension with gallery extension if local extension doesnot has metadata
if (extension.local) {
const local = extension.local.identifier.uuid ? extension.local : await this.server.extensionManagementService.updateMetadata(extension.local, { id: compatible.identifier.uuid, publisherDisplayName: compatible.publisherDisplayName, publisherId: compatible.publisherId });
extension.local = local;
extension.gallery = compatible;
hasChanged = true;
}
const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined;
if (unsupportedPreRelease) {
if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) {
extension.isUnsupported = { preReleaseExtension: unsupportedPreRelease };
hasChanged = true;
}
} else if (extension.isUnsupported) {
extension.isUnsupported = false;
hasChanged = true;
}
if (hasChanged) {
this._onChange.fire({ extension });
return true;
}
return hasChanged;
return false;
}
private async getCompatibleExtension(extensionOrIdentifier: IGalleryExtension | IExtensionIdentifier, includePreRelease: boolean): Promise<IGalleryExtension | null> {
@ -765,10 +749,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
options.text = options.text ? this.resolveQueryText(options.text) : options.text;
options.includePreRelease = isUndefined(options.includePreRelease) ? this.preferPreReleases : options.includePreRelease;
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
const report = await this.extensionManagementService.getExtensionsReport();
const maliciousSet = getMaliciousExtensionsSet(report);
try {
const result = await this.galleryService.query(options, token);
return mapPager(result, gallery => this.fromGallery(gallery, extensionsControlManifest));
return mapPager(result, gallery => this.fromGallery(gallery, maliciousSet));
} catch (error) {
if (/No extension gallery service configured/.test(error.message)) {
return Promise.resolve(singlePagePager([]));
@ -905,11 +890,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return extension || extensions[0];
}
private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): IExtension {
Promise.all([
this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false),
this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false),
this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false)
this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false),
this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false),
this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false)
])
.then(result => {
if (result[0] || result[1] || result[2]) {
@ -922,15 +907,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return installed;
}
const extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), undefined, undefined, gallery);
if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) {
if (maliciousExtensionSet.has(extension.identifier.id)) {
extension.isMalicious = true;
}
const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined;
if (unsupportedPreRelease) {
if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) {
extension.isUnsupported = { preReleaseExtension: unsupportedPreRelease };
}
}
return extension;
}
@ -1052,10 +1031,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return false;
}
if (extension.isUnsupported) {
return false;
}
if (!extension.gallery) {
return false;
}

View file

@ -261,32 +261,13 @@
margin-top: 2px;
}
.extension-editor > .header > .details > .pre-release-text p,
.extension-editor > .header > .details > .actions-status-container > .status > .status-text p {
margin-top: 0px;
margin-bottom: 0px;
}
.extension-editor > .header > .details > .pre-release-text a,
.extension-editor > .header > .details > .actions-status-container > .status > .status-text a {
color: var(--vscode-textLink-foreground)
}
.extension-editor > .header > .details > .pre-release-text a:hover,
.extension-editor > .header > .details > .actions-status-container > .status > .status-text a:hover {
text-decoration: underline;
color: var(--vscode-textLink-activeForeground)
}
.extension-editor > .header > .details > .pre-release-text a:active,
.extension-editor > .header > .details > .actions-status-container > .status > .status-text a:active {
color: var(--vscode-textLink-activeForeground)
}
.extension-editor > .header > .details > .pre-release-text:not(:empty){
margin-top: 5px;
display: flex;
font-size: 90%;
}
.extension-editor > .header > .details > .recommendation {

View file

@ -60,6 +60,6 @@
}
/* codicon colors */
.codicon.codicon-extensions-pre-release {
.codicon .codicon-extensions-pre-release {
color: var(--vscode-extensionIcon-preReleaseForeground);
}

View file

@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContribution {
constructor(
@INotificationService private readonly notificationService: INotificationService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
this.notifyUnsupportedPreReleaseExtensions();
}
private async notifyUnsupportedPreReleaseExtensions(): Promise<void> {
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
if (!extensionsControlManifest.unsupportedPreReleaseExtensions) {
return;
}
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
const unsupportedLocalExtensionsWithIdentifiers: [ILocalExtension, IExtensionIdentifier][] = [];
for (const extension of installed) {
const preReleaseExtension = extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()];
if (preReleaseExtension) {
unsupportedLocalExtensionsWithIdentifiers.push([extension, { id: preReleaseExtension.id }]);
}
}
if (!unsupportedLocalExtensionsWithIdentifiers.length) {
return;
}
const unsupportedPreReleaseExtensions: [ILocalExtension, IGalleryExtension][] = [];
const galleryExensions = await this.extensionGalleryService.getExtensions(unsupportedLocalExtensionsWithIdentifiers.map(([, identifier]) => identifier), true, CancellationToken.None);
for (const gallery of galleryExensions) {
const unsupportedLocalExtension = unsupportedLocalExtensionsWithIdentifiers.find(([, identifier]) => areSameExtensions(identifier, gallery.identifier));
if (unsupportedLocalExtension) {
unsupportedPreReleaseExtensions.push([unsupportedLocalExtension[0], gallery]);
}
}
if (!unsupportedPreReleaseExtensions.length) {
return;
}
if (unsupportedPreReleaseExtensions.length === 1) {
const [local, gallery] = unsupportedPreReleaseExtensions[0];
const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1], true);
this.notificationService.notify({
severity: Severity.Info,
message: localize('unsupported prerelease message', "'{0}' extension is no longer supported and is now part of the '{1}' extension as a pre-release version. Would you like to switch to it?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName),
actions: {
primary: [action]
},
sticky: true
});
return;
}
}
}

View file

@ -76,7 +76,6 @@ export interface IExtension {
readonly local?: ILocalExtension;
gallery?: IGalleryExtension;
readonly isMalicious: boolean;
readonly isUnsupported: boolean | { preReleaseExtension: { id: string, displayName: string } };
}
export const SERVICE_ID = 'extensionsWorkbenchService';

View file

@ -218,7 +218,7 @@ suite('ExtensionRecommendationsService Test', () => {
onDidUninstallExtension: didUninstallEvent.event,
async getInstalled() { return []; },
async canInstall() { return true; },
async getExtensionsControlManifest() { return { malicious: [] }; },
async getExtensionsReport() { return []; },
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
});
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{

View file

@ -101,7 +101,7 @@ async function setupTest() {
onUninstallExtension: uninstallEvent.event,
onDidUninstallExtension: didUninstallEvent.event,
async getInstalled() { return []; },
async getExtensionsControlManifest() { return { malicious: [] }; },
async getExtensionsReport() { return []; },
async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) {
local.identifier.uuid = metadata.id;
local.publisherDisplayName = metadata.publisherDisplayName;

View file

@ -98,7 +98,7 @@ suite('ExtensionsListView Tests', () => {
onDidUninstallExtension: didUninstallEvent.event,
async getInstalled() { return []; },
async canInstall() { return true; },
async getExtensionsControlManifest() { return { malicious: [] }; },
async getExtensionsReport() { return []; },
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
});
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
@ -163,7 +163,7 @@ suite('ExtensionsListView Tests', () => {
setup(async () => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]);
instantiationService.stubPromise(IExtensionManagementService, 'getExtensgetExtensionsControlManifestionsReport', {});
instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []);

View file

@ -94,7 +94,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
onUninstallExtension: uninstallEvent.event,
onDidUninstallExtension: didUninstallEvent.event,
async getInstalled() { return []; },
async getExtensionsControlManifest() { return { malicious: [] }; },
async getExtensionsReport() { return []; },
async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) {
local.identifier.uuid = metadata.id;
local.publisherDisplayName = metadata.publisherDisplayName;

View file

@ -66,8 +66,9 @@ class EditorStatusContribution implements IWorkbenchContribution {
_storageService.onDidChangeValue(this._handleStorageChange, this, this._disposables);
this._restoreState();
_languageStatusService.onDidChange(this._update, this, this._disposables);
_editorService.onDidActiveEditorChange(this._update, this, this._disposables);
_editorService.onDidActiveEditorChange(() => this._update(), this, this._disposables);
_languageStatusService.onDidChange(() => this._update(), this, this._disposables);
_languageStatusService.onDidChangeBusy(() => this._update(true), this, this._disposables);
this._update();
_statusBarService.onDidChangeEntryVisibility(e => {
@ -136,11 +137,11 @@ class EditorStatusContribution implements IWorkbenchContribution {
return new LanguageStatusViewModel(combined, dedicated);
}
private _update(): void {
private _update(force?: boolean): void {
const model = this._createViewModel();
if (this._model?.isEqual(model)) {
if (this._model?.isEqual(model) && !force) {
return;
}
@ -160,20 +161,21 @@ class EditorStatusContribution implements IWorkbenchContribution {
const showSeverity = first.severity >= Severity.Warning;
const text = EditorStatusContribution._severityToComboCodicon(first.severity);
let isOneBusy = false;
let isBusy = false;
const ariaLabels: string[] = [];
const element = document.createElement('div');
for (const status of model.combined) {
element.appendChild(this._renderStatus(status, showSeverity, this._renderDisposables));
const thisIsBusy = this._languageStatusService.isBusy(status);
element.appendChild(this._renderStatus(status, showSeverity, thisIsBusy, this._renderDisposables));
ariaLabels.push(this._asAriaLabel(status));
isOneBusy = isOneBusy || status.busy;
isBusy = isBusy || thisIsBusy;
}
const props: IStatusbarEntry = {
name: localize('langStatus.name', "Editor Language Status"),
ariaLabel: localize('langStatus.aria', "Editor Language Status: {0}", ariaLabels.join(', next: ')),
tooltip: element,
command: ShowTooltipCommand,
text: isOneBusy ? `${text}\u00A0\u00A0$(sync~spin)` : text,
text: isBusy ? `${text}\u00A0\u00A0$(loading~spin)` : text,
};
if (!this._combinedEntry) {
this._combinedEntry = this._statusBarService.addEntry(props, EditorStatusContribution._id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.LEFT, compact: true });
@ -185,7 +187,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
// dedicated status bar items are shows as-is in the status bar
const newDedicatedEntries = new Map<string, IStatusbarEntryAccessor>();
for (const status of model.dedicated) {
const props = EditorStatusContribution._asStatusbarEntry(status);
const props = EditorStatusContribution._asStatusbarEntry(status, this._languageStatusService.isBusy(status));
let entry = this._dedicatedEntries.get(status.id);
if (!entry) {
entry = this._statusBarService.addEntry(props, status.id, StatusbarAlignment.RIGHT, 100.09999);
@ -199,7 +201,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
this._dedicatedEntries = newDedicatedEntries;
}
private _renderStatus(status: ILanguageStatus, showSeverity: boolean, store: DisposableStore): HTMLElement {
private _renderStatus(status: ILanguageStatus, showSeverity: boolean, isBusy: boolean, store: DisposableStore): HTMLElement {
const parent = document.createElement('div');
parent.classList.add('hover-language-status');
@ -221,7 +223,10 @@ class EditorStatusContribution implements IWorkbenchContribution {
const label = document.createElement('span');
label.classList.add('label');
dom.append(label, ...renderLabelWithIcons(status.busy ? `$(sync~spin)\u00A0\u00A0${status.label}` : status.label));
if (isBusy) {
dom.append(label, ...renderLabelWithIcons('$(loading~spin)\u00A0\u00A0'));
}
dom.append(label, ...renderLabelWithIcons(status.label));
left.appendChild(label);
const detail = document.createElement('span');
@ -299,7 +304,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
// ---
private static _asStatusbarEntry(item: ILanguageStatus): IStatusbarEntry {
private static _asStatusbarEntry(item: ILanguageStatus, isBusy: boolean): IStatusbarEntry {
let color: ThemeColor | undefined;
let backgroundColor: ThemeColor | undefined;
@ -313,7 +318,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
return {
name: localize('name.pattern', '{0} (Language Status)', item.name),
text: item.busy ? `${item.label}\u00A0\u00A0$(sync~spin)` : item.label,
text: isBusy ? `${item.label}\u00A0\u00A0$(loading~spin)` : item.label,
ariaLabel: item.accessibilityInfo?.label ?? item.label,
role: item.accessibilityInfo?.role,
tooltip: item.command?.tooltip || new MarkdownString(item.detail, true),

View file

@ -25,10 +25,6 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { firstOrDefault } from 'vs/base/common/arrays';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { PersistentConnection } from 'vs/platform/remote/common/remoteAgentConnection';
export class LabelContribution implements IWorkbenchContribution {
constructor(
@ -165,43 +161,6 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChan
workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready);
const enableDiagnostics = true;
if (enableDiagnostics) {
class TriggerReconnectAction extends Action2 {
constructor() {
super({
id: 'workbench.action.triggerReconnect',
title: { value: localize('triggerReconnect', "Connection: Trigger Reconnect"), original: 'Connection: Trigger Reconnect' },
category: CATEGORIES.Developer,
f1: true,
});
}
async run(accessor: ServicesAccessor): Promise<void> {
PersistentConnection.debugTriggerReconnection();
}
}
class PauseSocketWriting extends Action2 {
constructor() {
super({
id: 'workbench.action.pauseSocketWriting',
title: { value: localize('pauseSocketWriting', "Connection: Pause socket writing"), original: 'Connection: Pause socket writing' },
category: CATEGORIES.Developer,
f1: true,
});
}
async run(accessor: ServicesAccessor): Promise<void> {
PersistentConnection.debugPauseSocketWriting();
}
}
registerAction2(TriggerReconnectAction);
registerAction2(PauseSocketWriting);
}
const extensionKindSchema: IJSONSchema = {
type: 'string',
enum: [

View file

@ -1162,7 +1162,7 @@ export class DirtyDiffModel extends Disposable {
}
private diff(): Promise<IChange[] | null> {
return this.progressService.withProgress({ location: ProgressLocation.Scm, delay: 250 }, async () => {
return this.progressService.withProgress({ location: ProgressLocation.Scm }, async () => {
return this.getOriginalURIPromise().then(originalURI => {
if (this._disposed || this._model.isDisposed() || !originalURI) {
return Promise.resolve([]); // disposed

View file

@ -18,7 +18,6 @@ import { TestResultService } from 'vs/workbench/contrib/testing/common/testResul
import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage';
import { Convert, getInitializedMainTestCollection, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { isNative, isElectron } from 'vs/base/common/platform';
export const emptyOutputController = () => new LiveOutputController(
new Lazy(() => [newWriteableBufferStream(), Promise.resolve()]),
@ -312,7 +311,7 @@ suite('Workbench - Test Results Service', () => {
});
});
((isNative && !isElectron) ? test.skip /* TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 */ : test)('resultItemParents', function () {
test('resultItemParents', () => {
assert.deepStrictEqual([...resultItemParents(r, r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())!)], [
r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString()),
r.getStateById(new TestId(['ctrlId', 'id-a']).toString()),

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry';
@ -30,15 +31,211 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Emitter } from 'vs/base/common/event';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.'));
type PickerResult = 'back' | 'selected' | 'cancelled';
export class SelectColorThemeAction extends Action {
class MarketplaceThemesPicker {
static readonly ID = 'workbench.action.selectTheme';
static readonly LABEL = localize('selectTheme.label', "Color Theme");
static readonly INSTALL_ADDITIONAL = localize('installColorThemes', "Install Additional Color Themes...");
static readonly BROWSE_ADDITIONAL = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes...");
constructor(
id: string,
label: string,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService,
@ILogService private readonly logService: ILogService,
@IProgressService private progressService: IProgressService
) {
super(id, label);
}
override run(): Promise<void> {
return this.themeService.getColorThemes().then(themes => {
const currentTheme = this.themeService.getColorTheme();
const supportsGallery = this.extensionGalleryService.isEnabled();
const supportsGalleryPreview = supportsGallery && this.extensionResourceLoaderService.supportsExtensionGalleryResources;
const picks: QuickPickInput<ThemeItem>[] = [
...(supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.BROWSE_ADDITIONAL) : []),
...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")),
...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")),
...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")),
...(supportsGallery && !supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.INSTALL_ADDITIONAL) : []),
];
let selectThemeTimeout: number | undefined;
const selectTheme = (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => {
if (selectThemeTimeout) {
clearTimeout(selectThemeTimeout);
}
selectThemeTimeout = window.setTimeout(() => {
selectThemeTimeout = undefined;
const newTheme = (theme ?? currentTheme) as IWorkbenchColorTheme;
this.themeService.setColorTheme(newTheme, applyTheme ? 'auto' : 'preview').then(undefined,
err => {
onUnexpectedError(err);
this.themeService.setColorTheme(currentTheme.id, undefined);
}
);
}, applyTheme ? 0 : 200);
};
return new Promise((s, _) => {
const browseInstalledThemes = (activeItemId: string | undefined) => {
let isCompleted = false;
const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId);
const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
quickpick.items = picks;
quickpick.sortByLabel = false;
quickpick.matchOnDescription = true;
quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)");
quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem];
quickpick.canSelectMany = false;
quickpick.onDidAccept(async _ => {
const themeItem = quickpick.activeItems[0];
if (!themeItem || themeItem.theme === undefined) { // 'pick in marketplace' entry
if (supportsGalleryPreview) {
browseMarketplaceThemes(quickpick.value);
} else {
openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`);
}
} else {
let themeToSet = themeItem.theme;
selectTheme(themeToSet, true);
}
isCompleted = true;
quickpick.hide();
s();
});
quickpick.onDidTriggerItemButton(e => {
if (isItem(e.item)) {
const extensionId = e.item.theme?.extensionData?.extensionId;
if (extensionId) {
openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`);
} else {
openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`);
}
}
});
quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false));
quickpick.onDidHide(() => {
if (!isCompleted) {
selectTheme(currentTheme, true);
s();
isCompleted = true;
}
});
quickpick.show();
};
const browseMarketplaceThemes = (value: string) => {
let isCompleted = false;
const marketplaceThemes = new MarketplaceThemes(this.extensionGalleryService, this.extensionManagementService, this.themeService, this.logService);
const mp_quickpick = this.quickInputService.createQuickPick<ThemeItem>();
mp_quickpick.items = [];
mp_quickpick.sortByLabel = false;
mp_quickpick.matchOnDescription = true;
mp_quickpick.buttons = [this.quickInputService.backButton];
mp_quickpick.title = 'Marketplace Themes';
mp_quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview");
mp_quickpick.canSelectMany = false;
mp_quickpick.onDidChangeValue(() => marketplaceThemes.trigger(mp_quickpick.value));
mp_quickpick.onDidAccept(async _ => {
let themeItem = mp_quickpick.activeItems[0];
if (themeItem?.galleryExtension) {
isCompleted = true;
mp_quickpick.hide();
const success = await this.installExtension(themeItem.galleryExtension);
if (success) {
selectTheme(themeItem.theme, true);
}
s();
}
});
mp_quickpick.onDidTriggerItemButton(e => {
if (isItem(e.item)) {
const extensionId = e.item.theme?.extensionData?.extensionId;
if (extensionId) {
openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`);
} else {
openExtensionViewlet(this.paneCompositeService, `category:themes ${mp_quickpick.value}`);
}
}
});
mp_quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false));
mp_quickpick.onDidHide(() => {
if (!isCompleted) {
selectTheme(currentTheme, true);
isCompleted = true;
}
marketplaceThemes.dispose();
});
mp_quickpick.onDidTriggerButton(e => {
if (e === this.quickInputService.backButton) {
mp_quickpick.hide();
browseInstalledThemes(undefined);
}
});
marketplaceThemes.onDidChange(() => {
let items = marketplaceThemes.themes;
if (marketplaceThemes.isSearching) {
items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true });
}
const activeItemId = mp_quickpick.activeItems[0]?.id;
const newActiveItem = activeItemId ? items.find(i => isItem(i) && i.id === activeItemId) : undefined;
mp_quickpick.items = items;
if (newActiveItem) {
mp_quickpick.activeItems = [newActiveItem as ThemeItem];
}
});
marketplaceThemes.trigger(value);
mp_quickpick.show();
};
browseInstalledThemes(currentTheme.id);
});
});
}
private async installExtension(galleryExtension: IGalleryExtension) {
try {
openExtensionViewlet(this.paneCompositeService, `@id:${galleryExtension.identifier.id}`);
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('installing extensions', "Installing Extension {0}...", galleryExtension.displayName)
}, async () => {
await this.extensionManagementService.installFromGallery(galleryExtension);
});
return true;
} catch (e) {
this.logService.error(`Problem installing extension ${galleryExtension.identifier.id}`, e);
return false;
}
}
}
class MarketplaceThemes {
private readonly _installedExtensions: Promise<Set<string>>;
private readonly _marketplaceExtensions: Set<string> = new Set();
private readonly _marketplaceThemes: ThemeItem[] = [];
@ -50,15 +247,10 @@ class MarketplaceThemesPicker {
private readonly _queryDelayer = new ThrottledDelayer<void>(200);
constructor(
private readonly getMarketplaceColorThemes: (publisher: string, name: string, version: string) => Promise<IWorkbenchTheme[]>,
private readonly marketplaceQuery: string,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ILogService private readonly logService: ILogService,
@IProgressService private readonly progressService: IProgressService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService
private extensionGalleryService: IExtensionGalleryService,
extensionManagementService: IExtensionManagementService,
private themeService: IWorkbenchThemeService,
private logService: ILogService
) {
this._installedExtensions = extensionManagementService.getInstalled().then(installed => {
const result = new Set<string>();
@ -98,7 +290,7 @@ class MarketplaceThemesPicker {
try {
const installedExtensions = await this._installedExtensions;
const options = { text: `${this.marketplaceQuery} ${value}`, pageSize: 20 };
const options = { text: `category:themes ${value}`, pageSize: 10 };
const pager = await this.extensionGalleryService.query(options, token);
for (let i = 0; i < pager.total && i < 2; i++) {
if (token.isCancellationRequested) {
@ -115,7 +307,7 @@ class MarketplaceThemesPicker {
const ext = gallery[i];
if (!installedExtensions.has(ext.identifier.id) && !this._marketplaceExtensions.has(ext.identifier.id)) {
this._marketplaceExtensions.add(ext.identifier.id);
const themes = await this.getMarketplaceColorThemes(ext.publisher, ext.name, ext.version);
const themes = await this.themeService.getMarketplaceColorThemes(ext.identifier.id, ext.version);
for (const theme of themes) {
this._marketplaceThemes.push({ id: theme.id, theme: theme, label: theme.label, description: `${ext.displayName} · ${ext.publisherDisplayName}`, galleryExtension: ext, buttons: [configureButton] });
}
@ -130,101 +322,11 @@ class MarketplaceThemesPicker {
if (!isPromiseCanceledError(e)) {
this.logService.error(`Error while searching for themes:`, e);
}
} finally {
this._searchOngoing = false;
this._onDidChange.fire();
}
this._searchOngoing = false;
this._onDidChange.fire();
}
public openQuickPick(value: string, currentTheme: IWorkbenchTheme | undefined, selectTheme: (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => void): Promise<PickerResult> {
let result: PickerResult | undefined = undefined;
return new Promise<PickerResult>((s, _) => {
const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
quickpick.items = [];
quickpick.sortByLabel = false;
quickpick.matchOnDescription = true;
quickpick.buttons = [this.quickInputService.backButton];
quickpick.title = 'Marketplace Themes';
quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview");
quickpick.canSelectMany = false;
quickpick.onDidChangeValue(() => this.trigger(quickpick.value));
quickpick.onDidAccept(async _ => {
let themeItem = quickpick.selectedItems[0];
if (themeItem?.galleryExtension) {
result = 'selected';
quickpick.hide();
const success = await this.installExtension(themeItem.galleryExtension);
if (success) {
selectTheme(themeItem.theme, true);
}
}
});
quickpick.onDidTriggerItemButton(e => {
if (isItem(e.item)) {
const extensionId = e.item.theme?.extensionData?.extensionId;
if (extensionId) {
openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`);
} else {
openExtensionViewlet(this.paneCompositeService, `${this.marketplaceQuery} ${quickpick.value}`);
}
}
});
quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false));
quickpick.onDidHide(() => {
if (result === undefined) {
selectTheme(currentTheme, true);
result = 'cancelled';
}
quickpick.dispose();
s(result);
});
quickpick.onDidTriggerButton(e => {
if (e === this.quickInputService.backButton) {
result = 'back';
quickpick.hide();
}
});
this.onDidChange(() => {
let items = this.themes;
if (this.isSearching) {
items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true });
}
const activeItemId = quickpick.activeItems[0]?.id;
const newActiveItem = activeItemId ? items.find(i => isItem(i) && i.id === activeItemId) : undefined;
quickpick.items = items;
if (newActiveItem) {
quickpick.activeItems = [newActiveItem as ThemeItem];
}
});
this.trigger(value);
quickpick.show();
});
}
private async installExtension(galleryExtension: IGalleryExtension) {
try {
openExtensionViewlet(this.paneCompositeService, `@id:${galleryExtension.identifier.id}`);
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('installing extensions', "Installing Extension {0}...", galleryExtension.displayName)
}, async () => {
await this.extensionManagementService.installFromGallery(galleryExtension);
});
return true;
} catch (e) {
this.logService.error(`Problem installing extension ${galleryExtension.identifier.id}`, e);
return false;
}
}
public dispose() {
if (this._tokenSource) {
this._tokenSource.cancel();
@ -236,240 +338,144 @@ class MarketplaceThemesPicker {
}
}
class InstalledThemesPicker {
abstract class AbstractIconThemeAction extends Action {
constructor(
private readonly installMessage: string,
private readonly browseMessage: string,
private readonly placeholderMessage: string,
private readonly marketplaceTag: string,
private readonly setTheme: (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => Promise<any>,
private readonly getMarketplaceColorThemes: (publisher: string, name: string, version: string) => Promise<IWorkbenchTheme[]>,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
@IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService,
@IInstantiationService private readonly instantiationService: IInstantiationService
id: string,
label: string,
private readonly quickInputService: IQuickInputService,
private readonly extensionGalleryService: IExtensionGalleryService,
private readonly paneCompositeService: IPaneCompositePartService
) {
super(id, label);
}
public async openQuickPick(picks: QuickPickInput<ThemeItem>[], currentTheme: IWorkbenchTheme) {
let marketplaceThemePicker: MarketplaceThemesPicker | undefined;
if (this.extensionGalleryService.isEnabled()) {
if (this.extensionResourceLoaderService.supportsExtensionGalleryResources) {
marketplaceThemePicker = this.instantiationService.createInstance(MarketplaceThemesPicker, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag);
picks = [...configurationEntries(this.browseMessage), ...picks];
} else {
picks = [...picks, ...configurationEntries(this.installMessage)];
}
}
protected abstract get builtInEntry(): QuickPickInput<ThemeItem>;
protected abstract get installMessage(): string;
protected abstract get placeholderMessage(): string;
protected abstract get marketplaceTag(): string;
protected abstract setTheme(id: string, settingsTarget: ThemeSettingTarget): Promise<any>;
protected pick(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme) {
let picks: QuickPickInput<ThemeItem>[] = [this.builtInEntry, ...toEntries(themes), ...(this.extensionGalleryService.isEnabled() ? configurationEntries(this.installMessage) : [])];
let selectThemeTimeout: number | undefined;
const selectTheme = (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => {
const selectTheme = (theme: ThemeItem, applyTheme: boolean) => {
if (selectThemeTimeout) {
clearTimeout(selectThemeTimeout);
}
selectThemeTimeout = window.setTimeout(() => {
selectThemeTimeout = undefined;
const newTheme = (theme ?? currentTheme) as IWorkbenchTheme;
this.setTheme(newTheme, applyTheme ? 'auto' : 'preview').then(undefined,
const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id;
this.setTheme(themeId, applyTheme ? 'auto' : 'preview').then(undefined,
err => {
onUnexpectedError(err);
this.setTheme(currentTheme, undefined);
this.setTheme(currentTheme.id, undefined);
}
);
}, applyTheme ? 0 : 200);
};
const pickInstalledThemes = (activeItemId: string | undefined) => {
return new Promise<void>((s, _) => {
let isCompleted = false;
return new Promise<void>((s, _) => {
let isCompleted = false;
const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId);
const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
quickpick.items = picks;
quickpick.placeholder = this.placeholderMessage;
quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem];
quickpick.canSelectMany = false;
quickpick.onDidAccept(async _ => {
isCompleted = true;
const theme = quickpick.selectedItems[0];
if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry
if (marketplaceThemePicker) {
const res = await marketplaceThemePicker.openQuickPick(quickpick.value, currentTheme, selectTheme);
if (res === 'back') {
await pickInstalledThemes(undefined);
}
} else {
openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`);
}
} else {
selectTheme(theme.theme, true);
}
quickpick.hide();
s();
});
quickpick.onDidChangeActive(themes => selectTheme(themes[0].theme, false));
quickpick.onDidHide(() => {
if (!isCompleted) {
selectTheme(currentTheme, true);
s();
}
quickpick.dispose();
});
quickpick.onDidTriggerItemButton(e => {
if (isItem(e.item)) {
const extensionId = e.item.theme?.extensionData?.extensionId;
if (extensionId) {
openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`);
} else {
openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`);
}
}
});
quickpick.show();
const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id);
const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
quickpick.items = picks;
quickpick.placeholder = this.placeholderMessage;
quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem];
quickpick.canSelectMany = false;
quickpick.onDidAccept(_ => {
const theme = quickpick.activeItems[0];
if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry
openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`);
} else {
selectTheme(theme, true);
}
isCompleted = true;
quickpick.hide();
s();
});
};
await pickInstalledThemes(currentTheme.id);
marketplaceThemePicker?.dispose();
quickpick.onDidChangeActive(themes => selectTheme(themes[0], false));
quickpick.onDidHide(() => {
if (!isCompleted) {
selectTheme(currentTheme, true);
s();
}
});
quickpick.show();
});
}
}
const SelectColorThemeCommandId = 'workbench.action.selectTheme';
class SelectFileIconThemeAction extends AbstractIconThemeAction {
registerAction2(class extends Action2 {
static readonly ID = 'workbench.action.selectIconTheme';
static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme");
constructor() {
super({
id: SelectColorThemeCommandId,
title: localize('selectTheme.label', "Color Theme"),
category: CATEGORIES.Preferences,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyT)
}
});
constructor(
id: string,
label: string,
@IQuickInputService quickInputService: IQuickInputService,
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IPaneCompositePartService paneCompositeService: IPaneCompositePartService
) {
super(id, label, quickInputService, extensionGalleryService, paneCompositeService);
}
override async run(accessor: ServicesAccessor) {
const themeService = accessor.get(IWorkbenchThemeService);
const installMessage = localize('installColorThemes', "Install Additional Color Themes...");
const browseMessage = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes...");
const placeholderMessage = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)");
const marketplaceTag = 'category:themes';
const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setColorTheme(theme as IWorkbenchColorTheme, settingsTarget);
const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceColorThemes(publisher, name, version);
const instantiationService = accessor.get(IInstantiationService);
const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes);
const themes = await themeService.getColorThemes();
const currentTheme = themeService.getColorTheme();
const picks: QuickPickInput<ThemeItem>[] = [
...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")),
...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")),
...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")),
];
await picker.openQuickPick(picks, currentTheme);
}
});
const SelectFileIconThemeCommandId = 'workbench.action.selectIconTheme';
registerAction2(class extends Action2 {
constructor() {
super({
id: SelectFileIconThemeCommandId,
title: localize('selectIconTheme.label', "File Icon Theme"),
category: CATEGORIES.Preferences,
f1: true
});
protected builtInEntry: QuickPickInput<ThemeItem> = { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') };
protected installMessage = localize('installIconThemes', "Install Additional File Icon Themes...");
protected placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme");
protected marketplaceTag = 'tag:icon-theme';
protected setTheme(id: string, settingsTarget: ThemeSettingTarget) {
return this.themeService.setFileIconTheme(id, settingsTarget);
}
override async run(accessor: ServicesAccessor) {
const themeService = accessor.get(IWorkbenchThemeService);
const installMessage = localize('installIconThemes', "Install Additional File Icon Themes...");
const browseMessage = '$(plus) ' + localize('browseIconThemes', "Browse Additional File Icon Themes...");
const placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme (Up/Down Keys to Preview)");
const marketplaceTag = 'tag:icon-theme';
const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setFileIconTheme(theme as IWorkbenchFileIconTheme, settingsTarget);
const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceFileIconThemes(publisher, name, version);
const instantiationService = accessor.get(IInstantiationService);
const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes);
const picks: QuickPickInput<ThemeItem>[] = [
{ type: 'separator', label: localize('fileIconThemeCategory', 'file icon themes') },
{ id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') },
...toEntries(await themeService.getFileIconThemes()),
];
await picker.openQuickPick(picks, themeService.getFileIconTheme());
override async run(): Promise<void> {
this.pick(await this.themeService.getFileIconThemes(), this.themeService.getFileIconTheme());
}
});
}
const SelectProductIconThemeCommandId = 'workbench.action.selectProductIconTheme';
registerAction2(class extends Action2 {
class SelectProductIconThemeAction extends AbstractIconThemeAction {
constructor() {
super({
id: SelectProductIconThemeCommandId,
title: localize('selectProductIconTheme.label', "Product Icon Theme"),
category: CATEGORIES.Preferences,
f1: true
});
static readonly ID = 'workbench.action.selectProductIconTheme';
static readonly LABEL = localize('selectProductIconTheme.label', "Product Icon Theme");
constructor(
id: string,
label: string,
@IQuickInputService quickInputService: IQuickInputService,
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IPaneCompositePartService paneCompositeService: IPaneCompositePartService
) {
super(id, label, quickInputService, extensionGalleryService, paneCompositeService);
}
override async run(accessor: ServicesAccessor) {
const themeService = accessor.get(IWorkbenchThemeService);
const installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes...");
const browseMessage = '$(plus) ' + localize('browseProductIconThemes', "Browse Additional Product Icon Themes...");
const placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme (Up/Down Keys to Preview)");
const marketplaceTag = 'tag:product-icon-theme';
const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setProductIconTheme(theme as IWorkbenchProductIconTheme, settingsTarget);
const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceProductIconThemes(publisher, name, version);
const instantiationService = accessor.get(IInstantiationService);
const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes);
const picks: QuickPickInput<ThemeItem>[] = [
{ type: 'separator', label: localize('productIconThemeCategory', 'product icon themes') },
{ id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') },
...toEntries(await themeService.getProductIconThemes()),
];
await picker.openQuickPick(picks, themeService.getProductIconTheme());
protected builtInEntry: QuickPickInput<ThemeItem> = { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') };
protected installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes...");
protected placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme");
protected marketplaceTag = 'tag:product-icon-theme';
protected setTheme(id: string, settingsTarget: ThemeSettingTarget) {
return this.themeService.setProductIconTheme(id, settingsTarget);
}
});
CommandsRegistry.registerCommand('workbench.action.previewColorTheme', async function (accessor: ServicesAccessor, extension: { publisher: string, name: string, version: string }, themeSettingsId?: string) {
const themeService = accessor.get(IWorkbenchThemeService);
const themes = await themeService.getMarketplaceColorThemes(extension.publisher, extension.name, extension.version);
for (const theme of themes) {
if (!themeSettingsId || theme.settingsId === themeSettingsId) {
await themeService.setColorTheme(theme, 'preview');
return theme.settingsId;
}
override async run(): Promise<void> {
this.pick(await this.themeService.getProductIconThemes(), this.themeService.getProductIconTheme());
}
return undefined;
});
}
function configurationEntries(label: string): QuickPickInput<ThemeItem>[] {
return [
{
type: 'separator'
type: 'separator',
label: 'marketplace themes'
},
{
id: undefined,
@ -490,12 +496,12 @@ function openExtensionViewlet(paneCompositeService: IPaneCompositePartService, q
});
}
interface ThemeItem extends IQuickPickItem {
readonly id: string | undefined;
readonly theme?: IWorkbenchTheme;
readonly galleryExtension?: IGalleryExtension;
readonly label: string;
readonly description?: string;
readonly alwaysShow?: boolean;
id: string | undefined;
theme?: IWorkbenchTheme;
galleryExtension?: IGalleryExtension;
label: string;
description?: string;
alwaysShow?: boolean;
}
function isItem(i: QuickPickInput<ThemeItem>): i is ThemeItem {
@ -523,26 +529,27 @@ const configureButton: IQuickInputButton = {
iconClass: ThemeIcon.asClassName(manageExtensionIcon),
tooltip: localize('manage extension', "Manage Extension"),
};
class GenerateColorThemeAction extends Action {
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.generateColorTheme',
title: localize('generateColorTheme.label', "Generate Color Theme From Current Settings"),
category: CATEGORIES.Developer,
f1: true
});
static readonly ID = 'workbench.action.generateColorTheme';
static readonly LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings");
constructor(
id: string,
label: string,
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
@IEditorService private readonly editorService: IEditorService,
) {
super(id, label);
}
override run(accessor: ServicesAccessor) {
const themeService = accessor.get(IWorkbenchThemeService);
const theme = themeService.getColorTheme();
const colors = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution).getColors();
const colorIds = colors.map(c => c.id).sort();
const resultingColors: { [key: string]: string | null } = {};
const inherited: string[] = [];
for (const colorId of colorIds) {
override run(): Promise<any> {
let theme = this.themeService.getColorTheme();
let colors = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution).getColors();
let colorIds = colors.map(c => c.id).sort();
let resultingColors: { [key: string]: string | null } = {};
let inherited: string[] = [];
for (let colorId of colorIds) {
const color = theme.getColor(colorId, false);
if (color) {
resultingColors[colorId] = Color.Format.CSS.formatHexA(color, true);
@ -551,7 +558,7 @@ registerAction2(class extends Action2 {
}
}
const nullDefaults = [];
for (const id of inherited) {
for (let id of inherited) {
const color = theme.getColor(id);
if (color) {
resultingColors['__' + id] = Color.Format.CSS.formatHexA(color, true);
@ -559,7 +566,7 @@ registerAction2(class extends Action2 {
nullDefaults.push(id);
}
}
for (const id of nullDefaults) {
for (let id of nullDefaults) {
resultingColors['__' + id] = null;
}
let contents = JSON.stringify({
@ -570,15 +577,29 @@ registerAction2(class extends Action2 {
}, null, '\t');
contents = contents.replace(/\"__/g, '//"');
const editorService = accessor.get(IEditorService);
return editorService.openEditor({ resource: undefined, contents, mode: 'jsonc', options: { pinned: true } });
return this.editorService.openEditor({ resource: undefined, contents, mode: 'jsonc', options: { pinned: true } });
}
});
}
const category = localize('preferences', "Preferences");
const colorThemeDescriptor = SyncActionDescriptor.from(SelectColorThemeAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyT) });
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category);
const fileIconThemeDescriptor = SyncActionDescriptor.from(SelectFileIconThemeAction);
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(fileIconThemeDescriptor, 'Preferences: File Icon Theme', category);
const productIconThemeDescriptor = SyncActionDescriptor.from(SelectProductIconThemeAction);
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(productIconThemeDescriptor, 'Preferences: Product Icon Theme', category);
const generateColorThemeDescriptor = SyncActionDescriptor.from(GenerateColorThemeAction);
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', CATEGORIES.Developer.value);
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '4_themes',
command: {
id: SelectColorThemeCommandId,
id: SelectColorThemeAction.ID,
title: localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme")
},
order: 1
@ -587,7 +608,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '4_themes',
command: {
id: SelectFileIconThemeCommandId,
id: SelectFileIconThemeAction.ID,
title: localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme")
},
order: 2
@ -596,7 +617,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '4_themes',
command: {
id: SelectProductIconThemeCommandId,
id: SelectProductIconThemeAction.ID,
title: localize({ key: 'miSelectProductIconTheme', comment: ['&& denotes a mnemonic'] }, "&&Product Icon Theme")
},
order: 3
@ -606,7 +627,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '4_themes',
command: {
id: SelectColorThemeCommandId,
id: SelectColorThemeAction.ID,
title: localize('selectTheme.label', "Color Theme")
},
order: 1
@ -615,7 +636,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '4_themes',
command: {
id: SelectFileIconThemeCommandId,
id: SelectFileIconThemeAction.ID,
title: localize('themes.selectIconTheme.label', "File Icon Theme")
},
order: 2
@ -624,7 +645,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '4_themes',
command: {
id: SelectProductIconThemeCommandId,
id: SelectProductIconThemeAction.ID,
title: localize('themes.selectProductIconTheme.label', "Product Icon Theme")
},
order: 3

View file

@ -96,9 +96,9 @@ export class DefaultConfiguration extends Disposable {
private async updateCachedConfigurationDefaultsOverrides(): Promise<void> {
const cachedConfigurationDefaultsOverrides: IStringDictionary<any> = {};
const configurationDefaultsOverrides = this.configurationRegistry.getConfigurationDefaultsOverrides();
for (const [key, value] of configurationDefaultsOverrides) {
if (!OVERRIDE_PROPERTY_REGEX.test(key) && value.value !== undefined) {
cachedConfigurationDefaultsOverrides[key] = value.value;
for (const key of Object.keys(configurationDefaultsOverrides)) {
if (!OVERRIDE_PROPERTY_REGEX.test(key) && configurationDefaultsOverrides[key] !== undefined) {
cachedConfigurationDefaultsOverrides[key] = configurationDefaultsOverrides[key];
}
}
try {

View file

@ -36,9 +36,6 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w
import { delta, distinct } from 'vs/base/common/arrays';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
import { isUndefined } from 'vs/base/common/types';
import { localize } from 'vs/nls';
class Workspace extends BaseWorkspace {
initialized: boolean = false;
@ -1119,45 +1116,6 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo
}
}
class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenchContribution {
private readonly processedExperimentalSettings = new Set<string>();
private readonly configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
constructor(
@IWorkbenchAssignmentService private readonly workbenchAssignmentService: IWorkbenchAssignmentService
) {
super();
this.processExperimentalSettings(Object.keys(this.configurationRegistry.getConfigurationProperties()));
this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties }) => this.processExperimentalSettings(properties)));
}
private async processExperimentalSettings(properties: string[]): Promise<void> {
const overrides: IStringDictionary<any> = {};
const allProperties = this.configurationRegistry.getConfigurationProperties();
for (const property of properties) {
const schema = allProperties[property];
if (!schema.tags?.includes('experimental')) {
continue;
}
if (this.processedExperimentalSettings.has(property)) {
continue;
}
this.processedExperimentalSettings.add(property);
try {
const value = await this.workbenchAssignmentService.getTreatment(`config.${property}`);
if (!isUndefined(value) && !equals(value, schema.default)) {
overrides[property] = value;
}
} catch (error) {/*ignore */ }
}
if (Object.keys(overrides).length) {
this.configurationRegistry.registerDefaultConfigurations([{ overrides, source: localize('experimental', "Experiments") }]);
}
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored);
workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(UpdateExperimentalSettingsDefaults, LifecyclePhase.Restored);

View file

@ -44,8 +44,7 @@ suite('DefaultConfiguration', () => {
teardown(() => {
configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations());
const configurationDefaultsOverrides = configurationRegistry.getConfigurationDefaultsOverrides();
configurationRegistry.deregisterDefaultConfigurations([...configurationDefaultsOverrides.keys()].map(key => ({ extensionId: configurationDefaultsOverrides.get(key)?.source, overrides: { [key]: configurationDefaultsOverrides.get(key)?.value } })));
configurationRegistry.deregisterDefaultConfigurations([{ overrides: configurationRegistry.getConfigurationDefaultsOverrides() }]);
});
test('configuration default overrides are read from environment', async () => {

View file

@ -5,7 +5,7 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, TargetPlatform, ExtensionManagementError, ExtensionManagementErrorCode
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, TargetPlatform, ExtensionManagementError, ExtensionManagementErrorCode
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage } from 'vs/platform/extensions/common/extensions';
@ -28,7 +28,6 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { isUndefined } from 'vs/base/common/types';
export class ExtensionManagementService extends Disposable implements IWorkbenchExtensionManagementService {
@ -293,7 +292,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
}
if (servers.length) {
if (!installOptions || isUndefined(installOptions.isMachineScoped)) {
if (!installOptions) {
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
installOptions = { isMachineScoped, isBuiltin: false };
}
@ -370,17 +369,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return false;
}
getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
getExtensionsReport(): Promise<IReportedExtension[]> {
if (this.extensionManagementServerService.localExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
if (this.extensionManagementServerService.webExtensionManagementServer) {
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
}
return Promise.resolve({ malicious: [] });
return Promise.resolve([]);
}
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {

View file

@ -450,7 +450,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection, 'renderer-exthost')));
resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection)));
});
// Now that the named pipe listener is installed, start the ext host process

View file

@ -123,10 +123,10 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
let socket: NodeSocket | WebSocketNodeSocket;
if (msg.skipWebSocketFrames) {
socket = new NodeSocket(handle, 'extHost-socket');
socket = new NodeSocket(handle);
} else {
const inflateBytes = VSBuffer.wrap(Buffer.from(msg.inflateBytes, 'base64'));
socket = new WebSocketNodeSocket(new NodeSocket(handle, 'extHost-socket'), msg.permessageDeflate, inflateBytes, false);
socket = new WebSocketNodeSocket(new NodeSocket(handle), msg.permessageDeflate, inflateBytes, false);
}
if (protocol) {
// reconnection case
@ -134,11 +134,9 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
disconnectRunner2.cancel();
protocol.beginAcceptReconnection(socket, initialDataChunk);
protocol.endAcceptReconnection();
protocol.sendResume();
} else {
clearTimeout(timer);
protocol = new PersistentProtocol(socket, initialDataChunk);
protocol.sendResume();
protocol.onDidDispose(() => onTerminate('renderer disconnected'));
resolve(protocol);
@ -176,7 +174,7 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
const socket = net.createConnection(pipeName, () => {
socket.removeListener('error', reject);
resolve(new PersistentProtocol(new NodeSocket(socket, 'extHost-renderer')));
resolve(new PersistentProtocol(new NodeSocket(socket)));
});
socket.once('error', reject);

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { compare } from 'vs/base/common/strings';
import { ITextModel } from 'vs/editor/common/model';
@ -15,6 +15,7 @@ import { LanguageSelector } from 'vs/editor/common/modes/languageSelector';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProgressService, Progress } from 'vs/platform/progress/common/progress';
export interface ILanguageStatus {
readonly id: string;
@ -23,7 +24,6 @@ export interface ILanguageStatus {
readonly severity: Severity;
readonly label: string;
readonly detail: string;
readonly busy: boolean;
readonly source: string;
readonly command: Command | undefined;
readonly accessibilityInfo: IAccessibilityInformation | undefined;
@ -41,9 +41,13 @@ export interface ILanguageStatusService {
onDidChange: Event<void>;
onDidChangeBusy: Event<ILanguageStatus>;
addStatus(status: ILanguageStatus): IDisposable;
getLanguageStatus(model: ITextModel): ILanguageStatus[];
isBusy(status: ILanguageStatus): boolean;
}
@ -52,11 +56,47 @@ class LanguageStatusServiceImpl implements ILanguageStatusService {
declare _serviceBrand: undefined;
private readonly _provider = new LanguageFeatureRegistry<ILanguageStatus>();
readonly onDidChange: Event<any> = this._provider.onDidChange;
private readonly _busyStatus = new Map<ILanguageStatus, number>();
private readonly _onDidChangeBusy = new Emitter<ILanguageStatus>();
readonly onDidChangeBusy: Event<ILanguageStatus> = this._onDidChangeBusy.event;
constructor(@IProgressService private readonly _progressService: IProgressService) { }
isBusy(status: ILanguageStatus): boolean {
return (this._busyStatus.get(status) ?? 0) > 0;
}
addStatus(status: ILanguageStatus): IDisposable {
return this._provider.register(status.selector, status);
const d1 = this._provider.register(status.selector, status);
const d2 = this._progressService.registerProgressLocation(status.id, {
startProgress: () => {
let value = this._busyStatus.get(status);
if (value === undefined) {
this._busyStatus.set(status, 1);
this._onDidChangeBusy.fire(status);
} else {
this._busyStatus.set(status, value + 1);
}
return {
progress: new Progress(_data => { }),
stop: () => {
let value = this._busyStatus.get(status);
if (value !== undefined) {
if (value === 1) {
this._busyStatus.delete(status);
this._onDidChangeBusy.fire(status);
} else {
this._busyStatus.set(status, value - 1);
}
}
}
};
}
});
return combinedDisposable(d1, d2);
}
getLanguageStatus(model: ITextModel): ILanguageStatus[] {

View file

@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, EditPresentationTypes, IExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationScope, EditPresentationTypes, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry';
import { EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
@ -44,7 +44,7 @@ export interface ISettingsGroup {
titleRange: IRange;
sections: ISettingsSection[];
order?: number;
extensionInfo?: IExtensionInfo;
extensionInfo?: IConfigurationExtensionInfo;
}
export interface ISettingsSection {
@ -81,7 +81,7 @@ export interface ISetting {
tags?: string[];
disallowSyncIgnore?: boolean;
restricted?: boolean;
extensionInfo?: IExtensionInfo;
extensionInfo?: IConfigurationExtensionInfo;
validator?: (value: any) => string | null;
enumItemLabels?: string[];
allKeysAreBoolean?: boolean;

View file

@ -15,7 +15,7 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
@ -607,7 +607,7 @@ export class DefaultSettings extends Disposable {
return result;
}
private parseSettings(settingsObject: { [path: string]: IConfigurationPropertySchema; }, extensionInfo?: IExtensionInfo): ISetting[] {
private parseSettings(settingsObject: { [path: string]: IConfigurationPropertySchema; }, extensionInfo?: IConfigurationExtensionInfo): ISetting[] {
const result: ISetting[] = [];
for (const key in settingsObject) {
const prop = settingsObject[key];

View file

@ -7,7 +7,7 @@ import 'vs/css!./media/progressService';
import { localize } from 'vs/nls';
import { IDisposable, dispose, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions, IProgressDialogOptions } from 'vs/platform/progress/common/progress';
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions, IProgressDialogOptions, ICustomProgressLocation } from 'vs/platform/progress/common/progress';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar';
import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async';
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity';
@ -26,10 +26,12 @@ import { parseLinkedText } from 'vs/base/common/linkedText';
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
export class ProgressService extends Disposable implements IProgressService {
export class ProgressService implements IProgressService {
declare readonly _serviceBrand: undefined;
private readonly customProgessLocations = new Map<string, ICustomProgressLocation>();
constructor(
@IActivityService private readonly activityService: IActivityService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
@ -41,7 +43,15 @@ export class ProgressService extends Disposable implements IProgressService {
@IThemeService private readonly themeService: IThemeService,
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
}
registerProgressLocation(location: string, handle: ICustomProgressLocation): IDisposable {
if (this.customProgessLocations.has(location)) {
throw new Error(`${location} already used`);
}
this.customProgessLocations.set(location, handle);
return toDisposable(() => this.customProgessLocations.delete(location));
}
async withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: (choice?: number) => void): Promise<R> {
@ -60,6 +70,14 @@ export class ProgressService extends Disposable implements IProgressService {
return this.withViewProgress(location, task, { ...options, location });
}
const customLocation = this.customProgessLocations.get(location);
if (customLocation) {
const obj = customLocation.startProgress();
const promise = task(obj.progress);
promise.finally(() => obj.stop());
return promise;
}
throw new Error(`Bad progress location: ${location}`);
}

View file

@ -407,15 +407,21 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return this.colorThemeRegistry.getThemes();
}
public async getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise<IWorkbenchColorTheme[]> {
public async getMarketplaceColorThemes(id: string, version: string): Promise<IWorkbenchColorTheme[]> {
const [publisher, name] = id.split('.');
const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension');
if (extensionLocation) {
try {
const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json'));
return this.colorThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name));
} catch (e) {
this.logService.error('Problem loading themes from marketplace', e);
}
if (!extensionLocation) {
return [];
}
try {
const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json'));
const data: ExtensionData = { extensionPublisher: publisher, extensionId: id, extensionName: name, extensionIsBuiltin: false };
return this.colorThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, data);
} catch (e) {
this.logService.error(`Problem loading themes from marketplace ${e}`);
}
return [];
}
@ -447,7 +453,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
try {
await themeData.ensureLoaded(this.extensionResourceLoaderService);
themeData.setCustomizations(this.settings);
return this.applyTheme(themeData, settingsTarget);
} catch (error) {
throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message));
@ -587,21 +592,13 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return this.onFileIconThemeChange.event;
}
public async setFileIconTheme(iconThemeOrId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchFileIconTheme> {
return this.fileIconThemeSequencer.queue(async () => {
if (iconThemeOrId === undefined) {
iconThemeOrId = '';
}
const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id;
if (themeId !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) {
let newThemeData = this.fileIconThemeRegistry.findThemeById(themeId);
if (!newThemeData && iconThemeOrId instanceof FileIconThemeData) {
newThemeData = iconThemeOrId;
}
if (!newThemeData) {
newThemeData = FileIconThemeData.noIconTheme;
}
public async setFileIconTheme(iconTheme: string | undefined, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchFileIconTheme> {
return this.fileIconThemeSequencer.queue(async () => {
iconTheme = iconTheme || '';
if (iconTheme !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) {
const newThemeData = this.fileIconThemeRegistry.findThemeById(iconTheme) || FileIconThemeData.noIconTheme;
await newThemeData.ensureLoaded(this.extensionResourceLoaderService);
this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme
@ -619,19 +616,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
});
}
public async getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise<IWorkbenchFileIconTheme[]> {
const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension');
if (extensionLocation) {
try {
const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json'));
return this.fileIconThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name));
} catch (e) {
this.logService.error('Problem loading themes from marketplace', e);
}
}
return [];
}
private async reloadCurrentFileIconTheme() {
return this.fileIconThemeSequencer.queue(async () => {
await this.currentFileIconTheme.reload(this.extensionResourceLoaderService);
@ -640,19 +624,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
public async restoreFileIconTheme(): Promise<boolean> {
return this.fileIconThemeSequencer.queue(async () => {
const settingId = this.settings.fileIconTheme;
const theme = this.fileIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentFileIconTheme.settingsId) {
await this.setFileIconTheme(theme.id, undefined);
} else if (theme !== this.currentFileIconTheme) {
this.applyAndSetFileIconTheme(theme, true);
}
return true;
const settingId = this.settings.fileIconTheme;
const theme = this.fileIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentFileIconTheme.settingsId) {
await this.setFileIconTheme(theme.id, undefined);
}
return false;
});
return true;
}
return false;
}
private applyAndSetFileIconTheme(iconThemeData: FileIconThemeData, silent = false): void {
@ -689,20 +669,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return this.onProductIconThemeChange.event;
}
public async setProductIconTheme(iconThemeOrId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchProductIconTheme> {
public async setProductIconTheme(iconTheme: string | undefined, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchProductIconTheme> {
return this.productIconThemeSequencer.queue(async () => {
if (iconThemeOrId === undefined) {
iconThemeOrId = '';
}
const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id;
if (themeId !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) {
let newThemeData = this.productIconThemeRegistry.findThemeById(themeId);
if (!newThemeData && iconThemeOrId instanceof ProductIconThemeData) {
newThemeData = iconThemeOrId;
}
if (!newThemeData) {
newThemeData = ProductIconThemeData.defaultTheme;
}
iconTheme = iconTheme || '';
if (iconTheme !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) {
const newThemeData = this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme;
await newThemeData.ensureLoaded(this.extensionResourceLoaderService, this.logService);
this.applyAndSetProductIconTheme(newThemeData); // updates this.currentProductIconTheme
@ -719,19 +690,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
});
}
public async getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise<IWorkbenchProductIconTheme[]> {
const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension');
if (extensionLocation) {
try {
const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json'));
return this.productIconThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name));
} catch (e) {
this.logService.error('Problem loading themes from marketplace', e);
}
}
return [];
}
private async reloadCurrentProductIconTheme() {
return this.productIconThemeSequencer.queue(async () => {
await this.currentProductIconTheme.reload(this.extensionResourceLoaderService, this.logService);
@ -740,19 +698,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
public async restoreProductIconTheme(): Promise<boolean> {
return this.productIconThemeSequencer.queue(async () => {
const settingId = this.settings.productIconTheme;
const theme = this.productIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentProductIconTheme.settingsId) {
await this.setProductIconTheme(theme.id, undefined);
} else if (theme !== this.currentProductIconTheme) {
this.applyAndSetProductIconTheme(theme, true);
}
return true;
const settingId = this.settings.productIconTheme;
const theme = this.productIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentProductIconTheme.settingsId) {
await this.setProductIconTheme(theme.id, undefined);
}
return false;
});
return true;
}
return false;
}
private applyAndSetProductIconTheme(iconThemeData: ProductIconThemeData, silent = false): void {

View file

@ -141,8 +141,13 @@ export class ThemeRegistry<T extends IThemeData> {
previousIds[theme.id] = theme;
}
this.extensionThemes.length = 0;
for (const ext of extensions) {
const extensionData = ExtensionData.fromName(ext.description.publisher, ext.description.name, ext.description.isBuiltin);
for (let ext of extensions) {
let extensionData: ExtensionData = {
extensionId: ext.description.identifier.value,
extensionPublisher: ext.description.publisher,
extensionName: ext.description.name,
extensionIsBuiltin: ext.description.isBuiltin
};
this.onThemes(extensionData, ext.description.extensionLocation, ext.value, this.extensionThemes, ext.collector);
}
for (const theme of this.extensionThemes) {

View file

@ -71,19 +71,18 @@ export interface IWorkbenchThemeService extends IThemeService {
setColorTheme(themeId: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchColorTheme | null>;
getColorTheme(): IWorkbenchColorTheme;
getColorThemes(): Promise<IWorkbenchColorTheme[]>;
getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise<IWorkbenchColorTheme[]>;
getMarketplaceColorThemes(id: string, version: string): Promise<IWorkbenchColorTheme[]>;
onDidColorThemeChange: Event<IWorkbenchColorTheme>;
restoreColorTheme(): void;
setFileIconTheme(iconThemeId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchFileIconTheme>;
setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchFileIconTheme>;
getFileIconTheme(): IWorkbenchFileIconTheme;
getFileIconThemes(): Promise<IWorkbenchFileIconTheme[]>;
getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise<IWorkbenchFileIconTheme[]>;
onDidFileIconThemeChange: Event<IWorkbenchFileIconTheme>;
setProductIconTheme(iconThemeId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchProductIconTheme>;
setProductIconTheme(iconThemeId: string | undefined, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchProductIconTheme>;
getProductIconTheme(): IWorkbenchProductIconTheme;
getProductIconThemes(): Promise<IWorkbenchProductIconTheme[]>;
getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise<IWorkbenchProductIconTheme[]>;
onDidProductIconThemeChange: Event<IWorkbenchProductIconTheme>;
}
@ -200,9 +199,6 @@ export namespace ExtensionData {
}
return undefined;
}
export function fromName(publisher: string, name: string, isBuiltin = false) : ExtensionData {
return { extensionPublisher: publisher, extensionId: `${publisher}.${name}`, extensionName: name, extensionIsBuiltin: isBuiltin };
}
}
export interface IThemeExtensionPoint {

View file

@ -395,7 +395,7 @@ class NewExtensionsInitializer implements IUserDataInitializer {
storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService);
}
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* set isMachineScoped to prevent install and sync dialog in web */);
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* pass options to prevent install and sync dialog in web */);
if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
newlyEnabledExtensions.push(local);
}

View file

@ -132,7 +132,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit
import { IWorkingCopyEditorService, WorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService';
import { BrowserElevatedFileService } from 'vs/workbench/services/files/browser/elevatedFileService';
import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/modes';
import { ResourceMap } from 'vs/base/common/map';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
@ -441,6 +441,10 @@ export class TestProgressService implements IProgressService {
declare readonly _serviceBrand: undefined;
registerProgressLocation(): IDisposable {
return Disposable.None;
}
withProgress(
options: IProgressOptions | IProgressDialogOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions,
task: (progress: IProgress<IProgressStep>) => Promise<any>,
@ -1866,7 +1870,7 @@ export class TestEditorWorkerService implements IEditorWorkerService {
declare readonly _serviceBrand: undefined;
canComputeUnicodeHighlights(uri: URI): boolean { return false; }
async computedUnicodeHighlights(uri: URI): Promise<IUnicodeHighlightsResult> { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; }
async computedUnicodeHighlights(uri: URI): Promise<IRange[]> { return []; }
async computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> { return null; }
canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; }
async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> { return null; }

View file

@ -14,59 +14,15 @@ declare module 'vscode' {
}
interface LanguageStatusItem {
/**
* The identifier of this item.
*/
readonly id: string;
/**
* The short name of this item, like 'Java Language Status', etc.
*/
name: string | undefined;
/**
* A {@link DocumentSelector selector} that defines for what documents
* this item shows.
*/
selector: DocumentSelector;
// todo@jrieken replace with boolean ala needsAttention
severity: LanguageStatusSeverity;
/**
* The text to show for the entry. You can embed icons in the text by leveraging the syntax:
*
* `My text $(icon-name) contains icons like $(icon-name) this one.`
*
* Where the icon-name is taken from the ThemeIcon [icon set](https://code.visualstudio.com/api/references/icons-in-labels#icon-listing), e.g.
* `light-bulb`, `thumbsup`, `zap` etc.
*/
name: string | undefined;
text: string;
/**
* Optional, human-readable details for this item.
*/
detail?: string;
/**
* Controls whether the item is shown as "busy". Defaults to `false`.
*/
busy: boolean;
/**
* A {@linkcode Command command} for this item.
*/
command: Command | undefined;
/**
* Accessibility information used when a screen reader interacts with this item
*/
accessibilityInformation?: AccessibilityInformation;
/**
* Dispose and free associated resources.
*/
dispose(): void;
}

View file

@ -75,9 +75,10 @@ export class Application {
await this.code.waitForElement('.explorer-folders-view');
}
async restart(options?: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
await this.stop();
await this._start(options?.workspaceOrFolder, options?.extraArgs);
await new Promise(c => setTimeout(c, 1000));
await this._start(options.workspaceOrFolder, options.extraArgs);
}
private async _start(workspaceOrFolder = this.workspacePathOrFolder, extraArgs: string[] = []): Promise<any> {
@ -86,6 +87,15 @@ export class Application {
await this.checkWindowReady();
}
async reload(): Promise<any> {
this.code.reload()
.catch(err => null); // ignore the connection drop errors
// needs to be enough to propagate the 'Reload Window' command
await new Promise(c => setTimeout(c, 1500));
await this.checkWindowReady();
}
async stop(): Promise<any> {
if (this._code) {
await this._code.exit();

View file

@ -63,7 +63,7 @@ function getBuildOutPath(root: string): string {
}
}
async function connect(connectDriver: typeof connectElectronDriver | typeof connectPlaywrightDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise<Code> {
async function connect(connectDriver: typeof connectElectronDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise<Code> {
let errCount = 0;
while (true) {
@ -79,7 +79,7 @@ async function connect(connectDriver: typeof connectElectronDriver | typeof conn
}
// retry
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise(c => setTimeout(c, 100));
}
}
}
@ -116,12 +116,14 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
const handle = await createDriverHandle();
let child: cp.ChildProcess | undefined;
let connectDriver: typeof connectElectronDriver;
copyExtension(options.extensionsPath, 'vscode-notebook-tests');
if (options.web) {
await launch(options.userDataDir, options.workspacePath, options.codePath, options.extensionsPath, Boolean(options.verbose));
return connect(connectPlaywrightDriver.bind(connectPlaywrightDriver, options), child, '', handle, options.logger);
connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options);
return connect(connectDriver, child, '', handle, options.logger);
}
const env = { ...process.env };
@ -197,7 +199,8 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
child = cp.spawn(electronPath, args, spawnOptions);
instances.add(child);
child.once('exit', () => instances.delete(child!));
return connect(connectElectronDriver, child, outPath, handle, options.logger);
connectDriver = connectElectronDriver;
return connect(connectDriver, child, outPath, handle, options.logger);
}
async function copyExtension(extensionsPath: string, extId: string): Promise<void> {
@ -287,55 +290,34 @@ export class Code {
await this.driver.dispatchKeybinding(windowId, keybinding);
}
async reload(): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.driver.reloadWindow(windowId);
}
async exit(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let done = false;
const exitPromise = this.driver.exitApplication();
// Start the exit flow via driver
const exitPromise = this.driver.exitApplication().then(veto => {
if (veto) {
done = true;
reject(new Error('Smoke test exit call resulted in unexpected veto'));
}
});
// If we know the `pid` of the smoke tested application
// use that as way to detect the exit of the application
const pid = this.pid;
if (typeof pid === 'number') {
(async () => {
let killCounter = 0;
while (!done) {
killCounter++;
if (killCounter > 40) {
done = true;
reject(new Error('Smoke test exit call did not terminate main process after 20s, giving up'));
}
try {
process.kill(pid, 0); // throws an exception if the main process doesn't exist anymore.
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
done = true;
resolve();
}
}
})();
}
// Otherwise await the exit promise (web).
else {
(async () => {
// If we know the `pid`, use that to await the
// process to terminate (desktop).
const pid = this.pid;
if (typeof pid === 'number') {
await (async () => {
while (true) {
try {
await exitPromise;
resolve();
process.kill(pid, 0); // throws an exception if the main process doesn't exist anymore.
await new Promise(c => setTimeout(c, 100));
} catch (error) {
reject(new Error(`Smoke test exit call resulted in error: ${error}`));
return;
}
})();
}
});
}
})();
}
// Otherwise await the exit promise (web).
else {
await exitPromise;
}
}
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise<string> {

View file

@ -57,15 +57,9 @@ class PlaywrightDriver implements IDriver {
try {
await this._context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) });
} catch (error) {
console.warn(`Failed to stop playwright tracing: ${error}`);
console.warn(`Failed to stop playwright tracing.`); // do not fail the build when this fails
}
try {
await this._browser.close();
} catch (error) {
console.warn(`Failed to close browser: ${error}`);
}
await this._browser.close();
await teardown();
return false;
@ -213,9 +207,9 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe
async function teardown(): Promise<void> {
if (server) {
try {
await new Promise<void>((resolve, reject) => kill(server!.pid, err => err ? reject(err) : resolve()));
} catch (error) {
console.warn(`Error tearing down server: ${error}`);
await new Promise<void>((c, e) => kill(server!.pid, err => err ? e(err) : c()));
} catch {
// noop
}
server = undefined;

View file

@ -80,8 +80,8 @@ export class QuickAccess {
}
}
async openFile(fileName: string, fileSearch = fileName): Promise<void> {
await this.openQuickAccessAndWait(fileSearch, true);
async openFile(fileName: string): Promise<void> {
await this.openQuickAccessAndWait(fileName, true);
await this.code.dispatchKeybinding('enter');
await this.editors.waitForActiveTab(fileName);

View file

@ -17,7 +17,7 @@ function toUri(path: string): string {
return `${path}`;
}
function createWorkspaceFile(workspacePath: string): string {
async function createWorkspaceFile(workspacePath: string): Promise<string> {
const workspaceFilePath = path.join(path.dirname(workspacePath), 'smoketest.code-workspace');
const workspace = {
folders: [
@ -39,7 +39,7 @@ function createWorkspaceFile(workspacePath: string): string {
export function setup(opts: minimist.ParsedArgs) {
describe('Multiroot', () => {
beforeSuite(opts, async opts => {
const workspacePath = createWorkspaceFile(opts.workspacePath);
const workspacePath = await createWorkspaceFile(opts.workspacePath);
return { ...opts, workspacePath };
});

View file

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup(opts: minimist.ParsedArgs) {
describe('Dataloss', () => {
beforeSuite(opts);
afterSuite(opts);
it(`verifies that 'hot exit' works for dirty files`, async function () {
const app = this.app as Application;
await app.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello from Untitled';
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await app.workbench.quickaccess.openFile(readmeMd);
await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await app.reload();
await app.workbench.editors.waitForActiveTab(readmeMd, true);
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await app.workbench.editors.waitForTab(untitled);
await app.workbench.editors.selectTab(untitled);
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
});
});
}

View file

@ -6,80 +6,20 @@
import { Application, ApplicationOptions, Quality } from '../../../../automation';
import { join } from 'path';
import { ParsedArgs } from 'minimist';
import { afterSuite, startApp } from '../../utils';
import { afterSuite, timeout } from '../../utils';
export function setup(opts: ParsedArgs, testDataPath: string) {
describe('Data Migration (insiders -> insiders)', () => {
let app: Application | undefined = undefined;
afterSuite(opts, () => app);
it(`verifies opened editors are restored`, async function () {
app = await startApp(opts, this.defaultOptions);
// Open 3 editors and pin 2 of them
await app.workbench.quickaccess.openFile('www');
await app.workbench.quickaccess.runCommand('View: Keep Editor');
await app.workbench.quickaccess.openFile('app.js');
await app.workbench.quickaccess.runCommand('View: Keep Editor');
await app.workbench.editors.newUntitledFile();
await app.restart();
// Verify 3 editors are open
await app.workbench.editors.selectTab('Untitled-1');
await app.workbench.editors.selectTab('app.js');
await app.workbench.editors.selectTab('www');
await app.stop();
app = undefined;
});
it(`verifies that 'hot exit' works for dirty files`, async function () {
app = await startApp(opts, this.defaultOptions);
await app.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello from Untitled';
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
await app.workbench.editors.waitForTab(untitled, true);
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await app.workbench.quickaccess.openFile(readmeMd);
await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await app.workbench.editors.waitForTab(readmeMd, true);
await app.restart();
await app.workbench.editors.waitForTab(readmeMd, true);
await app.workbench.quickaccess.openFile(readmeMd);
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await app.workbench.editors.waitForTab(untitled, true);
await app.workbench.quickaccess.openFile(untitled, textToTypeInUntitled);
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
await app.stop();
app = undefined;
});
});
describe('Data Migration (stable -> insiders)', () => {
describe('Datamigration', () => {
let insidersApp: Application | undefined = undefined;
let stableApp: Application | undefined = undefined;
afterSuite(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop());
afterSuite(opts, () => insidersApp, async () => stableApp?.stop());
it(`verifies opened editors are restored`, async function () {
const stableCodePath = opts['stable-build'];
if (!stableCodePath || opts.remote) {
if (!stableCodePath) {
this.skip();
}
@ -129,7 +69,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) {
it(`verifies that 'hot exit' works for dirty files`, async function () {
const stableCodePath = opts['stable-build'];
if (!stableCodePath || opts.remote) {
if (!stableCodePath) {
this.skip();
}
@ -148,13 +88,13 @@ export function setup(opts: ParsedArgs, testDataPath: string) {
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello from Untitled';
await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
await stableApp.workbench.editors.waitForTab(untitled, true);
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await stableApp.workbench.quickaccess.openFile(readmeMd);
await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await stableApp.workbench.editors.waitForTab(readmeMd, true);
await timeout(2000); // give time to store the backup before stopping the app
await stableApp.stop();
stableApp = undefined;
@ -166,11 +106,11 @@ export function setup(opts: ParsedArgs, testDataPath: string) {
await insidersApp.start();
await insidersApp.workbench.editors.waitForTab(readmeMd, true);
await insidersApp.workbench.quickaccess.openFile(readmeMd);
await insidersApp.workbench.editors.selectTab(readmeMd);
await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await insidersApp.workbench.editors.waitForTab(untitled, true);
await insidersApp.workbench.quickaccess.openFile(untitled, textToTypeInUntitled);
await insidersApp.workbench.editors.selectTab(untitled);
await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
await insidersApp.stop();

View file

@ -4,24 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { join } from 'path';
import { Application } from '../../../../automation';
import { afterSuite, startApp } from '../../utils';
import * as path from 'path';
import { Application, ApplicationOptions } from '../../../../automation';
import { afterSuite } from '../../utils';
export function setup(args: minimist.ParsedArgs) {
export function setup(opts: minimist.ParsedArgs) {
describe('Launch', () => {
let app: Application | undefined;
let app: Application;
afterSuite(args, () => app);
afterSuite(opts, () => app);
it(`verifies that application launches when user data directory has non-ascii characters`, async function () {
const massagedOptions = { ...this.defaultOptions, userDataDir: join(this.defaultOptions.userDataDir, 'ø') };
app = await startApp(args, massagedOptions);
await app.stop();
app = undefined;
const defaultOptions = this.defaultOptions as ApplicationOptions;
const options: ApplicationOptions = { ...defaultOptions, userDataDir: path.join(defaultOptions.userDataDir, 'abcdø') };
app = new Application(options);
await app.start();
});
});
}

View file

@ -5,23 +5,20 @@
import minimist = require('minimist');
import { Application, Quality } from '../../../../automation';
import { afterSuite, startApp } from '../../utils';
export function setup(args: minimist.ParsedArgs) {
import { afterSuite, beforeSuite } from '../../utils';
export function setup(opts: minimist.ParsedArgs) {
describe('Localization', () => {
let app: Application | undefined = undefined;
afterSuite(args, () => app);
beforeSuite(opts);
afterSuite(opts);
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
if (this.defaultOptions.quality === Quality.Dev || this.defaultOptions.remote) {
const app = this.app as Application;
if (app.quality === Quality.Dev || app.remote) {
return this.skip();
}
app = await startApp(args, this.defaultOptions);
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false);
await app.restart({ extraArgs: ['--locale=DE'] });
@ -29,9 +26,6 @@ export function setup(args: minimist.ParsedArgs) {
const result = await app.workbench.localization.getLocalizedStrings();
const localeInfo = await app.workbench.localization.getLocaleInfo();
await app.stop();
app = undefined;
if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') {
throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`);
}

View file

@ -17,6 +17,7 @@ import fetch from 'node-fetch';
import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation';
import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test';
import { setup as setupDataLossTests } from './areas/workbench/data-loss.test';
import { setup as setupPreferencesTests } from './areas/preferences/preferences.test';
import { setup as setupSearchTests } from './areas/search/search.test';
import { setup as setupNotebookTests } from './areas/notebook/notebook.test';
@ -345,8 +346,14 @@ after(async function () {
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c(undefined)));
});
if (!opts.web && opts['build'] && !opts['remote']) {
describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing`, () => {
setupDataMigrationTests(opts, testDataPath);
});
}
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
if (!opts.web) { setupDataMigrationTests(opts, testDataPath); }
if (!opts.web) { setupDataLossTests(opts); }
if (!opts.web) { setupPreferencesTests(opts); }
setupSearchTests(opts);
setupNotebookTests(opts);

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