From 41d6fc7e3a82a324a60ac93684f82d4721978b40 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Oct 2021 15:39:26 +0200 Subject: [PATCH 001/375] Create extension host processes from a node worker in the main process --- build/gulpfile.vscode.js | 1 + src/buildfile.js | 23 +- src/vs/base/common/worker/simpleWorker.ts | 330 +++++++++++++----- src/vs/code/electron-main/app.ts | 9 + .../extensions/node/extensionHostStarter.ts | 14 +- .../node/extensionHostStarterWorker.ts | 24 ++ .../node/extensionHostStarterWorkerMain.ts | 66 ++++ .../node/mainProcessExtensionHostStarter.ts | 127 +++++++ .../electron-sandbox/extensionHostStarter.ts | 10 +- 9 files changed, 509 insertions(+), 95 deletions(-) create mode 100644 src/vs/platform/extensions/node/extensionHostStarterWorker.ts create mode 100644 src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts create mode 100644 src/vs/platform/extensions/node/mainProcessExtensionHostStarter.ts diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index fbf4739f2ff..29a47b5d42d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -64,6 +64,7 @@ const vscodeResources = [ 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', 'out-build/vs/platform/environment/node/userDataPath.js', + 'out-build/vs/platform/extensions/node/extensionHostStarterWorkerMain.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/src/buildfile.js b/src/buildfile.js index 0dfc26c5935..7588e1f8b3c 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -5,13 +5,22 @@ const { createModuleDescription, createEditorWorkerModuleDescription } = require('./vs/base/buildfile'); -exports.base = [{ - name: 'vs/base/common/worker/simpleWorker', - include: ['vs/editor/common/services/editorSimpleWorker'], - prepend: ['vs/loader.js', 'vs/nls.js'], - append: ['vs/base/worker/workerMain'], - dest: 'vs/base/worker/workerMain.js' -}]; +exports.base = [ + { + name: 'vs/base/common/worker/simpleWorker', + include: ['vs/editor/common/services/editorSimpleWorker'], + prepend: ['vs/loader.js', 'vs/nls.js'], + append: ['vs/base/worker/workerMain'], + dest: 'vs/base/worker/workerMain.js' + }, + { + name: 'vs/base/common/worker/simpleWorker', + }, + { + name: 'vs/platform/extensions/node/extensionHostStarterWorker', + exclude: ['vs/base/common/worker/simpleWorker'] + } +]; exports.workerExtensionHost = [createEditorWorkerModuleDescription('vs/workbench/services/extensions/worker/extensionHostWorker')]; exports.workerNotebook = [createEditorWorkerModuleDescription('vs/workbench/contrib/notebook/common/services/notebookSimpleWorker')]; diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 1b81394ae64..9560ebf4682 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -4,19 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { transformErrorForSerialization } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { isWeb } from 'vs/base/common/platform'; +import { globals, isWeb } from 'vs/base/common/platform'; import * as types from 'vs/base/common/types'; +import * as strings from 'vs/base/common/strings'; const INITIALIZE = '$initialize'; export interface IWorker extends IDisposable { getId(): number; - postMessage(message: any, transfer: ArrayBuffer[]): void; + postMessage(message: Message, transfer: ArrayBuffer[]): void; } export interface IWorkerCallback { - (message: any): void; + (message: Message): void; } export interface IWorkerFactory { @@ -36,23 +38,56 @@ export function logOnceWebWorkerWarning(err: any): void { console.warn(err.message); } -interface IMessage { - vsWorker: number; - req?: string; - seq?: string; +const enum MessageType { + Request, + Reply, + SubscribeEvent, + Event, + UnsubscribeEvent } - -interface IRequestMessage extends IMessage { - req: string; - method: string; - args: any[]; +class RequestMessage { + public readonly type = MessageType.Request; + constructor( + public readonly vsWorker: number, + public readonly req: string, + public readonly method: string, + public readonly args: any[] + ) { } } - -interface IReplyMessage extends IMessage { - seq: string; - err: any; - res: any; +class ReplyMessage { + public readonly type = MessageType.Reply; + constructor( + public readonly vsWorker: number, + public readonly seq: string, + public readonly res: any, + public readonly err: any + ) { } } +class SubscribeEventMessage { + public readonly type = MessageType.SubscribeEvent; + constructor( + public readonly vsWorker: number, + public readonly req: string, + public readonly eventName: string, + public readonly arg: any + ) { } +} +class EventMessage { + public readonly type = MessageType.Event; + constructor( + public readonly vsWorker: number, + public readonly req: string, + public readonly event: any + ) { } +} +class UnsubscribeEventMessage { + public readonly type = MessageType.UnsubscribeEvent; + constructor( + public readonly vsWorker: number, + public readonly req: string + ) { } +} +type Message = RequestMessage | ReplyMessage | SubscribeEventMessage | EventMessage | UnsubscribeEventMessage; interface IMessageReply { resolve: (value?: any) => void; @@ -62,6 +97,7 @@ interface IMessageReply { interface IMessageHandler { sendMessage(msg: any, transfer?: ArrayBuffer[]): void; handleMessage(method: string, args: any[]): Promise; + handleEvent(eventName: string, arg: any): Event; } class SimpleWorkerProtocol { @@ -69,6 +105,8 @@ class SimpleWorkerProtocol { private _workerId: number; private _lastSentReq: number; private _pendingReplies: { [req: string]: IMessageReply; }; + private _pendingEmitters: Map>; + private _pendingEvents: Map; private _handler: IMessageHandler; constructor(handler: IMessageHandler) { @@ -76,6 +114,8 @@ class SimpleWorkerProtocol { this._handler = handler; this._lastSentReq = 0; this._pendingReplies = Object.create(null); + this._pendingEmitters = new Map>(); + this._pendingEvents = new Map(); } public setWorkerId(workerId: number): void { @@ -83,22 +123,34 @@ class SimpleWorkerProtocol { } public sendMessage(method: string, args: any[]): Promise { - let req = String(++this._lastSentReq); + const req = String(++this._lastSentReq); return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject }; - this._send({ - vsWorker: this._workerId, - req: req, - method: method, - args: args - }); + this._send(new RequestMessage(this._workerId, req, method, args)); }); } - public handleMessage(message: IMessage): void { + public listen(eventName: string, arg: any): Event { + let req: string | null = null; + const emitter = new Emitter({ + onFirstListenerAdd: () => { + req = String(++this._lastSentReq); + this._pendingEmitters.set(req, emitter); + this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg)); + }, + onLastListenerRemove: () => { + this._pendingEmitters.delete(req!); + this._send(new UnsubscribeEventMessage(this._workerId, req!)); + req = null; + } + }); + return emitter.event; + } + + public handleMessage(message: Message): void { if (!message || !message.vsWorker) { return; } @@ -108,70 +160,95 @@ class SimpleWorkerProtocol { this._handleMessage(message); } - private _handleMessage(msg: IMessage): void { - if (msg.seq) { - let replyMessage = msg; - if (!this._pendingReplies[replyMessage.seq]) { - console.warn('Got reply to unknown seq'); - return; - } + private _handleMessage(msg: Message): void { + switch (msg.type) { + case MessageType.Reply: + return this._handleReplyMessage(msg); + case MessageType.Request: + return this._handleRequestMessage(msg); + case MessageType.SubscribeEvent: + return this._handleSubscribeEventMessage(msg); + case MessageType.Event: + return this._handleEventMessage(msg); + case MessageType.UnsubscribeEvent: + return this._handleUnsubscribeEventMessage(msg); + } + } - let reply = this._pendingReplies[replyMessage.seq]; - delete this._pendingReplies[replyMessage.seq]; - - if (replyMessage.err) { - let err = replyMessage.err; - if (replyMessage.err.$isError) { - err = new Error(); - err.name = replyMessage.err.name; - err.message = replyMessage.err.message; - err.stack = replyMessage.err.stack; - } - reply.reject(err); - return; - } - - reply.resolve(replyMessage.res); + private _handleReplyMessage(replyMessage: ReplyMessage): void { + if (!this._pendingReplies[replyMessage.seq]) { + console.warn('Got reply to unknown seq'); return; } - let requestMessage = msg; + let reply = this._pendingReplies[replyMessage.seq]; + delete this._pendingReplies[replyMessage.seq]; + + if (replyMessage.err) { + let err = replyMessage.err; + if (replyMessage.err.$isError) { + err = new Error(); + err.name = replyMessage.err.name; + err.message = replyMessage.err.message; + err.stack = replyMessage.err.stack; + } + reply.reject(err); + return; + } + + reply.resolve(replyMessage.res); + } + + private _handleRequestMessage(requestMessage: RequestMessage): void { let req = requestMessage.req; let result = this._handler.handleMessage(requestMessage.method, requestMessage.args); result.then((r) => { - this._send({ - vsWorker: this._workerId, - seq: req, - res: r, - err: undefined - }); + this._send(new ReplyMessage(this._workerId, req, r, undefined)); }, (e) => { if (e.detail instanceof Error) { // Loading errors have a detail property that points to the actual error e.detail = transformErrorForSerialization(e.detail); } - this._send({ - vsWorker: this._workerId, - seq: req, - res: undefined, - err: transformErrorForSerialization(e) - }); + this._send(new ReplyMessage(this._workerId, req, undefined, transformErrorForSerialization(e))); }); } - private _send(msg: IRequestMessage | IReplyMessage): void { + private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void { + const req = msg.req; + const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => { + this._send(new EventMessage(this._workerId, req, event)); + }); + this._pendingEvents.set(req, disposable); + } + + private _handleEventMessage(msg: EventMessage): void { + if (!this._pendingEmitters.has(msg.req)) { + console.warn('Got event for unknown req'); + return; + } + this._pendingEmitters.get(msg.req)!.fire(msg.event); + } + + private _handleUnsubscribeEventMessage(msg: UnsubscribeEventMessage): void { + if (!this._pendingEvents.has(msg.req)) { + console.warn('Got unsubscribe for unknown req'); + return; + } + this._pendingEvents.get(msg.req)!.dispose(); + this._pendingEvents.delete(msg.req); + } + + private _send(msg: Message): void { let transfer: ArrayBuffer[] = []; - if (msg.req) { - const m = msg; - for (let i = 0; i < m.args.length; i++) { - if (m.args[i] instanceof ArrayBuffer) { - transfer.push(m.args[i]); + if (msg.type === MessageType.Request) { + for (let i = 0; i < msg.args.length; i++) { + if (msg.args[i] instanceof ArrayBuffer) { + transfer.push(msg.args[i]); } } - } else { - const m = msg; - if (m.res instanceof ArrayBuffer) { - transfer.push(m.res); + } else if (msg.type === MessageType.Reply) { + if (msg.res instanceof ArrayBuffer) { + transfer.push(msg.res); } } this._handler.sendMessage(msg, transfer); @@ -200,7 +277,7 @@ export class SimpleWorkerClient extends Disp this._worker = this._register(workerFactory.create( 'vs/base/common/worker/simpleWorker', - (msg: any) => { + (msg: Message) => { this._protocol.handleMessage(msg); }, (err: any) => { @@ -226,18 +303,35 @@ export class SimpleWorkerClient extends Disp } catch (e) { return Promise.reject(e); } + }, + handleEvent: (eventName: string, arg: any): Event => { + if (propertyIsDynamicEvent(eventName)) { + const event = (host as any)[eventName].call(host, arg); + if (typeof event !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on main thread host.`); + } + return event; + } + if (propertyIsEvent(eventName)) { + const event = (host as any)[eventName]; + if (typeof event !== 'function') { + throw new Error(`Missing event ${eventName} on main thread host.`); + } + return event; + } + throw new Error(`Malformed event name ${eventName}`); } }); this._protocol.setWorkerId(this._worker.getId()); // Gather loader configuration let loaderConfiguration: any = null; - if (typeof (self).require !== 'undefined' && typeof (self).require.getConfig === 'function') { + if (typeof globals.require !== 'undefined' && typeof globals.require.getConfig === 'function') { // Get the configuration from the Monaco AMD Loader - loaderConfiguration = (self).require.getConfig(); - } else if (typeof (self).requirejs !== 'undefined') { + loaderConfiguration = globals.require.getConfig(); + } else if (typeof globals.requirejs !== 'undefined') { // Get the configuration from requirejs - loaderConfiguration = (self).requirejs.s.contexts._.config; + loaderConfiguration = globals.requirejs.s.contexts._.config; } const hostMethods = types.getAllMethodNames(host); @@ -254,11 +348,14 @@ export class SimpleWorkerClient extends Disp const proxyMethodRequest = (method: string, args: any[]): Promise => { return this._request(method, args); }; + const proxyListen = (eventName: string, arg: any): Event => { + return this._protocol.listen(eventName, arg); + }; this._lazyProxy = new Promise((resolve, reject) => { lazyProxyReject = reject; this._onModuleLoaded.then((availableMethods: string[]) => { - resolve(types.createProxyObject(availableMethods, proxyMethodRequest)); + resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen)); }, (e) => { reject(e); this._onError('Worker failed to load ' + moduleId, e); @@ -284,6 +381,48 @@ export class SimpleWorkerClient extends Disp } } +function propertyIsEvent(name: string): boolean { + // Assume a property is an event if it has a form of "onSomething" + return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2)); +} + +function propertyIsDynamicEvent(name: string): boolean { + // Assume a property is a dynamic event (a method that returns an event) if it has a form of "onDynamicSomething" + return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9)); +} + +function createProxyObject( + methodNames: string[], + invoke: (method: string, args: unknown[]) => unknown, + proxyListen: (eventName: string, arg: any) => Event +): T { + const createProxyMethod = (method: string): () => unknown => { + return function () { + const args = Array.prototype.slice.call(arguments, 0); + return invoke(method, args); + }; + }; + const createProxyDynamicEvent = (eventName: string): (arg: any) => Event => { + return function (arg) { + return proxyListen(eventName, arg); + }; + }; + + let result = {} as T; + for (const methodName of methodNames) { + if (propertyIsDynamicEvent(methodName)) { + (result)[methodName] = createProxyDynamicEvent(methodName); + continue; + } + if (propertyIsEvent(methodName)) { + (result)[methodName] = proxyListen(methodName, undefined); + continue; + } + (result)[methodName] = createProxyMethod(methodName); + } + return result; +} + export interface IRequestHandler { _requestHandlerBrand: any; [prop: string]: any; @@ -302,14 +441,15 @@ export class SimpleWorkerServer { private _requestHandler: IRequestHandler | null; private _protocol: SimpleWorkerProtocol; - constructor(postMessage: (msg: any, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { + constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { this._requestHandlerFactory = requestHandlerFactory; this._requestHandler = null; this._protocol = new SimpleWorkerProtocol({ sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { postMessage(msg, transfer); }, - handleMessage: (method: string, args: any[]): Promise => this._handleMessage(method, args) + handleMessage: (method: string, args: any[]): Promise => this._handleMessage(method, args), + handleEvent: (eventName: string, arg: any): Event => this._handleEvent(eventName, arg) }); } @@ -333,14 +473,38 @@ export class SimpleWorkerServer { } } + private _handleEvent(eventName: string, arg: any): Event { + if (!this._requestHandler) { + throw new Error(`Missing requestHandler`); + } + if (propertyIsDynamicEvent(eventName)) { + const event = (this._requestHandler as any)[eventName].call(this._requestHandler, arg); + if (typeof event !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on request handler.`); + } + return event; + } + if (propertyIsEvent(eventName)) { + const event = (this._requestHandler as any)[eventName]; + if (typeof event !== 'function') { + throw new Error(`Missing event ${eventName} on request handler.`); + } + return event; + } + throw new Error(`Malformed event name ${eventName}`); + } + private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise { this._protocol.setWorkerId(workerId); const proxyMethodRequest = (method: string, args: any[]): Promise => { return this._protocol.sendMessage(method, args); }; + const proxyListen = (eventName: string, arg: any): Event => { + return this._protocol.listen(eventName, arg); + }; - const hostProxy = types.createProxyObject(hostMethods, proxyMethodRequest); + const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen); if (this._requestHandlerFactory) { // static request handler @@ -365,12 +529,12 @@ export class SimpleWorkerServer { // Since this is in a web worker, enable catching errors loaderConfig.catchError = true; - (self).require.config(loaderConfig); + globals.require.config(loaderConfig); } return new Promise((resolve, reject) => { // Use the global require to be sure to get the global config - (self).require([moduleId], (module: { create: IRequestHandlerFactory }) => { + (globals.require || require)([moduleId], (module: { create: IRequestHandlerFactory }) => { this._requestHandler = module.create(hostProxy); if (!this._requestHandler) { @@ -387,6 +551,6 @@ export class SimpleWorkerServer { /** * Called on the worker side */ -export function create(postMessage: (msg: string) => void): SimpleWorkerServer { +export function create(postMessage: (msg: Message, transfer?: Transferable[]) => void): SimpleWorkerServer { return new SimpleWorkerServer(postMessage, null); } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c4574912081..30ad484337f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -44,6 +44,8 @@ import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; 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'; +import { ExtensionHostStarter } from 'vs/platform/extensions/node/mainProcessExtensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -514,6 +516,9 @@ export class CodeApplication extends Disposable { // Extension URL Trust services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); + // Extension Host Starter + services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); + // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); @@ -638,6 +643,10 @@ export class CodeApplication extends Disposable { // Extension Host Debug Broadcasting const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); + + // Extension Host Starter + const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter)); + mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel); } private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] { diff --git a/src/vs/platform/extensions/node/extensionHostStarter.ts b/src/vs/platform/extensions/node/extensionHostStarter.ts index 162b85cd85d..8edfc9ca0de 100644 --- a/src/vs/platform/extensions/node/extensionHostStarter.ts +++ b/src/vs/platform/extensions/node/extensionHostStarter.ts @@ -15,6 +15,12 @@ import { ILogService } from 'vs/platform/log/common/log'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { mixin } from 'vs/base/common/objects'; import { cwd } from 'vs/base/common/process'; +import { StopWatch } from 'vs/base/common/stopwatch'; + +export interface IPartialLogService { + readonly _serviceBrand: undefined; + info(message: string): void; +} class ExtensionHostProcess extends Disposable { @@ -37,7 +43,7 @@ class ExtensionHostProcess extends Disposable { constructor( public readonly id: string, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: IPartialLogService ) { super(); } @@ -47,14 +53,16 @@ class ExtensionHostProcess extends Disposable { } start(opts: IExtensionHostProcessOptions): { pid: number; } { + const sw = StopWatch.create(false); this._process = fork( FileAccess.asFileUri('bootstrap-fork', require).fsPath, ['--type=extensionHost', '--skipWorkspaceStorageLock'], mixin({ cwd: cwd() }, opts), ); + const forkTime = sw.elapsed(); const pid = this._process.pid; - this._logService.info(`Starting extension host with pid ${pid}.`); + this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); const stdoutDecoder = new StringDecoder('utf-8'); this._process.stdout?.on('data', (chunk) => { @@ -125,7 +133,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter private readonly _extHosts: Map; constructor( - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: IPartialLogService ) { this._extHosts = new Map(); } diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts b/src/vs/platform/extensions/node/extensionHostStarterWorker.ts new file mode 100644 index 00000000000..6bd329425ab --- /dev/null +++ b/src/vs/platform/extensions/node/extensionHostStarterWorker.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionHostStarter, IPartialLogService } from 'vs/platform/extensions/node/extensionHostStarter'; + +export interface IExtensionHostStarterWorkerHost { + logInfo(message: string): Promise; +} + +/** + * The `create` function needs to be there by convention because + * we are loaded via the `vs/base/common/worker/simpleWorker` utility. + */ +export function create(host: IExtensionHostStarterWorkerHost) { + const partialLogService: IPartialLogService = { + _serviceBrand: undefined, + info: (message: string): void => { + host.logInfo(message); + } + }; + return new ExtensionHostStarter(partialLogService); +} diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts new file mode 100644 index 00000000000..c3fd7297860 --- /dev/null +++ b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(function () { + 'use strict'; + + const loader = require('../../../loader'); + const bootstrap = require('../../../../bootstrap'); + const path = require('path'); + const parentPort = require('worker_threads').parentPort; + + // Bootstrap: NLS + const nlsConfig = bootstrap.setupNLS(); + + // Bootstrap: Loader + loader.config({ + baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../../'), { isWindows: process.platform === 'win32' }), + catchError: true, + nodeRequire: require, + nodeMain: __filename, + 'vs/nls': nlsConfig, + amdModulesPattern: /^vs\//, + recordStats: true + }); + + let isFirstMessage = true; + let beforeReadyMessages: any[] = []; + + const initialMessageHandler = (data: any) => { + if (!isFirstMessage) { + beforeReadyMessages.push(data); + return; + } + + isFirstMessage = false; + loadCode(data); + }; + + parentPort.on('message', initialMessageHandler); + + const loadCode = function (moduleId: string) { + loader([moduleId], function (ws: any) { + setTimeout(() => { + + const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + parentPort.postMessage(msg, transfer); + }, null); + parentPort.off('message', initialMessageHandler); + parentPort.on('message', (data: any) => { + messageHandler.onmessage(data); + }); + while (beforeReadyMessages.length > 0) { + const msg = beforeReadyMessages.shift()!; + messageHandler.onmessage(msg); + } + + }); + }, (err: any) => console.error(err)); + }; + + parentPort.on('messageerror', (err: Error) => { + console.error(err); + }); +})(); diff --git a/src/vs/platform/extensions/node/mainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/node/mainProcessExtensionHostStarter.ts new file mode 100644 index 00000000000..9b25105919c --- /dev/null +++ b/src/vs/platform/extensions/node/mainProcessExtensionHostStarter.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SerializedError } from 'vs/base/common/errors'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; +import { Event } from 'vs/base/common/event'; +import { FileAccess } from 'vs/base/common/network'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Worker } from 'worker_threads'; +import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; + +class NodeWorker implements IWorker { + + private readonly _worker: Worker; + + constructor(callback: IWorkerCallback, onErrorCallback: (err: any) => void) { + this._worker = new Worker( + FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath, + ); + this._worker.on('message', callback); + this._worker.on('error', onErrorCallback); + // this._worker.on('exit', (code) => { + // console.log(`worker exited with code `, code); + // }); + } + + getId(): number { + return 1; + } + + postMessage(message: any, transfer: ArrayBuffer[]): void { + this._worker.postMessage(message, transfer); + } + + dispose(): void { + this._worker.terminate(); + } +} + +class ExtensionHostStarterWorkerHost implements IExtensionHostStarterWorkerHost { + constructor( + @ILogService private readonly _logService: ILogService + ) { } + + public async logInfo(message: string): Promise { + this._logService.info(message); + } +} + +export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { + _serviceBrand: undefined; + + private _proxy: ExtensionHostStarter | null; + private readonly _worker: SimpleWorkerClient; + + constructor( + @ILogService private readonly _logService: ILogService + ) { + this._proxy = null; + + const workerFactory: IWorkerFactory = { + create: (moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker => { + const worker = new NodeWorker(callback, onErrorCallback); + worker.postMessage(moduleId, []); + return worker; + } + }; + this._worker = new SimpleWorkerClient( + workerFactory, + 'vs/platform/extensions/node/extensionHostStarterWorker', + new ExtensionHostStarterWorkerHost(this._logService) + ); + this._initialize(); + } + + dispose(): void { + // Intentionally not killing the extension host processes + } + + async _initialize(): Promise { + this._proxy = await this._worker.getProxyObject(); + } + + onDynamicStdout(id: string): Event { + return this._proxy!.onDynamicStderr(id); + } + + onDynamicStderr(id: string): Event { + return this._proxy!.onDynamicStderr(id); + } + + onDynamicMessage(id: string): Event { + return this._proxy!.onDynamicMessage(id); + } + + onDynamicError(id: string): Event<{ error: SerializedError; }> { + return this._proxy!.onDynamicError(id); + } + + onDynamicExit(id: string): Event<{ code: number; signal: string; }> { + return this._proxy!.onDynamicExit(id); + } + + async createExtensionHost(): Promise<{ id: string; }> { + const proxy = await this._worker.getProxyObject(); + return proxy.createExtensionHost(); + } + + async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number; }> { + const proxy = await this._worker.getProxyObject(); + return proxy.start(id, opts); + } + + async enableInspectPort(id: string): Promise { + const proxy = await this._worker.getProxyObject(); + return proxy.enableInspectPort(id); + } + + async kill(id: string): Promise { + const proxy = await this._worker.getProxyObject(); + return proxy.kill(id); + } +} diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts index 3464798a9ac..9bdffcd67fd 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts @@ -3,7 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { registerMainProcessRemoteService, registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -registerSharedProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); +const location = 'main' as 'main' | 'shared'; + +if (location === 'main') { + registerMainProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); +} else { + registerSharedProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); +} From 3fc735d00a945487107e6c3608bd44ca1afa79e8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Oct 2021 16:00:03 +0200 Subject: [PATCH 002/375] Fix layering problem --- src/vs/base/common/worker/simpleWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 9560ebf4682..2f38c223da8 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -551,6 +551,6 @@ export class SimpleWorkerServer { /** * Called on the worker side */ -export function create(postMessage: (msg: Message, transfer?: Transferable[]) => void): SimpleWorkerServer { +export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer { return new SimpleWorkerServer(postMessage, null); } From 75d2f6d5f28d1fd1fe9458c21338ea57fd8883a3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Oct 2021 16:21:40 +0200 Subject: [PATCH 003/375] Fix layering problem --- .../platform/extensions/node/extensionHostStarterWorkerMain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts index c3fd7297860..b4efa0c798f 100644 --- a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts +++ b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts @@ -44,7 +44,7 @@ loader([moduleId], function (ws: any) { setTimeout(() => { - const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + const messageHandler = ws.create((msg: any, transfer?: ArrayBuffer[]) => { parentPort.postMessage(msg, transfer); }, null); parentPort.off('message', initialMessageHandler); From 2e2350f5cda16e237d1a5aca4587df001291d354 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Oct 2021 20:25:24 +0200 Subject: [PATCH 004/375] Fix ESM webpack problem --- src/vs/base/common/worker/simpleWorker.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 2f38c223da8..252f7f4ece8 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -534,7 +534,15 @@ export class SimpleWorkerServer { return new Promise((resolve, reject) => { // Use the global require to be sure to get the global config - (globals.require || require)([moduleId], (module: { create: IRequestHandlerFactory }) => { + + // ESM-comment-begin + const req = (globals.require || require); + // ESM-comment-end + // ESM-uncomment-begin + // const req = globals.require; + // ESM-uncomment-end + + req([moduleId], (module: { create: IRequestHandlerFactory }) => { this._requestHandler = module.create(hostProxy); if (!this._requestHandler) { From 6f72d1b1841d6d860a6e23a27ee94415f9166ffb Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 26 Oct 2021 11:16:47 +0200 Subject: [PATCH 005/375] Try to fork the extension host process directly from the main process --- src/vs/code/electron-main/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 3f7aa27b5cd..22d78d1cc98 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -45,7 +45,7 @@ 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'; -import { ExtensionHostStarter } from 'vs/platform/extensions/node/mainProcessExtensionHostStarter'; +import { ExtensionHostStarter } from 'vs/platform/extensions/node/extensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { IFileService } from 'vs/platform/files/common/files'; From 2af65ef77461eec76ecf1d9020862d7d1cdb5435 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 26 Oct 2021 22:46:31 +0200 Subject: [PATCH 006/375] Try to use `spawn` instead of `fork` --- .../extensions/node/extensionHostStarter.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/extensions/node/extensionHostStarter.ts b/src/vs/platform/extensions/node/extensionHostStarter.ts index 8edfc9ca0de..4f343392ecf 100644 --- a/src/vs/platform/extensions/node/extensionHostStarter.ts +++ b/src/vs/platform/extensions/node/extensionHostStarter.ts @@ -7,7 +7,7 @@ import { SerializedError, transformErrorForSerialization } from 'vs/base/common/ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { Emitter, Event } from 'vs/base/common/event'; -import { ChildProcess, fork } from 'child_process'; +import { ChildProcess, spawn } from 'child_process'; import { FileAccess } from 'vs/base/common/network'; import { StringDecoder } from 'string_decoder'; import * as platform from 'vs/base/common/platform'; @@ -54,11 +54,18 @@ class ExtensionHostProcess extends Disposable { start(opts: IExtensionHostProcessOptions): { pid: number; } { const sw = StopWatch.create(false); - this._process = fork( - FileAccess.asFileUri('bootstrap-fork', require).fsPath, - ['--type=extensionHost', '--skipWorkspaceStorageLock'], + opts.env = opts.env || {}; + opts.env.ELECTRON_RUN_AS_NODE = '1'; + this._process = spawn( + process.execPath, + [FileAccess.asFileUri('bootstrap-fork', require).fsPath, '--type=extensionHost', '--skipWorkspaceStorageLock'], mixin({ cwd: cwd() }, opts), ); + // this._process = fork( + // FileAccess.asFileUri('bootstrap-fork', require).fsPath, + // ['--type=extensionHost', '--skipWorkspaceStorageLock'], + // mixin({ cwd: cwd() }, opts), + // ); const forkTime = sw.elapsed(); const pid = this._process.pid; @@ -76,9 +83,9 @@ class ExtensionHostProcess extends Disposable { this._onStderr.fire(strChunk); }); - this._process.on('message', msg => { - this._onMessage.fire(msg); - }); + // this._process.on('message', msg => { + // this._onMessage.fire(msg); + // }); this._process.on('error', (err) => { this._onError.fire({ error: transformErrorForSerialization(err) }); From 0603dcd867c76721e51e7c4a023f6eeec87a93c6 Mon Sep 17 00:00:00 2001 From: Simon McEnlly Date: Wed, 3 Nov 2021 15:35:53 +1000 Subject: [PATCH 007/375] Add replaceAll method to OutputChannel to improve render flickering for extensions with minimal output contents; associated with https://github.com/microsoft/vscode/issues/132183 --- src/vs/vscode.d.ts | 8 + .../api/browser/mainThreadOutputService.ts | 8 + .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostOutput.ts | 10 ++ .../api/node/extHostOutputService.ts | 6 + .../contrib/output/browser/outputServices.ts | 4 + .../workbench/contrib/output/common/output.ts | 5 + .../output/common/outputChannelModel.ts | 149 ++++++++++++++++-- 8 files changed, 182 insertions(+), 9 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 88b9794ba21..77e5db6f471 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5662,6 +5662,14 @@ declare module 'vscode' { */ clear(): void; + /* + * Replaces the existing contents of the channel with the given value. + * + * *Note*: this method should only be used by extensions with smaller output + * channel text sizes. Use of `append` methods is preferred. + */ + replaceAll(value: string): void; + /** * Reveal this channel in the UI. * diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 246fad219b2..47ed29594b4 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -81,6 +81,14 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } + public $replaceAll(channelId: string, till: number, value: string): Promise | undefined { + const channel = this._getChannel(channelId); + if (channel) { + channel.replaceAll(till, value); + } + return undefined; + } + public $reveal(channelId: string, preserveFocus: boolean): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 28ba6c7b75f..75cfaf00eb2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -447,6 +447,7 @@ export interface MainThreadOutputServiceShape extends IDisposable { $append(channelId: string, value: string): Promise | undefined; $update(channelId: string): Promise | undefined; $clear(channelId: string, till: number): Promise | undefined; + $replaceAll(channelId: string, till: number, value: string): Promise | undefined; $reveal(channelId: string, preserveFocus: boolean): Promise | undefined; $close(channelId: string): Promise | undefined; $dispose(channelId: string): Promise | undefined; diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index a15b98d3e72..f633e4b1376 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -58,6 +58,13 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements this._id.then(id => this._proxy.$clear(id, till)); } + replaceAll(value: string): void { + this.validate(); + const till = this._offset; + this._offset += value ? VSBuffer.fromString(value).byteLength : 0; + this._id.then(id => this._proxy.$replaceAll(id, till, value)); + } + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { this.validate(); this._id.then(id => this._proxy.$reveal(id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus))); @@ -125,6 +132,9 @@ export class LazyOutputChannel implements vscode.OutputChannel { clear(): void { this._channel.then(channel => channel.clear()); } + replaceAll(value: string): void { + this._channel.then(channel => channel.replaceAll(value)); + } show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { this._channel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 3cccc42ef88..fdec4a891ee 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -55,6 +55,12 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { this._onDidAppend.fire(); } + override replaceAll(value: string): void { + this._appender.append(value); + this._appender.flush(); + super.replaceAll(value); + } + override update(): void { this._appender.flush(); super.update(); diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 533d3170fd8..9b0f75a29f3 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -52,6 +52,10 @@ class OutputChannel extends Disposable implements IOutputChannel { clear(till?: number): void { this.model.clear(till); } + + replaceAll(till: number, value: string): void { + this.model.replaceAll(till, value); + } } export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider { diff --git a/src/vs/workbench/contrib/output/common/output.ts b/src/vs/workbench/contrib/output/common/output.ts index ded98a2afbd..2d7c2b3ec20 100644 --- a/src/vs/workbench/contrib/output/common/output.ts +++ b/src/vs/workbench/contrib/output/common/output.ts @@ -127,6 +127,11 @@ export interface IOutputChannel { */ clear(till?: number): void; + /** + * Replaces the output of the channel. + */ + replaceAll(till: number, value: string): void; + /** * Disposes the output channel. */ diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 64396aaed14..49c96f36edc 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -6,6 +6,7 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as resources from 'vs/base/common/resources'; import { ITextModel } from 'vs/editor/common/model'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Promises, RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; @@ -16,8 +17,10 @@ import { Disposable, toDisposable, IDisposable, dispose } from 'vs/base/common/l import { isNumber } from 'vs/base/common/types'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; import { VSBuffer } from 'vs/base/common/buffer'; import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; export interface IOutputChannelModel extends IDisposable { readonly onDidAppendedContent: Event; @@ -26,6 +29,7 @@ export interface IOutputChannelModel extends IDisposable { update(): void; loadModel(): Promise; clear(till?: number): void; + replaceAll(till: number, value: string): void; } export const IOutputChannelModelService = createDecorator('outputChannelModelService'); @@ -75,6 +79,10 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen protected startOffset: number = 0; protected endOffset: number = 0; + protected replaceAllValue: string | null = null; + protected replaceAllValueSeenInAppend: boolean = false; + protected replaceAllCancellationToken: CancellationTokenSource | null = null; + constructor( private readonly modelUri: URI, private readonly mimeType: string, @@ -82,6 +90,7 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen protected fileService: IFileService, protected modelService: IModelService, protected modeService: IModeService, + protected editorWorkerService: IEditorWorkerService, ) { super(); this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); @@ -93,6 +102,11 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.modelUpdater.cancel(); this.onUpdateModelCancelled(); } + if (this.replaceAllCancellationToken) { + this.replaceAllCancellationToken.cancel(); + this.replaceAllCancellationToken = null; + this.replaceAllValue = null; + } if (this.model) { this.model.setValue(''); } @@ -100,6 +114,53 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.startOffset = this.endOffset; } + replaceAll(till: number, value: string): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + this.onUpdateModelCancelled(); + } + this.replaceAllValue = value; + this.replaceAllValueSeenInAppend = false; + this.endOffset = isNumber(till) ? till : this.endOffset; + this.startOffset = this.endOffset; + + if (this.model) { + if (this.replaceAllCancellationToken) { + this.replaceAllCancellationToken.cancel(); + this.replaceAllCancellationToken = null; + } + + const myToken = new CancellationTokenSource(); + this.replaceAllCancellationToken = myToken; + + this.editorWorkerService.computeMoreMinimalEdits(this.model.uri, [{ text: value, range: this.model.getFullModelRange() }]).then(edits => { + if (myToken.token.isCancellationRequested) { + return; + } + + this.replaceAllCancellationToken = null; + + if (edits && edits.length > 0) { + if (this.model) { + this.model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + this._onDidAppendedContent.fire(); + } + } + }).catch((e) => { + if (myToken.token.isCancellationRequested) { + return; + } + + this.replaceAllCancellationToken = null; + + if (this.model) { + this.model.setValue(value); + this._onDidAppendedContent.fire(); + } + }); + } + } + update(): void { } protected createModel(content: string): ITextModel { @@ -118,11 +179,63 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen } appendToModel(content: string): void { - if (this.model && content) { - const lastLine = this.model.getLineCount(); - const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); - this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); - this._onDidAppendedContent.fire(); + if (this.model) { + if (this.replaceAllValue === null && content) { + const lastLine = this.model.getLineCount(); + const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); + this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); + this._onDidAppendedContent.fire(); + } else if (this.replaceAllValue !== null) { + if (this.replaceAllCancellationToken) { + this.replaceAllCancellationToken.cancel(); + this.replaceAllCancellationToken = null; + } + + if (!this.replaceAllValueSeenInAppend && content === this.replaceAllValue) { + this.replaceAllValue = null; + return; + } + + if (content) { + if (!this.replaceAllValueSeenInAppend && content.length >= this.replaceAllValue.length && content.startsWith(this.replaceAllValue)) { + this.replaceAllValue += content.substring(this.replaceAllValue.length); + this.replaceAllValueSeenInAppend = true; + } else { + this.replaceAllValue += content; + } + } + + const myToken = new CancellationTokenSource(); + this.replaceAllCancellationToken = myToken; + + this.editorWorkerService.computeMoreMinimalEdits(this.model.uri, [{ text: this.replaceAllValue, range: this.model.getFullModelRange() }]).then(edits => { + if (myToken.token.isCancellationRequested) { + return; + } + + this.replaceAllCancellationToken = null; + this.replaceAllValue = null; + + if (edits && edits.length > 0) { + if (this.model) { + this.model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + this._onDidAppendedContent.fire(); + } + } + }).catch(() => { + if (myToken.token.isCancellationRequested) { + return; + } + + if (this.model && this.replaceAllValue !== null) { + this.model.setValue(this.replaceAllValue); + this._onDidAppendedContent.fire(); + } + + this.replaceAllCancellationToken = null; + this.replaceAllValue = null; + }); + } } } @@ -212,9 +325,10 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I @IFileService fileService: IFileService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IEditorWorkerService editorWorkerService: IEditorWorkerService ) { - super(modelUri, mimeType, file, fileService, modelService, modeService); + super(modelUri, mimeType, file, fileService, modelService, modeService, editorWorkerService); this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService, logService)); this._register(this.fileHandler.onDidContentChange(size => this.update(size))); @@ -250,6 +364,14 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I }); } + override replaceAll(till: number, value: string): void { + const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); + loadModelPromise.then(() => { + super.replaceAll(till, value); + this.update(); + }); + } + append(message: string): void { throw new Error('Not supported'); } @@ -316,9 +438,10 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement @IFileService fileService: IFileService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, - @ILoggerService loggerService: ILoggerService + @ILoggerService loggerService: ILoggerService, + @IEditorWorkerService editorWorkerService: IEditorWorkerService ) { - super(modelUri, mimeType, file, fileService, modelService, modeService); + super(modelUri, mimeType, file, fileService, modelService, modeService, editorWorkerService); this.appendedMessage = ''; this.loadingFromFileInProgress = false; @@ -354,6 +477,11 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement } } + override replaceAll(till: number, value: string): void { + super.replaceAll(till, value); + this.appendedMessage = ''; + } + override clear(till?: number): void { super.clear(till); this.appendedMessage = ''; @@ -463,4 +591,7 @@ class DelegatedOutputChannelModel extends Disposable implements IOutputChannelMo this.outputChannelModel.then(outputChannelModel => outputChannelModel.clear(till)); } + replaceAll(till: number, value: string): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.replaceAll(till, value)); + } } From 776a085e6ea23ad6195cf7617a253f2e9e4a02ca Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 4 Nov 2021 15:31:24 +0100 Subject: [PATCH 008/375] Share common code for the ExtensionResourceLoaderService --- .../themes/browser/themes.contribution.ts | 4 +- .../common/webExtensionsScannerService.ts | 7 +- .../browser/extensionResourceLoaderService.ts | 54 +++-------- .../common/extensionResourceLoader.ts | 89 +++++++++++++++++++ .../extensionResourceLoaderService.ts | 40 ++++----- .../themes/browser/workbenchThemeService.ts | 12 +-- .../tokenStyleResolving.test.ts | 8 +- 7 files changed, 131 insertions(+), 83 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 270fab33f64..3c45fdbe572 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -30,6 +30,7 @@ import { Codicon } from 'vs/base/common/codicons'; 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'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -49,6 +50,7 @@ export class SelectColorThemeAction extends Action { @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 ) { @@ -134,7 +136,7 @@ export class SelectColorThemeAction extends Action { }); quickpick.show(); - if (this.extensionGalleryService.isEnabled()) { + if (this.extensionGalleryService.isEnabled() && this.extensionResourceLoaderService.supportsExtensionGalleryResources) { const marketplaceThemes = new MarketplaceThemes(this.extensionGalleryService, this.extensionManagementService, this.themeService, this.logService); marketplaceThemes.onDidChange(() => { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 0cdb7aa1a29..05a29726e1d 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -24,8 +24,6 @@ import * as semver from 'vs/base/common/semver/semver'; import { isString } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ResourceMap } from 'vs/base/common/map'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { format2 } from 'vs/base/common/strings'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; @@ -74,7 +72,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IProductService private readonly productService: IProductService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, ) { @@ -334,10 +331,10 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private async toWebExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: IStringDictionary): Promise { - if (!this.productService.extensionsGallery) { + let extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL(galleryExtension, 'extension'); + if (!extensionLocation) { throw new Error('No extension gallery service configured.'); } - let extensionLocation = URI.parse(format2(this.productService.extensionsGallery.resourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' })); extensionLocation = galleryExtension.properties.targetPlatform === TargetPlatform.WEB ? extensionLocation.with({ query: `${extensionLocation.query ? `${extensionLocation.query}&` : ''}target=${galleryExtension.properties.targetPlatform}` }) : extensionLocation; const extensionResources = await this.listExtensionResources(extensionLocation); const packageNLSResource = extensionResources.find(e => basename(e) === 'package.nls.json'); diff --git a/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts index bd634b1e6ea..8f68d8a7ab2 100644 --- a/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts +++ b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts @@ -6,35 +6,27 @@ import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { AbstractExtensionResourceLoaderService, IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isWeb } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { +class ExtensionResourceLoaderService extends AbstractExtensionResourceLoaderService { declare readonly _serviceBrand: undefined; - private readonly _extensionGalleryResourceAuthority: string | undefined; - constructor( - @IFileService private readonly _fileService: IFileService, - @IProductService private readonly _productService: IProductService, - @IStorageService private readonly _storageService: IStorageService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @ILogService private readonly _logService: ILogService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IConfigurationService configurationService: IConfigurationService, + @ILogService readonly _logService: ILogService, ) { - if (_productService.extensionsGallery) { - this._extensionGalleryResourceAuthority = this._getExtensionResourceAuthority(URI.parse(_productService.extensionsGallery.resourceUrlTemplate)); - } + super(fileService, storageService, productService, environmentService, configurationService); } async readExtensionResource(uri: URI): Promise { @@ -46,18 +38,8 @@ class ExtensionResourceLoaderService implements IExtensionResourceLoaderService } const requestInit: RequestInit = {}; - if (this._extensionGalleryResourceAuthority && this._extensionGalleryResourceAuthority === this._getExtensionResourceAuthority(uri)) { - const machineId = await this._getServiceMachineId(); - requestInit.headers = { - 'X-Client-Name': `${this._productService.applicationName}${isWeb ? '-web' : ''}`, - 'X-Client-Version': this._productService.version - }; - if (supportsTelemetry(this._productService, this._environmentService) && getTelemetryLevel(this._configurationService) === TelemetryLevel.USAGE) { - requestInit.headers['X-Machine-Id'] = machineId; - } - if (this._productService.commit) { - requestInit.headers['X-Client-Commit'] = this._productService.commit; - } + if (this.isExtensionGalleryResource(uri)) { + requestInit.headers = await this.getExtensionGalleryRequestHeaders(); requestInit.mode = 'cors'; /* set mode to cors so that above headers are always passed */ } @@ -67,20 +49,6 @@ class ExtensionResourceLoaderService implements IExtensionResourceLoaderService throw new Error(response.statusText); } return response.text(); - - } - - private _serviceMachineIdPromise: Promise | undefined; - private _getServiceMachineId(): Promise { - if (!this._serviceMachineIdPromise) { - this._serviceMachineIdPromise = getServiceMachineId(this._environmentService, this._fileService, this._storageService); - } - return this._serviceMachineIdPromise; - } - - private _getExtensionResourceAuthority(uri: URI): string | undefined { - const index = uri.authority.indexOf('.'); - return index !== -1 ? uri.authority.substring(index + 1) : undefined; } } diff --git a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts index 0820d426cca..7b43fa7eb8f 100644 --- a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts @@ -3,8 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isWeb } from 'vs/base/common/platform'; +import { format2 } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import { IHeaders } from 'vs/base/parts/request/common/request'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; export const IExtensionResourceLoaderService = createDecorator('extensionResourceLoaderService'); @@ -18,4 +29,82 @@ export interface IExtensionResourceLoaderService { * Read a certain resource within an extension. */ readExtensionResource(uri: URI): Promise; + + /** + * Returns whether the gallery provides extension resources. + */ + supportsExtensionGalleryResources: boolean; + + /** + * Computes the URL of a extension gallery resource. Returns `undefined` if gallery does not provide extension resources. + */ + getExtensionGalleryResourceURL(galleryExtension: { publisher: string, name: string, version: string }, path?: string): URI | undefined; +} + + +export abstract class AbstractExtensionResourceLoaderService implements IExtensionResourceLoaderService { + + readonly _serviceBrand: undefined; + + private readonly _extensionGalleryResourceUrlTemplate: string | undefined; + private readonly _extensionGalleryAuthority: string | undefined; + + constructor( + readonly _fileService: IFileService, + readonly _storageService: IStorageService, + readonly _productService: IProductService, + readonly _environmentService: IEnvironmentService, + readonly _configurationService: IConfigurationService, + ) { + if (_productService.extensionsGallery) { + this._extensionGalleryResourceUrlTemplate = _productService.extensionsGallery.resourceUrlTemplate; + this._extensionGalleryAuthority = this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)); + } + } + + public get supportsExtensionGalleryResources(): boolean { + return this._extensionGalleryResourceUrlTemplate !== undefined; + } + + public getExtensionGalleryResourceURL(galleryExtension: { publisher: string, name: string, version: string }, path?: string): URI | undefined { + if (this._extensionGalleryResourceUrlTemplate) { + return URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' })); + } + return undefined; + } + + + public abstract readExtensionResource(uri: URI): Promise; + + protected isExtensionGalleryResource(uri: URI) { + return this._extensionGalleryAuthority && this._extensionGalleryAuthority === this._getExtensionGalleryAuthority(uri); + } + + protected async getExtensionGalleryRequestHeaders(): Promise { + const headers: IHeaders = { + 'X-Client-Name': `${this._productService.applicationName}${isWeb ? '-web' : ''}`, + 'X-Client-Version': this._productService.version + }; + if (supportsTelemetry(this._productService, this._environmentService) && getTelemetryLevel(this._configurationService) === TelemetryLevel.USAGE) { + headers['X-Machine-Id'] = await this._getServiceMachineId(); + } + if (this._productService.commit) { + headers['X-Client-Commit'] = this._productService.commit; + } + return headers; + } + + private _serviceMachineIdPromise: Promise | undefined; + private _getServiceMachineId(): Promise { + if (!this._serviceMachineIdPromise) { + this._serviceMachineIdPromise = getServiceMachineId(this._environmentService, this._fileService, this._storageService); + } + return this._serviceMachineIdPromise; + } + + private _getExtensionGalleryAuthority(uri: URI): string | undefined { + const index = uri.authority.indexOf('.'); + return index !== -1 ? uri.authority.substring(index + 1) : undefined; + } + } diff --git a/src/vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService.ts index a0f30427e40..4062092b43b 100644 --- a/src/vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService.ts +++ b/src/vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService.ts @@ -6,47 +6,37 @@ import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { AbstractExtensionResourceLoaderService, IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { IProductService } from 'vs/platform/product/common/productService'; import { asText, IRequestService } from 'vs/platform/request/common/request'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -export class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { - - declare readonly _serviceBrand: undefined; - - private readonly _extensionGalleryResourceAuthority: string | undefined; +export class ExtensionResourceLoaderService extends AbstractExtensionResourceLoaderService { constructor( - @IFileService private readonly _fileService: IFileService, - @IProductService _productService: IProductService, - @IRequestService private readonly _requestService: IRequestService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IConfigurationService configurationService: IConfigurationService, + @IRequestService readonly _requestService: IRequestService, ) { - if (_productService.extensionsGallery) { - this._extensionGalleryResourceAuthority = this._getExtensionResourceAuthority(URI.parse(_productService.extensionsGallery.resourceUrlTemplate)); - } - - + super(fileService, storageService, productService, environmentService, configurationService); } async readExtensionResource(uri: URI): Promise { - - if (this._extensionGalleryResourceAuthority && this._extensionGalleryResourceAuthority === this._getExtensionResourceAuthority(uri)) { - const requestContext = await this._requestService.request({ - url: uri.toString() - }, CancellationToken.None); - + if (this.isExtensionGalleryResource(uri)) { + const headers = await this.getExtensionGalleryRequestHeaders(); + const requestContext = await this._requestService.request({ url: uri.toString(), headers }, CancellationToken.None); return (await asText(requestContext)) || ''; } - const result = await this._fileService.readFile(uri); return result.value.toString(); } - private _getExtensionResourceAuthority(uri: URI): string | undefined { - const index = uri.authority.indexOf('.'); - return index !== -1 ? uri.authority.substring(index + 1) : undefined; - } } registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 9f4118d94fc..aa5970e8d29 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -40,8 +40,6 @@ import { RunOnceScheduler, Sequencer } from 'vs/base/common/async'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; import { asCssVariableName, getColorRegistry } from 'vs/platform/theme/common/colorRegistry'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { format2 } from 'vs/base/common/strings'; // implementation @@ -113,8 +111,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService, @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, - @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService, - @IProductService private readonly productService: IProductService + @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService ) { this.container = layoutService.container; this.settings = new ThemeConfiguration(configurationService); @@ -412,13 +409,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } public async getMarketplaceColorThemes(id: string, version: string): Promise { - const resourceUrlTemplate = this.productService.extensionsGallery?.resourceUrlTemplate; - if (!resourceUrlTemplate) { + const [publisher, name] = id.split('.'); + const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version}, 'extension'); + if (!extensionLocation) { return []; } try { - const [publisher, name] = id.split('.'); - const extensionLocation = URI.parse(format2(resourceUrlTemplate, { publisher, name, version, path: 'extension' })); const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json')); const data: ExtensionData = { extensionPublisher: publisher, extensionId: id, extensionName: name, extensionIsBuiltin: false }; diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 8031e5b4d41..8a3acdfc4d2 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -17,6 +17,9 @@ import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionR import { ITokenStyle } from 'vs/platform/theme/common/themeService'; import { mock, TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { IRequestService } from 'vs/platform/request/common/request'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined }; const unsetStyle = { bold: false, underline: false, italic: false }; @@ -81,8 +84,11 @@ function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClas suite('Themes - TokenStyleResolving', () => { const fileService = new FileService(new NullLogService()); const requestService = new (mock())(); + const storageService = new (mock())(); + const environmentService = new (mock())(); + const configurationService = new (mock())(); - const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService, TestProductService, requestService); + const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService, storageService, TestProductService, environmentService, configurationService, requestService); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); From b68e4d2bacdb613751d1fc50e4291146aeca307f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 4 Nov 2021 15:54:20 +0100 Subject: [PATCH 009/375] polish --- .../extensionResourceLoader/common/extensionResourceLoader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts index 7b43fa7eb8f..dad5b25ece3 100644 --- a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts @@ -38,7 +38,7 @@ export interface IExtensionResourceLoaderService { /** * Computes the URL of a extension gallery resource. Returns `undefined` if gallery does not provide extension resources. */ - getExtensionGalleryResourceURL(galleryExtension: { publisher: string, name: string, version: string }, path?: string): URI | undefined; + getExtensionGalleryResourceURL(galleryExtension: { publisher: string, name: string, version: string }, path?: string): URI | undefined; } @@ -58,7 +58,7 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi ) { if (_productService.extensionsGallery) { this._extensionGalleryResourceUrlTemplate = _productService.extensionsGallery.resourceUrlTemplate; - this._extensionGalleryAuthority = this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)); + this._extensionGalleryAuthority = this._extensionGalleryResourceUrlTemplate ? this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)) : undefined; } } From bb472c5519794f235013b45bfecbe8c1030d315a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 4 Nov 2021 09:45:14 -0700 Subject: [PATCH 010/375] xterm@4.15.0-beta.13 --- package.json | 4 ++-- remote/package.json | 4 ++-- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 ++++---- remote/yarn.lock | 16 ++++++++-------- yarn.lock | 16 ++++++++-------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 42a93255b05..987298959ba 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", + "xterm": "4.15.0-beta.13", "xterm-addon-search": "0.9.0-beta.5", "xterm-addon-serialize": "0.7.0-beta.2", "xterm-addon-unicode11": "0.3.0", "xterm-addon-webgl": "0.12.0-beta.15", - "xterm-headless": "4.15.0-beta.10", + "xterm-headless": "4.15.0-beta.13", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 1851794ddd6..f2659758d02 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", + "xterm": "4.15.0-beta.13", "xterm-addon-search": "0.9.0-beta.5", "xterm-addon-serialize": "0.7.0-beta.2", "xterm-addon-unicode11": "0.3.0", "xterm-addon-webgl": "0.12.0-beta.15", - "xterm-headless": "4.15.0-beta.10", + "xterm-headless": "4.15.0-beta.13", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 307a919a301..1cd5ff1f204 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,7 +10,7 @@ "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.5.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", + "xterm": "4.15.0-beta.13", "xterm-addon-search": "0.9.0-beta.5", "xterm-addon-unicode11": "0.3.0", "xterm-addon-webgl": "0.12.0-beta.15" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 384abe27126..4d08c124a7a 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -128,7 +128,7 @@ xterm-addon-webgl@0.12.0-beta.15: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm@4.15.0-beta.13: + version "4.15.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.13.tgz#a78af26e7065d64b8b49ab7a606ca63a3189af72" + integrity sha512-kz4U7dPIDxOvAZkXutsjju1cR7rJmOvI0oWXl337d2WnfY8qlnKDgu5DCSlfHSzDH8qaSQodzZaArd0/6RVdiQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index 091d7fe7dad..39f0e8f9aac 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -561,15 +561,15 @@ xterm-addon-webgl@0.12.0-beta.15: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== -xterm-headless@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.10.tgz#2dbcb40dfda7ecfdacc7b63889c80da965480ce7" - integrity sha512-kDAzmaeFX8hAJvbPUJc4dW4SoVBSg4onCVOPyi8QTmxZz1o7I9mX4U7DX1v3PceyfrU27A9k6zXjuTuPjxCCSQ== +xterm-headless@4.15.0-beta.13: + version "4.15.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.13.tgz#f9f8e3799209817e89a2961b3fe643d2ed4b71d5" + integrity sha512-csvpnLqaTi9ULNPcIWAh+o5xIswBYuD30fEmnOd6Az1tZhNaZVp27FnI1vszKdZ37DO8SxN7R1agvnz8II9QLw== -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm@4.15.0-beta.13: + version "4.15.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.13.tgz#a78af26e7065d64b8b49ab7a606ca63a3189af72" + integrity sha512-kz4U7dPIDxOvAZkXutsjju1cR7rJmOvI0oWXl337d2WnfY8qlnKDgu5DCSlfHSzDH8qaSQodzZaArd0/6RVdiQ== yauzl@^2.9.2: version "2.10.0" diff --git a/yarn.lock b/yarn.lock index 2e2f0822981..d171c13d3e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10890,15 +10890,15 @@ xterm-addon-webgl@0.12.0-beta.15: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== -xterm-headless@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.10.tgz#2dbcb40dfda7ecfdacc7b63889c80da965480ce7" - integrity sha512-kDAzmaeFX8hAJvbPUJc4dW4SoVBSg4onCVOPyi8QTmxZz1o7I9mX4U7DX1v3PceyfrU27A9k6zXjuTuPjxCCSQ== +xterm-headless@4.15.0-beta.13: + version "4.15.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.13.tgz#f9f8e3799209817e89a2961b3fe643d2ed4b71d5" + integrity sha512-csvpnLqaTi9ULNPcIWAh+o5xIswBYuD30fEmnOd6Az1tZhNaZVp27FnI1vszKdZ37DO8SxN7R1agvnz8II9QLw== -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm@4.15.0-beta.13: + version "4.15.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.13.tgz#a78af26e7065d64b8b49ab7a606ca63a3189af72" + integrity sha512-kz4U7dPIDxOvAZkXutsjju1cR7rJmOvI0oWXl337d2WnfY8qlnKDgu5DCSlfHSzDH8qaSQodzZaArd0/6RVdiQ== y18n@^3.2.1: version "3.2.2" From ff2bd8f82340f47ba0709364b3c93cc461fa7407 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 4 Nov 2021 09:48:41 -0700 Subject: [PATCH 011/375] Include margin in layout, only trigger resize when settings changed Part of #134513 --- .../terminal/browser/terminalInstance.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 6f5cc9defa0..fd4a62e86a2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -128,6 +128,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _fixedRows: number | undefined; private _cwd: string | undefined = undefined; private _initialCwd: string | undefined = undefined; + private _layoutSettingsChanged: boolean = true; private _dimensionsOverride: ITerminalDimensionsOverride | undefined; private _titleReadyPromise: Promise; private _titleReadyComplete: ((title: string) => any) | undefined; @@ -378,6 +379,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { 'editor.fontFamily' ]; if (layoutSettings.some(id => e.affectsConfiguration(id))) { + this._layoutSettingsChanged = true; await this._resize(); } if (e.affectsConfiguration(TerminalSettingId.UnicodeVersion)) { @@ -440,8 +442,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } const computedStyle = window.getComputedStyle(this._wrapperElement!); - const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); - const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); + const width = parseInt(computedStyle.width); + const height = parseInt(computedStyle.height); + this._evaluateColsAndRows(width, height); } @@ -1419,7 +1422,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } - const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); + // Evaluate columns and rows, exclude the wrapper element's margin + const computedStyle = window.getComputedStyle(this._wrapperElement!); + const horizontalMargin = parseInt(computedStyle.marginLeft) + parseInt(computedStyle.marginRight); + const verticalMargin = parseInt(computedStyle.marginTop) + parseInt(computedStyle.marginBottom); + const terminalWidth = this._evaluateColsAndRows(dimension.width - horizontalMargin, dimension.height - verticalMargin); if (!terminalWidth) { return; } @@ -1442,8 +1449,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this.xterm) { // Only apply these settings when the terminal is visible so that // the characters are measured correctly. - if (this._isVisible) { - const font = this.xterm ? this.xterm.getFont() : this._configHelper.getFont(); + if (this._isVisible && this._layoutSettingsChanged) { + const font = this.xterm.getFont(); const config = this._configHelper.config; this._safeSetOption('letterSpacing', font.letterSpacing); this._safeSetOption('lineHeight', font.lineHeight); @@ -1457,6 +1464,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._initDimensions(); cols = this.cols; rows = this.rows; + + this._layoutSettingsChanged = false; } if (isNaN(cols) || isNaN(rows)) { From 8970935a23a817077f07efe09c703354a1547560 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 4 Nov 2021 22:09:00 +0100 Subject: [PATCH 012/375] Fixes #123592: Move extension host spawning to the shared process --- src/vs/workbench/workbench.desktop.main.ts | 1 - src/vs/workbench/workbench.desktop.sandbox.main.ts | 1 - src/vs/workbench/workbench.sandbox.main.ts | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 57c2caf0a27..e71e108af60 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -59,7 +59,6 @@ import 'vs/workbench/electron-browser/desktop.main'; import 'vs/workbench/services/search/electron-browser/searchService'; import 'vs/workbench/services/extensions/electron-browser/extensionService'; -import 'vs/platform/extensions/node/extensionHostStarter'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts index 9279adf4e22..d7c7c3928ae 100644 --- a/src/vs/workbench/workbench.desktop.sandbox.main.ts +++ b/src/vs/workbench/workbench.desktop.sandbox.main.ts @@ -33,7 +33,6 @@ import 'vs/workbench/electron-sandbox/desktop.main'; //#region --- workbench services -import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; //#endregion diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 0ab403bf96c..9204a629994 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -52,6 +52,7 @@ import 'vs/workbench/services/credentials/electron-sandbox/credentialsService'; import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; import 'vs/workbench/services/localizations/electron-sandbox/localizationsService'; import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; +import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService'; From e9f829000372df0f1c8cd60fd645d2d8f83ad39c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 4 Nov 2021 22:15:48 +0100 Subject: [PATCH 013/375] Wait for up to 6s for extension hosts to exit, then proceed to kill them --- src/vs/code/electron-main/app.ts | 4 +-- .../directMainProcessExtensionHostStarter.ts | 27 +++++++++++++++++++ .../extensions/node/extensionHostStarter.ts | 24 +++++++++++++---- 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index aa46c23c6f4..7c0f550bf0c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -45,7 +45,7 @@ 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'; -import { ExtensionHostStarter } from 'vs/platform/extensions/node/extensionHostStarter'; +import { DirectMainProcessExtensionHostStarter } from 'vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -517,7 +517,7 @@ export class CodeApplication extends Disposable { services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); // Extension Host Starter - services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); + services.set(IExtensionHostStarter, new SyncDescriptor(DirectMainProcessExtensionHostStarter)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); diff --git a/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts new file mode 100644 index 00000000000..7f9a39f17e2 --- /dev/null +++ b/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionHostStarter, IPartialLogService } from 'vs/platform/extensions/node/extensionHostStarter'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class DirectMainProcessExtensionHostStarter extends ExtensionHostStarter { + + constructor( + @ILogService logService: IPartialLogService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService + ) { + super(logService); + + lifecycleMainService.onWillShutdown((e) => { + const exitPromises: Promise[] = []; + for (const [, extHost] of this._extHosts) { + exitPromises.push(extHost.waitForExit(6000)); + } + e.join(Promise.all(exitPromises).then(() => { })); + }); + } + +} diff --git a/src/vs/platform/extensions/node/extensionHostStarter.ts b/src/vs/platform/extensions/node/extensionHostStarter.ts index 4f343392ecf..ac208a54186 100644 --- a/src/vs/platform/extensions/node/extensionHostStarter.ts +++ b/src/vs/platform/extensions/node/extensionHostStarter.ts @@ -16,6 +16,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { mixin } from 'vs/base/common/objects'; import { cwd } from 'vs/base/common/process'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { timeout } from 'vs/base/common/async'; export interface IPartialLogService { readonly _serviceBrand: undefined; @@ -40,6 +41,7 @@ class ExtensionHostProcess extends Disposable { readonly onExit = this._onExit.event; private _process: ChildProcess | null = null; + private _hasExited: boolean = false; constructor( public readonly id: string, @@ -48,10 +50,6 @@ class ExtensionHostProcess extends Disposable { super(); } - register(disposable: IDisposable) { - this._register(disposable); - } - start(opts: IExtensionHostProcessOptions): { pid: number; } { const sw = StopWatch.create(false); opts.env = opts.env || {}; @@ -92,6 +90,7 @@ class ExtensionHostProcess extends Disposable { }); this._process.on('exit', (code: number, signal: string) => { + this._hasExited = true; this._onExit.fire({ pid, code, signal }); }); @@ -130,6 +129,21 @@ class ExtensionHostProcess extends Disposable { this._logService.info(`Killing extension host with pid ${this._process.pid}.`); this._process.kill(); } + + async waitForExit(maxWaitTimeMs: number): Promise { + if (!this._process) { + return; + } + const pid = this._process.pid; + this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); + await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); + + if (!this._hasExited) { + // looks like we timed out + this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._process.kill(); + } + } } export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { @@ -137,7 +151,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter private static _lastId: number = 0; - private readonly _extHosts: Map; + protected readonly _extHosts: Map; constructor( @ILogService private readonly _logService: IPartialLogService From 8d6b2af2d670191031299c40e2353f31e09dbf04 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 4 Nov 2021 14:20:11 -0700 Subject: [PATCH 014/375] Change xterm layout to push scroll bar to edge Part of #134513 --- .../terminal/browser/media/terminal.css | 70 ++++++++++++++++--- .../terminal/browser/media/widgets.css | 5 -- .../contrib/terminal/browser/media/xterm.css | 2 +- .../terminal/browser/terminalInstance.ts | 21 ++++-- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index cf005b14e5a..bb60b347829 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -40,25 +40,72 @@ .monaco-workbench .editor-instance .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper { display: none; - margin: 0 10px; + /* margin: 0 10px; */ height: 100%; - padding-bottom: 2px; + /* padding-bottom: 2px; */ box-sizing: border-box; } +.monaco-workbench .editor-instance .xterm, +.monaco-workbench .pane-body.integrated-terminal .xterm { + padding: 0 10px 2px; + /* Bottom align the terminal withing the split pane */ + /* position: absolute; + bottom: 0; + left: 0; + right: 0; */ +} + +.monaco-workbench .editor-instance .xterm-viewpo, +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { + z-index: 100; +} + +.monaco-workbench .editor-instance .xterm-screen, +.monaco-workbench .pane-body.integrated-terminal .xterm-screen { + z-index: 101; +} + +.xterm .xterm-screen { + cursor: text; +} + +.xterm.enable-mouse-events .xterm-screen { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.xterm.xterm-cursor-pointer .xterm-screen { + cursor: pointer; +} + +.xterm.column-select.focus .xterm-screen { + /* Column selection mode */ + cursor: crosshair; +} + .monaco-workbench .editor-instance .terminal-wrapper.active, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.active { display: block; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper, +.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .xterm { + padding-left: 20px; +} +.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm { + padding-right: 20px; +} + +/* .monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { - margin-left: 20px; + margin-left: 20px; } .monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { - margin-right: 20px; -} + margin-right: 20px; +} */ .monaco-workbench .editor-instance .xterm a:not(.xterm-invalid-link), .monaco-workbench .pane-body.integrated-terminal .xterm a:not(.xterm-invalid-link) { @@ -69,22 +116,23 @@ .monaco-workbench .editor-instance .terminal-wrapper > div, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div { height: 100%; + /* TODO: Align to bottom */ /* Align the viewport and canvases to the bottom of the panel */ - display: flex; - align-items: flex-end; + /* display: flex; + align-items: flex-end; */ } .monaco-workbench .editor-instance .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { box-sizing: border-box; - margin-right: -10px; + /* margin-right: -10px; */ /* Override xterm.js' width as we want to size the viewport to fill the panel so the scrollbar is on the right edge */ width: auto !important; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport, +/* .monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { margin-right: -20px; -} +} */ .monaco-workbench .pane-body.integrated-terminal { font-variant-ligatures: none; diff --git a/src/vs/workbench/contrib/terminal/browser/media/widgets.css b/src/vs/workbench/contrib/terminal/browser/media/widgets.css index 51a9ab8eff2..84b7a642a9c 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/widgets.css +++ b/src/vs/workbench/contrib/terminal/browser/media/widgets.css @@ -40,11 +40,6 @@ opacity: 1; } -.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-env-var-info { - /* Adjust for reduced margin in splits */ - right: -8px; -} - .monaco-workbench .terminal-env-var-info.codicon { line-height: 28px; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 018a797e85b..3a8f3534809 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -40,9 +40,9 @@ */ .xterm { - font-feature-settings: "liga" 0; position: relative; user-select: none; + -ms-user-select: none; -webkit-user-select: none; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index fd4a62e86a2..7bb0717365e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -455,21 +455,26 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { * @return The terminal's width if it requires a layout. */ private _evaluateColsAndRows(width: number, height: number): number | null { + console.trace(`Instance ${this._instanceId}: _evaluateColsAndRows`, width, height); // Ignore if dimensions are undefined or 0 if (!width || !height) { this._setLastKnownColsAndRows(); + console.log(`Instance ${this._instanceId}: set last known 1`); return null; } const dimension = this._getDimension(width, height); + console.log(`Instance ${this._instanceId}: dimension`, dimension?.width, dimension?.height); if (!dimension) { this._setLastKnownColsAndRows(); + console.log(`Instance ${this._instanceId}: set last known 2`); return null; } const font = this.xterm ? this.xterm.getFont() : this._configHelper.getFont(); if (!font.charWidth || !font.charHeight) { this._setLastKnownColsAndRows(); + console.log(`Instance ${this._instanceId}: set last known 3`); return null; } @@ -492,6 +497,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._rows = newRows; this._fireMaximumDimensionsChanged(); } + console.log(`Instance ${this._instanceId}: result`, this._cols, this._rows, dimension.width); return dimension.width; } @@ -515,10 +521,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return undefined; } - if (!this._wrapperElement) { + if (!this._wrapperElement || !this.xterm?.raw.element) { return undefined; } - TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(Math.min(Constants.MaxCanvasWidth, width), height + (this._hasScrollBar && !this._horizontalScrollbar ? -scrollbarHeight - 2 : 0)/* bottom padding */); + const computedStyle = window.getComputedStyle(this.xterm.raw.element); + const horizontalPadding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight); + const verticalPadding = parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom); + TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension( + Math.min(Constants.MaxCanvasWidth, width - horizontalPadding), + height + (this._hasScrollBar && !this._horizontalScrollbar ? -scrollbarHeight : 0) - 2/* bottom padding */ - verticalPadding); return TerminalInstance._lastKnownCanvasDimensions; } @@ -1411,6 +1422,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } layout(dimension: dom.Dimension): void { + console.log(`Instance ${this._instanceId}: layout`, dimension.width, dimension.height); this._lastLayoutDimensions = dimension; if (this.disableLayout) { return; @@ -1423,10 +1435,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // Evaluate columns and rows, exclude the wrapper element's margin - const computedStyle = window.getComputedStyle(this._wrapperElement!); - const horizontalMargin = parseInt(computedStyle.marginLeft) + parseInt(computedStyle.marginRight); - const verticalMargin = parseInt(computedStyle.marginTop) + parseInt(computedStyle.marginBottom); - const terminalWidth = this._evaluateColsAndRows(dimension.width - horizontalMargin, dimension.height - verticalMargin); + const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { return; } From 440002016512ec52ed9a8ad7b0939736a1d5b9b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 4 Nov 2021 14:32:33 -0700 Subject: [PATCH 015/375] Align to bottom using pos absolute --- src/vs/workbench/contrib/terminal/browser/media/terminal.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index bb60b347829..b443d637a27 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -50,10 +50,10 @@ .monaco-workbench .pane-body.integrated-terminal .xterm { padding: 0 10px 2px; /* Bottom align the terminal withing the split pane */ - /* position: absolute; + position: absolute; bottom: 0; left: 0; - right: 0; */ + right: 0; } .monaco-workbench .editor-instance .xterm-viewpo, From 1cd23628b3a09af9475dc8ea5c8cc1f5eef368f5 Mon Sep 17 00:00:00 2001 From: Simon McEnlly Date: Fri, 5 Nov 2021 13:20:39 +1000 Subject: [PATCH 016/375] Address some feedback from pull request: https://github.com/microsoft/vscode/pull/136402 --- src/vs/vscode.d.ts | 8 -------- src/vs/vscode.proposed.d.ts | 15 +++++++++++++++ src/vs/workbench/api/common/extHostOutput.ts | 17 ++++++++++++----- .../workbench/api/node/extHostOutputService.ts | 8 ++++---- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 8a2c3fd7f36..49a3c7df912 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5661,14 +5661,6 @@ declare module 'vscode' { */ clear(): void; - /* - * Replaces the existing contents of the channel with the given value. - * - * *Note*: this method should only be used by extensions with smaller output - * channel text sizes. Use of `append` methods is preferred. - */ - replaceAll(value: string): void; - /** * Reveal this channel in the UI. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b4af49f2c96..244df84bc85 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2833,4 +2833,19 @@ declare module 'vscode' { } //#endregion + + //#region https://github.com/microsoft/vscode/issues/132183 + export interface OutputChannel { + + /* + * Replaces the existing contents of the channel with the given value. + * + * *Note*: this method should only be used by extensions with smaller output + * channel text sizes. Use of `append` methods is preferred. + */ + replaceAll(value: string): void; + + } + + //#endregion } diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index f633e4b1376..b357acffddb 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -12,6 +12,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { @@ -20,18 +21,20 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements protected readonly _proxy: MainThreadOutputServiceShape; private _disposed: boolean; private _offset: number; + private _extension: IExtensionDescription | undefined; protected readonly _onDidAppend: Emitter = this._register(new Emitter()); readonly onDidAppend: Event = this._onDidAppend.event; - constructor(name: string, log: boolean, file: URI | undefined, extensionId: string | undefined, proxy: MainThreadOutputServiceShape) { + constructor(name: string, log: boolean, file: URI | undefined, extension: IExtensionDescription | undefined, proxy: MainThreadOutputServiceShape) { super(); this._name = name; this._proxy = proxy; - this._id = proxy.$register(this.name, log, file, extensionId); + this._id = proxy.$register(this.name, log, file, extension?.identifier.value); this._disposed = false; this._offset = 0; + this._extension = extension; } get name(): string { @@ -59,6 +62,10 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements } replaceAll(value: string): void { + if (this._extension) { + checkProposedApiEnabled(this._extension); + } + this.validate(); const till = this._offset; this._offset += value ? VSBuffer.fromString(value).byteLength : 0; @@ -94,8 +101,8 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { - constructor(name: string, extensionId: string, proxy: MainThreadOutputServiceShape) { - super(name, false, undefined, extensionId, proxy); + constructor(name: string, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { + super(name, false, undefined, extension, proxy); } override append(value: string): void { @@ -164,7 +171,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } - return new ExtHostPushOutputChannel(name, extension.identifier.value, this._proxy); + return new ExtHostPushOutputChannel(name, extension, this._proxy); } createOutputChannelFromLogFile(name: string, file: URI): vscode.OutputChannel { diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index fdec4a891ee..471b92a40d4 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -44,8 +44,8 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { private _appender: OutputAppender; - constructor(name: string, appender: OutputAppender, extensionId: string, proxy: MainThreadOutputServiceShape) { - super(name, false, URI.file(appender.file), extensionId, proxy); + constructor(name: string, appender: OutputAppender, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { + super(name, false, URI.file(appender.file), extension, proxy); this._appender = appender; } @@ -122,11 +122,11 @@ export class ExtHostOutputService2 extends ExtHostOutputService { const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDirPath, `${fileName}.log`)); const appender = await OutputAppender.create(fileName, file.fsPath); - return new ExtHostOutputChannelBackedByFile(name, appender, extension.identifier.value, this._proxy); + return new ExtHostOutputChannelBackedByFile(name, appender, extension, this._proxy); } catch (error) { // Do not crash if logger cannot be created this.logService.error(error); - return new ExtHostPushOutputChannel(name, extension.identifier.value, this._proxy); + return new ExtHostPushOutputChannel(name, extension, this._proxy); } } } From 8e2f1765b604e601705be0a559acdda319fbf830 Mon Sep 17 00:00:00 2001 From: Simon McEnlly Date: Fri, 5 Nov 2021 13:42:14 +1000 Subject: [PATCH 017/375] Update replaceAllCancellationToken cancellation to use optional chaining operator instead of if statement --- .../output/common/outputChannelModel.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 49c96f36edc..7e715cd198a 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -102,11 +102,10 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.modelUpdater.cancel(); this.onUpdateModelCancelled(); } - if (this.replaceAllCancellationToken) { - this.replaceAllCancellationToken.cancel(); - this.replaceAllCancellationToken = null; - this.replaceAllValue = null; - } + this.replaceAllCancellationToken?.cancel(); + this.replaceAllCancellationToken = null; + this.replaceAllValue = null; + if (this.model) { this.model.setValue(''); } @@ -125,10 +124,8 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.startOffset = this.endOffset; if (this.model) { - if (this.replaceAllCancellationToken) { - this.replaceAllCancellationToken.cancel(); - this.replaceAllCancellationToken = null; - } + this.replaceAllCancellationToken?.cancel(); + this.replaceAllCancellationToken = null; const myToken = new CancellationTokenSource(); this.replaceAllCancellationToken = myToken; @@ -186,10 +183,8 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); this._onDidAppendedContent.fire(); } else if (this.replaceAllValue !== null) { - if (this.replaceAllCancellationToken) { - this.replaceAllCancellationToken.cancel(); - this.replaceAllCancellationToken = null; - } + this.replaceAllCancellationToken?.cancel(); + this.replaceAllCancellationToken = null; if (!this.replaceAllValueSeenInAppend && content === this.replaceAllValue) { this.replaceAllValue = null; From efa5f01538488b6f20b9abda43200882254c7a0f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 5 Nov 2021 13:56:56 +0100 Subject: [PATCH 018/375] Gets rid of registerDecorationType in inlay hints controller, as part of #132537. --- .../inlayHints/inlayHintsController.ts | 117 +++++++------- src/vs/editor/contrib/inlayHints/utils.ts | 153 ++++++++++++++++++ 2 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 src/vs/editor/contrib/inlayHints/utils.ts diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 59b281a1a93..268dfd22c7e 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -6,26 +6,27 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { hash } from 'vs/base/common/hash'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IContentDecorationRenderOptions, IDecorationRenderOptions, IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; import { InlayHint, InlayHintKind, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/modes'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { DynamicCssRules } from 'vs/editor/contrib/inlayHints/utils'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { editorInlayHintBackground, editorInlayHintForeground, editorInlayHintParameterBackground, editorInlayHintParameterForeground, editorInlayHintTypeBackground, editorInlayHintTypeForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; + const MAX_DECORATORS = 1500; @@ -110,12 +111,10 @@ export class InlayHintsController implements IEditorContribution { private readonly _sessionDisposables = new DisposableStore(); private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); - - private _decorations = new Map(); + private readonly _decorationsMetadata = new Map(); constructor( - private readonly _editor: ICodeEditor, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + private readonly _editor: ICodeEditor ) { this._disposables.add(InlayHintsProviderRegistry.onDidChange(() => this._update())); this._disposables.add(_editor.onDidChangeModel(() => this._update())); @@ -168,7 +167,7 @@ export class InlayHintsController implements IEditorContribution { return; } this._updateHintsDecorators(ranges, result); - this._cache.set(model, Array.from(this._decorations.values()).map(obj => obj.hint)); + this._cache.set(model, Array.from(this._decorationsMetadata.values()).map(obj => obj.hint)); }, this._getInlayHintsDelays.get(model)); @@ -209,13 +208,14 @@ export class InlayHintsController implements IEditorContribution { return result; } + private readonly ruleFactory = new DynamicCssRules(this._editor); + private _updateHintsDecorators(ranges: Range[], hints: InlayHint[]): void { const { fontSize, fontFamily } = this._getLayoutInfo(); const model = this._editor.getModel()!; - const newDecorationsTypeIds: string[] = []; - const newDecorationsData: IModelDeltaDecoration[] = []; + const newDecorationsData: { decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); @@ -226,36 +226,38 @@ export class InlayHintsController implements IEditorContribution { const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; - const contentOptions: IContentDecorationRenderOptions = { - contentText: fixSpace(text), + let backgroundColor: ThemeColor; + let color: ThemeColor; + if (hint.kind === InlayHintKind.Parameter) { + backgroundColor = themeColorFromId(editorInlayHintParameterBackground); + color = themeColorFromId(editorInlayHintParameterForeground); + } else if (hint.kind === InlayHintKind.Type) { + backgroundColor = themeColorFromId(editorInlayHintTypeBackground); + color = themeColorFromId(editorInlayHintTypeForeground); + } else { + backgroundColor = themeColorFromId(editorInlayHintBackground); + color = themeColorFromId(editorInlayHintForeground); + } + + const classNameRef = this.ruleFactory.createClassNameRef({ fontSize: `${fontSize}px`, margin: `0px ${marginAfter}px 0px ${marginBefore}px`, fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, padding: `1px ${Math.max(1, fontSize / 4) | 0}px`, borderRadius: `${(fontSize / 4) | 0}px`, verticalAlign: 'middle', - backgroundColor: themeColorFromId(editorInlayHintBackground), - color: themeColorFromId(editorInlayHintForeground) - }; + backgroundColor, + color + }); - if (hint.kind === InlayHintKind.Parameter) { - contentOptions.backgroundColor = themeColorFromId(editorInlayHintParameterBackground); - contentOptions.color = themeColorFromId(editorInlayHintParameterForeground); - } else if (hint.kind === InlayHintKind.Type) { - contentOptions.backgroundColor = themeColorFromId(editorInlayHintTypeBackground); - contentOptions.color = themeColorFromId(editorInlayHintTypeForeground); - } - - let renderOptions: IDecorationRenderOptions = { beforeInjectedText: { ...contentOptions, affectsLetterSpacing: true } }; + let direction: 'before' | 'after' = 'before'; let range = Range.fromPositions(position); let word = model.getWordAtPosition(position); let usesWordRange = false; if (word) { if (word.endColumn === position.column) { - // change decoration to after - renderOptions.afterInjectedText = renderOptions.beforeInjectedText; - renderOptions.beforeInjectedText = undefined; + direction = 'after'; usesWordRange = true; range = wordToRange(word, position.lineNumber); } else if (word.startColumn === position.column) { @@ -264,43 +266,44 @@ export class InlayHintsController implements IEditorContribution { } } - const key = 'inlayHints-' + hash(renderOptions).toString(16); - this._codeEditorService.registerDecorationType('inlay-hints-controller', key, renderOptions, undefined, this._editor); - - // decoration types are ref-counted which means we only need to - // call register und remove equally often - newDecorationsTypeIds.push(key); - - const newLen = newDecorationsData.push({ - range, - options: { - ...this._codeEditorService.resolveDecorationOptions(key, true), - showIfCollapsed: !usesWordRange, - stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges - } + newDecorationsData.push({ + decoration: { + range, + options: { + [direction]: { + content: fixSpace(text), + inlineClassNameAffectsLetterSpacing: true, + inlineClassName: classNameRef.className, + } as InjectedTextOptions, + description: 'InlayHint', + showIfCollapsed: !usesWordRange, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + } + }, + classNameRef }); - if (newLen > MAX_DECORATORS) { + if (newDecorationsData.length > MAX_DECORATORS) { break; } } // collect all decoration ids that are affected by the ranges // and only update those decorations - const decorationIdsToUpdate: string[] = []; + const decorationIdsToReplace: string[] = []; for (const range of ranges) { for (const { id } of model.getDecorationsInRange(range, this._decorationOwnerId, true)) { - const obj = this._decorations.get(id); - if (obj) { - decorationIdsToUpdate.push(id); - this._codeEditorService.removeDecorationType(obj.decorationTypeId); - this._decorations.delete(id); + const metadata = this._decorationsMetadata.get(id); + if (metadata) { + decorationIdsToReplace.push(id); + metadata.classNameRef.dispose(); + this._decorationsMetadata.delete(id); } } } - const newDecorationIds = model.deltaDecorations(decorationIdsToUpdate, newDecorationsData, this._decorationOwnerId); + const newDecorationIds = model.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration), this._decorationOwnerId); for (let i = 0; i < newDecorationIds.length; i++) { - this._decorations.set(newDecorationIds[i], { hint: hints[i], decorationTypeId: newDecorationsTypeIds[i] }); + this._decorationsMetadata.set(newDecorationIds[i], { hint: hints[i], classNameRef: newDecorationsData[i].classNameRef }); } } @@ -316,11 +319,11 @@ export class InlayHintsController implements IEditorContribution { } private _removeAllDecorations(): void { - this._editor.deltaDecorations(Array.from(this._decorations.keys()), []); - for (let obj of this._decorations.values()) { - this._codeEditorService.removeDecorationType(obj.decorationTypeId); + this._editor.deltaDecorations(Array.from(this._decorationsMetadata.keys()), []); + for (let obj of this._decorationsMetadata.values()) { + obj.classNameRef.dispose(); } - this._decorations.clear(); + this._decorationsMetadata.clear(); } } @@ -333,6 +336,7 @@ function wordToRange(word: IWordAtPosition, lineNumber: number): Range { ); } +// Prevents the view from potentially visible whitespace function fixSpace(str: string): string { const noBreakWhitespace = '\xa0'; return str.replace(/[ \t]/g, noBreakWhitespace); @@ -355,3 +359,4 @@ CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, . ref.dispose(); } }); + diff --git a/src/vs/editor/contrib/inlayHints/utils.ts b/src/vs/editor/contrib/inlayHints/utils.ts new file mode 100644 index 00000000000..0fd75a3f50e --- /dev/null +++ b/src/vs/editor/contrib/inlayHints/utils.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import * as dom from 'vs/base/browser/dom'; +import { ThemeColor } from 'vs/platform/theme/common/themeService'; + +/** + * A helper to create dynamic css rules, bound to a class name. + * Rules are reused. + * Reference counting and delayed garbage collection ensure that no rules leak. +*/ +export class DynamicCssRules { + private counter = 0; + private readonly rules = new Map(); + + // We delay garbage collection so that hanging rules can be reused. + private readonly garbageCollectionScheduler = new RunOnceScheduler(() => this.garbageCollect(), 1000); + + constructor(private readonly editor: ICodeEditor) { + } + + public createClassNameRef(options: CssProperties): ClassNameReference { + const rule = this.getOrCreateRule(options); + rule.increaseRefCount(); + + return { + className: rule.className, + dispose: () => { + rule.decreaseRefCount(); + this.garbageCollectionScheduler.schedule(); + } + }; + } + + private getOrCreateRule(properties: CssProperties): RefCountedCssRule { + const key = this.computeUniqueKey(properties); + let existingRule = this.rules.get(key); + if (!existingRule) { + const counter = this.counter++; + existingRule = new RefCountedCssRule(key, `dyn-rule-${counter}`, + dom.isInShadowDOM(this.editor.getContainerDomNode()) + ? this.editor.getContainerDomNode() + : undefined, + properties + ); + this.rules.set(key, existingRule); + } + return existingRule; + } + + private computeUniqueKey(properties: CssProperties): string { + return JSON.stringify(properties); + } + + private garbageCollect() { + for (const rule of this.rules.values()) { + if (!rule.hasReferences()) { + this.rules.delete(rule.key); + rule.dispose(); + } + } + } +} + +export interface ClassNameReference extends IDisposable { + className: string; +} + +export interface CssProperties { + border?: string; + borderColor?: string | ThemeColor; + borderRadius?: string; + fontStyle?: string; + fontWeight?: string; + fontSize?: string; + fontFamily?: string; + textDecoration?: string; + color?: string | ThemeColor; + backgroundColor?: string | ThemeColor; + opacity?: string; + verticalAlign?: string; + + margin?: string; + padding?: string; + width?: string; + height?: string; +} + +class RefCountedCssRule { + private referenceCount: number = 0; + private styleElement: HTMLStyleElement; + + constructor( + public readonly key: string, + public readonly className: string, + containerElement: HTMLElement | undefined, + public readonly properties: CssProperties, + ) { + this.styleElement = dom.createStyleSheet( + containerElement + ); + + this.styleElement.textContent = this.getCssText(this.className, this.properties); + } + + private getCssText(className: string, properties: CssProperties): string { + let str = `.${className} {`; + for (const prop in properties) { + const value = (properties as any)[prop] as string | ThemeColor; + let cssValue; + if (typeof value === 'object') { + cssValue = `var(${themeColorToCssVar(value)})`; + } else { + cssValue = value; + } + + const cssPropName = camelToDashes(prop); + str += `\n\t${cssPropName}: ${cssValue};`; + } + str += `\n}`; + return str; + } + + public dispose(): void { + this.styleElement.remove(); + } + + public increaseRefCount(): void { + this.referenceCount++; + } + + public decreaseRefCount(): void { + this.referenceCount--; + } + + public hasReferences(): boolean { + return this.referenceCount > 0; + } +} + +function camelToDashes(str: string): string { + return str.replace(/(^[A-Z])/, ([first]) => first.toLowerCase()) + .replace(/([A-Z])/g, ([letter]) => `-${letter.toLowerCase()}`); +} + +function themeColorToCssVar(themeColor: ThemeColor): string { + return `--vscode-${themeColor.id.replace('.', '-')}`; +} From 0b0dc63ebdc20ae82b9e43d6c62f9fbac37587c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 07:12:43 -0700 Subject: [PATCH 019/375] Env var widget play nicely with scroll bar --- src/vs/workbench/contrib/terminal/browser/media/widgets.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/widgets.css b/src/vs/workbench/contrib/terminal/browser/media/widgets.css index 84b7a642a9c..56704ea529b 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/widgets.css +++ b/src/vs/workbench/contrib/terminal/browser/media/widgets.css @@ -26,12 +26,12 @@ .monaco-workbench .terminal-env-var-info { position: absolute; - right: 2px; + right: 10px; /* room for scroll bar */ top: 0; width: 28px; height: 28px; text-align: center; - z-index: 10; + z-index: 102; opacity: 0.5; } From 85aa8a4502dc80d9c4af0e80b8d8c9cb51a6f609 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 07:13:01 -0700 Subject: [PATCH 020/375] Remove logs --- .../workbench/contrib/terminal/browser/terminalInstance.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 73dc2812039..2c78022438e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -461,22 +461,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Ignore if dimensions are undefined or 0 if (!width || !height) { this._setLastKnownColsAndRows(); - console.log(`Instance ${this._instanceId}: set last known 1`); return null; } const dimension = this._getDimension(width, height); - console.log(`Instance ${this._instanceId}: dimension`, dimension?.width, dimension?.height); if (!dimension) { this._setLastKnownColsAndRows(); - console.log(`Instance ${this._instanceId}: set last known 2`); return null; } const font = this.xterm ? this.xterm.getFont() : this._configHelper.getFont(); if (!font.charWidth || !font.charHeight) { this._setLastKnownColsAndRows(); - console.log(`Instance ${this._instanceId}: set last known 3`); return null; } @@ -499,7 +495,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._rows = newRows; this._fireMaximumDimensionsChanged(); } - console.log(`Instance ${this._instanceId}: result`, this._cols, this._rows, dimension.width); return dimension.width; } @@ -1424,7 +1419,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } layout(dimension: dom.Dimension): void { - console.log(`Instance ${this._instanceId}: layout`, dimension.width, dimension.height); this._lastLayoutDimensions = dimension; if (this.disableLayout) { return; From ab97bc8b0bf1dfd061541050d300cb295cc7ebd0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 08:26:55 -0700 Subject: [PATCH 021/375] Progress on fixed dimensions sizing --- .../terminal/browser/media/terminal.css | 23 ++++++-- .../contrib/terminal/browser/terminal.ts | 2 + .../terminal/browser/terminalInstance.ts | 58 +++++++++++++------ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index b443d637a27..71b7df499e2 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -56,7 +56,12 @@ right: 0; } -.monaco-workbench .editor-instance .xterm-viewpo, +.monaco-workbench .editor-instance .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper .xterm { + position: static; +} + +.monaco-workbench .editor-instance .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { z-index: 100; } @@ -125,14 +130,20 @@ .monaco-workbench .editor-instance .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { box-sizing: border-box; - /* margin-right: -10px; */ +} + +.monaco-workbench .editor-instance .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper, +.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper { + /* The viewport should be positioned against this so it does't conflict with a fixed dimensions terminal horizontal scroll bar*/ + position: relative; +} + +/* TODO: Add class for fixed dimensions */ +.monaco-workbench .editor-instance :not(.terminal-split-pane > .monaco-scrollable-element) > .terminal-wrapper .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal :not(.terminal-split-pane > .monaco-scrollable-element) > .terminal-wrapper .xterm-viewport { /* Override xterm.js' width as we want to size the viewport to fill the panel so the scrollbar is on the right edge */ width: auto !important; } -/* .monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { - margin-right: -20px; -} */ .monaco-workbench .pane-body.integrated-terminal { font-variant-ligatures: none; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 44a198e9ad5..31894a9ee79 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -387,6 +387,8 @@ export interface ITerminalInstance { readonly rows: number; readonly maxCols: number; readonly maxRows: number; + readonly fixedCols?: number; + readonly fixedRows?: number; readonly icon?: TerminalIcon; readonly color?: string; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2c78022438e..7537026e092 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -457,7 +457,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { * @return The terminal's width if it requires a layout. */ private _evaluateColsAndRows(width: number, height: number): number | null { - console.trace(`Instance ${this._instanceId}: _evaluateColsAndRows`, width, height); // Ignore if dimensions are undefined or 0 if (!width || !height) { this._setLastKnownColsAndRows(); @@ -1611,6 +1610,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedCols = this._parseFixedDimension(cols); + this._labelComputer?.refreshLabel(); this._terminalHasFixedWidth.set(!!this._fixedCols); const rows = await this._quickInputService.input({ title: nls.localize('setTerminalDimensionsRow', "Set Fixed Dimensions: Row"), @@ -1646,39 +1646,44 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._fixedCols = undefined; this._fixedRows = undefined; this._hasScrollBar = false; + // TODO: Merge into refresh scroll bar that uses fixedCols/Rows? + await this._removeScrollbar(); this._initDimensions(); await this._resize(); - this._horizontalScrollbar?.setScrollDimensions({ scrollWidth: 0 }); } else { - let maxCols = 0; + let maxLineLength = 0; if (!this.xterm.raw.buffer.active.getLine(0)) { return; } - const lineWidth = this.xterm.raw.buffer.active.getLine(0)!.length; for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { const lineInfo = this._getWrappedLineCount(i, this.xterm.raw.buffer.active); - maxCols = Math.max(maxCols, ((lineInfo.lineCount * lineWidth) - lineInfo.endSpaces) || 0); + maxLineLength = Math.max(maxLineLength, ((lineInfo.lineCount * this.xterm.raw.cols) - lineInfo.endSpaces) || 0); i = lineInfo.currentIndex; } - maxCols = Math.min(maxCols, Constants.MaxSupportedCols); - this._fixedCols = maxCols; + // Fixed columns should be at least xterm.js' regular column count + this._fixedCols = Math.max(this.maxCols, Math.min(maxLineLength, Constants.MaxSupportedCols)); await this._addScrollbar(); } + this._labelComputer?.refreshLabel(); this.focus(); } + // TODO: Add fixed dimensions class + // TODO: Remove right padding when using fixed diemnsions private async _addScrollbar(): Promise { const charWidth = (this.xterm ? this.xterm.getFont() : this._configHelper.getFont()).charWidth; if (!this.xterm?.raw.element || !this._wrapperElement || !this._container || !charWidth || !this._fixedCols) { return; } - if (this._fixedCols < this.xterm.raw.buffer.active.getLine(0)!.length) { - // no scrollbar needed - return; - } + // TODO: Look into effects of this; fixed dims always have room for scrollbar + // if (this._fixedCols < this.xterm.raw.buffer.active.getLine(0)!.length) { + // // no scrollbar needed + // return; + // } this._hasScrollBar = true; this._initDimensions(); - this._fixedRows = this.rows; + // Always remove a row to make room for the scroll bar + this._fixedRows = this.rows - 1; await this._resize(); this._terminalHasFixedWidth.set(true); if (!this._horizontalScrollbar) { @@ -1691,12 +1696,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { })); this._container.appendChild(this._horizontalScrollbar.getDomNode()); } - this._horizontalScrollbar.setScrollDimensions( - { - width: this.xterm.raw.element.clientWidth, - scrollWidth: this._fixedCols * charWidth - }); - this._horizontalScrollbar!.getDomNode().style.paddingBottom = '16px'; + this._horizontalScrollbar.setScrollDimensions({ + width: this.xterm.raw.element.clientWidth, + scrollWidth: this._fixedCols * charWidth + }); + this._horizontalScrollbar.getDomNode().style.paddingBottom = '16px'; // work around for https://github.com/xtermjs/xterm.js/issues/3482 for (let i = this.xterm.raw.buffer.active.viewportY; i < this.xterm.raw.buffer.active.length; i++) { @@ -1705,6 +1709,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + private async _removeScrollbar(): Promise { + if (!this._container || !this._wrapperElement) { + return; + } + this._horizontalScrollbar?.getDomNode().remove(); + this._horizontalScrollbar?.dispose(); + this._horizontalScrollbar = undefined; + this._wrapperElement.remove(); + this._container.appendChild(this._wrapperElement); + } + private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { let line = buffer.getLine(index); if (!line) { @@ -2077,6 +2092,7 @@ export interface ITerminalLabelTemplateProperties { process?: string | null | undefined; sequence?: string | null | undefined; task?: string | null | undefined; + fixedDimensions?: string | null | undefined; separator?: string | ISeparator | null | undefined; } @@ -2095,11 +2111,12 @@ export class TerminalLabelComputer extends Disposable { readonly onDidChangeLabel = this._onDidChangeLabel.event; constructor( private readonly _configHelper: TerminalConfigHelper, - private readonly _instance: Pick, + private readonly _instance: Pick, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(); } + refreshLabel(): void { this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title); this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description); @@ -2120,6 +2137,9 @@ export class TerminalLabelComputer extends Disposable { process: this._instance.processName, sequence: this._instance.sequence, task: this._instance.shellLaunchConfig.description === 'Task' ? 'Task' : undefined, + fixedDimensions: this._instance.fixedCols + ? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`) + : (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''), separator: { label: this._configHelper.config.tabs.separator } }; labelTemplate = labelTemplate.trim(); From ea074f283a7c370fcb2ee5ff9b40f88fa208a44d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 08:29:05 -0700 Subject: [PATCH 022/375] Add fixed-dims class --- .../contrib/terminal/browser/media/terminal.css | 12 ++++++------ .../contrib/terminal/browser/terminalInstance.ts | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 71b7df499e2..07b3b70a9c6 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -56,8 +56,8 @@ right: 0; } -.monaco-workbench .editor-instance .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper .xterm, -.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper .xterm { +.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims .xterm { position: static; } @@ -132,15 +132,15 @@ box-sizing: border-box; } -.monaco-workbench .editor-instance .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper, -.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane > .monaco-scrollable-element > .terminal-wrapper { +.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims { /* The viewport should be positioned against this so it does't conflict with a fixed dimensions terminal horizontal scroll bar*/ position: relative; } /* TODO: Add class for fixed dimensions */ -.monaco-workbench .editor-instance :not(.terminal-split-pane > .monaco-scrollable-element) > .terminal-wrapper .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal :not(.terminal-split-pane > .monaco-scrollable-element) > .terminal-wrapper .xterm-viewport { +.monaco-workbench .editor-instance .terminal-wrapper:not(.fixed-dims) .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper:not(.fixed-dims) .xterm-viewport { /* Override xterm.js' width as we want to size the viewport to fill the panel so the scrollbar is on the right edge */ width: auto !important; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 7537026e092..ed8fb746c31 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1668,13 +1668,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.focus(); } - // TODO: Add fixed dimensions class + // TODO: Ideally don't set fixed rows when sizing to content // TODO: Remove right padding when using fixed diemnsions private async _addScrollbar(): Promise { const charWidth = (this.xterm ? this.xterm.getFont() : this._configHelper.getFont()).charWidth; if (!this.xterm?.raw.element || !this._wrapperElement || !this._container || !charWidth || !this._fixedCols) { return; } + this._wrapperElement.classList.add('fixed-dims'); // TODO: Look into effects of this; fixed dims always have room for scrollbar // if (this._fixedCols < this.xterm.raw.buffer.active.getLine(0)!.length) { // // no scrollbar needed @@ -1717,6 +1718,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._horizontalScrollbar?.dispose(); this._horizontalScrollbar = undefined; this._wrapperElement.remove(); + this._wrapperElement.classList.remove('fixed-dims'); this._container.appendChild(this._wrapperElement); } From cedaa04e87ec03663820a1b1661becfe88eb70d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 09:46:20 -0700 Subject: [PATCH 023/375] Polish fixed dimensions, remove scrollable when toggled off --- .../terminal/browser/terminalInstance.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ed8fb746c31..9943af41351 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1646,7 +1646,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._fixedCols = undefined; this._fixedRows = undefined; this._hasScrollBar = false; - // TODO: Merge into refresh scroll bar that uses fixedCols/Rows? await this._removeScrollbar(); this._initDimensions(); await this._resize(); @@ -1661,26 +1660,24 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { i = lineInfo.currentIndex; } // Fixed columns should be at least xterm.js' regular column count - this._fixedCols = Math.max(this.maxCols, Math.min(maxLineLength, Constants.MaxSupportedCols)); - await this._addScrollbar(); + const proposedCols = Math.max(this.maxCols, Math.min(maxLineLength, Constants.MaxSupportedCols)); + // Don't switch to fixed dimensions if the content already fits as it makes the scroll + // bar look bad being off the edge + if (proposedCols > this.xterm.raw.cols) { + this._fixedCols = proposedCols; + await this._addScrollbar(); + } } this._labelComputer?.refreshLabel(); this.focus(); } - // TODO: Ideally don't set fixed rows when sizing to content - // TODO: Remove right padding when using fixed diemnsions private async _addScrollbar(): Promise { const charWidth = (this.xterm ? this.xterm.getFont() : this._configHelper.getFont()).charWidth; if (!this.xterm?.raw.element || !this._wrapperElement || !this._container || !charWidth || !this._fixedCols) { return; } this._wrapperElement.classList.add('fixed-dims'); - // TODO: Look into effects of this; fixed dims always have room for scrollbar - // if (this._fixedCols < this.xterm.raw.buffer.active.getLine(0)!.length) { - // // no scrollbar needed - // return; - // } this._hasScrollBar = true; this._initDimensions(); // Always remove a row to make room for the scroll bar @@ -1699,7 +1696,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } this._horizontalScrollbar.setScrollDimensions({ width: this.xterm.raw.element.clientWidth, - scrollWidth: this._fixedCols * charWidth + scrollWidth: this._fixedCols * charWidth + 30 }); this._horizontalScrollbar.getDomNode().style.paddingBottom = '16px'; @@ -1722,14 +1719,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._container.appendChild(this._wrapperElement); } + // TODO: Move into XtermTerminal and write tests private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { let line = buffer.getLine(index); if (!line) { throw new Error('Could not get line'); } let currentIndex = index; - let endSpaces = -1; - for (let i = line?.length || 0; i > 0; i--) { + let endSpaces = 0; + // line.length may exceed cols as it doesn't necessarily trim the backing array on resize + for (let i = Math.min(line.length, this.xterm!.raw.cols) - 1; i >= 0; i--) { if (line && !line?.getCell(i)?.getChars()) { endSpaces++; } else { From 9419cdcf892e0afa696fecdff758e79c3909bce2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 09:48:23 -0700 Subject: [PATCH 024/375] Remove scroll bar when setting fixed dimensions to dynamic --- .../workbench/contrib/terminal/browser/terminalInstance.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9943af41351..888f1e58a90 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1621,7 +1621,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedRows = this._parseFixedDimension(rows); - this._addScrollbar(); + if (this._fixedRows || this._fixedCols) { + this._addScrollbar(); + } else { + this._removeScrollbar(); + } this._resize(); this.focus(); } @@ -1696,6 +1700,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } this._horizontalScrollbar.setScrollDimensions({ width: this.xterm.raw.element.clientWidth, + // TODO: Use const/property for padding scrollWidth: this._fixedCols * charWidth + 30 }); this._horizontalScrollbar.getDomNode().style.paddingBottom = '16px'; From f2fa8597a31fa497b3b3edea170a130a9bb6add1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 09:53:30 -0700 Subject: [PATCH 025/375] Create refresh scroll bar method, increase scroll padding --- .../terminal/browser/terminalInstance.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 888f1e58a90..6e43621a153 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1621,11 +1621,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedRows = this._parseFixedDimension(rows); - if (this._fixedRows || this._fixedCols) { - this._addScrollbar(); - } else { - this._removeScrollbar(); - } + this._labelComputer?.refreshLabel(); + await this._refreshScrollbar(); this._resize(); this.focus(); } @@ -1650,7 +1647,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._fixedCols = undefined; this._fixedRows = undefined; this._hasScrollBar = false; - await this._removeScrollbar(); this._initDimensions(); await this._resize(); } else { @@ -1669,13 +1665,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // bar look bad being off the edge if (proposedCols > this.xterm.raw.cols) { this._fixedCols = proposedCols; - await this._addScrollbar(); } } + await this._refreshScrollbar(); this._labelComputer?.refreshLabel(); this.focus(); } + private _refreshScrollbar(): Promise { + if (this._fixedCols || this._fixedRows) { + return this._addScrollbar(); + } + return this._removeScrollbar(); + } + private async _addScrollbar(): Promise { const charWidth = (this.xterm ? this.xterm.getFont() : this._configHelper.getFont()).charWidth; if (!this.xterm?.raw.element || !this._wrapperElement || !this._container || !charWidth || !this._fixedCols) { @@ -1701,7 +1704,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._horizontalScrollbar.setScrollDimensions({ width: this.xterm.raw.element.clientWidth, // TODO: Use const/property for padding - scrollWidth: this._fixedCols * charWidth + 30 + scrollWidth: this._fixedCols * charWidth + 40 }); this._horizontalScrollbar.getDomNode().style.paddingBottom = '16px'; @@ -1713,11 +1716,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private async _removeScrollbar(): Promise { - if (!this._container || !this._wrapperElement) { + if (!this._container || !this._wrapperElement || !this._horizontalScrollbar) { return; } - this._horizontalScrollbar?.getDomNode().remove(); - this._horizontalScrollbar?.dispose(); + this._horizontalScrollbar.getDomNode().remove(); + this._horizontalScrollbar.dispose(); this._horizontalScrollbar = undefined; this._wrapperElement.remove(); this._wrapperElement.classList.remove('fixed-dims'); From 14595210d2bc3017a9e55463e92fbf758404ff26 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Nov 2021 12:51:21 -0700 Subject: [PATCH 026/375] Move line counting into XtermTerminal --- .../terminal/browser/terminalInstance.ts | 64 +++++++++---------- .../terminal/browser/xterm/xtermTerminal.ts | 34 +++++++++- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 6e43621a153..4e2fbcfd915 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -29,7 +29,7 @@ import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; -import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; +import type { Terminal as XTermTerminal, ITerminalAddon } from 'xterm'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/xterm/navigationModeAddon'; import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget'; @@ -1650,17 +1650,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._initDimensions(); await this._resize(); } else { - let maxLineLength = 0; - if (!this.xterm.raw.buffer.active.getLine(0)) { - return; - } - for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { - const lineInfo = this._getWrappedLineCount(i, this.xterm.raw.buffer.active); - maxLineLength = Math.max(maxLineLength, ((lineInfo.lineCount * this.xterm.raw.cols) - lineInfo.endSpaces) || 0); - i = lineInfo.currentIndex; - } + // let maxLineLength = 0; + // if (!this.xterm.raw.buffer.active.getLine(0)) { + // return; + // } + // for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { + // const lineInfo = this._getWrappedLineCount(i, this.xterm.raw.buffer.active); + // maxLineLength = Math.max(maxLineLength, ((lineInfo.lineCount * this.xterm.raw.cols) - lineInfo.endSpaces) || 0); + // i = lineInfo.currentIndex; + // } // Fixed columns should be at least xterm.js' regular column count - const proposedCols = Math.max(this.maxCols, Math.min(maxLineLength, Constants.MaxSupportedCols)); + const proposedCols = Math.max(this.maxCols, Math.min(this.xterm.getLongestViewportWrappedLineLength(), Constants.MaxSupportedCols)); // Don't switch to fixed dimensions if the content already fits as it makes the scroll // bar look bad being off the edge if (proposedCols > this.xterm.raw.cols) { @@ -1728,27 +1728,27 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // TODO: Move into XtermTerminal and write tests - private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { - let line = buffer.getLine(index); - if (!line) { - throw new Error('Could not get line'); - } - let currentIndex = index; - let endSpaces = 0; - // line.length may exceed cols as it doesn't necessarily trim the backing array on resize - for (let i = Math.min(line.length, this.xterm!.raw.cols) - 1; i >= 0; i--) { - if (line && !line?.getCell(i)?.getChars()) { - endSpaces++; - } else { - break; - } - } - while (line?.isWrapped && currentIndex > 0) { - currentIndex--; - line = buffer.getLine(currentIndex); - } - return { lineCount: index - currentIndex + 1, currentIndex, endSpaces }; - } + // private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { + // let line = buffer.getLine(index); + // if (!line) { + // throw new Error('Could not get line'); + // } + // let currentIndex = index; + // let endSpaces = 0; + // // line.length may exceed cols as it doesn't necessarily trim the backing array on resize + // for (let i = Math.min(line.length, this.xterm!.raw.cols) - 1; i >= 0; i--) { + // if (line && !line?.getCell(i)?.getChars()) { + // endSpaces++; + // } else { + // break; + // } + // } + // while (line?.isWrapped && currentIndex > 0) { + // currentIndex--; + // line = buffer.getLine(currentIndex); + // } + // return { lineCount: index - currentIndex + 1, currentIndex, endSpaces }; + // } private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void { this._shellLaunchConfig.args = shellLaunchConfig.args; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index dfc69cec4fd..213eea3cde1 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ITheme, RendererType, Terminal as RawXtermTerminal } from 'xterm'; +import type { IBuffer, ITheme, RendererType, Terminal as RawXtermTerminal } from 'xterm'; import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search'; import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11'; import type { WebglAddon as WebglAddonType } from 'xterm-addon-webgl'; @@ -222,6 +222,38 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { return this._configHelper.getFont(this._core); } + getLongestViewportWrappedLineLength(): number { + let maxLineLength = 0; + for (let i = this.raw.buffer.active.length - 1; i >= this.raw.buffer.active.viewportY; i--) { + const lineInfo = this._getWrappedLineCount(i, this.raw.buffer.active); + maxLineLength = Math.max(maxLineLength, ((lineInfo.lineCount * this.raw.cols) - lineInfo.endSpaces) || 0); + i = lineInfo.currentIndex; + } + return maxLineLength; + } + + private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { + let line = buffer.getLine(index); + if (!line) { + throw new Error('Could not get line'); + } + let currentIndex = index; + let endSpaces = 0; + // line.length may exceed cols as it doesn't necessarily trim the backing array on resize + for (let i = Math.min(line.length, this.raw.cols) - 1; i >= 0; i--) { + if (line && !line?.getCell(i)?.getChars()) { + endSpaces++; + } else { + break; + } + } + while (line?.isWrapped && currentIndex > 0) { + currentIndex--; + line = buffer.getLine(currentIndex); + } + return { lineCount: index - currentIndex + 1, currentIndex, endSpaces }; + } + scrollDownLine(): void { this.raw.scrollLines(1); } From 8d78e70f782298e1a843a4b5c3d78b7b29512b23 Mon Sep 17 00:00:00 2001 From: Robert Jin Date: Fri, 5 Nov 2021 20:15:01 +0000 Subject: [PATCH 027/375] Update html-language-features documentation and tasks to yarn --- extensions/html-language-features/.vscode/tasks.json | 8 ++++---- extensions/html-language-features/CONTRIBUTING.md | 2 +- .../html-language-features/server/.vscode/tasks.json | 8 ++++---- extensions/html-language-features/server/package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/html-language-features/.vscode/tasks.json b/extensions/html-language-features/.vscode/tasks.json index 7bbb2d58733..7abfc71df26 100644 --- a/extensions/html-language-features/.vscode/tasks.json +++ b/extensions/html-language-features/.vscode/tasks.json @@ -2,9 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "npm", - "command": "npm", - "args": ["run", "compile"], + "label": "yarn", + "command": "yarn", + "args": ["compile"], "type": "shell", "presentation": { "reveal": "silent", @@ -15,4 +15,4 @@ "problemMatcher": "$tsc-watch" } ], -} \ No newline at end of file +} diff --git a/extensions/html-language-features/CONTRIBUTING.md b/extensions/html-language-features/CONTRIBUTING.md index 2d3476d3cf9..441baa4d318 100644 --- a/extensions/html-language-features/CONTRIBUTING.md +++ b/extensions/html-language-features/CONTRIBUTING.md @@ -30,7 +30,7 @@ However, within this extension, you can run a development version of `vscode-htm - Clone [microsoft/vscode-html-languageservice](https://github.com/microsoft/vscode-html-languageservice) - Run `yarn` in `vscode-html-languageservice` - Run `yarn link` in `vscode-html-languageservice`. This will compile and link `vscode-html-languageservice` -- In `html-language-features/server/`, run `npm link vscode-html-languageservice` +- In `html-language-features/server/`, run `yarn link vscode-html-languageservice` #### Testing the development version of `vscode-html-languageservice` diff --git a/extensions/html-language-features/server/.vscode/tasks.json b/extensions/html-language-features/server/.vscode/tasks.json index 8bca03bcb8c..c26073e1d4f 100644 --- a/extensions/html-language-features/server/.vscode/tasks.json +++ b/extensions/html-language-features/server/.vscode/tasks.json @@ -2,9 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "npm watch", - "command": "npm", - "args": ["run", "watch"], + "label": "yarn watch", + "command": "yarn", + "args": ["watch"], "type": "shell", "presentation": { "reveal": "silent", @@ -15,4 +15,4 @@ "problemMatcher": "$tsc-watch" } ], -} \ No newline at end of file +} diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 264cd1c2c25..48f4c0f6259 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -27,6 +27,6 @@ "install-service-local": "yarn link vscode-css-languageservice && yarn link vscode-html-languageservice", "install-server-next": "yarn add vscode-languageserver@next", "install-server-local": "yarn link vscode-languageserver", - "test": "npm run compile && node ./test/index.js" + "test": "yarn compile && node ./test/index.js" } } From 82c003a988345acbcf1c9fb0cd431890446d5677 Mon Sep 17 00:00:00 2001 From: Simon McEnlly Date: Sat, 6 Nov 2021 09:27:51 +1000 Subject: [PATCH 028/375] Update to use nullExtensionDescription instead of undefined when no extension is available in Host Outpu `ExtHostLogFileOutputChannel` context --- src/vs/workbench/api/common/extHostOutput.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index b357acffddb..b99751fa4f7 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -13,6 +13,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { @@ -21,12 +22,12 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements protected readonly _proxy: MainThreadOutputServiceShape; private _disposed: boolean; private _offset: number; - private _extension: IExtensionDescription | undefined; + private _extension: IExtensionDescription; protected readonly _onDidAppend: Emitter = this._register(new Emitter()); readonly onDidAppend: Event = this._onDidAppend.event; - constructor(name: string, log: boolean, file: URI | undefined, extension: IExtensionDescription | undefined, proxy: MainThreadOutputServiceShape) { + constructor(name: string, log: boolean, file: URI | undefined, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { super(); this._name = name; @@ -62,9 +63,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements } replaceAll(value: string): void { - if (this._extension) { - checkProposedApiEnabled(this._extension); - } + checkProposedApiEnabled(this._extension); this.validate(); const till = this._offset; @@ -115,7 +114,7 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { constructor(name: string, file: URI, proxy: MainThreadOutputServiceShape) { - super(name, true, file, undefined, proxy); + super(name, true, file, nullExtensionDescription, proxy); } override append(value: string): void { From 3afa5c64200a8b338b28e6324a2eb1945884a2b1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 6 Nov 2021 13:46:18 +0100 Subject: [PATCH 029/375] - Do not send replace content from ext host - implement replaceAll in the model - cancel all model update operations on replace request - queue append operation after replace - clean ups --- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostOutput.ts | 56 +-- .../api/node/extHostOutputService.ts | 39 ++- .../output/common/outputChannelModel.ts | 318 ++++++++---------- 4 files changed, 186 insertions(+), 229 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 6779800a4b7..03d33fe3739 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -447,7 +447,7 @@ export interface MainThreadOutputServiceShape extends IDisposable { $append(channelId: string, value: string): Promise | undefined; $update(channelId: string): Promise | undefined; $clear(channelId: string, till: number): Promise | undefined; - $replaceAll(channelId: string, till: number, value: string): Promise | undefined; + $replaceAll(channelId: string, till: number, value?: string): Promise | undefined; $reveal(channelId: string, preserveFocus: boolean): Promise | undefined; $close(channelId: string): Promise | undefined; $dispose(channelId: string): Promise | undefined; diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index b99751fa4f7..3845c68ff02 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -6,14 +6,12 @@ import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } from './extHost.protocol'; import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { @@ -22,17 +20,16 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements protected readonly _proxy: MainThreadOutputServiceShape; private _disposed: boolean; private _offset: number; - private _extension: IExtensionDescription; + private readonly _extension: IExtensionDescription; - protected readonly _onDidAppend: Emitter = this._register(new Emitter()); - readonly onDidAppend: Event = this._onDidAppend.event; + public visible: boolean = false; constructor(name: string, log: boolean, file: URI | undefined, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { super(); this._name = name; this._proxy = proxy; - this._id = proxy.$register(this.name, log, file, extension?.identifier.value); + this._id = proxy.$register(this.name, log, file, extension.identifier.value); this._disposed = false; this._offset = 0; this._extension = extension; @@ -44,11 +41,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements append(value: string): void { this.validate(); - this._offset += value ? VSBuffer.fromString(value).byteLength : 0; - } - - update(): void { - this._id.then(id => this._proxy.$update(id)); + this.incrementOffset(value); } appendLine(value: string): void { @@ -62,13 +55,11 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements this._id.then(id => this._proxy.$clear(id, till)); } - replaceAll(value: string): void { - checkProposedApiEnabled(this._extension); - - this.validate(); + replaceAll(value: string, donotSendValue?: boolean): void { + this.validate(true); const till = this._offset; - this._offset += value ? VSBuffer.fromString(value).byteLength : 0; - this._id.then(id => this._proxy.$replaceAll(id, till, value)); + this.incrementOffset(value); + this._id.then(id => this._proxy.$replaceAll(id, till, donotSendValue ? undefined : value)); } show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { @@ -81,12 +72,19 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements this._id.then(id => this._proxy.$close(id)); } - protected validate(): void { + protected validate(checkProposedApi?: boolean): void { + if (checkProposedApi) { + checkProposedApiEnabled(this._extension); + } if (this._disposed) { throw new Error('Channel has been closed'); } } + private incrementOffset(value: string) { + this._offset += value ? VSBuffer.fromString(value).byteLength : 0; + } + override dispose(): void { super.dispose(); @@ -107,19 +105,9 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { override append(value: string): void { super.append(value); this._id.then(id => this._proxy.$append(id, value)); - this._onDidAppend.fire(); - } -} - -class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { - - constructor(name: string, file: URI, proxy: MainThreadOutputServiceShape) { - super(name, true, file, nullExtensionDescription, proxy); } - override append(value: string): void { - throw new Error('Not supported'); - } + } export class LazyOutputChannel implements vscode.OutputChannel { @@ -173,16 +161,6 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return new ExtHostPushOutputChannel(name, extension, this._proxy); } - createOutputChannelFromLogFile(name: string, file: URI): vscode.OutputChannel { - name = name.trim(); - if (!name) { - throw new Error('illegal argument `name`. must not be falsy'); - } - if (!file) { - throw new Error('illegal argument `file`. must not be falsy'); - } - return new ExtHostLogFileOutputChannel(name, file, this._proxy); - } } export interface IExtHostOutputService extends ExtHostOutputService { } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 471b92a40d4..e057eab0d37 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -12,7 +12,6 @@ import { Promises, SymlinkSupport } from 'vs/base/node/pfs'; import { AbstractExtHostOutputChannel, ExtHostPushOutputChannel, ExtHostOutputService, LazyOutputChannel } from 'vs/workbench/api/common/extHostOutput'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { createRotatingLogger } from 'vs/platform/log/node/spdlogLog'; import { Logger } from 'spdlog'; @@ -52,18 +51,17 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { override append(value: string): void { super.append(value); this._appender.append(value); - this._onDidAppend.fire(); + if (this.visible) { + this.update(); + } } override replaceAll(value: string): void { + super.replaceAll(value, true); this._appender.append(value); - this._appender.flush(); - super.replaceAll(value); - } - - override update(): void { - this._appender.flush(); - super.update(); + if (this.visible) { + this._appender.flush(); + } } override show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { @@ -75,6 +73,11 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { this._appender.flush(); super.clear(); } + + private update(): void { + this._appender.flush(); + this._id.then(id => this._proxy.$update(id)); + } } export class ExtHostOutputService2 extends ExtHostOutputService { @@ -82,7 +85,8 @@ export class ExtHostOutputService2 extends ExtHostOutputService { private _logsLocation: URI; private _namePool: number = 1; private readonly _channels: Map = new Map(); - private readonly _visibleChannelDisposable = new MutableDisposable(); + + private visibleChannelId: string | null = null; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -93,12 +97,10 @@ export class ExtHostOutputService2 extends ExtHostOutputService { this._logsLocation = initData.logsLocation; } - override $setVisibleChannel(channelId: string): void { - if (channelId) { - const channel = this._channels.get(channelId); - if (channel) { - this._visibleChannelDisposable.value = channel.onDidAppend(() => channel.update()); - } + override $setVisibleChannel(visibleChannelId: string | null): void { + this.visibleChannelId = visibleChannelId; + for (const [id, channel] of this._channels) { + channel.visible = id === this.visibleChannelId; } } @@ -108,7 +110,10 @@ export class ExtHostOutputService2 extends ExtHostOutputService { throw new Error('illegal argument `name`. must not be falsy'); } const extHostOutputChannel = this._doCreateOutChannel(name, extension); - extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel))); + extHostOutputChannel.then(channel => channel._id.then(id => { + this._channels.set(id, channel); + channel.visible = id === this.visibleChannelId; + })); return new LazyOutputChannel(name, extHostOutputChannel); } diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index cde06f58c72..4e340d24ddb 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -5,51 +5,52 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as resources from 'vs/base/common/resources'; -import { ITextModel } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { Promises, RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; +import { Promises, ThrottledDelayer } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { Disposable, toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable, IDisposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { VSBuffer } from 'vs/base/common/buffer'; import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; export interface IOutputChannelModel extends IDisposable { - readonly onDidAppendedContent: Event; readonly onDispose: Event; append(output: string): void; update(): void; loadModel(): Promise; clear(till?: number): void; - replaceAll(till: number, value: string): void; + replaceAll(till: number, value?: string): void; +} + +const enum ModelUpdateMode { + Append = 1, + Replace, + Clear } export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel { - protected readonly _onDidAppendedContent = this._register(new Emitter()); - readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - protected readonly _onDispose = this._register(new Emitter()); readonly onDispose: Event = this._onDispose.event; - protected modelUpdater: RunOnceScheduler; protected model: ITextModel | null = null; + private modelUpdateInProgress: boolean = false; + private modelUpdateCancellationSource = this._register(new MutableDisposable()); + private appendThrottler = this._register(new ThrottledDelayer(300)); + private replacePromise: Promise | undefined; protected startOffset: number = 0; protected endOffset: number = 0; - protected replaceAllValue: string | null = null; - protected replaceAllValueSeenInAppend: boolean = false; - protected replaceAllCancellationToken: CancellationTokenSource | null = null; - constructor( private readonly modelUri: URI, private readonly mimeType: string, @@ -60,69 +61,16 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen protected editorWorkerService: IEditorWorkerService, ) { super(); - this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); - this._register(toDisposable(() => this.modelUpdater.cancel())); } clear(till?: number): void { - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - this.onUpdateModelCancelled(); - } - this.replaceAllCancellationToken?.cancel(); - this.replaceAllCancellationToken = null; - this.replaceAllValue = null; - - if (this.model) { - this.model.setValue(''); - } - this.endOffset = isNumber(till) ? till : this.endOffset; - this.startOffset = this.endOffset; + this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; + this.updateModel(ModelUpdateMode.Clear); } - replaceAll(till: number, value: string): void { - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - this.onUpdateModelCancelled(); - } - this.replaceAllValue = value; - this.replaceAllValueSeenInAppend = false; - this.endOffset = isNumber(till) ? till : this.endOffset; - this.startOffset = this.endOffset; - - if (this.model) { - this.replaceAllCancellationToken?.cancel(); - this.replaceAllCancellationToken = null; - - const myToken = new CancellationTokenSource(); - this.replaceAllCancellationToken = myToken; - - this.editorWorkerService.computeMoreMinimalEdits(this.model.uri, [{ text: value, range: this.model.getFullModelRange() }]).then(edits => { - if (myToken.token.isCancellationRequested) { - return; - } - - this.replaceAllCancellationToken = null; - - if (edits && edits.length > 0) { - if (this.model) { - this.model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); - this._onDidAppendedContent.fire(); - } - } - }).catch((e) => { - if (myToken.token.isCancellationRequested) { - return; - } - - this.replaceAllCancellationToken = null; - - if (this.model) { - this.model.setValue(value); - this._onDidAppendedContent.fire(); - } - }); - } + replaceAll(till: number, message: string): void { + this.startOffset = this.endOffset = till; + this.updateModel(ModelUpdateMode.Replace); } update(): void { } @@ -134,6 +82,7 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); this.onModelCreated(this.model); const disposable = this.model.onWillDispose(() => { + this.cancelModelUpdate(); this.onModelWillDispose(this.model); this.model = null; dispose(disposable); @@ -142,63 +91,118 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen return this.model; } - appendToModel(content: string): void { - if (this.model) { - if (this.replaceAllValue === null && content) { - const lastLine = this.model.getLineCount(); - const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); - this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); - this._onDidAppendedContent.fire(); - } else if (this.replaceAllValue !== null) { - this.replaceAllCancellationToken?.cancel(); - this.replaceAllCancellationToken = null; + protected updateModel(mode: ModelUpdateMode): void { + if (mode !== ModelUpdateMode.Append) { + this.cancelModelUpdate(); + } + if (!this.model) { + return; + } + if (!this.modelUpdateCancellationSource.value) { + this.modelUpdateCancellationSource.value = new CancellationTokenSource(); + } + this.modelUpdateInProgress = true; + const token = this.modelUpdateCancellationSource.value.token; + switch (mode) { + case ModelUpdateMode.Clear: + this.clearContent(this.model); + break; + case ModelUpdateMode.Replace: + this.replacePromise = this.replaceContent(this.model, token).finally(() => this.replacePromise = undefined); + break; + case ModelUpdateMode.Append: + this.appendContent(this.model, token); + break; + } + } - if (!this.replaceAllValueSeenInAppend && content === this.replaceAllValue) { - this.replaceAllValue = null; + private clearContent(model: ITextModel): void { + this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString('')); + } + + private async appendContent(model: ITextModel, token: CancellationToken): Promise { + this.appendThrottler.trigger(async () => { + /* Abort if operation is cancelled */ + if (token.isCancellationRequested) { + return; + } + + /* Wait for replace to finish */ + if (this.replacePromise) { + try { await this.replacePromise; } catch (e) { /* Ignore */ } + /* Abort if operation is cancelled */ + if (token.isCancellationRequested) { return; } + } - if (content) { - if (!this.replaceAllValueSeenInAppend && content.length >= this.replaceAllValue.length && content.startsWith(this.replaceAllValue)) { - this.replaceAllValue += content.substring(this.replaceAllValue.length); - this.replaceAllValueSeenInAppend = true; - } else { - this.replaceAllValue += content; - } - } + /* Get content to append */ + const contentToAppend = await this.getContentToUpdate(); + /* Abort if operation is cancelled */ + if (token.isCancellationRequested) { + return; + } - const myToken = new CancellationTokenSource(); - this.replaceAllCancellationToken = myToken; + /* Appned Content */ + const lastLine = model.getLineCount(); + const lastLineMaxColumn = model.getLineMaxColumn(lastLine); + const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())]; + this.doUpdateModel(model, edits, contentToAppend); + }); + } - this.editorWorkerService.computeMoreMinimalEdits(this.model.uri, [{ text: this.replaceAllValue, range: this.model.getFullModelRange() }]).then(edits => { - if (myToken.token.isCancellationRequested) { - return; - } + private async replaceContent(model: ITextModel, token: CancellationToken): Promise { + /* Get content to replace */ + const contentToReplace = await this.getContentToUpdate(); + /* Abort if operation is cancelled */ + if (token.isCancellationRequested) { + return; + } - this.replaceAllCancellationToken = null; - this.replaceAllValue = null; + /* Compute Edits */ + const edits = await this.getReplaceEdits(model, contentToReplace.toString()); + /* Abort if operation is cancelled */ + if (token.isCancellationRequested) { + return; + } - if (edits && edits.length > 0) { - if (this.model) { - this.model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); - this._onDidAppendedContent.fire(); - } - } - }).catch(() => { - if (myToken.token.isCancellationRequested) { - return; - } + /* Apply Edits */ + this.doUpdateModel(model, edits, contentToReplace); + } - if (this.model && this.replaceAllValue !== null) { - this.model.setValue(this.replaceAllValue); - this._onDidAppendedContent.fire(); - } - - this.replaceAllCancellationToken = null; - this.replaceAllValue = null; - }); + private async getReplaceEdits(model: ITextModel, contentToReplace: string): Promise { + if (!contentToReplace) { + return [EditOperation.delete(model.getFullModelRange())]; + } + if (contentToReplace !== model.getValue()) { + const edits = await this.editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: contentToReplace.toString(), range: model.getFullModelRange() }]); + if (edits?.length) { + return edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); } } + return []; + } + + private doUpdateModel(model: ITextModel, edits: IIdentifiedSingleEditOperation[], content: VSBuffer): void { + if (edits.length) { + model.applyEdits(edits); + } + this.modelUpdateInProgress = false; + this.onDidModelUpdate(content); + } + + protected cancelModelUpdate(): void { + if (this.modelUpdateCancellationSource.value) { + this.modelUpdateCancellationSource.value.cancel(); + } + this.modelUpdateCancellationSource.value = undefined; + this.appendThrottler.cancel(); + this.replacePromise = undefined; + this.modelUpdateInProgress = false; + } + + protected isModelUpdateInProgress(): boolean { + return this.modelUpdateInProgress; } abstract loadModel(): Promise; @@ -206,8 +210,8 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen protected onModelCreated(model: ITextModel) { } protected onModelWillDispose(model: ITextModel | null) { } - protected onUpdateModelCancelled() { } - protected updateModel() { } + protected abstract getContentToUpdate(): Promise; + protected abstract onDidModelUpdate(content: VSBuffer): void; override dispose(): void { this._onDispose.fire(); @@ -276,7 +280,6 @@ export class FileOutputChannelModel extends AbstractFileOutputChannelModel imple private readonly fileHandler: OutputFileListener; - private updateInProgress: boolean = false; private etag: string | undefined = ''; private loadModelPromise: Promise | null = null; @@ -326,32 +329,18 @@ export class FileOutputChannelModel extends AbstractFileOutputChannelModel imple }); } - override replaceAll(till: number, value: string): void { - const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); - loadModelPromise.then(() => { - super.replaceAll(till, value); - this.update(); - }); - } - append(message: string): void { throw new Error('Not supported'); } - protected override updateModel(): void { - if (this.model) { - this.fileService.readFile(this.file, { position: this.endOffset }) - .then(content => { - this.etag = content.etag; - if (content.value) { - this.endOffset = this.endOffset + content.value.byteLength; - this.appendToModel(content.value.toString()); - } - this.updateInProgress = false; - }, () => this.updateInProgress = false); - } else { - this.updateInProgress = false; - } + protected async getContentToUpdate(): Promise { + const content = await this.fileService.readFile(this.file, { position: this.endOffset }); + this.etag = content.etag; + return content.value; + } + + protected override onDidModelUpdate(content: VSBuffer): void { + this.endOffset = this.endOffset + content.byteLength; } protected override onModelCreated(model: ITextModel): void { @@ -362,24 +351,16 @@ export class FileOutputChannelModel extends AbstractFileOutputChannelModel imple this.fileHandler.unwatch(); } - protected override onUpdateModelCancelled(): void { - this.updateInProgress = false; - } - - protected getByteLength(str: string): number { - return VSBuffer.fromString(str).byteLength; - } - override update(size?: number): void { if (this.model) { - if (!this.updateInProgress) { - this.updateInProgress = true; - if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed + if (!this.isModelUpdateInProgress()) { + if (isNumber(size) && this.endOffset > size) { + // Reset - Content is removed this.startOffset = this.endOffset = 0; - this.model.setValue(''); + this.updateModel(ModelUpdateMode.Clear); } - this.modelUpdater.schedule(); } + this.updateModel(ModelUpdateMode.Append); } } } @@ -432,16 +413,14 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement this.write(message); if (this.model) { this.appendedMessage += message; - if (!this.modelUpdater.isScheduled()) { - this.modelUpdater.schedule(); - } + this.updateModel(ModelUpdateMode.Append); } } } override replaceAll(till: number, value: string): void { + this.appendedMessage = value; super.replaceAll(till, value); - this.appendedMessage = ''; } override clear(till?: number): void { @@ -451,9 +430,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement async loadModel(): Promise { this.loadingFromFileInProgress = true; - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - } + this.cancelModelUpdate(); this.appendedMessage = ''; let content = await this.loadFile(); if (this.endOffset !== this.startOffset + VSBuffer.fromString(content).byteLength) { @@ -483,11 +460,12 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement return this.appendedMessage ? content.value + this.appendedMessage : content.value.toString(); } - protected override updateModel(): void { - if (this.model && this.appendedMessage) { - this.appendToModel(this.appendedMessage); - this.appendedMessage = ''; - } + protected async getContentToUpdate(): Promise { + return VSBuffer.fromString(this.appendedMessage); + } + + protected override onDidModelUpdate(content: VSBuffer): void { + this.appendedMessage = ''; } private write(content: string): void { @@ -501,9 +479,6 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement export class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel { - private readonly _onDidAppendedContent: Emitter = this._register(new Emitter()); - readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - private readonly _onDispose: Emitter = this._register(new Emitter()); readonly onDispose: Event = this._onDispose.event; @@ -526,7 +501,6 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh const file = resources.joinPath(outputDir, `${id.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); await this.fileService.createFile(file); const outputChannelModel = this._register(this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType, file)); - this._register(outputChannelModel.onDidAppendedContent(() => this._onDidAppendedContent.fire())); this._register(outputChannelModel.onDispose(() => this._onDispose.fire())); return outputChannelModel; } From a7104f13ff61bcd9f4c827d8fe3c0d27bb479fd8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 7 Nov 2021 19:48:40 -0800 Subject: [PATCH 030/375] Remove unused fs import #133654 --- src/vs/workbench/services/search/node/rawSearchService.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 4e8a5782041..49a7002e948 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; -import * as gracefulFs from 'graceful-fs'; import * as arrays from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -19,8 +17,6 @@ import { ICachedSearchStats, IFileQuery, IFileSearchProgressItem, IFileSearchSta import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; -gracefulFs.gracefulify(fs); - export type IProgressCallback = (p: ISerializedSearchProgressItem) => void; export type IFileProgressCallback = (p: IFileSearchProgressItem) => void; From 80c789277f1a0f3e88d292d09030eaba5cbe4b67 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 07:03:27 +0100 Subject: [PATCH 031/375] Stop parcel watcher on shutdown (#136569) * watcher - stop service on Linux (#136264) * watcher - cleanup * only dispose in electron --- .../files/common/diskFileSystemProvider.ts | 2 -- .../files/node/diskFileSystemProvider.ts | 1 - .../watcher/parcel/parcelWatcherService.ts | 14 ++++--------- .../diskFileSystemProvider.ts | 1 - .../electron-sandbox/parcelWatcherService.ts | 20 +++++++++++++++++-- .../sharedProcessWorkerWorkbenchService.ts | 1 - 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/files/common/diskFileSystemProvider.ts b/src/vs/platform/files/common/diskFileSystemProvider.ts index ee10991eae2..2e2fec710c8 100644 --- a/src/vs/platform/files/common/diskFileSystemProvider.ts +++ b/src/vs/platform/files/common/diskFileSystemProvider.ts @@ -75,7 +75,6 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { // Create watcher if this is the first time if (!this.recursiveWatcher) { this.recursiveWatcher = this._register(this.createRecursiveWatcher( - this.recursiveFoldersToWatch.length, changes => this._onDidChangeFile.fire(toFileChanges(changes)), msg => this.onWatcherLogMessage(msg), this.logService.getLevel() === LogLevel.Trace @@ -96,7 +95,6 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { } protected abstract createRecursiveWatcher( - folders: number, onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index cb867f1a057..40a9029b88e 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -552,7 +552,6 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple //#region File Watching protected createRecursiveWatcher( - folders: number, onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts index 3ee0ccaae13..a731395f51b 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts @@ -12,7 +12,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { isEqualOrParent } from 'vs/base/common/extpath'; import { parse, ParsedPattern } from 'vs/base/common/glob'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import { normalizeNFC } from 'vs/base/common/normalization'; import { dirname, isAbsolute, join, normalize, sep } from 'vs/base/common/path'; @@ -24,7 +24,7 @@ import { watchFolder } from 'vs/base/node/watcher'; import { FileChangeType } from 'vs/platform/files/common/files'; import { IDiskFileChange, ILogMessage, normalizeFileChanges, IWatchRequest, IWatcherService } from 'vs/platform/files/common/watcher'; -export interface IWatcher extends IDisposable { +export interface IWatcher { /** * Signals when the watcher is ready to watch. @@ -48,8 +48,8 @@ export interface IWatcher extends IDisposable { readonly token: CancellationToken; /** - * Stops and disposes the watcher. Same as `dispose` but allows to await - * the watcher getting unsubscribed. + * Stops and disposes the watcher. This operation is async to await + * unsubscribe call in Parcel. */ stop(): Promise; } @@ -259,9 +259,6 @@ export class ParcelWatcherService extends Disposable implements IWatcherService cts.dispose(true); pollingWatcher.dispose(); unlinkSync(snapshotFile); - }, - dispose: () => { - watcher.stop(); } }; this.watchers.set(request.path, watcher); @@ -332,9 +329,6 @@ export class ParcelWatcherService extends Disposable implements IWatcherService const watcherInstance = await instance; await watcherInstance?.unsubscribe(); - }, - dispose: () => { - watcher.stop(); } }; this.watchers.set(request.path, watcher); diff --git a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts b/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts index 3d84135a2d4..5542f717998 100644 --- a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts @@ -134,7 +134,6 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } protected createRecursiveWatcher( - folders: number, onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean diff --git a/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts b/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts index 4edab669348..1f3987192a4 100644 --- a/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts +++ b/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { getDelayedChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { AbstractWatcherService, IDiskFileChange, ILogMessage, IWatcherService } from 'vs/platform/files/common/watcher'; import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; @@ -22,9 +22,16 @@ export class ParcelFileWatcher extends AbstractWatcherService { } protected override createService(disposables: DisposableStore): IWatcherService { - return ProxyChannel.toService(getDelayedChannel((async () => { + const service = ProxyChannel.toService(getDelayedChannel((async () => { // Acquire parcel watcher via shared process worker + // + // We explicitly do not add the worker as a disposable + // because we need to call `stop` on disposal to prevent + // a crash on shutdown (see below). + // + // The shared process worker services ensures to terminate + // the process automatically when the window closes or reloads. const { client, onDidTerminate } = await this.sharedProcessWorkerWorkbenchService.createWorker({ moduleId: 'vs/platform/files/node/watcher/parcel/watcherApp', type: 'watcherServiceParcelSharedProcess' @@ -41,5 +48,14 @@ export class ParcelFileWatcher extends AbstractWatcherService { return client.getChannel('watcher'); })())); + + // Looks like parcel needs an explicit stop to prevent + // access on data structures after process exit. This + // only seem to be happening when used from Electron, + // not pure node.js. + // https://github.com/microsoft/vscode/issues/136264 + disposables.add(toDisposable(() => service.stop())); + + return service; } } diff --git a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts b/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts index b9b89e84f70..c10645a5116 100644 --- a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts @@ -87,7 +87,6 @@ export class SharedProcessWorkerWorkbenchService extends Disposable implements I async createWorker(process: ISharedProcessWorkerProcess): Promise { this.logService.trace('Renderer->SharedProcess#createWorker'); - // Get ready to acquire the message port from the shared process worker const nonce = generateUuid(); const responseChannel = 'vscode:createSharedProcessWorkerMessageChannelResult'; From bdbaac08a1db332fceb42b0b3e86092c2bac418c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 07:17:15 +0100 Subject: [PATCH 032/375] :lipstick: --- .../platform/storage/electron-sandbox/storageService.ts | 1 + src/vs/workbench/services/search/common/searchService.ts | 1 - .../electron-sandbox/workspaceEditingService.ts | 8 ++------ 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/storage/electron-sandbox/storageService.ts b/src/vs/platform/storage/electron-sandbox/storageService.ts index 95c29dbaab3..424b5dafc1f 100644 --- a/src/vs/platform/storage/electron-sandbox/storageService.ts +++ b/src/vs/platform/storage/electron-sandbox/storageService.ts @@ -66,6 +66,7 @@ export class NativeStorageService extends AbstractStorageService { } protected async doInitialize(): Promise { + // Init all storage locations await Promises.settled([ this.globalStorage.init(), diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index b28c1227bb2..480160c5cf8 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -196,7 +196,6 @@ export class SearchService extends Disposable implements ISearchService { } private async searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { - console.log(`searchWithProviders`); const e2eSW = StopWatch.create(false); const searchPs: Promise[] = []; diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index 42ac9f0212c..da5f69daf8d 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -8,7 +8,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo import { URI } from 'vs/base/common/uri'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorkspaceFileExtension, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, isUntitledWorkspace, hasWorkspaceFileExtension, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -164,7 +164,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi if (result) { // Migrate storage to new workspace - await this.migrateStorage(result.workspace); + await this.storageService.migrate(result.workspace); // Reinitialize backup service if (this.workingCopyBackupService instanceof WorkingCopyBackupService) { @@ -184,10 +184,6 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi this.extensionService.restartExtensionHost(); } } - - private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { - return this.storageService.migrate(toWorkspace); - } } registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, true); From 8d5348aba635b8aec54ed8643fd0dfa212b26d19 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 08:53:25 +0100 Subject: [PATCH 033/375] web - rewrite how storage is handled on shutdown cc @sandy081 --- src/vs/base/parts/storage/common/storage.ts | 19 ++++++++++-- .../parts/storage/test/node/storage.test.ts | 19 ++++++++++++ .../quickinput/browser/quickinput.test.ts | 1 - .../storage/browser/storageService.ts | 20 +++++++++---- src/vs/platform/storage/common/storage.ts | 29 +++++++++++++++---- src/vs/workbench/browser/web.main.ts | 9 ++---- src/vs/workbench/browser/workbench.ts | 6 +--- .../host/browser/browserHostService.ts | 16 ++-------- .../lifecycle/browser/lifecycleService.ts | 18 +++++++++--- .../lifecycle/common/lifecycleService.ts | 2 +- 10 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 1a62836f8c1..c892f8fbef3 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -68,6 +68,7 @@ export interface IStorage extends IDisposable { set(key: string, value: string | boolean | number | undefined | null): Promise; delete(key: string): Promise; + flush(delay?: number): Promise; whenFlushed(): Promise; close(): Promise; @@ -236,7 +237,7 @@ export class Storage extends Disposable implements IStorage { this._onDidChangeStorage.fire(key); // Accumulate work by scheduling after timeout - return this.flushDelayer.trigger(() => this.flushPending()); + return this.doFlush(); } async delete(key: string): Promise { @@ -260,7 +261,7 @@ export class Storage extends Disposable implements IStorage { this._onDidChangeStorage.fire(key); // Accumulate work by scheduling after timeout - return this.flushDelayer.trigger(() => this.flushPending()); + return this.doFlush(); } async close(): Promise { @@ -283,7 +284,7 @@ export class Storage extends Disposable implements IStorage { // Recovery: we pass our cache over as recovery option in case // the DB is not healthy. try { - await this.flushDelayer.trigger(() => this.flushPending(), 0 /* as soon as possible */); + await this.doFlush(0 /* as soon as possible */); } catch (error) { // Ignore } @@ -318,6 +319,18 @@ export class Storage extends Disposable implements IStorage { }); } + async flush(delay?: number): Promise { + if (!this.hasPending) { + return; // return early if nothing to do + } + + return this.doFlush(delay); + } + + private async doFlush(delay?: number): Promise { + return this.flushDelayer.trigger(() => this.flushPending(), delay); + } + async whenFlushed(): Promise { if (!this.hasPending) { return; // return early if nothing to do diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 824d6b22f52..ba47114a16b 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -222,6 +222,25 @@ flakySuite('Storage Library', function () { await storage.close(); }); + test('explicit flush', async () => { + let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); + await storage.init(); + + storage.set('foo', 'bar'); + storage.set('bar', 'foo'); + + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + + strictEqual(flushPromiseResolved, false); + + await storage.flush(0); + + strictEqual(flushPromiseResolved, true); + + await storage.close(); + }); + test('conflicting updates', () => { return runWithFakedTimers({}, async function () { let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); diff --git a/src/vs/base/test/parts/quickinput/browser/quickinput.test.ts b/src/vs/base/test/parts/quickinput/browser/quickinput.test.ts index 368a8f0bccf..559e4e517d9 100644 --- a/src/vs/base/test/parts/quickinput/browser/quickinput.test.ts +++ b/src/vs/base/test/parts/quickinput/browser/quickinput.test.ts @@ -167,7 +167,6 @@ suite('QuickInput', () => { void (await new Promise(resolve => { quickpick.onDidAccept(() => { - console.log(quickpick.selectedItems.map(i => i.label).join(', ')); quickpick.canSelectMany = true; quickpick.items = [{ label: 'a' }, { label: 'b' }, { label: 'c' }]; resolve(); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index c4cf2f584c1..014c9ef4e1c 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isSafari } from 'vs/base/browser/browser'; import { IndexedDB } from 'vs/base/browser/indexedDB'; import { Promises } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -105,13 +106,20 @@ export class BrowserStorageService extends AbstractStorageService { } close(): void { - // We explicitly do not close our DBs because writing data onBeforeUnload() - // can result in unexpected results. Namely, it seems that - even though this - // operation is async - sometimes it is being triggered on unload and - // succeeds. Often though, the DBs turn out to be empty because the write - // never had a chance to complete. + + // Safari: there is an issue where the page can hang on load when + // a previous session has kept IndexedDB transactions running. + // The only fix seems to be to cancel any pending transactions + // (https://github.com/microsoft/vscode/issues/136295) // - // Instead we trigger dispose() to ensure that no timeouts or callbacks + // On all other browsers, we keep the databases opened because + // we expect data to be written when the unload happens. + if (isSafari) { + this.globalStorageDatabase?.close(); + this.workspaceStorageDatabase?.close(); + } + + // Always dispose to ensure that no timeouts or callbacks // get triggered in this phase. this.dispose(); } diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 6c60de2fb76..88d8146c40c 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -461,16 +461,33 @@ export abstract class AbstractStorageService extends Disposable implements IStor return this.getBoolean(IS_NEW_KEY, scope) === true; } - async flush(reason: WillSaveStateReason = WillSaveStateReason.NONE): Promise { + async flush(reason = WillSaveStateReason.NONE): Promise { // Signal event to collect changes this._onWillSaveState.fire({ reason }); - // Await flush - await Promises.settled([ - this.getStorage(StorageScope.GLOBAL)?.whenFlushed() ?? Promise.resolve(), - this.getStorage(StorageScope.WORKSPACE)?.whenFlushed() ?? Promise.resolve() - ]); + const globalStorage = this.getStorage(StorageScope.GLOBAL); + const workspaceStorage = this.getStorage(StorageScope.WORKSPACE); + + switch (reason) { + + // Unspecific reason: just wait when data is flushed + case WillSaveStateReason.NONE: + await Promises.settled([ + globalStorage?.whenFlushed() ?? Promise.resolve(), + workspaceStorage?.whenFlushed() ?? Promise.resolve() + ]); + break; + + // Shutdown: we want to flush as soon as possible + // and not hit any delays that might be there + case WillSaveStateReason.SHUTDOWN: + await Promises.settled([ + globalStorage?.flush(0) ?? Promise.resolve(), + workspaceStorage?.flush(0) ?? Promise.resolve() + ]); + break; + } } async logStorage(): Promise { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 5218660cd67..5589b239f8c 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -70,7 +70,7 @@ import { IndexedDB } from 'vs/base/browser/indexedDB'; class BrowserMain extends Disposable { - private readonly onWillShutdownDisposables = new DisposableStore(); + private readonly onWillShutdownDisposables = this._register(new DisposableStore()); constructor( private readonly domElement: HTMLElement, @@ -138,11 +138,6 @@ class BrowserMain extends Disposable { private registerListeners(workbench: Workbench, storageService: BrowserStorageService, logService: ILogService): void { // Workbench Lifecycle - this._register(workbench.onBeforeShutdown(event => { - if (storageService.hasPendingUpdate) { - event.veto(true, 'veto.pendingStorageUpdate'); // prevent data loss from pending storage update - } - })); this._register(workbench.onWillShutdown(() => this.onWillShutdownDisposables.clear())); this._register(workbench.onDidShutdown(() => this.dispose())); } @@ -348,7 +343,7 @@ class BrowserMain extends Disposable { try { await storageService.initialize(); - // Close onWillShutdown + // Register to close on shutdown this.onWillShutdownDisposables.add(toDisposable(() => storageService.close())); return storageService; diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index cb7ca84a865..ed8bd49ae03 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -20,7 +20,7 @@ import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } fro import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { LifecyclePhase, ILifecycleService, WillShutdownEvent, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { LifecyclePhase, ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; @@ -50,9 +50,6 @@ export interface IWorkbenchOptions { export class Workbench extends Layout { - private readonly _onBeforeShutdown = this._register(new Emitter()); - readonly onBeforeShutdown = this._onBeforeShutdown.event; - private readonly _onWillShutdown = this._register(new Emitter()); readonly onWillShutdown = this._onWillShutdown.event; @@ -239,7 +236,6 @@ export class Workbench extends Layout { } // Lifecycle - this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event))); this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); this._register(lifecycleService.onDidShutdown(() => { this._onDidShutdown.fire(); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index e8a8ad93def..16f27d54067 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -32,7 +32,6 @@ import Severity from 'vs/base/common/severity'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DomEmitter } from 'vs/base/browser/event'; import { isUndefined } from 'vs/base/common/types'; -import { IStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; /** * A workspace to open in the workbench can either be: @@ -109,8 +108,7 @@ export class BrowserHostService extends Disposable implements IHostService { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILifecycleService private readonly lifecycleService: BrowserLifecycleService, @ILogService private readonly logService: ILogService, - @IDialogService private readonly dialogService: IDialogService, - @IStorageService private readonly storageService: IStorageService + @IDialogService private readonly dialogService: IDialogService ) { super(); @@ -138,13 +136,6 @@ export class BrowserHostService extends Disposable implements IHostService { private onBeforeShutdown(e: BeforeShutdownEvent): void { - // Optimistically trigger a UI state flush - // without waiting for it. The browser does - // not guarantee that this is being executed - // but if a dialog opens, we have a chance - // to succeed. - this.storageService.flush(WillSaveStateReason.SHUTDOWN); - switch (this.shutdownReason) { // Unknown / Keyboard shows veto depending on setting @@ -469,10 +460,7 @@ export class BrowserHostService extends Disposable implements IHostService { this.shutdownReason = HostShutdownReason.Api; // Signal shutdown reason to lifecycle - this.lifecycleService.withExpectedShutdown(reason); - - // Ensure UI state is persisted - await this.storageService.flush(WillSaveStateReason.SHUTDOWN); + return this.lifecycleService.withExpectedShutdown(reason); } //#endregion diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index d50305d48ab..599e82e764f 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; export class BrowserLifecycleService extends AbstractLifecycleService { @@ -99,9 +99,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { event.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again."); } - withExpectedShutdown(reason: ShutdownReason): void; - withExpectedShutdown(reason: { disableShutdownHandling: true }, callback: Function): void; - withExpectedShutdown(reason: ShutdownReason | { disableShutdownHandling: true }, callback?: Function): void { + withExpectedShutdown(reason: ShutdownReason): Promise; + withExpectedShutdown(reason: { disableShutdownHandling: true }, callback: Function): Promise; + withExpectedShutdown(reason: ShutdownReason | { disableShutdownHandling: true }, callback?: Function): Promise { // Standard shutdown if (typeof reason === 'number') { @@ -117,6 +117,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { this.disableBeforeUnloadVeto = false; } } + + // Ensure UI state is persisted + return this.storageService.flush(WillSaveStateReason.SHUTDOWN); } shutdown(): void { @@ -135,6 +138,13 @@ export class BrowserLifecycleService extends AbstractLifecycleService { this.didBeforeUnload = true; + // Optimistically trigger a UI state flush + // without waiting for it. The browser does + // not guarantee that this is being executed + // but if a dialog opens, we have a chance + // to succeed. + this.storageService.flush(WillSaveStateReason.SHUTDOWN); + let veto = false; // Before Shutdown diff --git a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts index 89c9e11d413..61575986d46 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts @@ -38,7 +38,7 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi constructor( @ILogService protected readonly logService: ILogService, - @IStorageService private readonly storageService: IStorageService + @IStorageService protected readonly storageService: IStorageService ) { super(); From 39c7f7b1a6f3892a9365638bcf845f2d6231e0a1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 5 Nov 2021 14:29:58 +0100 Subject: [PATCH 034/375] Fixes nits. --- .../inlayHints/inlayHintsController.ts | 8 ++- src/vs/editor/contrib/inlayHints/utils.ts | 51 +++++++++---------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 268dfd22c7e..19b0dee4719 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -24,8 +24,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { DynamicCssRules } from 'vs/editor/contrib/inlayHints/utils'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { editorInlayHintBackground, editorInlayHintForeground, editorInlayHintParameterBackground, editorInlayHintParameterForeground, editorInlayHintTypeBackground, editorInlayHintTypeForeground } from 'vs/platform/theme/common/colorRegistry'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; +import { ThemeColor, themeColorFromId } from 'vs/platform/theme/common/themeService'; const MAX_DECORATORS = 1500; @@ -112,6 +111,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); private readonly _decorationsMetadata = new Map(); + private readonly _ruleFactory = new DynamicCssRules(this._editor); constructor( private readonly _editor: ICodeEditor @@ -208,8 +208,6 @@ export class InlayHintsController implements IEditorContribution { return result; } - private readonly ruleFactory = new DynamicCssRules(this._editor); - private _updateHintsDecorators(ranges: Range[], hints: InlayHint[]): void { const { fontSize, fontFamily } = this._getLayoutInfo(); @@ -239,7 +237,7 @@ export class InlayHintsController implements IEditorContribution { color = themeColorFromId(editorInlayHintForeground); } - const classNameRef = this.ruleFactory.createClassNameRef({ + const classNameRef = this._ruleFactory.createClassNameRef({ fontSize: `${fontSize}px`, margin: `0px ${marginAfter}px 0px ${marginBefore}px`, fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, diff --git a/src/vs/editor/contrib/inlayHints/utils.ts b/src/vs/editor/contrib/inlayHints/utils.ts index 0fd75a3f50e..3e6cecc8bf2 100644 --- a/src/vs/editor/contrib/inlayHints/utils.ts +++ b/src/vs/editor/contrib/inlayHints/utils.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import * as dom from 'vs/base/browser/dom'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { asCssVariableName } from 'vs/platform/theme/common/colorRegistry'; /** * A helper to create dynamic css rules, bound to a class name. @@ -15,13 +16,13 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService'; * Reference counting and delayed garbage collection ensure that no rules leak. */ export class DynamicCssRules { - private counter = 0; - private readonly rules = new Map(); + private _counter = 0; + private readonly _rules = new Map(); // We delay garbage collection so that hanging rules can be reused. - private readonly garbageCollectionScheduler = new RunOnceScheduler(() => this.garbageCollect(), 1000); + private readonly _garbageCollectionScheduler = new RunOnceScheduler(() => this.garbageCollect(), 1000); - constructor(private readonly editor: ICodeEditor) { + constructor(private readonly _editor: ICodeEditor) { } public createClassNameRef(options: CssProperties): ClassNameReference { @@ -32,23 +33,23 @@ export class DynamicCssRules { className: rule.className, dispose: () => { rule.decreaseRefCount(); - this.garbageCollectionScheduler.schedule(); + this._garbageCollectionScheduler.schedule(); } }; } private getOrCreateRule(properties: CssProperties): RefCountedCssRule { const key = this.computeUniqueKey(properties); - let existingRule = this.rules.get(key); + let existingRule = this._rules.get(key); if (!existingRule) { - const counter = this.counter++; + const counter = this._counter++; existingRule = new RefCountedCssRule(key, `dyn-rule-${counter}`, - dom.isInShadowDOM(this.editor.getContainerDomNode()) - ? this.editor.getContainerDomNode() + dom.isInShadowDOM(this._editor.getContainerDomNode()) + ? this._editor.getContainerDomNode() : undefined, properties ); - this.rules.set(key, existingRule); + this._rules.set(key, existingRule); } return existingRule; } @@ -58,9 +59,9 @@ export class DynamicCssRules { } private garbageCollect() { - for (const rule of this.rules.values()) { + for (const rule of this._rules.values()) { if (!rule.hasReferences()) { - this.rules.delete(rule.key); + this._rules.delete(rule.key); rule.dispose(); } } @@ -92,20 +93,20 @@ export interface CssProperties { } class RefCountedCssRule { - private referenceCount: number = 0; - private styleElement: HTMLStyleElement; + private _referenceCount: number = 0; + private _styleElement: HTMLStyleElement; constructor( public readonly key: string, public readonly className: string, - containerElement: HTMLElement | undefined, + _containerElement: HTMLElement | undefined, public readonly properties: CssProperties, ) { - this.styleElement = dom.createStyleSheet( - containerElement + this._styleElement = dom.createStyleSheet( + _containerElement ); - this.styleElement.textContent = this.getCssText(this.className, this.properties); + this._styleElement.textContent = this.getCssText(this.className, this.properties); } private getCssText(className: string, properties: CssProperties): string { @@ -114,7 +115,7 @@ class RefCountedCssRule { const value = (properties as any)[prop] as string | ThemeColor; let cssValue; if (typeof value === 'object') { - cssValue = `var(${themeColorToCssVar(value)})`; + cssValue = `var(${asCssVariableName(value.id)})`; } else { cssValue = value; } @@ -127,19 +128,19 @@ class RefCountedCssRule { } public dispose(): void { - this.styleElement.remove(); + this._styleElement.remove(); } public increaseRefCount(): void { - this.referenceCount++; + this._referenceCount++; } public decreaseRefCount(): void { - this.referenceCount--; + this._referenceCount--; } public hasReferences(): boolean { - return this.referenceCount > 0; + return this._referenceCount > 0; } } @@ -147,7 +148,3 @@ function camelToDashes(str: string): string { return str.replace(/(^[A-Z])/, ([first]) => first.toLowerCase()) .replace(/([A-Z])/g, ([letter]) => `-${letter.toLowerCase()}`); } - -function themeColorToCssVar(themeColor: ThemeColor): string { - return `--vscode-${themeColor.id.replace('.', '-')}`; -} From 00d94046fe717660f2f4ebd79df1f38646ee82bf Mon Sep 17 00:00:00 2001 From: Johannes Herchen <34076797+jherchen@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:50:56 +0100 Subject: [PATCH 035/375] fix preinstall script (#136638) detect yarn with .cjs extensions --- build/npm/preinstall.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 6319e5d1279..34618a58b68 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -23,7 +23,7 @@ if (majorYarnVersion < 1 || minorYarnVersion < 10) { err = true; } -if (!/yarn[\w-.]*\.js$|yarnpkg$/.test(process.env['npm_execpath'])) { +if (!/yarn[\w-.]*\.c?js$|yarnpkg$/.test(process.env['npm_execpath'])) { console.error('\033[1;31m*** Please use yarn to install dependencies.\033[0;0m'); err = true; } From 3ba528bde5b18a713ca14aadf7d600a15f7095ec Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Nov 2021 09:57:01 +0100 Subject: [PATCH 036/375] don't show "Activating Extensions..." repeatedly --- .../browser/extensionsActivationProgress.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts index 636edeccca3..6370029fda8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts @@ -8,7 +8,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { timeout } from 'vs/base/common/async'; +import { DeferredPromise, timeout } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; export class ExtensionActivationProgress implements IWorkbenchContribution { @@ -26,9 +26,25 @@ export class ExtensionActivationProgress implements IWorkbenchContribution { title: localize('activation', "Activating Extensions...") }; + let deferred: DeferredPromise | undefined; + let count = 0; + this._listener = extensionService.onWillActivateByEvent(e => { logService.trace('onWillActivateByEvent: ', e.event); - progressService.withProgress(options, _ => Promise.race([e.activation, timeout(5000)])); + + if (!deferred) { + deferred = new DeferredPromise(); + progressService.withProgress(options, _ => deferred!.p); + } + + count++; + + Promise.race([e.activation, timeout(5000)]).finally(() => { + if (--count === 0) { + deferred!.complete(undefined); + deferred = undefined; + } + }); }); } From a8e37a4b43398363fbe832bc47e5314853be96a9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 8 Nov 2021 11:09:21 +0100 Subject: [PATCH 037/375] Kill extension host processes immediately when doing extension test --- .../directMainProcessExtensionHostStarter.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts index 7f9a39f17e2..98bbbff95c6 100644 --- a/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ExtensionHostStarter, IPartialLogService } from 'vs/platform/extensions/node/extensionHostStarter'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -11,16 +12,24 @@ export class DirectMainProcessExtensionHostStarter extends ExtensionHostStarter constructor( @ILogService logService: IPartialLogService, - @ILifecycleMainService lifecycleMainService: ILifecycleMainService + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, + @IEnvironmentMainService environmentService: IEnvironmentMainService, ) { super(logService); lifecycleMainService.onWillShutdown((e) => { - const exitPromises: Promise[] = []; - for (const [, extHost] of this._extHosts) { - exitPromises.push(extHost.waitForExit(6000)); + if (environmentService.extensionTestsLocationURI) { + // extension testing => don't wait for graceful shutdown + for (const [, extHost] of this._extHosts) { + extHost.kill(); + } + } else { + const exitPromises: Promise[] = []; + for (const [, extHost] of this._extHosts) { + exitPromises.push(extHost.waitForExit(6000)); + } + e.join(Promise.all(exitPromises).then(() => { })); } - e.join(Promise.all(exitPromises).then(() => { })); }); } From e54933ab2212104b8dfbd353a630a79fb7bed3c4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 8 Nov 2021 11:15:21 +0100 Subject: [PATCH 038/375] Remove initial text line skipping in tasks Part of #136585 --- .../contrib/tasks/browser/terminalTaskSystem.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index acd81bf835e..4d4726e6371 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -829,12 +829,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this.logService.error('Task terminal process never got ready'); }); this.fireTaskEvent(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId)); - let skipLine: boolean = (!!task.command.presentation && task.command.presentation.echo); const onData = terminal.onLineData((line) => { - if (skipLine) { - skipLine = false; - return; - } watchingProblemMatcher.processLine(line); if (!delayer) { delayer = new Async.Delayer(3000); @@ -920,12 +915,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let problemMatchers = await this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); this.terminalStatusManager.addTerminal(task, terminal, startStopProblemMatcher); - let skipLine: boolean = (!!task.command.presentation && task.command.presentation.echo); const onData = terminal.onLineData((line) => { - if (skipLine) { - skipLine = false; - return; - } startStopProblemMatcher.processLine(line); }); promise = new Promise((resolve, reject) => { From 37a3ce24f033fb9e3f07c0ec7bacf033ce58fa0d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Nov 2021 11:24:12 +0100 Subject: [PATCH 039/375] cleanup proposed api --- src/vs/vscode.proposed.d.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 244df84bc85..39040b04bf7 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2834,17 +2834,13 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/132183 - export interface OutputChannel { + //#region @sandy081 https://github.com/microsoft/vscode/issues/132183 + export interface OutputChannel { /* * Replaces the existing contents of the channel with the given value. - * - * *Note*: this method should only be used by extensions with smaller output - * channel text sizes. Use of `append` methods is preferred. */ replaceAll(value: string): void; - } //#endregion From cfb6bc83c9ff70be68c433984b7c64913994ff3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 11:55:18 +0100 Subject: [PATCH 040/375] fix(scm): :zap: dont create event listeners per scm row Closes: #115016 --- .vscode/settings.json | 2 +- .../contrib/scm/browser/scmViewPane.ts | 103 ++++++++++++------ 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b4df37acfaa..0596a84d1ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,5 +79,5 @@ }, "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", - "conventionalCommits.scopes": ["tree"] + "conventionalCommits.scopes": ["tree", "scm"] } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 5660be7d8c5..65706a8b69b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -11,7 +11,7 @@ import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/par import { append, $, Dimension, asCSSUrl, trackFocus, clearNode } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton } from 'vs/workbench/contrib/scm/common/scm'; -import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; +import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -350,6 +350,13 @@ interface ResourceTemplate { disposables: IDisposable; } +interface RenderedResourceData { + readonly tooltip: string; + readonly uri: URI; + readonly fileLabelOptions: Partial; + readonly iconResource: ISCMResource | undefined; +} + class RepositoryPaneActionRunner extends ActionRunner { constructor(private getSelectedResources: () => (ISCMResource | IResourceNode)[]) { @@ -374,6 +381,9 @@ class ResourceRenderer implements ICompressibleTreeRenderer(); + constructor( private viewModelProvider: () => ViewModel, private labels: ResourceLabels, @@ -381,7 +391,9 @@ class ResourceRenderer implements ICompressibleTreeRenderer { - const theme = this.themeService.getColorTheme(); - const icon = iconResource && (theme.type === ColorScheme.LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark); - - template.fileLabel.setFile(uri, { - fileDecorations: { colors: false, badges: !icon }, + const renderedData: RenderedResourceData = { + tooltip, + uri, + fileLabelOptions: { hidePath: viewModel.mode === ViewModelMode.Tree, fileKind, matches, descriptionMatches, strikethrough - }); - - if (icon) { - if (ThemeIcon.isThemeIcon(icon)) { - template.decorationIcon.className = `decoration-icon ${ThemeIcon.asClassName(icon)}`; - if (icon.color) { - template.decorationIcon.style.color = theme.getColor(icon.color.id)?.toString() ?? ''; - } - template.decorationIcon.style.display = ''; - template.decorationIcon.style.backgroundImage = ''; - } else { - template.decorationIcon.className = 'decoration-icon'; - template.decorationIcon.style.color = ''; - template.decorationIcon.style.display = ''; - template.decorationIcon.style.backgroundImage = asCSSUrl(icon); - } - template.decorationIcon.title = tooltip; - } else { - template.decorationIcon.className = 'decoration-icon'; - template.decorationIcon.style.color = ''; - template.decorationIcon.style.display = 'none'; - template.decorationIcon.style.backgroundImage = ''; - template.decorationIcon.title = ''; - } + }, + iconResource }; - elementDisposables.add(this.themeService.onDidColorThemeChange(render)); - render(); + this.renderIcon(template, renderedData); + + this.renderedResources.set(template, renderedData); + elementDisposables.add(toDisposable(() => this.renderedResources.delete(template))); template.element.setAttribute('data-tooltip', tooltip); template.elementDisposables = elementDisposables; @@ -575,6 +565,49 @@ class ResourceRenderer implements ICompressibleTreeRenderer { @@ -2065,7 +2098,7 @@ export class SCMViewPane extends ViewPane { this.inputRenderer, this.instantiationService.createInstance(ActionButtonRenderer), this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner) + this._register(this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner)) ]; const filter = new SCMTreeFilter(); From 2c4eb0298dd5e6015491f10fddc4480dbcd18bd7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 12:05:58 +0100 Subject: [PATCH 041/375] errors - do no log undefined rejection reasons //cc @alexdima --- src/vs/platform/telemetry/node/errorTelemetry.ts | 4 +++- .../services/extensions/node/extensionHostProcessSetup.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/telemetry/node/errorTelemetry.ts b/src/vs/platform/telemetry/node/errorTelemetry.ts index 1f456018fd4..07bf1bb82f7 100644 --- a/src/vs/platform/telemetry/node/errorTelemetry.ts +++ b/src/vs/platform/telemetry/node/errorTelemetry.ts @@ -26,7 +26,9 @@ export default class ErrorTelemetry extends BaseErrorTelemetry { if (e.stack) { console.warn(`stack trace: ${e.stack}`); } - onUnexpectedError(reason); + if (reason) { + onUnexpectedError(reason); + } } }); } diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 00b73dbff90..dc5c4cf13eb 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -255,7 +255,9 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise Date: Mon, 8 Nov 2021 12:29:08 +0100 Subject: [PATCH 042/375] go back to fork --- .../extensions/node/extensionHostStarter.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/extensions/node/extensionHostStarter.ts b/src/vs/platform/extensions/node/extensionHostStarter.ts index ac208a54186..2b68c208de1 100644 --- a/src/vs/platform/extensions/node/extensionHostStarter.ts +++ b/src/vs/platform/extensions/node/extensionHostStarter.ts @@ -7,7 +7,7 @@ import { SerializedError, transformErrorForSerialization } from 'vs/base/common/ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { Emitter, Event } from 'vs/base/common/event'; -import { ChildProcess, spawn } from 'child_process'; +import { ChildProcess, fork } from 'child_process'; import { FileAccess } from 'vs/base/common/network'; import { StringDecoder } from 'string_decoder'; import * as platform from 'vs/base/common/platform'; @@ -52,18 +52,18 @@ class ExtensionHostProcess extends Disposable { start(opts: IExtensionHostProcessOptions): { pid: number; } { const sw = StopWatch.create(false); - opts.env = opts.env || {}; - opts.env.ELECTRON_RUN_AS_NODE = '1'; - this._process = spawn( - process.execPath, - [FileAccess.asFileUri('bootstrap-fork', require).fsPath, '--type=extensionHost', '--skipWorkspaceStorageLock'], - mixin({ cwd: cwd() }, opts), - ); - // this._process = fork( - // FileAccess.asFileUri('bootstrap-fork', require).fsPath, - // ['--type=extensionHost', '--skipWorkspaceStorageLock'], + // opts.env = opts.env || {}; + // opts.env.ELECTRON_RUN_AS_NODE = '1'; + // this._process = spawn( + // process.execPath, + // [FileAccess.asFileUri('bootstrap-fork', require).fsPath, '--type=extensionHost', '--skipWorkspaceStorageLock'], // mixin({ cwd: cwd() }, opts), // ); + this._process = fork( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + ['--type=extensionHost', '--skipWorkspaceStorageLock'], + mixin({ cwd: cwd() }, opts), + ); const forkTime = sw.elapsed(); const pid = this._process.pid; @@ -81,9 +81,9 @@ class ExtensionHostProcess extends Disposable { this._onStderr.fire(strChunk); }); - // this._process.on('message', msg => { - // this._onMessage.fire(msg); - // }); + this._process.on('message', msg => { + this._onMessage.fire(msg); + }); this._process.on('error', (err) => { this._onError.fire({ error: transformErrorForSerialization(err) }); From 5ce44b2e0160900bd6c1680a557c98df38f4da60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 13:58:29 +0100 Subject: [PATCH 043/375] feat(grid): :children_crossing: 2x2 mode can be enabled by splitting Closes: #117866 --- .vscode/settings.json | 2 +- src/vs/base/browser/ui/grid/gridview.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0596a84d1ea..cae51b7b605 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,5 +79,5 @@ }, "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", - "conventionalCommits.scopes": ["tree", "scm"] + "conventionalCommits.scopes": ["tree", "scm", "grid"] } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index ea9de1c345d..bd1e2b4d548 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -1024,6 +1024,8 @@ export class GridView implements IDisposable { const node = new LeafNode(view, grandParent.orientation, this.layoutController, parent.size); newParent.addChild(node, size, index); } + + this.trySet2x2(); } removeView(location: number[], sizing?: Sizing): IView { @@ -1050,6 +1052,7 @@ export class GridView implements IDisposable { } if (parent.children.length > 1) { + this.trySet2x2(); return node.view; } @@ -1064,6 +1067,7 @@ export class GridView implements IDisposable { parent.removeChild(0); this.root = sibling; this.boundarySashes = this.boundarySashes; + this.trySet2x2(); return node.view; } @@ -1094,6 +1098,7 @@ export class GridView implements IDisposable { grandParent.resizeChild(i, sizes[i]); } + this.trySet2x2(); return node.view; } @@ -1105,6 +1110,8 @@ export class GridView implements IDisposable { } parent.moveChild(from, to); + + this.trySet2x2(); } swapViews(from: number[], to: number[]): void { @@ -1145,6 +1152,8 @@ export class GridView implements IDisposable { fromParent.addChild(toNode, fromSize, fromIndex); toParent.addChild(fromNode, toSize, toIndex); } + + this.trySet2x2(); } resizeView(location: number[], { width, height }: Partial): void { @@ -1171,6 +1180,8 @@ export class GridView implements IDisposable { if (typeof parentSize === 'number') { parent.resizeChild(index, parentSize); } + + this.trySet2x2(); } getViewSize(location?: number[]): IViewSize { @@ -1218,6 +1229,7 @@ export class GridView implements IDisposable { } node.distributeViewSizes(); + this.trySet2x2(); } isViewVisible(location: number[]): boolean { From 0600ecc75b0305426d94d099f15027b05cb2be27 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 8 Nov 2021 14:05:42 +0100 Subject: [PATCH 044/375] Fix problem location URI Fixes #136588 --- src/vs/workbench/api/browser/mainThreadTask.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 457402e8fcb..585790283e4 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -659,7 +659,7 @@ export class MainThreadTask implements MainThreadTaskShape { this._taskService.registerTaskSystem(key, { platform: platform, uriProvider: (path: string): URI => { - return URI.parse(`${info.scheme}://${info.authority}${path}`); + return URI.from({ scheme: info.scheme, authority: info.authority, path }); }, context: this._extHostContext, resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise => { From 6f0346f2cb31222ff99df02a50e1d6be08aec782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 14:21:16 +0100 Subject: [PATCH 045/375] fix(splitview): :wheelchair: up/down arrows should change focus between pane view headers Closes: #117440 --- .vscode/settings.json | 2 +- src/vs/base/browser/ui/splitview/paneview.ts | 37 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cae51b7b605..e320fc5ca7d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,5 +79,5 @@ }, "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", - "conventionalCommits.scopes": ["tree", "scm", "grid"] + "conventionalCommits.scopes": ["tree", "scm", "grid", "splitview"] } diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index cd090be5093..b02f4066ef0 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -457,6 +457,17 @@ export class PaneView extends Disposable { this.onDidSashReset = this.splitview.onDidSashReset; this.onDidSashChange = this.splitview.onDidSashChange; this.onDidScroll = this.splitview.onDidScroll; + + const onKeyDown = this._register(new DomEmitter(this.element, 'keydown')); + const onHeaderKeyDown = Event.chain(onKeyDown.event) + .filter(e => e.target instanceof HTMLElement && e.target.classList.contains('pane-header')) + .map(e => new StandardKeyboardEvent(e)); + + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow) + .event(() => this.focusPrevious())); + + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow) + .event(() => this.focusNext())); } addPane(pane: Pane, size: number, index = this.splitview.length): void { @@ -572,6 +583,32 @@ export class PaneView extends Disposable { }, 200); } + private getPaneHeaderElements(): HTMLElement[] { + return [...this.element.querySelectorAll('.pane-header')] as HTMLElement[]; + } + + private focusPrevious(): void { + const headers = this.getPaneHeaderElements(); + const index = headers.indexOf(document.activeElement as HTMLElement); + + if (index === -1) { + return; + } + + headers[Math.max(index - 1, 0)].focus(); + } + + private focusNext(): void { + const headers = this.getPaneHeaderElements(); + const index = headers.indexOf(document.activeElement as HTMLElement); + + if (index === -1) { + return; + } + + headers[Math.min(index + 1, headers.length - 1)].focus(); + } + override dispose(): void { super.dispose(); From 10c3fec7fb9d033529578cd8ec845b319b28a6da Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Nov 2021 14:47:14 +0100 Subject: [PATCH 046/375] #132183 clean up - Use FileOutputChannelModel for OutputChannelBackedByFile - Rename replaceAll to replace - simplify exthost output service and channel impl - enhance update api --- src/vs/vscode.proposed.d.ts | 2 +- .../api/browser/mainThreadOutputService.ts | 19 +- .../workbench/api/common/extHost.protocol.ts | 8 +- src/vs/workbench/api/common/extHostOutput.ts | 163 ++++--- .../api/node/extHost.node.services.ts | 4 +- .../api/node/extHostOutputService.ts | 71 ++- .../contrib/output/browser/outputServices.ts | 14 +- .../workbench/contrib/output/common/output.ts | 23 +- .../output/common/outputChannelModel.ts | 458 +++++++----------- 9 files changed, 329 insertions(+), 433 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 39040b04bf7..f5f1304f3d9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2840,7 +2840,7 @@ declare module 'vscode' { /* * Replaces the existing contents of the channel with the given value. */ - replaceAll(value: string): void; + replace(value: string): void; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 47ed29594b4..5078c34fce2 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputService, IOutputChannel, OUTPUT_VIEW_ID } from 'vs/workbench/contrib/output/common/output'; +import { IOutputService, IOutputChannel, OUTPUT_VIEW_ID, OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; import { Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output'; import { MainThreadOutputServiceShape, MainContext, IExtHostContext, ExtHostOutputServiceShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -12,6 +12,7 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IViewsService } from 'vs/workbench/common/views'; +import { isNumber } from 'vs/base/common/types'; @extHostNamedCustomer(MainContext.MainThreadOutputService) export class MainThreadOutputService extends Disposable implements MainThreadOutputServiceShape { @@ -65,26 +66,30 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $update(channelId: string): Promise | undefined { + public $update(channelId: string, mode: OutputChannelUpdateMode, till?: number): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { - channel.update(); + if (mode === OutputChannelUpdateMode.Append) { + channel.update(mode); + } else if (isNumber(till)) { + channel.update(mode, till); + } } return undefined; } - public $clear(channelId: string, till: number): Promise | undefined { + public $clear(channelId: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { - channel.clear(till); + channel.clear(); } return undefined; } - public $replaceAll(channelId: string, till: number, value: string): Promise | undefined { + public $replace(channelId: string, value: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { - channel.replaceAll(till, value); + channel.replace(value); } return undefined; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 03d33fe3739..17738393365 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -56,6 +56,7 @@ import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType, ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -445,9 +446,10 @@ export interface MainThreadMessageServiceShape extends IDisposable { export interface MainThreadOutputServiceShape extends IDisposable { $register(label: string, log: boolean, file?: UriComponents, extensionId?: string): Promise; $append(channelId: string, value: string): Promise | undefined; - $update(channelId: string): Promise | undefined; - $clear(channelId: string, till: number): Promise | undefined; - $replaceAll(channelId: string, till: number, value?: string): Promise | undefined; + $clear(channelId: string): Promise | undefined; + $replace(channelId: string, value: string): Promise | undefined; + $update(channelId: string, mode: OutputChannelUpdateMode.Append): Promise | undefined; + $update(channelId: string, mode: OutputChannelUpdateMode, till: number): Promise | undefined; $reveal(channelId: string, preserveFocus: boolean): Promise | undefined; $close(channelId: string): Promise | undefined; $dispose(channelId: string): Promise | undefined; diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index 3845c68ff02..facadbcd405 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -7,7 +7,6 @@ import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; -import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -18,73 +17,37 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements readonly _id: Promise; private readonly _name: string; protected readonly _proxy: MainThreadOutputServiceShape; + private _disposed: boolean; - private _offset: number; - private readonly _extension: IExtensionDescription; + get disposed(): boolean { return this._disposed; } public visible: boolean = false; - constructor(name: string, log: boolean, file: URI | undefined, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { + constructor(name: string, log: boolean, file: URI | undefined, extensionId: string, proxy: MainThreadOutputServiceShape) { super(); this._name = name; this._proxy = proxy; - this._id = proxy.$register(this.name, log, file, extension.identifier.value); + this._id = proxy.$register(this.name, log, file, extensionId); this._disposed = false; - this._offset = 0; - this._extension = extension; } get name(): string { return this._name; } - append(value: string): void { - this.validate(); - this.incrementOffset(value); - } - appendLine(value: string): void { - this.validate(); this.append(value + '\n'); } - clear(): void { - this.validate(); - const till = this._offset; - this._id.then(id => this._proxy.$clear(id, till)); - } - - replaceAll(value: string, donotSendValue?: boolean): void { - this.validate(true); - const till = this._offset; - this.incrementOffset(value); - this._id.then(id => this._proxy.$replaceAll(id, till, donotSendValue ? undefined : value)); - } - show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - this.validate(); this._id.then(id => this._proxy.$reveal(id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus))); } hide(): void { - this.validate(); this._id.then(id => this._proxy.$close(id)); } - protected validate(checkProposedApi?: boolean): void { - if (checkProposedApi) { - checkProposedApiEnabled(this._extension); - } - if (this._disposed) { - throw new Error('Channel has been closed'); - } - } - - private incrementOffset(value: string) { - this._offset += value ? VSBuffer.fromString(value).byteLength : 0; - } - override dispose(): void { super.dispose(); @@ -94,50 +57,30 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements .then(() => this._disposed = true); } } + + abstract append(value: string): void; + abstract clear(): void; + abstract replace(value: string): void; } export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { - constructor(name: string, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { - super(name, false, undefined, extension, proxy); + constructor(name: string, extensionId: string, proxy: MainThreadOutputServiceShape) { + super(name, false, undefined, extensionId, proxy); } - override append(value: string): void { - super.append(value); + append(value: string): void { this._id.then(id => this._proxy.$append(id, value)); } - -} - -export class LazyOutputChannel implements vscode.OutputChannel { - - constructor( - readonly name: string, - private readonly _channel: Promise - ) { } - - append(value: string): void { - this._channel.then(channel => channel.append(value)); - } - appendLine(value: string): void { - this._channel.then(channel => channel.appendLine(value)); - } clear(): void { - this._channel.then(channel => channel.clear()); + this._id.then(id => this._proxy.$clear(id)); } - replaceAll(value: string): void { - this._channel.then(channel => channel.replaceAll(value)); - } - show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - this._channel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); - } - hide(): void { - this._channel.then(channel => channel.hide()); - } - dispose(): void { - this._channel.then(channel => channel.dispose()); + + replace(value: string): void { + this._id.then(id => this._proxy.$replace(id, value)); } + } export class ExtHostOutputService implements ExtHostOutputServiceShape { @@ -145,12 +88,18 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { readonly _serviceBrand: undefined; protected readonly _proxy: MainThreadOutputServiceShape; + private readonly _channels: Map = new Map(); + private visibleChannelId: string | null = null; constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); } - $setVisibleChannel(channelId: string): void { + $setVisibleChannel(visibleChannelId: string | null): void { + this.visibleChannelId = visibleChannelId; + for (const [id, channel] of this._channels) { + channel.visible = id === this.visibleChannelId; + } } createOutputChannel(name: string, extension: IExtensionDescription): vscode.OutputChannel { @@ -158,9 +107,73 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } - return new ExtHostPushOutputChannel(name, extension, this._proxy); + const extHostOutputChannel = this.doCreateOutChannel(name, extension); + extHostOutputChannel.then(channel => channel._id.then(id => { + this._channels.set(id, channel); + channel.visible = id === this.visibleChannelId; + })); + return this.createExtHostOutputChannel(name, extHostOutputChannel, extension); } + protected async doCreateOutChannel(name: string, extension: IExtensionDescription): Promise { + return new ExtHostPushOutputChannel(name, extension.identifier.value, this._proxy); + } + + private createExtHostOutputChannel(name: string, channelPromise: Promise, extensionDescription: IExtensionDescription): vscode.OutputChannel { + const validate = (channel: AbstractExtHostOutputChannel, checkProposedApi?: boolean) => { + if (checkProposedApi) { + checkProposedApiEnabled(extensionDescription); + } + if (channel.disposed) { + throw new Error('Channel has been closed'); + } + }; + return { + get name(): string { return name; }, + append(value: string): void { + channelPromise.then(channel => { + validate(channel); + channel.append(value); + }); + }, + appendLine(value: string): void { + channelPromise.then(channel => { + validate(channel); + channel.appendLine(value); + }); + }, + clear(): void { + channelPromise.then(channel => { + validate(channel); + channel.clear(); + }); + }, + replace(value: string): void { + channelPromise.then(channel => { + validate(channel, true); + channel.replace(value); + }); + }, + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + channelPromise.then(channel => { + validate(channel); + channel.show(columnOrPreserveFocus, preserveFocus); + }); + }, + hide(): void { + channelPromise.then(channel => { + validate(channel); + channel.hide(); + }); + }, + dispose(): void { + channelPromise.then(channel => { + validate(channel); + channel.dispose(); + }); + } + }; + } } export interface IExtHostOutputService extends ExtHostOutputService { } diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts index 540563fad58..6016baa59b8 100644 --- a/src/vs/workbench/api/node/extHost.node.services.ts +++ b/src/vs/workbench/api/node/extHost.node.services.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService'; +import { ExtHostOutputService } from 'vs/workbench/api/node/extHostOutputService'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; @@ -34,7 +34,7 @@ registerSingleton(ILogService, ExtHostLogService); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostDebugService, ExtHostDebugService); -registerSingleton(IExtHostOutputService, ExtHostOutputService2); +registerSingleton(IExtHostOutputService, ExtHostOutputService); registerSingleton(IExtHostSearch, NativeExtHostSearch); registerSingleton(IExtHostTask, ExtHostTask); registerSingleton(IExtHostTerminalService, ExtHostTerminalService); diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index e057eab0d37..971129b7c86 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { join } from 'vs/base/common/path'; import { toLocalISOString } from 'vs/base/common/date'; import { Promises, SymlinkSupport } from 'vs/base/node/pfs'; -import { AbstractExtHostOutputChannel, ExtHostPushOutputChannel, ExtHostOutputService, LazyOutputChannel } from 'vs/workbench/api/common/extHostOutput'; +import { AbstractExtHostOutputChannel, ExtHostOutputService as BaseExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -17,6 +17,8 @@ import { createRotatingLogger } from 'vs/platform/log/node/spdlogLog'; import { Logger } from 'spdlog'; import { ByteSize } from 'vs/platform/files/common/files'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; +import { VSBuffer } from 'vs/base/common/buffer'; class OutputAppender { @@ -41,23 +43,34 @@ class OutputAppender { class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { - private _appender: OutputAppender; + private _offset: number; + private readonly _appender: OutputAppender; - constructor(name: string, appender: OutputAppender, extension: IExtensionDescription, proxy: MainThreadOutputServiceShape) { - super(name, false, URI.file(appender.file), extension, proxy); + constructor(name: string, appender: OutputAppender, extensionId: string, proxy: MainThreadOutputServiceShape) { + super(name, false, URI.file(appender.file), extensionId, proxy); + this._offset = 0; this._appender = appender; } - override append(value: string): void { - super.append(value); + append(value: string): void { + this.incrementOffset(value); this._appender.append(value); if (this.visible) { - this.update(); + this._appender.flush(); + this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Append)); } } - override replaceAll(value: string): void { - super.replaceAll(value, true); + clear(): void { + const till = this._offset; + this._appender.flush(); + this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Clear, till)); + } + + replace(value: string): void { + const till = this._offset; + this.incrementOffset(value); + this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Replace, till)); this._appender.append(value); if (this.visible) { this._appender.flush(); @@ -69,24 +82,16 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { super.show(columnOrPreserveFocus, preserveFocus); } - override clear(): void { - this._appender.flush(); - super.clear(); + private incrementOffset(value: string) { + this._offset += VSBuffer.fromString(value).byteLength; } - private update(): void { - this._appender.flush(); - this._id.then(id => this._proxy.$update(id)); - } } -export class ExtHostOutputService2 extends ExtHostOutputService { +export class ExtHostOutputService extends BaseExtHostOutputService { private _logsLocation: URI; private _namePool: number = 1; - private readonly _channels: Map = new Map(); - - private visibleChannelId: string | null = null; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -97,27 +102,7 @@ export class ExtHostOutputService2 extends ExtHostOutputService { this._logsLocation = initData.logsLocation; } - override $setVisibleChannel(visibleChannelId: string | null): void { - this.visibleChannelId = visibleChannelId; - for (const [id, channel] of this._channels) { - channel.visible = id === this.visibleChannelId; - } - } - - override createOutputChannel(name: string, extension: IExtensionDescription): vscode.OutputChannel { - name = name.trim(); - if (!name) { - throw new Error('illegal argument `name`. must not be falsy'); - } - const extHostOutputChannel = this._doCreateOutChannel(name, extension); - extHostOutputChannel.then(channel => channel._id.then(id => { - this._channels.set(id, channel); - channel.visible = id === this.visibleChannelId; - })); - return new LazyOutputChannel(name, extHostOutputChannel); - } - - private async _doCreateOutChannel(name: string, extension: IExtensionDescription): Promise { + protected override async doCreateOutChannel(name: string, extension: IExtensionDescription): Promise { try { const outputDirPath = join(this._logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); const exists = await SymlinkSupport.existsDirectory(outputDirPath); @@ -127,11 +112,11 @@ export class ExtHostOutputService2 extends ExtHostOutputService { const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDirPath, `${fileName}.log`)); const appender = await OutputAppender.create(fileName, file.fsPath); - return new ExtHostOutputChannelBackedByFile(name, appender, extension, this._proxy); + return new ExtHostOutputChannelBackedByFile(name, appender, extension.identifier.value, this._proxy); } catch (error) { // Do not crash if logger cannot be created this.logService.error(error); - return new ExtHostPushOutputChannel(name, extension, this._proxy); } + return super.doCreateOutChannel(name, extension); } } diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 74840be69b9..a8dc8204d1b 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_SCHEME, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output'; +import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; import { IOutputChannelDescriptor, Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; @@ -46,16 +46,16 @@ class OutputChannel extends Disposable implements IOutputChannel { this.model.append(output); } - update(): void { - this.model.update(); + update(mode: OutputChannelUpdateMode, till?: number): void { + this.model.update(mode, till); } - clear(till?: number): void { - this.model.clear(till); + clear(): void { + this.model.clear(); } - replaceAll(till: number, value: string): void { - this.model.replaceAll(till, value); + replace(value: string): void { + this.model.replace(value); } } diff --git a/src/vs/workbench/contrib/output/common/output.ts b/src/vs/workbench/contrib/output/common/output.ts index 2d7c2b3ec20..902e024acc8 100644 --- a/src/vs/workbench/contrib/output/common/output.ts +++ b/src/vs/workbench/contrib/output/common/output.ts @@ -95,6 +95,12 @@ export interface IOutputService { onActiveOutputChannel: Event; } +export enum OutputChannelUpdateMode { + Append = 1, + Replace, + Clear +} + export interface IOutputChannel { /** @@ -117,20 +123,21 @@ export interface IOutputChannel { */ append(output: string): void; - /** - * Update the channel. - */ - update(): void; - /** * Clears all received output for this channel. */ - clear(till?: number): void; + clear(): void; /** - * Replaces the output of the channel. + * Replaces the content of the channel with given output */ - replaceAll(till: number, value: string): void; + replace(output: string): void; + + /** + * Update the channel. + */ + update(mode: OutputChannelUpdateMode.Append): void; + update(mode: OutputChannelUpdateMode, till: number): void; /** * Disposes the output channel. diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 4e340d24ddb..99b4a96ace1 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -21,69 +21,153 @@ import { Range } from 'vs/editor/common/core/range'; import { VSBuffer } from 'vs/base/common/buffer'; import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; export interface IOutputChannelModel extends IDisposable { readonly onDispose: Event; append(output: string): void; - update(): void; + update(mode: OutputChannelUpdateMode, till?: number): void; loadModel(): Promise; - clear(till?: number): void; - replaceAll(till: number, value?: string): void; + clear(): void; + replace(value: string): void; } -const enum ModelUpdateMode { - Append = 1, - Replace, - Clear +class OutputFileListener extends Disposable { + + private readonly _onDidContentChange = new Emitter(); + readonly onDidContentChange: Event = this._onDidContentChange.event; + + private watching: boolean = false; + private syncDelayer: ThrottledDelayer; + private etag: string | undefined; + + constructor( + private readonly file: URI, + private readonly fileService: IFileService, + private readonly logService: ILogService + ) { + super(); + this.syncDelayer = new ThrottledDelayer(500); + } + + watch(eTag: string | undefined): void { + if (!this.watching) { + this.etag = eTag; + this.poll(); + this.logService.trace('Started polling', this.file.toString()); + this.watching = true; + } + } + + private poll(): void { + const loop = () => this.doWatch().then(() => this.poll()); + this.syncDelayer.trigger(loop); + } + + private async doWatch(): Promise { + const stat = await this.fileService.resolve(this.file, { resolveMetadata: true }); + if (stat.etag !== this.etag) { + this.etag = stat.etag; + this._onDidContentChange.fire(stat.size); + } + } + + unwatch(): void { + if (this.watching) { + this.syncDelayer.cancel(); + this.watching = false; + this.logService.trace('Stopped polling', this.file.toString()); + } + } + + override dispose(): void { + this.unwatch(); + super.dispose(); + } } -export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel { +export class FileOutputChannelModel extends Disposable implements IOutputChannelModel { - protected readonly _onDispose = this._register(new Emitter()); + private readonly _onDispose = this._register(new Emitter()); readonly onDispose: Event = this._onDispose.event; - protected model: ITextModel | null = null; + private readonly fileHandler: OutputFileListener; + private etag: string | undefined = ''; + + private loadModelPromise: Promise | null = null; + private model: ITextModel | null = null; private modelUpdateInProgress: boolean = false; - private modelUpdateCancellationSource = this._register(new MutableDisposable()); - private appendThrottler = this._register(new ThrottledDelayer(300)); + private readonly modelUpdateCancellationSource = this._register(new MutableDisposable()); + private readonly appendThrottler = this._register(new ThrottledDelayer(300)); private replacePromise: Promise | undefined; - protected startOffset: number = 0; - protected endOffset: number = 0; + private startOffset: number = 0; + private endOffset: number = 0; constructor( private readonly modelUri: URI, private readonly mimeType: string, - protected readonly file: URI, - protected fileService: IFileService, - protected modelService: IModelService, - protected modeService: IModeService, - protected editorWorkerService: IEditorWorkerService, + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + @ILogService logService: ILogService, + @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, ) { super(); + + this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService, logService)); + this._register(this.fileHandler.onDidContentChange(size => this.onDidContentChange(size))); + this._register(toDisposable(() => this.fileHandler.unwatch())); } - clear(till?: number): void { - this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; - this.updateModel(ModelUpdateMode.Clear); + append(message: string): void { + throw new Error('Not supported'); } - replaceAll(till: number, message: string): void { - this.startOffset = this.endOffset = till; - this.updateModel(ModelUpdateMode.Replace); + replace(message: string): void { + throw new Error('Not supported'); } - update(): void { } + clear(): void { + this.update(OutputChannelUpdateMode.Clear, this.endOffset); + } - protected createModel(content: string): ITextModel { + update(mode: OutputChannelUpdateMode, till?: number): void { + const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); + loadModelPromise.then(() => this.doUpdate(mode, till)); + } + + loadModel(): Promise { + this.loadModelPromise = Promises.withAsyncBody(async (c, e) => { + try { + let content = ''; + if (await this.fileService.exists(this.file)) { + const fileContent = await this.fileService.readFile(this.file, { position: this.startOffset }); + this.endOffset = this.startOffset + fileContent.value.byteLength; + this.etag = fileContent.etag; + content = fileContent.value.toString(); + } else { + this.startOffset = 0; + this.endOffset = 0; + } + c(this.createModel(content)); + } catch (error) { + e(error); + } + }); + return this.loadModelPromise; + } + + private createModel(content: string): ITextModel { if (this.model) { this.model.setValue(content); } else { this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); - this.onModelCreated(this.model); + this.fileHandler.watch(this.etag); const disposable = this.model.onWillDispose(() => { this.cancelModelUpdate(); - this.onModelWillDispose(this.model); + this.fileHandler.unwatch(); this.model = null; dispose(disposable); }); @@ -91,28 +175,31 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen return this.model; } - protected updateModel(mode: ModelUpdateMode): void { - if (mode !== ModelUpdateMode.Append) { + private doUpdate(mode: OutputChannelUpdateMode, till?: number): void { + if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) { + this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; this.cancelModelUpdate(); } if (!this.model) { return; } + + this.modelUpdateInProgress = true; if (!this.modelUpdateCancellationSource.value) { this.modelUpdateCancellationSource.value = new CancellationTokenSource(); } - this.modelUpdateInProgress = true; const token = this.modelUpdateCancellationSource.value.token; - switch (mode) { - case ModelUpdateMode.Clear: - this.clearContent(this.model); - break; - case ModelUpdateMode.Replace: - this.replacePromise = this.replaceContent(this.model, token).finally(() => this.replacePromise = undefined); - break; - case ModelUpdateMode.Append: - this.appendContent(this.model, token); - break; + + if (mode === OutputChannelUpdateMode.Clear) { + this.clearContent(this.model); + } + + else if (mode === OutputChannelUpdateMode.Replace) { + this.replacePromise = this.replaceContent(this.model, token).finally(() => this.replacePromise = undefined); + } + + else { + this.appendContent(this.model, token); } } @@ -187,8 +274,8 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen if (edits.length) { model.applyEdits(edits); } + this.endOffset = this.endOffset + content.byteLength; this.modelUpdateInProgress = false; - this.onDidModelUpdate(content); } protected cancelModelUpdate(): void { @@ -201,17 +288,27 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen this.modelUpdateInProgress = false; } - protected isModelUpdateInProgress(): boolean { - return this.modelUpdateInProgress; + private async getContentToUpdate(): Promise { + const content = await this.fileService.readFile(this.file, { position: this.endOffset }); + this.etag = content.etag; + return content.value; } - abstract loadModel(): Promise; - abstract append(message: string): void; + private onDidContentChange(size: number | undefined): void { + if (this.model) { + if (!this.modelUpdateInProgress) { + if (isNumber(size) && this.endOffset > size) { + // Reset - Content is removed + this.update(OutputChannelUpdateMode.Clear, 0); + } + } + this.update(OutputChannelUpdateMode.Append); + } + } - protected onModelCreated(model: ITextModel) { } - protected onModelWillDispose(model: ITextModel | null) { } - protected abstract getContentToUpdate(): Promise; - protected abstract onDidModelUpdate(content: VSBuffer): void; + protected isVisible(): boolean { + return !!this.model; + } override dispose(): void { this._onDispose.fire(); @@ -219,159 +316,10 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen } } -class OutputFileListener extends Disposable { - - private readonly _onDidContentChange = new Emitter(); - readonly onDidContentChange: Event = this._onDidContentChange.event; - - private watching: boolean = false; - private syncDelayer: ThrottledDelayer; - private etag: string | undefined; - - constructor( - private readonly file: URI, - private readonly fileService: IFileService, - private readonly logService: ILogService - ) { - super(); - this.syncDelayer = new ThrottledDelayer(500); - } - - watch(eTag: string | undefined): void { - if (!this.watching) { - this.etag = eTag; - this.poll(); - this.logService.trace('Started polling', this.file.toString()); - this.watching = true; - } - } - - private poll(): void { - const loop = () => this.doWatch().then(() => this.poll()); - this.syncDelayer.trigger(loop); - } - - private async doWatch(): Promise { - const stat = await this.fileService.resolve(this.file, { resolveMetadata: true }); - if (stat.etag !== this.etag) { - this.etag = stat.etag; - this._onDidContentChange.fire(stat.size); - } - } - - unwatch(): void { - if (this.watching) { - this.syncDelayer.cancel(); - this.watching = false; - this.logService.trace('Stopped polling', this.file.toString()); - } - } - - override dispose(): void { - this.unwatch(); - super.dispose(); - } -} - -/** - * An output channel driven by a file and does not support appending messages. - */ -export class FileOutputChannelModel extends AbstractFileOutputChannelModel implements IOutputChannelModel { - - private readonly fileHandler: OutputFileListener; - - private etag: string | undefined = ''; - private loadModelPromise: Promise | null = null; - - constructor( - modelUri: URI, - mimeType: string, - file: URI, - @IFileService fileService: IFileService, - @IModelService modelService: IModelService, - @IModeService modeService: IModeService, - @ILogService logService: ILogService, - @IEditorWorkerService editorWorkerService: IEditorWorkerService - ) { - super(modelUri, mimeType, file, fileService, modelService, modeService, editorWorkerService); - - this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService, logService)); - this._register(this.fileHandler.onDidContentChange(size => this.update(size))); - this._register(toDisposable(() => this.fileHandler.unwatch())); - } - - loadModel(): Promise { - this.loadModelPromise = Promises.withAsyncBody(async (c, e) => { - try { - let content = ''; - if (await this.fileService.exists(this.file)) { - const fileContent = await this.fileService.readFile(this.file, { position: this.startOffset }); - this.endOffset = this.startOffset + fileContent.value.byteLength; - this.etag = fileContent.etag; - content = fileContent.value.toString(); - } else { - this.startOffset = 0; - this.endOffset = 0; - } - c(this.createModel(content)); - } catch (error) { - e(error); - } - }); - return this.loadModelPromise; - } - - override clear(till?: number): void { - const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); - loadModelPromise.then(() => { - super.clear(till); - this.update(); - }); - } - - append(message: string): void { - throw new Error('Not supported'); - } - - protected async getContentToUpdate(): Promise { - const content = await this.fileService.readFile(this.file, { position: this.endOffset }); - this.etag = content.etag; - return content.value; - } - - protected override onDidModelUpdate(content: VSBuffer): void { - this.endOffset = this.endOffset + content.byteLength; - } - - protected override onModelCreated(model: ITextModel): void { - this.fileHandler.watch(this.etag); - } - - protected override onModelWillDispose(model: ITextModel | null): void { - this.fileHandler.unwatch(); - } - - override update(size?: number): void { - if (this.model) { - if (!this.isModelUpdateInProgress()) { - if (isNumber(size) && this.endOffset > size) { - // Reset - Content is removed - this.startOffset = this.endOffset = 0; - this.updateModel(ModelUpdateMode.Clear); - } - } - this.updateModel(ModelUpdateMode.Append); - } - } -} - -class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { +class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutputChannelModel { private logger: ILogger; - private appendedMessage: string; - private loadingFromFileInProgress: boolean; - private resettingDelayer: ThrottledDelayer; - private readonly rotatingFilePath: URI; + private _offset: number; constructor( id: string, @@ -382,99 +330,35 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement @IModelService modelService: IModelService, @IModeService modeService: IModeService, @ILoggerService loggerService: ILoggerService, + @ILogService logService: ILogService, @IEditorWorkerService editorWorkerService: IEditorWorkerService ) { - super(modelUri, mimeType, file, fileService, modelService, modeService, editorWorkerService); - this.appendedMessage = ''; - this.loadingFromFileInProgress = false; + super(modelUri, mimeType, file, fileService, modelService, modeService, logService, editorWorkerService); // Donot rotate to check for the file reset - this.logger = loggerService.createLogger(this.file, { always: true, donotRotate: true, donotUseFormatters: true }); - - const rotatingFilePathDirectory = resources.dirname(this.file); - this.rotatingFilePath = resources.joinPath(rotatingFilePathDirectory, `${id}.1.log`); - - this._register(fileService.watch(rotatingFilePathDirectory)); - this._register(fileService.onDidFilesChange(e => { - if (e.contains(this.rotatingFilePath)) { - this.resettingDelayer.trigger(() => this.resetModel()); - } - })); - - this.resettingDelayer = new ThrottledDelayer(50); + this.logger = loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); + this._offset = 0; } - append(message: string): void { - // update end offset always as message is read - this.endOffset = this.endOffset + VSBuffer.fromString(message).byteLength; - if (this.loadingFromFileInProgress) { - this.appendedMessage += message; - } else { - this.write(message); - if (this.model) { - this.appendedMessage += message; - this.updateModel(ModelUpdateMode.Append); - } - } + override append(message: string): void { + this.write(message); + this.update(OutputChannelUpdateMode.Append); } - override replaceAll(till: number, value: string): void { - this.appendedMessage = value; - super.replaceAll(till, value); - } - - override clear(till?: number): void { - super.clear(till); - this.appendedMessage = ''; - } - - async loadModel(): Promise { - this.loadingFromFileInProgress = true; - this.cancelModelUpdate(); - this.appendedMessage = ''; - let content = await this.loadFile(); - if (this.endOffset !== this.startOffset + VSBuffer.fromString(content).byteLength) { - // Queue content is not written into the file - // Flush it and load file again - this.flush(); - content = await this.loadFile(); - } - if (this.appendedMessage) { - this.write(this.appendedMessage); - this.appendedMessage = ''; - } - this.loadingFromFileInProgress = false; - return this.createModel(content); - } - - private async resetModel(): Promise { - this.startOffset = 0; - this.endOffset = 0; - if (this.model) { - await this.loadModel(); - } - } - - private async loadFile(): Promise { - const content = await this.fileService.readFile(this.file, { position: this.startOffset }); - return this.appendedMessage ? content.value + this.appendedMessage : content.value.toString(); - } - - protected async getContentToUpdate(): Promise { - return VSBuffer.fromString(this.appendedMessage); - } - - protected override onDidModelUpdate(content: VSBuffer): void { - this.appendedMessage = ''; + override replace(message: string): void { + const till = this._offset; + this.write(message); + this.update(OutputChannelUpdateMode.Replace, till); } private write(content: string): void { + this._offset += VSBuffer.fromString(content).byteLength; this.logger.info(content); + if (this.isVisible()) { + this.logger.flush(); + } } - private flush(): void { - this.logger.flush(); - } } export class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel { @@ -509,19 +393,19 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output)); } - update(): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.update()); + update(mode: OutputChannelUpdateMode, till?: number): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till)); } loadModel(): Promise { return this.outputChannelModel.then(outputChannelModel => outputChannelModel.loadModel()); } - clear(till?: number): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.clear(till)); + clear(): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.clear()); } - replaceAll(till: number, value: string): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.replaceAll(till, value)); + replace(value: string): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.replace(value)); } } From 1788b8afd8d0d82a1c2a90e811be423f4f55faef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 14:40:02 +0100 Subject: [PATCH 047/375] chore: :wrench: clean smoketest dependencies --- test/automation/package.json | 13 ++-- test/automation/yarn.lock | 51 ++++++++------- test/smoke/package.json | 16 ++--- test/smoke/yarn.lock | 121 +---------------------------------- 4 files changed, 45 insertions(+), 156 deletions(-) diff --git a/test/automation/package.json b/test/automation/package.json index 59c7da43bf2..0f7a64daeb5 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -19,20 +19,21 @@ "copy-package-version": "node tools/copy-package-version.js", "prepublishOnly": "npm run copy-package-version" }, + "dependencies": { + "mkdirp": "^1.0.4", + "ncp": "^2.0.0", + "tmp": "0.1.0", + "tree-kill": "1.2.2", + "vscode-uri": "^2.0.3" + }, "devDependencies": { - "@types/debug": "4.1.5", "@types/mkdirp": "^1.0.1", "@types/ncp": "2.0.1", "@types/node": "14.x", "@types/tmp": "0.1.0", "cpx2": "3.0.0", - "mkdirp": "^1.0.4", - "ncp": "^2.0.0", "npm-run-all": "^4.1.5", - "tmp": "0.1.0", - "tree-kill": "1.2.2", "typescript": "^4.3.2", - "vscode-uri": "^2.0.3", "watch": "^1.0.2" } } diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 255c3ef3402..c4d5ceeb0d8 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@types/debug@4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" - integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== - "@types/mkdirp@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" @@ -135,9 +130,9 @@ debounce@^1.2.0: integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" @@ -149,9 +144,9 @@ define-properties@^1.1.3: object-keys "^1.0.12" duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== error-ex@^1.3.1: version "1.3.2" @@ -253,9 +248,9 @@ glob@^7.1.3: path-is-absolute "^1.0.0" glob@^7.1.4: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -270,9 +265,9 @@ graceful-fs@^4.1.2: integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== has-flag@^3.0.0: version "3.0.0" @@ -319,13 +314,20 @@ is-callable@^1.1.4, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-core-module@^2.1.0, is-core-module@^2.2.0: +is-core-module@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== + dependencies: + has "^1.0.3" + is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" @@ -396,7 +398,12 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.0, minimist@^1.2.0: +minimist@^1.1.0: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -573,9 +580,9 @@ shell-quote@^1.6.1: integrity sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg== shell-quote@^1.7.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== spdx-correct@^3.0.0: version "3.1.1" diff --git a/test/smoke/package.json b/test/smoke/package.json index a2ab6e499e5..3fb62eab3f9 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -10,24 +10,22 @@ "watch": "npm-run-all -lp watch-automation watch-smoke", "mocha": "node ../node_modules/mocha/bin/mocha" }, + "dependencies": { + "mkdirp": "^1.0.4", + "ncp": "^2.0.0", + "node-fetch": "^2.6.1", + "rimraf": "^2.6.1", + "vscode-test": "^1.6.1" + }, "devDependencies": { - "@types/htmlparser2": "3.7.29", "@types/mkdirp": "^1.0.1", "@types/mocha": "^8.2.0", "@types/ncp": "2.0.1", "@types/node": "14.x", "@types/node-fetch": "^2.5.10", "@types/rimraf": "^2.0.4", - "htmlparser2": "^3.9.2", - "mkdirp": "^1.0.4", - "ncp": "^2.0.0", - "node-fetch": "^2.6.1", "npm-run-all": "^4.1.5", - "portastic": "^1.0.1", - "rimraf": "^2.6.1", - "strip-json-comments": "^2.0.1", "typescript": "^4.3.2", - "vscode-test": "^1.6.1", "watch": "^1.0.2" } } diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index e781ab0cc8c..b5b30bcd9c0 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -21,11 +21,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/htmlparser2@3.7.29": - version "3.7.29" - resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.7.29.tgz#d2ae2c9874ec8829e03f00baa4635b4229ce8cf1" - integrity sha1-0q4smHTsiCngPwC6pGNbQinOjPE= - "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -113,11 +108,6 @@ binary@~0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" -bluebird@^2.9.34: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= - bluebird@~3.4.1: version "3.4.7" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" @@ -184,11 +174,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^2.8.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -217,13 +202,6 @@ debug@4: dependencies: ms "2.1.2" -debug@^2.2.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -236,39 +214,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -276,16 +221,6 @@ duplexer2@~0.1.4: dependencies: readable-stream "^2.0.2" -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== - error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -414,18 +349,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -htmlparser2@^3.9.2: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -451,7 +374,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: +inherits@2, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -578,11 +501,6 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -695,15 +613,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -portastic@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/portastic/-/portastic-1.0.1.tgz#1c9805d43fae8f6a40cf0dbc7794091a2e9d0d2a" - integrity sha1-HJgF1D+uj2pAzw28d5QJGi6dDSo= - dependencies: - bluebird "^2.9.34" - commander "^2.8.1" - debug "^2.2.0" - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -731,15 +640,6 @@ readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - resolve@^1.10.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" @@ -767,11 +667,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -850,13 +745,6 @@ string.prototype.trimstart@^1.0.1: call-bind "^1.0.0" define-properties "^1.1.3" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -869,11 +757,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-json-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -907,7 +790,7 @@ unzipper@^0.10.11: readable-stream "~2.3.6" setimmediate "~1.0.4" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= From 491a86b974c2fee46d9a55917c4f16b5d26c33c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 14:57:36 +0100 Subject: [PATCH 048/375] fix(table): :lipstick: alternating background colors for table widget Closes: #125099 --- src/vs/base/browser/ui/list/listWidget.ts | 14 +++++++++++++- src/vs/platform/theme/common/colorRegistry.ts | 3 ++- src/vs/platform/theme/common/styler.ts | 6 ++++-- .../preferences/browser/keybindingsEditor.ts | 4 ++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 2ea8f0ac904..153ea140b78 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -897,6 +897,16 @@ export class DefaultStyleController implements IStyleController { }`); } + if (styles.tableOddRowsBackgroundColor) { + content.push(` + .monaco-table .monaco-list-row[data-parity=odd]:not(.focused):not(.selected):not(:hover) .monaco-table-tr, + .monaco-table .monaco-list:not(:focus) .monaco-list-row[data-parity=odd].focused:not(.selected):not(:hover) .monaco-table-tr, + .monaco-table .monaco-list:not(.focused) .monaco-list-row[data-parity=odd].focused:not(.selected):not(:hover) .monaco-table-tr { + background-color: ${styles.tableOddRowsBackgroundColor}; + } + `); + } + this.styleElement.textContent = content.join('\n'); } } @@ -959,6 +969,7 @@ export interface IListStyles { listMatchesShadow?: Color; treeIndentGuidesStroke?: Color; tableColumnsBorder?: Color; + tableOddRowsBackgroundColor?: Color; } const defaultStyles: IListStyles = { @@ -973,7 +984,8 @@ const defaultStyles: IListStyles = { listHoverBackground: Color.fromHex('#2A2D2E'), listDropBackground: Color.fromHex('#383B3D'), treeIndentGuidesStroke: Color.fromHex('#a9a9a9'), - tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2) + tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2), + tableOddRowsBackgroundColor: Color.fromHex('#cccccc').transparent(0.04) }; const DefaultOptions: IListOptions = { diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 5a19922b283..1c0695f665f 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -423,7 +423,8 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget. export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.')); export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.')); export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); -export const tableColumnsBorder = registerColor('tree.tableColumnsBorder', { dark: '#CCCCCC20', light: '#61616120', hc: null }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); +export const tableColumnsBorder = registerColor('tree.tableColumnsBorder', { dark: '#CCCCCC20', light: '#61616120', hc: null }, nls.localize('tableColumnsBorder', "Table border color between columns.")); +export const tableOddRowsBackgroundColor = registerColor('tree.tableOddRowsBackground', { dark: transparent(foreground, 0.04), light: transparent(foreground, 0.04), hc: null }, nls.localize('tableOddRowsBackgroundColor', "Background color for odd table rows.")); export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hc: '#A7A8A9' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized. ")); /** diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 2200839c921..b4dac78d2a6 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -6,7 +6,7 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IThemable, styleFn } from 'vs/base/common/styler'; -import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, selectBackground, selectBorder, selectForeground, selectListBackground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, tableColumnsBorder, textLinkForeground, treeIndentGuidesStroke, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, selectBackground, selectBorder, selectForeground, selectListBackground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -186,6 +186,7 @@ export interface IListStyleOverrides extends IStyleOverrides { listMatchesShadow?: ColorIdentifier; treeIndentGuidesStroke?: ColorIdentifier; tableColumnsBorder?: ColorIdentifier; + tableOddRowsBackgroundColor?: ColorIdentifier; } export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IColorMapping): IDisposable { @@ -216,7 +217,8 @@ export const defaultListStyles: IColorMapping = { listFilterWidgetNoMatchesOutline, listMatchesShadow: widgetShadow, treeIndentGuidesStroke, - tableColumnsBorder + tableColumnsBorder, + tableOddRowsBackgroundColor }; export interface IButtonStyleOverrides extends IStyleOverrides { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index b505606236d..43e50f31bfb 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -29,7 +29,7 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground, editorBackground, foreground, listActiveSelectionBackground, listInactiveSelectionBackground, listFocusBackground, listHoverBackground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground, editorBackground, foreground, listActiveSelectionBackground, listInactiveSelectionBackground, listFocusBackground, listHoverBackground, registerColor, transparent, tableOddRowsBackgroundColor } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { WorkbenchTable } from 'vs/platform/list/browser/listService'; @@ -1166,7 +1166,7 @@ class AccessibilityProvider implements IListAccessibilityProvider { From 39e3b69db0c7967dc0535186a7423b0237db758f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 14:58:10 +0100 Subject: [PATCH 049/375] chore: :wrench: update conventional commit scopes --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e320fc5ca7d..e14fbca0929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,5 +79,5 @@ }, "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", - "conventionalCommits.scopes": ["tree", "scm", "grid", "splitview"] + "conventionalCommits.scopes": ["tree", "scm", "grid", "splitview", "table"] } From 6c2ccebbe1b4f3b3c1868eb776300a4b06ebba08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 15:18:30 +0100 Subject: [PATCH 050/375] feat: :sparkles: use `startIn` property for save and open dialogs Closes: #130034 --- .../files/browser/htmlFileSystemProvider.ts | 14 +++++++++----- .../services/dialogs/browser/fileDialogService.ts | 15 +++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/files/browser/htmlFileSystemProvider.ts b/src/vs/platform/files/browser/htmlFileSystemProvider.ts index af572ff9621..4d42c21fe15 100644 --- a/src/vs/platform/files/browser/htmlFileSystemProvider.ts +++ b/src/vs/platform/files/browser/htmlFileSystemProvider.ts @@ -285,15 +285,19 @@ export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWr //#region File/Directoy Handle Registry - private readonly files = new Map(); - private readonly directories = new Map(); + private readonly _files = new Map(); + private readonly _directories = new Map(); registerFileHandle(handle: FileSystemFileHandle): URI { - return this.registerHandle(handle, this.files); + return this.registerHandle(handle, this._files); } registerDirectoryHandle(handle: FileSystemDirectoryHandle): URI { - return this.registerHandle(handle, this.directories); + return this.registerHandle(handle, this._directories); + } + + get directories(): Iterable { + return this._directories.values(); } private registerHandle(handle: FileSystemHandle, map: Map): URI { @@ -346,7 +350,7 @@ export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWr } const handleId = resource.path.replace(/\/$/, ''); // remove potential slash from the end of the path - const handle = this.files.get(handleId) ?? this.directories.get(handleId); + const handle = this._files.get(handleId) ?? this._directories.get(handleId); if (!handle) { throw this.createFileSystemProviderError(resource, 'No file system handle registered', FileSystemProviderErrorCode.Unavailable); diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 99a3c0ec8c5..22608278df2 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -17,6 +17,7 @@ import { triggerDownload, triggerUpload, WebFileSystemAccess } from 'vs/base/bro import Severity from 'vs/base/common/severity'; import { VSBuffer } from 'vs/base/common/buffer'; import { extractFilesDropData } from 'vs/workbench/browser/dnd'; +import { Iterable } from 'vs/base/common/iterator'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -113,8 +114,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil } let fileHandle: FileSystemHandle | undefined = undefined; + const startIn = Iterable.first(this.fileSystemProvider.directories); + try { - fileHandle = await window.showSaveFilePicker({ types: this.getFilePickerTypes(options.filters), ...{ suggestedName: basename(defaultUri) } }); + fileHandle = await window.showSaveFilePicker({ types: this.getFilePickerTypes(options.filters), ...{ suggestedName: basename(defaultUri), startIn } }); } catch (error) { return; // `showSaveFilePicker` will throw an error when the user cancels } @@ -148,8 +151,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil } let fileHandle: FileSystemHandle | undefined = undefined; + const startIn = Iterable.first(this.fileSystemProvider.directories); + try { - fileHandle = await window.showSaveFilePicker({ types: this.getFilePickerTypes(options.filters), ...options.defaultUri ? { suggestedName: basename(options.defaultUri) } : undefined }); + fileHandle = await window.showSaveFilePicker({ types: this.getFilePickerTypes(options.filters), ...options.defaultUri ? { suggestedName: basename(options.defaultUri) } : undefined, ...{ startIn } }); } catch (error) { return; // `showSaveFilePicker` will throw an error when the user cancels } @@ -169,14 +174,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil } let uri: URI | undefined; + const startIn = Iterable.first(this.fileSystemProvider.directories) ?? 'documents'; + try { if (options.canSelectFiles) { - const handle = await window.showOpenFilePicker({ multiple: false, types: this.getFilePickerTypes(options.filters) }); + const handle = await window.showOpenFilePicker({ multiple: false, types: this.getFilePickerTypes(options.filters), ...{ startIn } }); if (handle.length === 1) { uri = this.fileSystemProvider.registerFileHandle(handle[0]); } } else { - const handle = await window.showDirectoryPicker(); + const handle = await window.showDirectoryPicker({ ...{ startIn } }); uri = this.fileSystemProvider.registerDirectoryHandle(handle); } } catch (error) { From 0961dbb56d601b4571432fe979c44dc17dbc531d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 15:26:31 +0100 Subject: [PATCH 051/375] style: :wastebasket: --- .../services/contextmenu/electron-sandbox/contextmenuService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b6ef5caeb41..18357cc267d 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -19,7 +19,6 @@ import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { isMacintosh } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -41,7 +40,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic @ITelemetryService telemetryService: ITelemetryService, @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, - @IEnvironmentService environmentService: IEnvironmentService, @IContextViewService contextViewService: IContextViewService, @IThemeService themeService: IThemeService ) { From 144d7e3f68b25a1b13366f30fd22707563531d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 15:51:31 +0100 Subject: [PATCH 052/375] fix(list): :bug: list/tree should show focus outline while context menu is open Closes: #123771 --- .vscode/settings.json | 9 +++++- src/vs/base/browser/ui/list/listWidget.ts | 1 + .../contextview/browser/contextMenuService.ts | 18 ++++++++++-- .../contextview/browser/contextView.ts | 1 + src/vs/platform/list/browser/listService.ts | 17 ++++++++--- .../browser/contextmenu.contribution.ts | 28 +++++++++++++++++++ .../electron-sandbox/contextmenuService.ts | 16 +++++++---- src/vs/workbench/workbench.common.main.ts | 3 ++ 8 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index e14fbca0929..5fcdc566ba0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,5 +79,12 @@ }, "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", - "conventionalCommits.scopes": ["tree", "scm", "grid", "splitview", "table"] + "conventionalCommits.scopes": [ + "tree", + "scm", + "grid", + "splitview", + "table", + "list" + ] } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 153ea140b78..6579ff8fbeb 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -854,6 +854,7 @@ export class DefaultStyleController implements IStyleController { content.push(` .monaco-drag-image, .monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + .monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } `); } diff --git a/src/vs/platform/contextview/browser/contextMenuService.ts b/src/vs/platform/contextview/browser/contextMenuService.ts index be0cf0c540d..437b3ffd8ce 100644 --- a/src/vs/platform/contextview/browser/contextMenuService.ts +++ b/src/vs/platform/contextview/browser/contextMenuService.ts @@ -19,7 +19,11 @@ export class ContextMenuService extends Disposable implements IContextMenuServic private contextMenuHandler: ContextMenuHandler; - readonly onDidShowContextMenu = new Emitter().event; + private readonly _onDidShowContextMenu = new Emitter(); + readonly onDidShowContextMenu = this._onDidShowContextMenu.event; + + private readonly _onDidHideContextMenu = new Emitter(); + readonly onDidHideContextMenu = this._onDidHideContextMenu.event; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -40,7 +44,17 @@ export class ContextMenuService extends Disposable implements IContextMenuServic // ContextMenu showContextMenu(delegate: IContextMenuDelegate): void { - this.contextMenuHandler.showContextMenu(delegate); + this.contextMenuHandler.showContextMenu({ + ...delegate, + onHide: (didCancel) => { + if (delegate.onHide) { + delegate.onHide(didCancel); + } + + this._onDidHideContextMenu.fire(); + } + }); ModifierKeyEmitter.getInstance().resetKeyStatus(); + this._onDidShowContextMenu.fire(); } } diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 09fd4a24569..0b01f2e40c8 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -42,6 +42,7 @@ export interface IContextMenuService { readonly _serviceBrand: undefined; readonly onDidShowContextMenu: Event; + readonly onDidHideContextMenu: Event; showContextMenu(delegate: IContextMenuDelegate): void; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 332b3dd2ce8..875f8dc075f 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -62,7 +62,16 @@ export class ListService implements IListService { return this._lastFocusedWidget; } - constructor(@IThemeService private readonly _themeService: IThemeService) { + constructor(@IThemeService private readonly _themeService: IThemeService) { } + + private setLastFocusedList(widget: WorkbenchListWidget | undefined): void { + if (widget === this._lastFocusedWidget) { + return; + } + + this._lastFocusedWidget?.getHTMLElement().classList.remove('last-focused'); + this._lastFocusedWidget = widget; + this._lastFocusedWidget?.getHTMLElement().classList.add('last-focused'); } register(widget: WorkbenchListWidget, extraContextKeys?: (IContextKey)[]): IDisposable { @@ -83,16 +92,16 @@ export class ListService implements IListService { // Check for currently being focused if (widget.getHTMLElement() === document.activeElement) { - this._lastFocusedWidget = widget; + this.setLastFocusedList(widget); } return combinedDisposable( - widget.onDidFocus(() => this._lastFocusedWidget = widget), + widget.onDidFocus(() => this.setLastFocusedList(widget)), toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)), widget.onDidDispose(() => { this.lists = this.lists.filter(l => l !== registeredList); if (this._lastFocusedWidget === widget) { - this._lastFocusedWidget = undefined; + this.setLastFocusedList(undefined); } }) ); diff --git a/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts b/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts new file mode 100644 index 00000000000..ee27b0aefa9 --- /dev/null +++ b/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +class ContextMenuContribution implements IWorkbenchContribution { + + private readonly disposables = new DisposableStore(); + + constructor( + @ILayoutService layoutService: ILayoutService, + @IContextMenuService contextMenuService: IContextMenuService + ) { + const update = (visible: boolean) => layoutService.container.classList.toggle('context-menu-visible', visible); + contextMenuService.onDidShowContextMenu(() => update(true), null, this.disposables); + contextMenuService.onDidHideContextMenu(() => update(false), null, this.disposables); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(ContextMenuContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 18357cc267d..c7a18004dc9 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -24,7 +24,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { stripIcons } from 'vs/base/common/iconLabels'; import { coalesce } from 'vs/base/common/arrays'; -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; export class ContextMenuService extends Disposable implements IContextMenuService { @@ -32,8 +32,8 @@ export class ContextMenuService extends Disposable implements IContextMenuServic private impl: IContextMenuService; - private readonly _onDidShowContextMenu = this._register(new Emitter()); - readonly onDidShowContextMenu = this._onDidShowContextMenu.event; + get onDidShowContextMenu(): Event { return this.impl.onDidShowContextMenu; } + get onDidHideContextMenu(): Event { return this.impl.onDidHideContextMenu; } constructor( @INotificationService notificationService: INotificationService, @@ -58,7 +58,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic showContextMenu(delegate: IContextMenuDelegate): void { this.impl.showContextMenu(delegate); - this._onDidShowContextMenu.fire(); } } @@ -66,7 +65,11 @@ class NativeContextMenuService extends Disposable implements IContextMenuService declare readonly _serviceBrand: undefined; - readonly onDidShowContextMenu = new Emitter().event; + private readonly _onDidShowContextMenu = new Emitter(); + readonly onDidShowContextMenu = this._onDidShowContextMenu.event; + + private readonly _onDidHideContextMenu = new Emitter(); + readonly onDidHideContextMenu = this._onDidHideContextMenu.event; constructor( @INotificationService private readonly notificationService: INotificationService, @@ -85,6 +88,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } dom.ModifierKeyEmitter.getInstance().resetKeyStatus(); + this._onDidHideContextMenu.fire(); }); const menu = this.createMenu(delegate, actions, onHide); @@ -120,6 +124,8 @@ class NativeContextMenuService extends Disposable implements IContextMenuService y: Math.floor(y), positioningItem: delegate.autoSelectFirstItem ? 0 : undefined, }, () => onHide()); + + this._onDidShowContextMenu.fire(); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 44c58d72571..39cf934c361 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -157,6 +157,9 @@ import 'vs/workbench/contrib/preferences/browser/preferencesSearch'; // Performance import 'vs/workbench/contrib/performance/browser/performance.contribution'; +// Context Menus +import 'vs/workbench/contrib/contextmenu/browser/contextmenu.contribution'; + // Notebook import 'vs/workbench/contrib/notebook/browser/notebook.contribution'; From 00de6a5f2fee42479cefa58a0effc2cee0ae4763 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 8 Nov 2021 10:00:36 -0500 Subject: [PATCH 053/375] Update telemetry module --- extensions/git/package.json | 2 +- extensions/git/yarn.lock | 8 ++++---- extensions/github-authentication/package.json | 2 +- extensions/github-authentication/yarn.lock | 8 ++++---- extensions/html-language-features/package.json | 2 +- extensions/html-language-features/yarn.lock | 8 ++++---- extensions/image-preview/package.json | 2 +- extensions/image-preview/yarn.lock | 8 ++++---- extensions/json-language-features/package.json | 2 +- extensions/json-language-features/yarn.lock | 8 ++++---- extensions/markdown-language-features/package.json | 2 +- extensions/markdown-language-features/yarn.lock | 8 ++++---- extensions/microsoft-authentication/package.json | 2 +- extensions/microsoft-authentication/yarn.lock | 8 ++++---- extensions/simple-browser/package.json | 2 +- extensions/simple-browser/yarn.lock | 8 ++++---- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 8 ++++---- 18 files changed, 45 insertions(+), 45 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index a5e84c7b7e1..48828c4e262 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2406,7 +2406,7 @@ "file-type": "^7.2.0", "iconv-lite-umd": "0.6.8", "jschardet": "3.0.0", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^4.0.0", "vscode-uri": "^2.0.0", "which": "^1.3.0" diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 93d14932c08..36a12fb119e 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -61,10 +61,10 @@ jschardet@3.0.0: resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^4.0.0: version "4.0.0" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 4bfa241496a..45c6a4b5175 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -61,7 +61,7 @@ "dependencies": { "node-fetch": "2.6.1", "uuid": "8.1.0", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0", "vscode-tas-client": "^0.1.22" }, diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index f2e97bceab1..a272bf6ec8e 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -92,10 +92,10 @@ uuid@8.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 0b1f1957ef9..ddaf33bcb65 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -256,7 +256,7 @@ ] }, "dependencies": { - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" }, diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 38b0ae6efff..20d1c9c9da4 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -46,10 +46,10 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-jsonrpc@6.0.0: version "6.0.0" diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index de4f79d4420..c9aa3d23a85 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -80,7 +80,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0" }, "repository": { diff --git a/extensions/image-preview/yarn.lock b/extensions/image-preview/yarn.lock index cab716b8031..b256b61ab95 100644 --- a/extensions/image-preview/yarn.lock +++ b/extensions/image-preview/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index b92282c62b9..91dcbb8d554 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -135,7 +135,7 @@ }, "dependencies": { "request-light": "^0.5.4", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" }, diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 4ae959a91a7..2a4755d6816 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -51,10 +51,10 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-jsonrpc@6.0.0: version "6.0.0" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 94d9e4f3ae5..ba2157fde37 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -357,7 +357,7 @@ "markdown-it": "^12.2.0", "markdown-it-front-matter": "^0.2.1", "morphdom": "^2.6.1", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0" }, "devDependencies": { diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 1200fc4c7d5..e2bb0ca59b6 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -117,10 +117,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 2272f946e67..ef97ae07a9b 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -58,7 +58,7 @@ "sha.js": "2.4.11", "stream": "0.0.2", "uuid": "^8.2.0", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0" }, "repository": { diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index b18b7e61e93..0d7e5ededf9 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -147,10 +147,10 @@ uuid@^8.2.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index c1382be241f..a1b2560bbc9 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -66,7 +66,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0" }, "devDependencies": { diff --git a/extensions/simple-browser/yarn.lock b/extensions/simple-browser/yarn.lock index 66748309e70..97c9ef794c2 100644 --- a/extensions/simple-browser/yarn.lock +++ b/extensions/simple-browser/yarn.lock @@ -12,10 +12,10 @@ vscode-codicons@^0.0.14: resolved "https://registry.yarnpkg.com/vscode-codicons/-/vscode-codicons-0.0.14.tgz#e0d05418e2e195564ff6f6a2199d70415911c18f" integrity sha512-6CEH5KT9ct5WMw7n5dlX7rB8ya4CUI2FSq1Wk36XaW+c5RglFtAanUV0T+gvZVVFhl/WxfjTvFHq06Hz9c1SLA== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 6e17079b171..934456be2f6 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -33,7 +33,7 @@ "dependencies": { "jsonc-parser": "^2.2.1", "semver": "5.5.1", - "vscode-extension-telemetry": "0.4.2", + "vscode-extension-telemetry": "0.4.3", "vscode-nls": "^5.0.0" }, "devDependencies": { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 80279864187..464c24c7de0 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -22,10 +22,10 @@ semver@5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -vscode-extension-telemetry@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5" - integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw== +vscode-extension-telemetry@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.3.tgz#ea389b3d14b65d4fd5a6cf3760b3155e930193f2" + integrity sha512-opiIFOaAwyfACYMXByDqFMAlJ2iFMJR65/vIogJ960aLZWp9zaMdwY9CsY02EOYjHxPpjI7QeOQM3sYCb3xtJg== vscode-nls@^5.0.0: version "5.0.0" From 15bff5b95ea13a23b7982a422d6bae103de4ecc5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 8 Nov 2021 10:12:33 -0500 Subject: [PATCH 054/375] Flatten out properties or measurements --- .../workbench/services/telemetry/browser/telemetryService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 4d08912f92f..e01a9a7cf93 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -69,6 +69,10 @@ class WebAppInsightsAppender implements ITelemetryAppender { data = validateTelemetryData(data); + // Web does not expect properties and measurements so we must + // spread them out. This is different from desktop which expects them + data = { ...data.properties, ...data.measurements }; + // undefined assertion is ok since above two if statements cover both cases this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data); } From 98f5bf9b9bea2fe075d3b07fbf0edf5b3576e08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Nov 2021 16:53:08 +0100 Subject: [PATCH 055/375] feat(scm): :sparkles: introduce `scm.diffDecorationsIgnoreTrimWhitespace` Closes: #130956 --- .../contrib/scm/browser/dirtydiffDecorator.ts | 13 ++++++++++++- .../contrib/scm/browser/scm.contribution.ts | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 03a9183ba42..8f2cd3f4a7a 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -1087,12 +1087,18 @@ export class DirtyDiffModel extends Disposable { textFileModel: IResolvedTextFileEditorModel, @ISCMService private readonly scmService: ISCMService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); this._model = textFileModel; this._register(textFileModel.textEditorModel.onDidChangeContent(() => this.triggerDiff())); + this._register( + Event.filter(configurationService.onDidChangeConfiguration, + e => e.affectsConfiguration('scm.diffDecorationsIgnoreTrimWhitespace') || e.affectsConfiguration('diffEditor.ignoreTrimWhitespace') + )(this.triggerDiff, this) + ); this._register(scmService.onDidAddRepository(this.onDidAddRepository, this)); scmService.repositories.forEach(r => this.onDidAddRepository(r)); @@ -1163,7 +1169,12 @@ export class DirtyDiffModel extends Disposable { return Promise.resolve([]); // Files too large } - return this.editorWorkerService.computeDirtyDiff(originalURI, this._model.resource, false); + const ignoreTrimWhitespaceSetting = this.configurationService.getValue<'true' | 'false' | 'inherit'>('scm.diffDecorationsIgnoreTrimWhitespace'); + const ignoreTrimWhitespace = ignoreTrimWhitespaceSetting === 'inherit' + ? this.configurationService.getValue('diffEditor.ignoreTrimWhitespace') + : ignoreTrimWhitespaceSetting !== 'false'; + + return this.editorWorkerService.computeDirtyDiff(originalURI, this._model.resource, ignoreTrimWhitespace); }); } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 41385f691b3..8728eff160a 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -146,6 +146,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('scm.diffDecorationsGutterAction', "Controls the behavior of Source Control diff gutter decorations."), default: 'diff' }, + 'scm.diffDecorationsIgnoreTrimWhitespace': { + type: 'string', + enum: ['true', 'false', 'inherit'], + enumDescriptions: [ + localize('scm.diffDecorationsIgnoreTrimWhitespace.true', "Ignore leading and trailing whitespace."), + localize('scm.diffDecorationsIgnoreTrimWhitespace.false', "Do not ignore leading and trailing whitespace."), + localize('scm.diffDecorationsIgnoreTrimWhitespace.inherit', "Inherit from `diffEditor.ignoreTrimWhitespace`.") + ], + description: localize('diffDecorationsIgnoreTrimWhitespace', "Controls whether leading and trailing whitespace is ignored in Source Control diff gutter decorations."), + default: 'false' + }, 'scm.alwaysShowActions': { type: 'boolean', description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."), From d6cc630086c29e7015e21e714e0331c0f5064fec Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 8 Nov 2021 17:36:14 +0100 Subject: [PATCH 056/375] Fixes bug when normalizing positions around injected text. --- src/vs/editor/common/viewModel/viewModel.ts | 4 +- .../common/viewModel/lineBreakData.test.ts | 40 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 7876b615979..313e48af997 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -270,7 +270,7 @@ export class LineBreakData { public normalizeOutputPosition(outputLineIndex: number, outputOffset: number, affinity: PositionAffinity): OutputPosition { if (this.injectionOffsets !== null) { - const offsetInInputWithInjections = this.outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset + this.wrappedTextIndentLength); + const offsetInInputWithInjections = this.outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset); const normalizedOffsetInUnwrappedLine = this.normalizeOffsetInInputWithInjectionsAroundInjections(offsetInInputWithInjections, affinity); if (normalizedOffsetInUnwrappedLine !== offsetInInputWithInjections) { // injected text caused a change @@ -295,7 +295,7 @@ export class LineBreakData { private outputPositionToOffsetInInputWithInjections(outputLineIndex: number, outputOffset: number): number { if (outputLineIndex > 0) { - outputOffset -= this.wrappedTextIndentLength; + outputOffset = Math.max(0, outputOffset - this.wrappedTextIndentLength); } const result = (outputLineIndex > 0 ? this.breakOffsets[outputLineIndex - 1] : 0) + outputOffset; return result; diff --git a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts index 286c6ef8e04..3606dc9cce9 100644 --- a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts +++ b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts @@ -44,7 +44,7 @@ suite('Editor ViewModel - LineBreakData', () => { } suite('Injected Text 1', () => { - const data = new LineBreakData([2, 3, 10], mapTextToInjectedTextOptions(['1', '22', '333']), [10, 100], [], 0); + const data = new LineBreakData([2, 3, 10], mapTextToInjectedTextOptions(['1', '22', '333']), [10, 100], [], 10); test('getInputOffsetOfOutputPosition', () => { // For every view model position, what is the model position? @@ -73,6 +73,44 @@ suite('Editor ViewModel - LineBreakData', () => { test('getInputOffsetOfOutputPosition is inverse of getOutputPositionOfInputOffset', () => { testInverse(data); }); + + + test('normalization', () => { + assert.deepStrictEqual( + sequence(25) + .map((v) => + data.normalizeOutputPosition(1, v, PositionAffinity.Right) + ) + .map((s) => s.toString()), + [ + '1:0', + '1:1', + '1:2', + '1:3', + '1:4', + '1:5', + '1:6', + '1:7', + '1:8', + '1:9', + '1:10', + '1:11', + '1:12', + '1:16', + '1:16', + '1:16', + '1:16', + '1:17', + '1:18', + '1:19', + '1:20', + '1:21', + '1:22', + '1:23', + '1:24', + ] + ); + }); }); suite('Injected Text 2', () => { From a1de3ce2a5c029b6bc68cd0f1cf1403023e5b269 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Nov 2021 16:59:18 +0100 Subject: [PATCH 057/375] introduce logger service in ext host and use it to create log service --- src/vs/platform/log/common/fileLog.ts | 37 +++------- src/vs/platform/log/common/log.ts | 62 +++++++++------- src/vs/platform/log/common/logIpc.ts | 12 +--- .../api/browser/mainThreadLogService.ts | 50 ++++++------- .../workbench/api/common/extHost.api.impl.ts | 7 +- .../api/common/extHost.common.services.ts | 5 ++ .../workbench/api/common/extHost.protocol.ts | 13 ++-- .../workbench/api/common/extHostLogService.ts | 20 ++++++ .../api/common/extHostLoggerService.ts | 70 +++++++++++++++++++ .../api/node/extHost.node.services.ts | 6 +- .../workbench/api/node/extHostLogService.ts | 25 ------- .../api/node/extHostLoggerService.ts | 26 +++++++ .../api/worker/extHost.worker.services.ts | 3 - .../workbench/api/worker/extHostLogService.ts | 70 ------------------- 14 files changed, 205 insertions(+), 201 deletions(-) create mode 100644 src/vs/workbench/api/common/extHostLogService.ts create mode 100644 src/vs/workbench/api/common/extHostLoggerService.ts delete mode 100644 src/vs/workbench/api/node/extHostLogService.ts create mode 100644 src/vs/workbench/api/node/extHostLoggerService.ts delete mode 100644 src/vs/workbench/api/worker/extHostLogService.ts diff --git a/src/vs/platform/log/common/fileLog.ts b/src/vs/platform/log/common/fileLog.ts index b760f25fa30..97e5badf0c5 100644 --- a/src/vs/platform/log/common/fileLog.ts +++ b/src/vs/platform/log/common/fileLog.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ByteSize, FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -import { AbstractLogger, AbstractLoggerService, ILogger, ILoggerOptions, ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { AbstractLogger, AbstractLoggerService, format, ILogger, ILoggerOptions, ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log'; const MAX_FILE_SIZE = 5 * ByteSize.MB; @@ -35,25 +35,25 @@ export class FileLogger extends AbstractLogger implements ILogger { trace(): void { if (this.getLevel() <= LogLevel.Trace) { - this._log(LogLevel.Trace, this.format(arguments)); + this._log(LogLevel.Trace, format(arguments)); } } debug(): void { if (this.getLevel() <= LogLevel.Debug) { - this._log(LogLevel.Debug, this.format(arguments)); + this._log(LogLevel.Debug, format(arguments)); } } info(): void { if (this.getLevel() <= LogLevel.Info) { - this._log(LogLevel.Info, this.format(arguments)); + this._log(LogLevel.Info, format(arguments)); } } warn(): void { if (this.getLevel() <= LogLevel.Warning) { - this._log(LogLevel.Warning, this.format(arguments)); + this._log(LogLevel.Warning, format(arguments)); } } @@ -64,26 +64,22 @@ export class FileLogger extends AbstractLogger implements ILogger { if (arg instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; array[0] = arg.stack; - this._log(LogLevel.Error, this.format(array)); + this._log(LogLevel.Error, format(array)); } else { - this._log(LogLevel.Error, this.format(arguments)); + this._log(LogLevel.Error, format(arguments)); } } } critical(): void { if (this.getLevel() <= LogLevel.Critical) { - this._log(LogLevel.Critical, this.format(arguments)); + this._log(LogLevel.Critical, format(arguments)); } } flush(): void { } - log(level: LogLevel, args: any[]): void { - this._log(level, this.format(args)); - } - private async initialize(): Promise { try { await this.fileService.createFile(this.resource); @@ -144,23 +140,6 @@ export class FileLogger extends AbstractLogger implements ILogger { return ''; } - private format(args: any): string { - let result = ''; - - for (let i = 0; i < args.length; i++) { - let a = args[i]; - - if (typeof a === 'object') { - try { - a = JSON.stringify(a); - } catch (e) { } - } - - result += (i > 0 ? ' ' : '') + a; - } - - return result; - } } export class FileLoggerService extends AbstractLoggerService implements ILoggerService { diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 3fcc7682e08..a8a6ea1c7b5 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -48,6 +48,36 @@ export interface ILogger extends IDisposable { flush(): void; } +export function log(logger: ILogger, level: LogLevel, message: string): void { + switch (level) { + case LogLevel.Trace: logger.trace(message); break; + case LogLevel.Debug: logger.debug(message); break; + case LogLevel.Info: logger.info(message); break; + case LogLevel.Warning: logger.warn(message); break; + case LogLevel.Error: logger.error(message); break; + case LogLevel.Critical: logger.critical(message); break; + default: throw new Error('Invalid log level'); + } +} + +export function format(args: any): string { + let result = ''; + + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + if (typeof a === 'object') { + try { + a = JSON.stringify(a); + } catch (e) { } + } + + result += (i > 0 ? ' ' : '') + a; + } + + return result; +} + export interface ILogService extends ILogger { readonly _serviceBrand: undefined; } @@ -122,25 +152,25 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL trace(message: string, ...args: any[]): void { if (this.checkLogLevel(LogLevel.Trace)) { - this.log(LogLevel.Trace, this.format([message, ...args])); + this.log(LogLevel.Trace, format([message, ...args])); } } debug(message: string, ...args: any[]): void { if (this.checkLogLevel(LogLevel.Debug)) { - this.log(LogLevel.Debug, this.format([message, ...args])); + this.log(LogLevel.Debug, format([message, ...args])); } } info(message: string, ...args: any[]): void { if (this.checkLogLevel(LogLevel.Info)) { - this.log(LogLevel.Info, this.format([message, ...args])); + this.log(LogLevel.Info, format([message, ...args])); } } warn(message: string, ...args: any[]): void { if (this.checkLogLevel(LogLevel.Warning)) { - this.log(LogLevel.Warning, this.format([message, ...args])); + this.log(LogLevel.Warning, format([message, ...args])); } } @@ -150,38 +180,20 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL if (message instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; array[0] = message.stack; - this.log(LogLevel.Error, this.format(array)); + this.log(LogLevel.Error, format(array)); } else { - this.log(LogLevel.Error, this.format([message, ...args])); + this.log(LogLevel.Error, format([message, ...args])); } } } critical(message: string | Error, ...args: any[]): void { if (this.checkLogLevel(LogLevel.Critical)) { - this.log(LogLevel.Critical, this.format([message, ...args])); + this.log(LogLevel.Critical, format([message, ...args])); } } flush(): void { } - - private format(args: any): string { - let result = ''; - - for (let i = 0; i < args.length; i++) { - let a = args[i]; - - if (typeof a === 'object') { - try { - a = JSON.stringify(a); - } catch (e) { } - } - - result += (i > 0 ? ' ' : '') + a; - } - - return result; - } } diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index cbefc550ccc..8783df9cbb7 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { AbstractLoggerService, AbstractMessageLogger, AdapterLogger, ILogger, ILoggerOptions, ILoggerService, ILogService, LogLevel, LogService } from 'vs/platform/log/common/log'; +import { AbstractLoggerService, AbstractMessageLogger, AdapterLogger, ILogger, ILoggerOptions, ILoggerService, ILogService, log, LogLevel, LogService } from 'vs/platform/log/common/log'; export class LogLevelChannel implements IServerChannel { @@ -100,15 +100,7 @@ export class LoggerChannel implements IServerChannel { throw new Error('Create the logger before logging'); } for (const [level, message] of messages) { - switch (level) { - case LogLevel.Trace: logger.trace(message); break; - case LogLevel.Debug: logger.debug(message); break; - case LogLevel.Info: logger.info(message); break; - case LogLevel.Warning: logger.warn(message); break; - case LogLevel.Error: logger.error(message); break; - case LogLevel.Critical: logger.critical(message); break; - default: throw new Error('Invalid log level'); - } + log(logger, level, message); } } } diff --git a/src/vs/workbench/api/browser/mainThreadLogService.ts b/src/vs/workbench/api/browser/mainThreadLogService.ts index 1b7a39d9d63..515b60645b4 100644 --- a/src/vs/workbench/api/browser/mainThreadLogService.ts +++ b/src/vs/workbench/api/browser/mainThreadLogService.ts @@ -4,48 +4,44 @@ *--------------------------------------------------------------------------------------------*/ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerOptions, ILoggerService, ILogService, log, LogLevel } from 'vs/platform/log/common/log'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtHostContext, ExtHostContext, MainThreadLogShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostContext, ExtHostContext, MainThreadLoggerShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { UriComponents, URI } from 'vs/base/common/uri'; -import { FileLogger } from 'vs/platform/log/common/fileLog'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { basename } from 'vs/base/common/path'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -@extHostNamedCustomer(MainContext.MainThreadLog) -export class MainThreadLogService implements MainThreadLogShape { +@extHostNamedCustomer(MainContext.MainThreadLogger) +export class MainThreadLoggerService implements MainThreadLoggerShape { - private readonly _loggers = new Map(); private readonly _logListener: IDisposable; constructor( extHostContext: IExtHostContext, - @ILogService private readonly _logService: ILogService, - @IInstantiationService private readonly _instaService: IInstantiationService, + @ILogService logService: ILogService, + @ILoggerService private readonly _loggerService: ILoggerService, ) { - const proxy = extHostContext.getProxy(ExtHostContext.ExtHostLogService); - this._logListener = _logService.onDidChangeLogLevel(level => { - proxy.$setLevel(level); - this._loggers.forEach(value => value.setLevel(level)); - }); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostLogLevelServiceShape); + this._logListener = logService.onDidChangeLogLevel(level => proxy.$setLevel(level)); + } + + $log(file: UriComponents, messages: [LogLevel, string][]): void { + const logger = this._loggerService.getLogger(URI.revive(file)); + if (!logger) { + throw new Error('Create the logger before logging'); + } + for (const [level, message] of messages) { + log(logger, level, message); + } + } + + async $createLogger(file: UriComponents, options?: ILoggerOptions): Promise { + this._loggerService.createLogger(URI.revive(file), options); } dispose(): void { this._logListener.dispose(); - this._loggers.forEach(value => value.dispose()); - this._loggers.clear(); - } - - $log(file: UriComponents, level: LogLevel, message: any[]): void { - const uri = URI.revive(file); - let logger = this._loggers.get(uri.toString()); - if (!logger) { - logger = this._instaService.createInstance(FileLogger, basename(file.path), URI.revive(file), this._logService.getLevel(), false); - this._loggers.set(uri.toString(), logger); - } - logger.log(level, message); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6418034b29b..e631f08178c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -13,7 +13,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext, ExtHostLogServiceShape, UIKind, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext, UIKind, CandidatePortSource, ExtHostLogLevelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -60,7 +60,7 @@ import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations' import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -113,6 +113,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const rpcProtocol = accessor.get(IExtHostRpcService); const extHostStorage = accessor.get(IExtHostStorage); const extensionStoragePaths = accessor.get(IExtensionStoragePaths); + const extHostLoggerService = accessor.get(ILoggerService); const extHostLogService = accessor.get(ILogService); const extHostTunnelService = accessor.get(IExtHostTunnelService); const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); @@ -122,7 +123,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); - rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); + rpcProtocol.set(ExtHostContext.ExtHostLogLevelServiceShape, extHostLoggerService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index c0f4a70e196..e8ec83f8d66 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -23,7 +23,12 @@ import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/ import { IExtHostSecretState, ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import { ExtHostEditorTabs, IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; +import { ExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerService'; +import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; +import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; +registerSingleton(ILoggerService, ExtHostLoggerService); +registerSingleton(ILogService, ExtHostLogService); registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); registerSingleton(IExtHostCommands, ExtHostCommands); registerSingleton(IExtHostConfiguration, ExtHostConfiguration); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 17738393365..5956c737a03 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -31,7 +31,7 @@ import { ConfigurationScope } from 'vs/platform/configuration/common/configurati import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; -import { LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerOptions, LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import * as quickInput from 'vs/platform/quickinput/common/quickInput'; @@ -1895,12 +1895,13 @@ export interface ExtHostWindowShape { $onDidChangeWindowFocus(value: boolean): void; } -export interface ExtHostLogServiceShape { +export interface ExtHostLogLevelServiceShape { $setLevel(level: LogLevel): void; } -export interface MainThreadLogShape { - $log(file: UriComponents, level: LogLevel, args: any[]): void; +export interface MainThreadLoggerShape { + $log(file: UriComponents, messages: [LogLevel, string][]): void; + $createLogger(file: UriComponents, options?: ILoggerOptions): Promise; } export interface ExtHostOutputServiceShape { @@ -2213,7 +2214,7 @@ export const MainContext = { MainThreadKeytar: createMainId('MainThreadKeytar'), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures'), MainThreadLanguages: createMainId('MainThreadLanguages'), - MainThreadLog: createMainId('MainThread'), + MainThreadLogger: createMainId('MainThreadLogger'), MainThreadMessageService: createMainId('MainThreadMessageService'), MainThreadOutputService: createMainId('MainThreadOutputService'), MainThreadProgress: createMainId('MainThreadProgress'), @@ -2268,7 +2269,7 @@ export const ExtHostContext = { ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), ExtHostExtensionService: createExtId('ExtHostExtensionService'), - ExtHostLogService: createExtId('ExtHostLogService'), + ExtHostLogLevelServiceShape: createExtId('ExtHostLogLevelServiceShape'), ExtHostTerminalService: createExtId('ExtHostTerminalService'), ExtHostSCM: createExtId('ExtHostSCM'), ExtHostSearch: createExtId('ExtHostSearch'), diff --git a/src/vs/workbench/api/common/extHostLogService.ts b/src/vs/workbench/api/common/extHostLogService.ts new file mode 100644 index 00000000000..553a4557e5a --- /dev/null +++ b/src/vs/workbench/api/common/extHostLogService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILoggerService, LogService } from 'vs/platform/log/common/log'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; + +export class ExtHostLogService extends LogService { + + declare readonly _serviceBrand: undefined; + + constructor( + @ILoggerService loggerService: ILoggerService, + @IExtHostInitDataService initData: IExtHostInitDataService, + ) { + super(loggerService.createLogger(initData.logFile)); + } + +} diff --git a/src/vs/workbench/api/common/extHostLoggerService.ts b/src/vs/workbench/api/common/extHostLoggerService.ts new file mode 100644 index 00000000000..6501df99bf0 --- /dev/null +++ b/src/vs/workbench/api/common/extHostLoggerService.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogger, ILoggerOptions, AbstractMessageLogger, LogLevel, AbstractLoggerService } from 'vs/platform/log/common/log'; +import { MainThreadLoggerShape, MainContext, ExtHostLogLevelServiceShape as ExtHostLogLevelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { URI } from 'vs/base/common/uri'; +import { Emitter } from 'vs/base/common/event'; + +export class ExtHostLoggerService extends AbstractLoggerService implements ExtHostLogLevelServiceShape { + + declare readonly _serviceBrand: undefined; + private readonly _onDidChangeLogLevel: Emitter; + private readonly _proxy: MainThreadLoggerShape; + + constructor( + @IExtHostRpcService rpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + ) { + const emitter = new Emitter(); + super(initData.logLevel, emitter.event); + this._proxy = rpc.getProxy(MainContext.MainThreadLogger); + this._onDidChangeLogLevel = this._register(emitter); + } + + $setLevel(level: LogLevel): void { + this._onDidChangeLogLevel.fire(level); + } + + protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { + return new Logger(this._proxy, resource, logLevel, options); + } +} + +class Logger extends AbstractMessageLogger { + + private isLoggerCreated: boolean = false; + private buffer: [LogLevel, string][] = []; + + constructor( + private readonly proxy: MainThreadLoggerShape, + private readonly file: URI, + logLevel: LogLevel, + loggerOptions?: ILoggerOptions, + ) { + super(loggerOptions?.always); + this.setLevel(logLevel); + this.proxy.$createLogger(file, loggerOptions) + .then(() => { + this.doLog(this.buffer); + this.isLoggerCreated = true; + }); + } + + protected log(level: LogLevel, message: string) { + const messages: [LogLevel, string][] = [[level, message]]; + if (this.isLoggerCreated) { + this.doLog(messages); + } else { + this.buffer.push(...messages); + } + } + + private doLog(messages: [LogLevel, string][]) { + this.proxy.$log(this.file, messages); + } +} diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts index 6016baa59b8..021500c5ce0 100644 --- a/src/vs/workbench/api/node/extHost.node.services.ts +++ b/src/vs/workbench/api/node/extHost.node.services.ts @@ -10,7 +10,6 @@ import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; @@ -19,9 +18,10 @@ import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; +import { ExtHostLoggerService } from 'vs/workbench/api/node/extHostLoggerService'; +import { ILoggerService } from 'vs/platform/log/common/log'; // ######################################################################### // ### ### @@ -30,7 +30,7 @@ import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths // ######################################################################### registerSingleton(IExtHostExtensionService, ExtHostExtensionService); -registerSingleton(ILogService, ExtHostLogService); +registerSingleton(ILoggerService, ExtHostLoggerService); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostDebugService, ExtHostDebugService); diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts deleted file mode 100644 index fb05723b9eb..00000000000 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ /dev/null @@ -1,25 +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 { ILogService, LogService, LogLevel } from 'vs/platform/log/common/log'; -import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { Schemas } from 'vs/base/common/network'; -import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; - -export class ExtHostLogService extends LogService implements ILogService, ExtHostLogServiceShape { - - constructor( - @IExtHostInitDataService initData: IExtHostInitDataService, - ) { - if (initData.logFile.scheme !== Schemas.file) { throw new Error('Only file-logging supported'); } - super(new SpdLogLogger(ExtensionHostLogFileName, initData.logFile.fsPath, true, initData.logLevel)); - } - - $setLevel(level: LogLevel): void { - this.setLevel(level); - } -} diff --git a/src/vs/workbench/api/node/extHostLoggerService.ts b/src/vs/workbench/api/node/extHostLoggerService.ts new file mode 100644 index 00000000000..5a4a0800476 --- /dev/null +++ b/src/vs/workbench/api/node/extHostLoggerService.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogger, ILoggerOptions, LogLevel } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; +import { ExtHostLoggerService as BaseExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerService'; +import { Schemas } from 'vs/base/common/network'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { generateUuid } from 'vs/base/common/uuid'; + +export class ExtHostLoggerService extends BaseExtHostLoggerService { + + protected override doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { + if (resource.scheme === Schemas.file) { + const logger = new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, logLevel); + if (options?.donotUseFormatters) { + (logger).clearFormatters(); + } + return logger; + } + return super.doCreateLogger(resource, logLevel, options); + } + +} diff --git a/src/vs/workbench/api/worker/extHost.worker.services.ts b/src/vs/workbench/api/worker/extHost.worker.services.ts index 7f3e969b132..7f152d32669 100644 --- a/src/vs/workbench/api/worker/extHost.worker.services.ts +++ b/src/vs/workbench/api/worker/extHost.worker.services.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtensionStoragePaths, IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService'; -import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; // ######################################################################### // ### ### @@ -17,5 +15,4 @@ import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; // ######################################################################### registerSingleton(IExtHostExtensionService, ExtHostExtensionService); -registerSingleton(ILogService, ExtHostLogService); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); diff --git a/src/vs/workbench/api/worker/extHostLogService.ts b/src/vs/workbench/api/worker/extHostLogService.ts deleted file mode 100644 index 7cf2d35a30e..00000000000 --- a/src/vs/workbench/api/worker/extHostLogService.ts +++ /dev/null @@ -1,70 +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 { ILogService, LogLevel, AbstractLogger } from 'vs/platform/log/common/log'; -import { ExtHostLogServiceShape, MainThreadLogShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { UriComponents } from 'vs/base/common/uri'; - -export class ExtHostLogService extends AbstractLogger implements ILogService, ExtHostLogServiceShape { - - declare readonly _serviceBrand: undefined; - - private readonly _proxy: MainThreadLogShape; - private readonly _logFile: UriComponents; - - constructor( - @IExtHostRpcService rpc: IExtHostRpcService, - @IExtHostInitDataService initData: IExtHostInitDataService, - ) { - super(); - this._proxy = rpc.getProxy(MainContext.MainThreadLog); - this._logFile = initData.logFile.toJSON(); - this.setLevel(initData.logLevel); - } - - $setLevel(level: LogLevel): void { - this.setLevel(level); - } - - trace(_message: string, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Trace) { - this._proxy.$log(this._logFile, LogLevel.Trace, Array.from(arguments)); - } - } - - debug(_message: string, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Debug) { - this._proxy.$log(this._logFile, LogLevel.Debug, Array.from(arguments)); - } - } - - info(_message: string, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Info) { - this._proxy.$log(this._logFile, LogLevel.Info, Array.from(arguments)); - } - } - - warn(_message: string, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Warning) { - this._proxy.$log(this._logFile, LogLevel.Warning, Array.from(arguments)); - } - } - - error(_message: string | Error, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Error) { - this._proxy.$log(this._logFile, LogLevel.Error, Array.from(arguments)); - } - } - - critical(_message: string | Error, ..._args: any[]): void { - if (this.getLevel() <= LogLevel.Critical) { - this._proxy.$log(this._logFile, LogLevel.Critical, Array.from(arguments)); - } - } - - flush(): void { } -} From 9e3b8c9e50284e73d83ac448a1ae156d5e13a17c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 19:23:41 +0100 Subject: [PATCH 058/375] web - properly ignore shutdown when expected --- .../lifecycle/browser/lifecycleService.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 599e82e764f..b72ed0df604 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -16,7 +16,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { private beforeUnloadListener: IDisposable | undefined = undefined; - private disableBeforeUnloadVeto = false; + private ignoreBeforeUnload = false; private didBeforeUnload = false; private didUnload = false; @@ -79,14 +79,14 @@ export class BrowserLifecycleService extends AbstractLifecycleService { private onBeforeUnload(event: BeforeUnloadEvent): void { - // Unload without veto support - if (this.disableBeforeUnloadVeto) { - this.logService.info('[lifecycle] onBeforeUnload triggered and handled without veto support'); + // Before unload ignored (once) + if (this.ignoreBeforeUnload) { + this.logService.info('[lifecycle] onBeforeUnload triggered but ignored once'); - this.doShutdown(); + this.ignoreBeforeUnload = false; } - // Unload with veto support + // Before unload with veto support else { this.logService.info('[lifecycle] onBeforeUnload triggered and handled with veto support'); @@ -108,13 +108,13 @@ export class BrowserLifecycleService extends AbstractLifecycleService { this.shutdownReason = reason; } - // Veto handling disabled for duration of callback + // Before unload handling ignored for duration of callback else { - this.disableBeforeUnloadVeto = true; + this.ignoreBeforeUnload = true; try { callback?.(); } finally { - this.disableBeforeUnloadVeto = false; + this.ignoreBeforeUnload = false; } } From b3c13934b1f9ec3e31d619eaa0ce94bd9bcc188f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 8 Nov 2021 10:24:29 -0800 Subject: [PATCH 059/375] use allow opener on non-Safari --- src/vs/workbench/browser/window.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 550a064ae2d..36de699c0a6 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -175,7 +175,9 @@ export class BrowserWindow extends Disposable { } } } else { - windowOpenNoOpener(href); + isAllowedOpener + ? windowOpenPopup(href) + : windowOpenNoOpener(href); } } From 419744928a3cb7cc5207ca46a6aef75bc0ac39b8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Nov 2021 19:35:22 +0100 Subject: [PATCH 060/375] editors - await closing of editor before opening to the side (fix #122363) (#136602) --- .../api/browser/mainThreadEditorTabs.ts | 2 +- .../api/browser/mainThreadEditors.ts | 3 ++- .../browser/parts/editor/editorActions.ts | 8 ++++--- .../browser/parts/editor/editorGroupView.ts | 4 ++-- .../editor/browser/editorResolverService.ts | 23 +++++++++++++------ .../editor/common/editorGroupsService.ts | 6 +++-- .../test/browser/editorGroupsService.test.ts | 15 ++++++++---- .../test/browser/workbenchTestServices.ts | 2 +- 8 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index a6fd0b055f2..8c78575c99f 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -280,7 +280,7 @@ export class MainThreadEditorTabs { if (!editor) { return; } - return group.closeEditor(editor); + await group.closeEditor(editor); } //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index d458ccae1ac..99fe9d8e332 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -177,7 +177,8 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { const editorPanes = this._editorService.visibleEditorPanes; for (let editorPane of editorPanes) { if (mainThreadEditor.matches(editorPane)) { - return editorPane.group.closeEditor(editorPane.input); + await editorPane.group.closeEditor(editorPane.input); + return; } } } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 3b221a22be1..5724e89dfc9 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -460,13 +460,15 @@ export class CloseOneEditorAction extends Action { if (typeof editorIndex === 'number') { const editorAtIndex = group.getEditorByIndex(editorIndex); if (editorAtIndex) { - return group.closeEditor(editorAtIndex); + await group.closeEditor(editorAtIndex); + return; } } // Otherwise close active editor in group if (group.activeEditor) { - return group.closeEditor(group.activeEditor); + await group.closeEditor(group.activeEditor); + return; } } } @@ -501,7 +503,7 @@ export class RevertAndCloseEditorAction extends Action { await this.editorService.revert({ editor, groupId: group.id }, { soft: true }); } - return group.closeEditor(editor); + await group.closeEditor(editor); } } } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a1e60ea3fb3..411f50891eb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1372,8 +1372,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeEditor() - async closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise { - await this.doCloseEditorWithDirtyHandling(editor, options); + async closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise { + return this.doCloseEditorWithDirtyHandling(editor, options); } private async doCloseEditorWithDirtyHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise { diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 6a3714379a0..fe5f390dfb4 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -463,20 +463,23 @@ export class EditorResolverService extends Disposable implements IEditorResolver // If the editor states it can only be opened once per resource we must close all existing ones first const singleEditorPerResource = typeof selectedEditor.options?.singlePerResource === 'function' ? selectedEditor.options.singlePerResource() : selectedEditor.options?.singlePerResource; if (singleEditorPerResource) { - this.closeExistingEditorsForResource(resource, selectedEditor.editorInfo.id, group); + const closed = await this.closeExistingEditorsForResource(resource, selectedEditor.editorInfo.id, group); + if (!closed) { + return undefined; + } } return { editor: input, options }; } - private closeExistingEditorsForResource( + private async closeExistingEditorsForResource( resource: URI, viewType: string, targetGroup: IEditorGroup, - ): void { + ): Promise { const editorInfoForResource = this.findExistingEditorsForResource(resource, viewType); if (!editorInfoForResource.length) { - return; + return true; } const editorToUse = editorInfoForResource[0]; @@ -484,14 +487,20 @@ export class EditorResolverService extends Disposable implements IEditorResolver // Replace all other editors for (const { editor, group } of editorInfoForResource) { if (editor !== editorToUse.editor) { - group.closeEditor(editor); + const closed = await group.closeEditor(editor); + if (!closed) { + return false; + } } } if (targetGroup.id !== editorToUse.group.id) { - editorToUse.group.closeEditor(editorToUse.editor); + const closed = await editorToUse.group.closeEditor(editorToUse.editor); + if (!closed) { + return false; + } } - return; + return true; } /** diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 4aa219be99b..a0ec7b7eb71 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -654,9 +654,11 @@ export interface IEditorGroup { * @param editor the editor to close, or the currently active editor * if unspecified. * - * @returns a promise when the editor is closed. + * @returns a promise when the editor is closed or not. If `true`, the editor + * is closed and if `false` there was a veto closing the editor, e.g. when it + * is dirty. */ - closeEditor(editor?: EditorInput, options?: ICloseEditorOptions): Promise; + closeEditor(editor?: EditorInput, options?: ICloseEditorOptions): Promise; /** * Closes specific editors in this group. This may trigger a confirmation dialog if diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index d668eded170..e4de03f00cf 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -490,7 +490,8 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.activeEditor, inputInactive); await group.openEditor(input); - await group.closeEditor(inputInactive); + const closed = await group.closeEditor(inputInactive); + assert.strictEqual(closed, true); assert.strictEqual(activeEditorChangeCounter, 3); assert.strictEqual(editorCloseCounter, 1); @@ -553,12 +554,14 @@ suite('EditorGroupsService', () => { await group.openEditor(input); accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); - await group.closeEditor(input); + let closed = await group.closeEditor(input); + assert.strictEqual(closed, false); assert.ok(!input.gotDisposed); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); - await group.closeEditor(input); + closed = await group.closeEditor(input); + assert.strictEqual(closed, true); assert.ok(input.gotDisposed); }); @@ -576,11 +579,13 @@ suite('EditorGroupsService', () => { await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); await rightGroup.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); - await rightGroup.closeEditor(input); + let closed = await rightGroup.closeEditor(input); + assert.strictEqual(closed, true); assert.ok(!input.gotDisposed); - await group.closeEditor(input); + closed = await group.closeEditor(input); + assert.strictEqual(closed, true); assert.ok(input.gotDisposed); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d78d69a0612..c655f96a87c 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -821,7 +821,7 @@ export class TestEditorGroupView implements IEditorGroupView { moveEditors(_editors: EditorInputWithOptions[], _target: IEditorGroup): void { } copyEditor(_editor: EditorInput, _target: IEditorGroup, _options?: IEditorOptions): void { } copyEditors(_editors: EditorInputWithOptions[], _target: IEditorGroup): void { } - async closeEditor(_editor?: EditorInput, options?: ICloseEditorOptions): Promise { } + async closeEditor(_editor?: EditorInput, options?: ICloseEditorOptions): Promise { return true; } async closeEditors(_editors: EditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise { } async closeAllEditors(options?: ICloseAllEditorsOptions): Promise { } async replaceEditors(_editors: IEditorReplacement[]): Promise { } From 472b3e5447c6fa1cc1323a5177509ce53fe80162 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 8 Nov 2021 19:52:24 +0100 Subject: [PATCH 061/375] Fixes LineBreakData unit test. --- .../common/viewModel/lineBreakData.test.ts | 113 ++++++++++++++++-- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts index 3606dc9cce9..0372cc1ad6c 100644 --- a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts +++ b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts @@ -32,7 +32,7 @@ suite('Editor ViewModel - LineBreakData', () => { } function getInputOffsets(data: LineBreakData, outputLineIdx: number): number[] { - return sequence(11).map(i => data.translateToInputOffset(outputLineIdx, i)); + return sequence(20).map(i => data.translateToInputOffset(outputLineIdx, i)); } function getOutputOffsets(data: LineBreakData, affinity: PositionAffinity): string[] { @@ -48,25 +48,94 @@ suite('Editor ViewModel - LineBreakData', () => { test('getInputOffsetOfOutputPosition', () => { // For every view model position, what is the model position? - assert.deepStrictEqual(getInputOffsets(data, 0), [0, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7]); - assert.deepStrictEqual(getInputOffsets(data, 1), [7, 8, 9, 10, 10, 10, 10, 11, 12, 13, 14]); + assert.deepStrictEqual(getInputOffsets(data, 0), ([0, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11, 12, 13])); + assert.deepStrictEqual(getInputOffsets(data, 1), ([7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 10, 10, 10, 10, 11, 12, 13])); }); test('getOutputPositionOfInputOffset', () => { data.translateToOutputPosition(20); assert.deepStrictEqual(getOutputOffsets(data, PositionAffinity.None), [ - '0:0', '0:1', '0:2', '0:4', '0:7', '0:8', '0:9', - '1:0', '1:1', '1:2', '1:3', '1:7', '1:8', '1:9', '1:10', '1:11', '1:12', '1:13', '1:14', '1:15', '1:16', '1:17', '1:18', '1:19', '1:20', + '0:0', + '0:1', + '0:2', + '0:4', + '0:7', + '0:8', + '0:9', + '1:10', + '1:11', + '1:12', + '1:13', + '1:17', + '1:18', + '1:19', + '1:20', + '1:21', + '1:22', + '1:23', + '1:24', + '1:25', + '1:26', + '1:27', + '1:28', + '1:29', + '1:30', ]); assert.deepStrictEqual(getOutputOffsets(data, PositionAffinity.Left), [ - '0:0', '0:1', '0:2', '0:4', '0:7', '0:8', '0:9', '0:10', - '1:1', '1:2', '1:3', '1:7', '1:8', '1:9', '1:10', '1:11', '1:12', '1:13', '1:14', '1:15', '1:16', '1:17', '1:18', '1:19', '1:20', + '0:0', + '0:1', + '0:2', + '0:4', + '0:7', + '0:8', + '0:9', + '0:10', + '1:11', + '1:12', + '1:13', + '1:17', + '1:18', + '1:19', + '1:20', + '1:21', + '1:22', + '1:23', + '1:24', + '1:25', + '1:26', + '1:27', + '1:28', + '1:29', + '1:30', ]); assert.deepStrictEqual(getOutputOffsets(data, PositionAffinity.Right), [ - '0:0', '0:1', '0:3', '0:6', '0:7', '0:8', '0:9', - '1:0', '1:1', '1:2', '1:6', '1:7', '1:8', '1:9', '1:10', '1:11', '1:12', '1:13', '1:14', '1:15', '1:16', '1:17', '1:18', '1:19', '1:20', + '0:0', + '0:1', + '0:3', + '0:6', + '0:7', + '0:8', + '0:9', + '1:10', + '1:11', + '1:12', + '1:16', + '1:17', + '1:18', + '1:19', + '1:20', + '1:21', + '1:22', + '1:23', + '1:24', + '1:25', + '1:26', + '1:27', + '1:28', + '1:29', + '1:30', ]); }); @@ -117,8 +186,17 @@ suite('Editor ViewModel - LineBreakData', () => { const data = new LineBreakData([2, 2, 6], mapTextToInjectedTextOptions(['1', '22', '333']), [10, 100], [], 0); test('getInputOffsetOfOutputPosition', () => { - assert.deepStrictEqual(getInputOffsets(data, 0), [0, 1, 2, 2, 2, 2, 3, 4, 5, 6, 6]); - assert.deepStrictEqual(getInputOffsets(data, 1), [6, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14]); + assert.deepStrictEqual( + getInputOffsets(data, 0), + [0, 1, 2, 2, 2, 2, 3, 4, 5, 6, 6, 6, 6, 7, 8, 9, 10, 11, 12, 13] + ); + assert.deepStrictEqual( + getInputOffsets(data, 1), + [ + 6, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, + ] + ); }); test('getInputOffsetOfOutputPosition is inverse of getOutputPositionOfInputOffset', () => { @@ -130,8 +208,17 @@ suite('Editor ViewModel - LineBreakData', () => { const data = new LineBreakData([2, 2, 7], mapTextToInjectedTextOptions(['1', '22', '333']), [10, 100], [], 0); test('getInputOffsetOfOutputPosition', () => { - assert.deepStrictEqual(getInputOffsets(data, 0), [0, 1, 2, 2, 2, 2, 3, 4, 5, 6, 7]); - assert.deepStrictEqual(getInputOffsets(data, 1), [7, 7, 7, 7, 8, 9, 10, 11, 12, 13, 14]); + assert.deepStrictEqual( + getInputOffsets(data, 0), + [0, 1, 2, 2, 2, 2, 3, 4, 5, 6, 7, 7, 7, 7, 8, 9, 10, 11, 12, 13] + ); + assert.deepStrictEqual( + getInputOffsets(data, 1), + [ + 7, 7, 7, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, + ] + ); }); test('getInputOffsetOfOutputPosition is inverse of getOutputPositionOfInputOffset', () => { From ecf994ecfa793df821c50b8d241b4b74daac839e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Nov 2021 21:15:15 +0100 Subject: [PATCH 062/375] - Use logger service to create output appender - Unify output channels and services in ext host - remove unused main output service methods --- .../api/browser/mainThreadOutputService.ts | 55 ++----- .../workbench/api/common/extHost.protocol.ts | 15 +- src/vs/workbench/api/common/extHostOutput.ts | 145 +++++++++++------- .../api/node/extHost.node.services.ts | 3 - .../api/node/extHostOutputService.ts | 122 --------------- 5 files changed, 102 insertions(+), 238 deletions(-) delete mode 100644 src/vs/workbench/api/node/extHostOutputService.ts diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 5078c34fce2..5e29097d76e 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -17,7 +17,6 @@ import { isNumber } from 'vs/base/common/types'; @extHostNamedCustomer(MainContext.MainThreadOutputService) export class MainThreadOutputService extends Disposable implements MainThreadOutputServiceShape { - private static _idPool = 1; private static _extensionIdPool = new Map(); private readonly _proxy: ExtHostOutputServiceShape; @@ -43,30 +42,17 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut setVisibleChannel(); } - public $register(label: string, log: boolean, file?: UriComponents, extensionId?: string): Promise { - let id: string; - if (extensionId) { - const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; - MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); - id = `extension-output-${extensionId}-#${idCounter}`; - } else { - id = `extension-output-#${(MainThreadOutputService._idPool++)}`; - } + public async $register(label: string, log: boolean, file: UriComponents, extensionId: string): Promise { + const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; + MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); + const id = `extension-output-${extensionId}-#${idCounter}`; - Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: file ? URI.revive(file) : undefined, log }); + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log }); this._register(toDisposable(() => this.$dispose(id))); - return Promise.resolve(id); + return id; } - public $append(channelId: string, value: string): Promise | undefined { - const channel = this._getChannel(channelId); - if (channel) { - channel.append(value); - } - return undefined; - } - - public $update(channelId: string, mode: OutputChannelUpdateMode, till?: number): Promise | undefined { + public async $update(channelId: string, mode: OutputChannelUpdateMode, till?: number): Promise { const channel = this._getChannel(channelId); if (channel) { if (mode === OutputChannelUpdateMode.Append) { @@ -75,50 +61,29 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut channel.update(mode, till); } } - return undefined; } - public $clear(channelId: string): Promise | undefined { - const channel = this._getChannel(channelId); - if (channel) { - channel.clear(); - } - return undefined; - } - - public $replace(channelId: string, value: string): Promise | undefined { - const channel = this._getChannel(channelId); - if (channel) { - channel.replace(value); - } - return undefined; - } - - public $reveal(channelId: string, preserveFocus: boolean): Promise | undefined { + public async $reveal(channelId: string, preserveFocus: boolean): Promise { const channel = this._getChannel(channelId); if (channel) { this._outputService.showChannel(channel.id, preserveFocus); } - return undefined; } - public $close(channelId: string): Promise | undefined { + public async $close(channelId: string): Promise { if (this._viewsService.isViewVisible(OUTPUT_VIEW_ID)) { const activeChannel = this._outputService.getActiveChannel(); if (activeChannel && channelId === activeChannel.id) { this._viewsService.closeView(OUTPUT_VIEW_ID); } } - - return undefined; } - public $dispose(channelId: string): Promise | undefined { + public async $dispose(channelId: string): Promise { const channel = this._getChannel(channelId); if (channel) { channel.dispose(); } - return undefined; } private _getChannel(channelId: string): IOutputChannel | undefined { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5956c737a03..2291bc2eea0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -444,15 +444,12 @@ export interface MainThreadMessageServiceShape extends IDisposable { } export interface MainThreadOutputServiceShape extends IDisposable { - $register(label: string, log: boolean, file?: UriComponents, extensionId?: string): Promise; - $append(channelId: string, value: string): Promise | undefined; - $clear(channelId: string): Promise | undefined; - $replace(channelId: string, value: string): Promise | undefined; - $update(channelId: string, mode: OutputChannelUpdateMode.Append): Promise | undefined; - $update(channelId: string, mode: OutputChannelUpdateMode, till: number): Promise | undefined; - $reveal(channelId: string, preserveFocus: boolean): Promise | undefined; - $close(channelId: string): Promise | undefined; - $dispose(channelId: string): Promise | undefined; + $register(label: string, log: boolean, file: UriComponents, extensionId: string): Promise; + $update(channelId: string, mode: OutputChannelUpdateMode.Append): Promise; + $update(channelId: string, mode: OutputChannelUpdateMode, till: number): Promise; + $reveal(channelId: string, preserveFocus: boolean): Promise; + $close(channelId: string): Promise; + $dispose(channelId: string): Promise; } export interface MainThreadProgressShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index facadbcd405..daa225bec96 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -11,88 +11,104 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; +import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { toLocalISOString } from 'vs/base/common/date'; +import { VSBuffer } from 'vs/base/common/buffer'; -export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { - - readonly _id: Promise; - private readonly _name: string; - protected readonly _proxy: MainThreadOutputServiceShape; - - private _disposed: boolean; - get disposed(): boolean { return this._disposed; } +export class ExtHostOutputChannel extends Disposable implements vscode.OutputChannel { + private _offset: number = 0; public visible: boolean = false; - constructor(name: string, log: boolean, file: URI | undefined, extensionId: string, proxy: MainThreadOutputServiceShape) { + private _disposed: boolean = false; + get disposed(): boolean { return this._disposed; } + + constructor( + readonly id: string, readonly name: string, + private readonly logger: ILogger, + private readonly proxy: MainThreadOutputServiceShape + ) { super(); - - this._name = name; - this._proxy = proxy; - this._id = proxy.$register(this.name, log, file, extensionId); - this._disposed = false; - } - - get name(): string { - return this._name; } appendLine(value: string): void { this.append(value + '\n'); } + append(value: string): void { + this.write(value); + if (this.visible) { + this.logger.flush(); + this.proxy.$update(this.id, OutputChannelUpdateMode.Append); + } + } + + clear(): void { + const till = this._offset; + this.logger.flush(); + this.proxy.$update(this.id, OutputChannelUpdateMode.Clear, till); + } + + replace(value: string): void { + const till = this._offset; + this.write(value); + this.proxy.$update(this.id, OutputChannelUpdateMode.Replace, till); + if (this.visible) { + this.logger.flush(); + } + } + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - this._id.then(id => this._proxy.$reveal(id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus))); + this.logger.flush(); + this.proxy.$reveal(this.id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus)); } hide(): void { - this._id.then(id => this._proxy.$close(id)); + this.proxy.$close(this.id); + } + + private write(value: string): void { + this._offset += VSBuffer.fromString(value).byteLength; + this.logger.info(value); } override dispose(): void { super.dispose(); if (!this._disposed) { - this._id - .then(id => this._proxy.$dispose(id)) - .then(() => this._disposed = true); + this.proxy.$dispose(this.id); + this._disposed = true; } } - abstract append(value: string): void; - abstract clear(): void; - abstract replace(value: string): void; -} - -export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { - - constructor(name: string, extensionId: string, proxy: MainThreadOutputServiceShape) { - super(name, false, undefined, extensionId, proxy); - } - - append(value: string): void { - this._id.then(id => this._proxy.$append(id, value)); - } - - clear(): void { - this._id.then(id => this._proxy.$clear(id)); - } - - replace(value: string): void { - this._id.then(id => this._proxy.$replace(id, value)); - } - } export class ExtHostOutputService implements ExtHostOutputServiceShape { readonly _serviceBrand: undefined; - protected readonly _proxy: MainThreadOutputServiceShape; - private readonly _channels: Map = new Map(); + private readonly proxy: MainThreadOutputServiceShape; + + private readonly _outputsLocation: URI; + private outputDirectoryPromise: Thenable | undefined; + private _namePool: number = 1; + + private readonly _channels: Map = new Map(); private visibleChannelId: string | null = null; - constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostConsumerFileSystem private readonly extHostFileSystem: IExtHostConsumerFileSystem, + @IExtHostFileSystemInfo private readonly extHostFileSystemInfo: IExtHostFileSystemInfo, + @ILoggerService private readonly loggerService: ILoggerService, + ) { + this.proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); + this._outputsLocation = this.extHostFileSystemInfo.extUri.joinPath(initData.logsLocation, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); } $setVisibleChannel(visibleChannelId: string | null): void { @@ -107,20 +123,31 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } - const extHostOutputChannel = this.doCreateOutChannel(name, extension); - extHostOutputChannel.then(channel => channel._id.then(id => { - this._channels.set(id, channel); - channel.visible = id === this.visibleChannelId; - })); + const extHostOutputChannel = this.doCreateOutputChannel(name, extension); + extHostOutputChannel.then(channel => { + this._channels.set(channel.id, channel); + channel.visible = channel.id === this.visibleChannelId; + }); return this.createExtHostOutputChannel(name, extHostOutputChannel, extension); } - protected async doCreateOutChannel(name: string, extension: IExtensionDescription): Promise { - return new ExtHostPushOutputChannel(name, extension.identifier.value, this._proxy); + private async doCreateOutputChannel(name: string, extension: IExtensionDescription): Promise { + const outputDir = await this.createOutputDirectory(); + const file = this.extHostFileSystemInfo.extUri.joinPath(outputDir, `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); + const logger = this.loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); + const id = await this.proxy.$register(name, false, file, extension.identifier.value); + return new ExtHostOutputChannel(id, name, logger, this.proxy); } - private createExtHostOutputChannel(name: string, channelPromise: Promise, extensionDescription: IExtensionDescription): vscode.OutputChannel { - const validate = (channel: AbstractExtHostOutputChannel, checkProposedApi?: boolean) => { + private createOutputDirectory(): Thenable { + if (!this.outputDirectoryPromise) { + this.outputDirectoryPromise = this.extHostFileSystem.value.createDirectory(this._outputsLocation).then(() => this._outputsLocation); + } + return this.outputDirectoryPromise; + } + + private createExtHostOutputChannel(name: string, channelPromise: Promise, extensionDescription: IExtensionDescription): vscode.OutputChannel { + const validate = (channel: ExtHostOutputChannel, checkProposedApi?: boolean) => { if (checkProposedApi) { checkProposedApiEnabled(extensionDescription); } diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts index 021500c5ce0..5bc2abbca64 100644 --- a/src/vs/workbench/api/node/extHost.node.services.ts +++ b/src/vs/workbench/api/node/extHost.node.services.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtHostOutputService } from 'vs/workbench/api/node/extHostOutputService'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; @@ -13,7 +12,6 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; -import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; @@ -34,7 +32,6 @@ registerSingleton(ILoggerService, ExtHostLoggerService); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostDebugService, ExtHostDebugService); -registerSingleton(IExtHostOutputService, ExtHostOutputService); registerSingleton(IExtHostSearch, NativeExtHostSearch); registerSingleton(IExtHostTask, ExtHostTask); registerSingleton(IExtHostTerminalService, ExtHostTerminalService); diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts deleted file mode 100644 index 971129b7c86..00000000000 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ /dev/null @@ -1,122 +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 { MainThreadOutputServiceShape } from '../common/extHost.protocol'; -import type * as vscode from 'vscode'; -import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/path'; -import { toLocalISOString } from 'vs/base/common/date'; -import { Promises, SymlinkSupport } from 'vs/base/node/pfs'; -import { AbstractExtHostOutputChannel, ExtHostOutputService as BaseExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { createRotatingLogger } from 'vs/platform/log/node/spdlogLog'; -import { Logger } from 'spdlog'; -import { ByteSize } from 'vs/platform/files/common/files'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; -import { VSBuffer } from 'vs/base/common/buffer'; - -class OutputAppender { - - static async create(name: string, file: string): Promise { - const appender = await createRotatingLogger(name, file, 30 * ByteSize.MB, 1); - appender.clearFormatters(); - - return new OutputAppender(name, file, appender); - } - - private constructor(readonly name: string, readonly file: string, private readonly appender: Logger) { } - - append(content: string): void { - this.appender.critical(content); - } - - flush(): void { - this.appender.flush(); - } -} - - -class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { - - private _offset: number; - private readonly _appender: OutputAppender; - - constructor(name: string, appender: OutputAppender, extensionId: string, proxy: MainThreadOutputServiceShape) { - super(name, false, URI.file(appender.file), extensionId, proxy); - this._offset = 0; - this._appender = appender; - } - - append(value: string): void { - this.incrementOffset(value); - this._appender.append(value); - if (this.visible) { - this._appender.flush(); - this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Append)); - } - } - - clear(): void { - const till = this._offset; - this._appender.flush(); - this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Clear, till)); - } - - replace(value: string): void { - const till = this._offset; - this.incrementOffset(value); - this._id.then(id => this._proxy.$update(id, OutputChannelUpdateMode.Replace, till)); - this._appender.append(value); - if (this.visible) { - this._appender.flush(); - } - } - - override show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - this._appender.flush(); - super.show(columnOrPreserveFocus, preserveFocus); - } - - private incrementOffset(value: string) { - this._offset += VSBuffer.fromString(value).byteLength; - } - -} - -export class ExtHostOutputService extends BaseExtHostOutputService { - - private _logsLocation: URI; - private _namePool: number = 1; - - constructor( - @IExtHostRpcService extHostRpc: IExtHostRpcService, - @ILogService private readonly logService: ILogService, - @IExtHostInitDataService initData: IExtHostInitDataService, - ) { - super(extHostRpc); - this._logsLocation = initData.logsLocation; - } - - protected override async doCreateOutChannel(name: string, extension: IExtensionDescription): Promise { - try { - const outputDirPath = join(this._logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); - const exists = await SymlinkSupport.existsDirectory(outputDirPath); - if (!exists) { - await Promises.mkdir(outputDirPath, { recursive: true }); - } - const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; - const file = URI.file(join(outputDirPath, `${fileName}.log`)); - const appender = await OutputAppender.create(fileName, file.fsPath); - return new ExtHostOutputChannelBackedByFile(name, appender, extension.identifier.value, this._proxy); - } catch (error) { - // Do not crash if logger cannot be created - this.logService.error(error); - } - return super.doCreateOutChannel(name, extension); - } -} From 0a8e75411e53ff307b3ca043ec7254d4daa5f88e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Nov 2021 21:19:18 +0100 Subject: [PATCH 063/375] clean up --- src/vs/workbench/api/common/extHostOutput.ts | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index daa225bec96..26dce856bc2 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -21,7 +21,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; export class ExtHostOutputChannel extends Disposable implements vscode.OutputChannel { - private _offset: number = 0; + private offset: number = 0; public visible: boolean = false; private _disposed: boolean = false; @@ -48,13 +48,13 @@ export class ExtHostOutputChannel extends Disposable implements vscode.OutputCha } clear(): void { - const till = this._offset; + const till = this.offset; this.logger.flush(); this.proxy.$update(this.id, OutputChannelUpdateMode.Clear, till); } replace(value: string): void { - const till = this._offset; + const till = this.offset; this.write(value); this.proxy.$update(this.id, OutputChannelUpdateMode.Replace, till); if (this.visible) { @@ -72,7 +72,7 @@ export class ExtHostOutputChannel extends Disposable implements vscode.OutputCha } private write(value: string): void { - this._offset += VSBuffer.fromString(value).byteLength; + this.offset += VSBuffer.fromString(value).byteLength; this.logger.info(value); } @@ -93,11 +93,11 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { private readonly proxy: MainThreadOutputServiceShape; - private readonly _outputsLocation: URI; + private readonly outputsLocation: URI; private outputDirectoryPromise: Thenable | undefined; - private _namePool: number = 1; + private namePool: number = 1; - private readonly _channels: Map = new Map(); + private readonly channels: Map = new Map(); private visibleChannelId: string | null = null; constructor( @@ -108,12 +108,12 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { @ILoggerService private readonly loggerService: ILoggerService, ) { this.proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); - this._outputsLocation = this.extHostFileSystemInfo.extUri.joinPath(initData.logsLocation, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + this.outputsLocation = this.extHostFileSystemInfo.extUri.joinPath(initData.logsLocation, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); } $setVisibleChannel(visibleChannelId: string | null): void { this.visibleChannelId = visibleChannelId; - for (const [id, channel] of this._channels) { + for (const [id, channel] of this.channels) { channel.visible = id === this.visibleChannelId; } } @@ -125,7 +125,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { } const extHostOutputChannel = this.doCreateOutputChannel(name, extension); extHostOutputChannel.then(channel => { - this._channels.set(channel.id, channel); + this.channels.set(channel.id, channel); channel.visible = channel.id === this.visibleChannelId; }); return this.createExtHostOutputChannel(name, extHostOutputChannel, extension); @@ -133,7 +133,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { private async doCreateOutputChannel(name: string, extension: IExtensionDescription): Promise { const outputDir = await this.createOutputDirectory(); - const file = this.extHostFileSystemInfo.extUri.joinPath(outputDir, `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); + const file = this.extHostFileSystemInfo.extUri.joinPath(outputDir, `${this.namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); const logger = this.loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); const id = await this.proxy.$register(name, false, file, extension.identifier.value); return new ExtHostOutputChannel(id, name, logger, this.proxy); @@ -141,7 +141,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { private createOutputDirectory(): Thenable { if (!this.outputDirectoryPromise) { - this.outputDirectoryPromise = this.extHostFileSystem.value.createDirectory(this._outputsLocation).then(() => this._outputsLocation); + this.outputDirectoryPromise = this.extHostFileSystem.value.createDirectory(this.outputsLocation).then(() => this.outputsLocation); } return this.outputDirectoryPromise; } From 5e6d0e1cbb7dae5b69c7e1cf14c8ada4a2010e97 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Nov 2021 11:59:44 -0800 Subject: [PATCH 064/375] Initial support for syntax highlighting md code blocks in notebooks For #136693 --- .../notebook/index.ts | 10 ++++- .../notebook/tsconfig.json | 1 + .../view/renderers/backLayerWebView.ts | 44 ++++++++++++++++++- .../browser/view/renderers/webviewMessages.ts | 19 +++++++- .../browser/view/renderers/webviewPreloads.ts | 39 ++++++++++++++++ 5 files changed, 108 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 2b6c893e3a1..7bda590e9e3 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const MarkdownIt: typeof import('markdown-it') = require('markdown-it'); import * as DOMPurify from 'dompurify'; +import MarkdownIt from 'markdown-it'; import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { ActivationFunction } from 'vscode-notebook-renderer'; @@ -13,9 +13,15 @@ const sanitizerOptions: DOMPurify.Config = { }; export const activate: ActivationFunction = (ctx) => { - const markdownIt = new MarkdownIt({ + const markdownIt: MarkdownIt = new MarkdownIt({ html: true, linkify: true, + highlight: (str: string, lang?: string) => { + if (lang) { + return `${markdownIt.utils.escapeHtml(str)}`; + } + return `${markdownIt.utils.escapeHtml(str)}`; + } }); markdownIt.linkify.set({ fuzzyLink: false }); diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index b90051ec35d..a94411a1e52 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "./dist/", "jsx": "react", "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, "module": "es2020", "lib": [ "es2018", diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index a50277d830a..d7fc86b2e6c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -15,6 +15,10 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { dirname, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -36,7 +40,7 @@ import { INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contr import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IWebviewService, WebviewContentPurpose, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages'; @@ -131,6 +135,7 @@ export class BackLayerWebView extends Disposable { @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IModeService private readonly modeService: IModeService, ) { super(); @@ -163,6 +168,13 @@ export class BackLayerWebView extends Disposable { isTrusted: e, }); })); + + this._register(TokenizationRegistry.onDidChange(() => { + this._sendMessageToWebview({ + type: 'tokenizedStylesChanged', + css: getTokenizationCss(), + }); + })); } updateOptions(options: { @@ -353,8 +365,8 @@ export class BackLayerWebView extends Disposable { tbody th { font-weight: normal; } - +