\ No newline at end of file diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index e30cadb0fc..120de744ca 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -19,6 +19,11 @@ if (!!process.send && process.env.PIPE_LOGGING === 'true') { // Disable IO if configured if (!process.env['VSCODE_ALLOW_IO']) { disableSTDIO(); +} else { + process.stdin.resume(); + process.stdin.on("close", () => { + process.exit(0); + }); } // Handle Exceptions diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 74be56db00..98e82ddd8a 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -122,7 +122,7 @@ export class IconLabel extends Disposable { ]); } - setValue(label?: string, description?: string, options?: IIconLabelValueOptions): void { + setValue(label?: string, description?: string, options?: IIconLabelValueOptions, element?: HTMLElement): void { const classes = ['monaco-icon-label']; if (options) { if (options.extraClasses) { @@ -161,6 +161,12 @@ export class IconLabel extends Disposable { this.descriptionNode.empty = !description; } } + + const oldElements = this.domNode.element.querySelectorAll(".decorations-wrapper"); + oldElements.forEach((e) => e.remove()); + if (element) { + this.domNode.element.appendChild(element); + } } } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index 362a2e1e6f..64c7289377 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -8,5 +8,17 @@ import { URI } from 'vs/base/common/uri'; export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string { + requirefn.toUrl = () => { + return ""; + }; + + // TODO@coder: Not sure if this is correct. Webpack makes things tricky. + if (process.mainModule && process.mainModule.filename) { + const index = process.mainModule.filename.lastIndexOf("/"); + return process.mainModule.filename.slice(0, index); + } + + return URI.parse("").fsPath; + return URI.parse(requirefn.toUrl(relativePath)).fsPath; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index f6a2fee985..45d77884dc 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -45,6 +45,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) }); }); + // @ts-ignore return new class implements CancelablePromise { cancel() { source.cancel(); diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 41dd989c5f..012d265683 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -358,11 +358,14 @@ export function template(template: string, values: { [key: string]: string | ISe * - macOS: Unsupported (replace && with empty string) */ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string { - if (isMacintosh || forceDisableMnemonics) { - return label.replace(/\(&&\w\)|&&/g, ''); - } + // NOTE@coder: We could write some custom support for this. For now, disable. + return label.replace(/&&/g, ''); + + // if (isMacintosh || forceDisableMnemonics) { + // return label.replace(/\(&&\w\)|&&/g, ''); + // } - return label.replace(/&&/g, '&'); + // return label.replace(/&&/g, '&'); } /** @@ -372,11 +375,14 @@ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean * - macOS: Unsupported (replace && with empty string) */ export function mnemonicButtonLabel(label: string): string { - if (isMacintosh) { - return label.replace(/\(&&\w\)|&&/g, ''); - } + // NOTE@coder: We could write some custom support for this. For now, disable. + return label.replace(/&&/g, ''); + + // if (isMacintosh) { + // return label.replace(/\(&&\w\)|&&/g, ''); + // } - return label.replace(/&&/g, isWindows ? '&' : '_'); + // return label.replace(/&&/g, isWindows ? '&' : '_'); } export function unmnemonicLabel(label: string): string { diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index fc6b2c285b..58d311c5bf 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -67,8 +67,11 @@ if (typeof process === 'object' && typeof process.nextTick === 'function' && typ _isMacintosh = userAgent.indexOf('Macintosh') >= 0; _isLinux = userAgent.indexOf('Linux') >= 0; _isWeb = true; - _locale = navigator.language; + // NOTE@coder: use en instead of en-US since that's vscode default and it uses + // that to determine whether to output aliases which will be pointless because + // they are the same language. + _locale = navigator.language === "en-US" ? "en" : navigator.language; _language = _locale; } diff --git a/src/vs/base/common/winjs.polyfill.promise.ts b/src/vs/base/common/winjs.polyfill.promise.ts index fb80ee9569..378aa7d689 100644 --- a/src/vs/base/common/winjs.polyfill.promise.ts +++ b/src/vs/base/common/winjs.polyfill.promise.ts @@ -73,6 +73,8 @@ export class PolyfillPromise implements Promise { } } + readonly [Symbol.toStringTag] = "Promise"; + then(onFulfilled?: any, onRejected?: any): PolyfillPromise { let sync = true; // To support chaining, we need to return the value of the @@ -126,4 +128,4 @@ export class PolyfillPromise implements Promise { catch(onRejected?: any): PolyfillPromise { return this.then(null, onRejected); } -} +}; diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index 31eea31a6b..67330b9db3 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -13,6 +13,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import * as extfs from 'vs/base/node/extfs'; import { isWindows } from 'vs/base/common/platform'; +import { isBrowserEnvironment } from 'coder/common'; export interface IConfigurationChangeEvent { config: T; @@ -61,7 +62,7 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { this.disposables.push(this._onDidUpdateConfiguration); this.registerWatcher(); - this.initAsync(); + this.init(); } public get path(): string { @@ -76,15 +77,20 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { return this._onDidUpdateConfiguration.event; } - private initAsync(): void { - this.loadAsync(config => { + private init(): void { + const callback = (config: T): void => { if (!this.loaded) { this.updateCache(config); // prevent race condition if config was loaded sync already } if (this.options.initCallback) { this.options.initCallback(this.getConfig()); } - }); + }; + if (isBrowserEnvironment()) { + this.loadAsync(callback); + } else { + this.loadSync(callback); + } } private updateCache(value: T): void { @@ -92,11 +98,11 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { this.loaded = true; } - private loadSync(): T { + private loadSync(callback: (config: T) => void): void { try { - return this.parse(fs.readFileSync(this._path).toString()); + callback(this.parse(fs.readFileSync(this._path).toString())); } catch (error) { - return this.options.defaultConfig; + callback(this.options.defaultConfig); } } @@ -216,7 +222,12 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { private ensureLoaded(): void { if (!this.loaded) { - this.updateCache(this.loadSync()); + // NOTE@coder: since loading asynchronously can sometimes take a while, + // this can come in, fail (since we can't support sync), then set itself + // to the default, which means the user's settings or keybinds don't take + // effect when the asynchronous call finally comes back. + // Commenting it out. + // this.updateCache(this.loadSync()); } } @@ -224,4 +235,4 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { this.disposed = true; this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index d1ed967b22..b182c9f969 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -10,7 +10,8 @@ import * as iconv from 'iconv-lite'; import { TPromise } from 'vs/base/common/winjs.base'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { exec } from 'child_process'; -import { Readable, Writable, WritableOptions } from 'stream'; +import { Readable, Transform, Writable, WritableOptions } from 'stream'; +import { IconvLiteDecoderStream } from './iconv'; export const UTF8 = 'utf8'; export const UTF8_with_bom = 'utf8bom'; @@ -132,7 +133,7 @@ export function encodingExists(encoding: string): boolean { } export function decodeStream(encoding: string): NodeJS.ReadWriteStream { - return iconv.decodeStream(toNodeEncoding(encoding)); + return new IconvLiteDecoderStream((iconv as any).getDecoder(encoding, undefined), undefined); } export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream { @@ -194,7 +195,7 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32']; * Guesses the encoding from buffer. */ export function guessEncodingByBuffer(buffer: Buffer): TPromise { - return TPromise.wrap(import('jschardet')).then(jschardet => { + return TPromise.wrap(require('jschardet')).then(jschardet => { jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD; const guessed = jschardet.detect(buffer); diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index c4c5a8ee43..cee84772fc 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -84,7 +84,7 @@ export function getMachineId(): TPromise { function getMacMachineId(): TPromise { return new TPromise(resolve => { - TPromise.join([import('crypto'), import('getmac')]).then(([crypto, getmac]) => { + TPromise.join([Promise.resolve(require('crypto')), Promise.resolve(require('getmac'))]).then(([crypto, getmac]) => { try { getmac.getMac((error, macAddress) => { if (!error) { diff --git a/src/vs/base/node/paths.ts b/src/vs/base/node/paths.ts index 66930cdaf4..4f00a4982e 100644 --- a/src/vs/base/node/paths.ts +++ b/src/vs/base/node/paths.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getPathFromAmdModule } from 'vs/base/common/amd'; +// import { getPathFromAmdModule } from 'vs/base/common/amd'; -interface IPaths { - getAppDataPath(platform: string): string; - getDefaultUserDataPath(platform: string): string; -} +// interface IPaths { +// getAppDataPath(platform: string): string; +// getDefaultUserDataPath(platform: string): string; +// } -const pathsPath = getPathFromAmdModule(require, 'paths'); -const paths = require.__$__nodeRequire(pathsPath); -export const getAppDataPath = paths.getAppDataPath; -export const getDefaultUserDataPath = paths.getDefaultUserDataPath; +// const pathsPath = getPathFromAmdModule(require, 'paths'); +// const paths = require.__$__nodeRequire(pathsPath); +export const getAppDataPath = () => "/tmp" // paths.getAppDataPath; +export const getDefaultUserDataPath = (arg?: string) => "/tmp" // paths.getDefaultUserDataPath; diff --git a/src/vs/base/node/proxy.ts b/src/vs/base/node/proxy.ts index d3d525ce0d..58ab1dd489 100644 --- a/src/vs/base/node/proxy.ts +++ b/src/vs/base/node/proxy.ts @@ -46,8 +46,8 @@ export async function getProxyAgent(rawRequestURL: string, options: IOptions = { }; const Ctor = requestURL.protocol === 'http:' - ? await import('http-proxy-agent') - : await import('https-proxy-agent'); + ? require('http-proxy-agent') + : require('https-proxy-agent'); return new Ctor(opts); } diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 488f8bf9bb..3e8df64db9 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -134,7 +134,7 @@ export function listProcesses(rootPid: number): Promise { } }; - (import('windows-process-tree')).then(windowsProcessTree => { + Promise.resolve(require('windows-process-tree')).then(windowsProcessTree => { windowsProcessTree.getProcessList(rootPid, (processList) => { windowsProcessTree.getProcessCpuUsage(processList, (completeProcessList) => { const processItems: Map = new Map(); diff --git a/src/vs/base/node/request.ts b/src/vs/base/node/request.ts index 23da75d32a..645c8d5ba3 100644 --- a/src/vs/base/node/request.ts +++ b/src/vs/base/node/request.ts @@ -53,7 +53,7 @@ export interface IRequestFunction { async function getNodeRequest(options: IRequestOptions): Promise { const endpoint = parseUrl(options.url); - const module = endpoint.protocol === 'https:' ? await import('https') : await import('http'); + const module = endpoint.protocol === 'https:' ? require('https') : require('http'); return module.request; } diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 5afeaad9c1..3529dbb97b 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -5,60 +5,46 @@ 'use strict'; -import { Menu, MenuItem, BrowserWindow, Event, ipcMain } from 'electron'; +import { Event, ipcMain } from 'electron'; import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu'; +import { ContextMenuManager, ContextMenu } from 'coder/element/contextmenu'; + +const manager = new ContextMenuManager(); export function registerContextMenuListener(): void { ipcMain.on(CONTEXT_MENU_CHANNEL, (event: Event, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => { const menu = createMenu(event, onClickChannel, items); - menu.popup({ - window: BrowserWindow.fromWebContents(event.sender), - x: options ? options.x : void 0, - y: options ? options.y : void 0, - positioningItem: options ? options.positioningItem : void 0, - callback: () => { - event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL); - } + manager.displayMenuAtPoint(menu, { + top: options ? options.y : void 0, + left: options ? options.x : void 0, + }); + manager.onceClose(() => { + event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL); }); }); } -function createMenu(event: Event, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu { - const menu = new Menu(); +function createMenu(event: Event, onClickChannel: string, items: ISerializableContextMenuItem[]): ContextMenu { + const menu = new ContextMenu(onClickChannel, manager); items.forEach(item => { - let menuitem: MenuItem; - // Separator if (item.type === 'separator') { - menuitem = new MenuItem({ - type: item.type, - }); + menu.addSpacer(item.id); } // Sub Menu else if (Array.isArray(item.submenu)) { - menuitem = new MenuItem({ - submenu: createMenu(event, onClickChannel, item.submenu), - label: item.label - }); + menu.addSubMenu(item.id, createMenu(event, onClickChannel, item.submenu), item.label); } // Normal Menu Item else { - menuitem = new MenuItem({ - label: item.label, - type: item.type, - accelerator: item.accelerator, - checked: item.checked, - enabled: item.enabled, - visible: item.visible, - click: (menuItem, win, contextmenuEvent) => event.sender.send(onClickChannel, item.id, contextmenuEvent) + menu.addEntry(item.id, item.label, item.accelerator, item.enabled, () => { + event.sender.send(onClickChannel, item.id); }); } - - menu.append(menuitem); }); return menu; diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index c802f0bd42..ea6ef313ac 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -5,6 +5,7 @@ 'use strict'; +import { EventEmitter } from "events"; import { Socket, Server as NetServer, createConnection, createServer } from 'net'; import { Event, Emitter, once, mapEvent, fromNodeEventEmitter } from 'vs/base/common/event'; import { IMessagePassingProtocol, ClientConnectionEvent, IPCServer, IPCClient } from 'vs/base/parts/ipc/node/ipc'; @@ -13,6 +14,7 @@ import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TimeoutTimer } from 'vs/base/common/async'; +import { Session } from '../../../../../../../../wush/src/index'; export function generateRandomPipeName(): string { const randomSuffix = generateUuid(); @@ -34,6 +36,100 @@ export function generateRandomPipeName(): string { export class Protocol implements IDisposable, IMessagePassingProtocol { + public static fromSession(session: Session, onExit: (code: number) => void): Protocol { + session.onDone((exitCode) => onExit(exitCode)); + return Protocol.fromStdio({ + onMessage: (cb) => { + session.onStdout((data) => { + cb(Buffer.from(data as any)); + }, true); + }, + sendMessage: (data) => { + session.sendStdin(data); + }, + }); + } + + public static fromStream( + inStream: { on: (event: "data", cb: (b: Buffer) => void) => void }, + outStream: { write: (b: Buffer) => void }, + ): Protocol { + return Protocol.fromStdio({ + onMessage: (cb) => { + inStream.on("data", (data) => { + cb(Buffer.from(data)); + }); + }, + sendMessage: (data: string) => { + outStream.write(Buffer.from(data)); + }, + }); + } + + public static fromWorker(worker: { + onmessage: (event: MessageEvent) => void; + postMessage: (data: string, origin?: string | string[]) => void; + }, ignoreFirst: boolean = false): Protocol { + return Protocol.fromStdio({ + onMessage: (cb) => { + worker.onmessage = (event: MessageEvent) => { + cb(event.data); + }; + }, + sendMessage: (data: string) => { + worker.postMessage(data); + }, + }, ignoreFirst); + } + + public static fromStdio(stdio: { + onMessage: (cb: (data: string | Uint8Array) => void) => void; + sendMessage: (data: string | Uint8Array) => void; + }, ignoreFirst: boolean = false): Protocol { + return new Protocol(new (class WorkerSocket { + + private readonly emitter: EventEmitter; + + public constructor() { + this.emitter = new EventEmitter(); + + let first = true; + stdio.onMessage((data) => { + if (ignoreFirst && first) { + first = false; + return; + } + this.emitter.emit("data", typeof data === "string" ? data : Buffer.from(data as any)); + }); + } + + public removeListener(event: string, listener: () => void): void { + this.emitter.removeListener(event, listener); + } + + public once(event: string, listener: () => void): void { + this.emitter.once(event, listener); + } + + public on(event: string, listener: () => void): void { + this.emitter.on(event, listener); + } + + public end(): void { + // TODO: figure it out + } + + public get destroyed() { + return false; + } + + public write(data: any): void { + stdio.sendMessage(data); + } + + }) as any); + } + private static readonly _headerLen = 4; private _isDisposed: boolean; diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 9bf45f0b20..763bbd09c8 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + (function () { 'use strict'; @@ -10,6 +11,7 @@ let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; if (typeof (self).define !== 'function' || !(self).define.amd) { + // @ts-ignore importScripts(monacoBaseUrl + 'vs/loader.js'); } diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 790b2886a7..9f440b754c 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -5,7 +5,6 @@ 'use strict'; -import 'vs/css!./media/issueReporter'; import { shell, ipcRenderer, webFrame, clipboard } from 'electron'; import { localize } from 'vs/nls'; import { $ } from 'vs/base/browser/dom'; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index ff48ec7cb6..9c65419c8e 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -6,7 +6,8 @@ 'use strict'; import * as fs from 'fs'; -import * as platform from 'vs/base/common/platform'; +import { join } from 'path'; +// import * as platform from 'vs/base/common/platform'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; @@ -23,7 +24,9 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/ex import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IRequestService } from 'vs/platform/request/node/request'; -import { RequestService } from 'vs/platform/request/electron-browser/requestService'; +// NOTE@coder: Use the node request instead of the xhr-based one. +import { RequestService } from 'vs/platform/request/node/requestService'; +// import { RequestService } from 'vs/platform/request/electron-browser/requestService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; @@ -32,7 +35,7 @@ import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows'; import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc'; -import { ipcRenderer } from 'electron'; +// import { ipcRenderer } from 'electron'; import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; @@ -46,13 +49,52 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadServiceChannelClient } from 'vs/platform/download/node/downloadIpc'; import { DefaultURITransformer } from 'vs/base/common/uriIpc'; +import { readdir, rimraf } from 'vs/base/node/pfs'; + +// NOTE:@coder: provide ipcRenderer. +const ipcRenderer = { + once: (_: string, cb: (_: any, r: IInitData) => void) => { + process.on('message', (message) => { + // Just going to assume the first message is the one we want for now. + const initData = JSON.parse(message) as IInitData; + process.env.VSCODE_LOGS = initData.logsDir; + product.extensionsGallery.serviceUrl = initData.serviceUrl; + // NOTE@coder: clean up old logs. This is mostly to reset the logs for the + // shared process itself and to account for logs sent after a shared + // client exits, but also cleans up logs that haven't been cleaned up due + // to an unexpected exit. + readdir(initData.logsDir) + .then((cs) => cs.filter((c) => !c.includes(`${initData.windowId}`))) + .then((cs) => TPromise.join(cs.map((c) => rimraf(join(initData.logsDir, c))))) + .then(() => cb(undefined, initData)); + }); + }, + send: (message: string) => { + process.send(message); + }, +}; + +// NOTE@coder: custom data for initialization. +export interface IInitData extends ISharedProcessInitData { + serviceUrl: string; + logsDir: string; + windowId: number; +} + +// NOTE@coder: start immediately. +startup({ + machineId: "1", +}); export interface ISharedProcessConfiguration { readonly machineId: string; } export function startup(configuration: ISharedProcessConfiguration) { - handshake(configuration); + // NOTE:@coder: exit on error. + handshake(configuration).then(null, (error) => { + process.exit(1); + }); } interface ISharedProcessInitData { @@ -148,7 +190,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I function setupIPC(hook: string): Thenable { function setup(retry: boolean): Thenable { return serve(hook).then(null, err => { - if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') { + // NOTE@coder: it's never Windows. + if (!retry || /*platform.isWindows ||*/ err.code !== 'EADDRINUSE') { return Promise.reject(err); } @@ -190,4 +233,4 @@ function handshake(configuration: ISharedProcessConfiguration): TPromise { return startHandshake() .then(data => setupIPC(data.sharedIPCHandle).then(server => main(server, data, configuration))) .then(() => ipcRenderer.send('handshake:im ready')); -} \ No newline at end of file +} diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4ade1e0175..549b95ca34 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -14,7 +14,7 @@ import { WindowsService } from 'vs/platform/windows/electron-main/windowsService import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { UpdateChannel } from 'vs/platform/update/node/updateIpc'; +// import { UpdateChannel } from 'vs/platform/update/node/updateIpc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main'; import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; @@ -192,7 +192,7 @@ export class CodeApplication { macOpenFileURIs = []; runningTimeout = null; } - }, 100); + }, 100) as any; }); app.on('new-window-for-tab', () => { @@ -427,9 +427,9 @@ export class CodeApplication { this.mainIpcServer.registerChannel('launch', launchChannel); // Register more Electron IPC services - const updateService = accessor.get(IUpdateService); - const updateChannel = new UpdateChannel(updateService); - this.electronIpcServer.registerChannel('update', updateChannel); + // const updateService = accessor.get(IUpdateService); + // const updateChannel = new UpdateChannel(updateService); + // this.electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); const issueChannel = new IssueChannel(issueService); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 46007f71cf..0954448ff4 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -336,8 +336,13 @@ function main() { assign(process.env, instanceEnv); // Startup - return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService))) - .then(() => instantiationService.invokeFunction(setupIPC)) + console.log("GEtting here"); + return instantiationService.invokeFunction(a => { + return createPaths(a.get(IEnvironmentService)); + }).then(() => { + console.log("INVoked stetup ipc"); + return instantiationService.invokeFunction(setupIPC); + }) .then(mainIpcServer => { bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup(); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index a67b887acd..c6d82e01e3 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -1718,6 +1718,7 @@ export class WindowsManager implements IWindowsMainService { } showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise { + console.log("GETTING HERE R0135 showOpen"); return this.dialogs.showOpenDialog(options, win); } @@ -1877,6 +1878,7 @@ class Dialogs { } showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise { + console.log("8ugr8w GETTING OPEN DIALOG CALLD"); function normalizePaths(paths: string[]): string[] { if (paths && paths.length > 0 && isMacintosh) { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 94b66896ed..6112b83ab2 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -273,7 +273,7 @@ export async function main(argv: string[]): Promise { processCallbacks.push(async _child => { try { // load and start profiler - const profiler = await import('v8-inspect-profiler'); + const profiler = require('v8-inspect-profiler'); const main = await profiler.startProfiling({ port: portMain }); const renderer = await profiler.startProfiling({ port: portRenderer, tries: 200 }); const extHost = await profiler.startProfiling({ port: portExthost, tries: 300 }); diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 7b006da43e..d69df8f07f 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -433,7 +433,8 @@ class DecorationCSSRules { if (typeof opts.gutterIconPath !== 'undefined') { if (typeof opts.gutterIconPath === 'string') { - cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.file(opts.gutterIconPath).toString())); + // NOTE@coder: change file() to parse() to allow HTTP URIs. + cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.parse(opts.gutterIconPath).toString())); } else { cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.revive(opts.gutterIconPath).toString(true).replace(/'/g, '%27'))); } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 5df5b9eca1..77b0d0e6c0 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -71,6 +71,9 @@ export function createTextBufferFactoryFromStream(stream: IStringStream, filter? c(builder.finish()); } }); + // We know internally we're passing this an EventEmitter. + // Definitely not the best way to handle this. + (stream as any).emit('ready'); }); } diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 4d0ec22210..acdf47482f 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -18,6 +18,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Handler } from 'vs/editor/common/editorCommon'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { nativeClipboard } from 'coder/workbench'; const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; @@ -28,7 +32,7 @@ const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeOrIE) // Chrome incorrectly returns true for document.queryCommandSupported('paste') // when the paste feature is available but the calling script has insufficient // privileges to actually perform the action -const supportsPaste = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste'))); +const supportsPaste = true; type ExecCommand = 'cut' | 'copy' | 'paste'; @@ -166,7 +170,7 @@ class ExecCommandPasteAction extends ExecCommandAction { id: 'editor.action.clipboardPasteAction', label: nls.localize('actions.clipboard.pasteLabel', "Paste"), alias: 'Paste', - precondition: EditorContextKeys.writable, + precondition: ContextKeyExpr.and(EditorContextKeys.writable, nativeClipboard.contextKey), kbOpts: kbOpts, menuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, @@ -180,6 +184,20 @@ class ExecCommandPasteAction extends ExecCommandAction { } }); } + + public async run(accessor, editor: ICodeEditor): Promise { + if (editor instanceof CodeEditorWidget) { + try { + editor.trigger('', Handler.Paste, { + text: await nativeClipboard.instance.readText(), + }); + } catch (ex) { + super.run(accessor, editor); + } + } else { + super.run(accessor, editor); + } + } } class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { diff --git a/src/vs/loader.js b/src/vs/loader.js index d02070659c..de85aefcdb 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -652,10 +652,10 @@ var AMDLoader; } this._didInitialize = true; // capture node modules - this._fs = nodeRequire('fs'); - this._vm = nodeRequire('vm'); - this._path = nodeRequire('path'); - this._crypto = nodeRequire('crypto'); + this._fs = require('fs'); + this._vm = require('vm'); + this._path = require('path'); + this._crypto = require('crypto'); // js-flags have an impact on cached data this._jsflags = ''; for (var _i = 0, _a = process.argv; _i < _a.length; _i++) { @@ -725,11 +725,18 @@ var AMDLoader; this._init(nodeRequire); this._initNodeRequire(nodeRequire, moduleManager); var recorder = moduleManager.getRecorder(); + const context = require.context("../", true, /.*/); + if (scriptSrc.indexOf("file:///") !== -1) { + const vsSrc = scriptSrc.split("file:///")[1].split(".js")[0]; + if (vsSrc && vsSrc.startsWith("vs/")) { + scriptSrc = `node|./${vsSrc}`; + } + } if (/^node\|/.test(scriptSrc)) { var pieces = scriptSrc.split('|'); var moduleExports_1 = null; try { - moduleExports_1 = nodeRequire(pieces[1]); + moduleExports_1 = context(pieces[1]); } catch (err) { errorback(err); @@ -823,14 +830,14 @@ var AMDLoader; path: cachedDataPath }); NodeScriptLoader._runSoon(function () { return _this._fs.unlink(cachedDataPath, function (err) { - if (err) { - moduleManager.getConfig().getOptionsLiteral().onNodeCachedData({ - errorCode: 'unlink', - path: cachedDataPath, - detail: err - }); - } - }); }, moduleManager.getConfig().getOptionsLiteral().nodeCachedDataWriteDelay); + if (err) { + moduleManager.getConfig().getOptionsLiteral().onNodeCachedData({ + errorCode: 'unlink', + path: cachedDataPath, + detail: err + }); + } + }); }, moduleManager.getConfig().getOptionsLiteral().nodeCachedDataWriteDelay); } else if (script.cachedDataProduced) { // data produced => tell outside world @@ -840,14 +847,14 @@ var AMDLoader; }); // data produced => write cache file NodeScriptLoader._runSoon(function () { return _this._fs.writeFile(cachedDataPath, script.cachedData, function (err) { - if (err) { - moduleManager.getConfig().getOptionsLiteral().onNodeCachedData({ - errorCode: 'writeFile', - path: cachedDataPath, - detail: err - }); - } - }); }, moduleManager.getConfig().getOptionsLiteral().nodeCachedDataWriteDelay); + if (err) { + moduleManager.getConfig().getOptionsLiteral().onNodeCachedData({ + errorCode: 'writeFile', + path: cachedDataPath, + detail: err + }); + } + }); }, moduleManager.getConfig().getOptionsLiteral().nodeCachedDataWriteDelay); } }; NodeScriptLoader._runSoon = function (callback, minTimeout) { diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index a4ef78d6f0..eaa348e689 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -18,6 +18,7 @@ import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemA import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { getFetchUri } from 'coder/api'; // The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136 class AlternativeKeyEmitter extends Emitter { @@ -231,8 +232,8 @@ export class MenuItemActionItem extends ActionItem { iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey); } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${item.iconLocation.dark.toString()}")`); + createCSSRule(`.icon.${iconClass}`, `background-image: url("${(getFetchUri(item.iconLocation.light || item.iconLocation.dark)).toString(true)}")`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${getFetchUri(item.iconLocation.dark).toString(true)}")`); MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/configuration/node/configuration.ts b/src/vs/platform/configuration/node/configuration.ts index 925ea03a57..44207f8afa 100644 --- a/src/vs/platform/configuration/node/configuration.ts +++ b/src/vs/platform/configuration/node/configuration.ts @@ -9,23 +9,30 @@ import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/config import { ConfigWatcher } from 'vs/base/node/config'; import { Event, Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; +import { isBrowserEnvironment } from 'coder/common'; export class UserConfiguration extends Disposable { private userConfigModelWatcher: ConfigWatcher; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + private readonly loadPromise: Promise; readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor(settingsPath: string) { super(); + let initRes: () => void; + this.loadPromise = new Promise((r) => initRes = r); this.userConfigModelWatcher = new ConfigWatcher(settingsPath, { changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new ConfigurationModelParser(settingsPath), parse: (content: string, parseErrors: any[]) => { const userConfigModelParser = new ConfigurationModelParser(settingsPath); userConfigModelParser.parse(content); parseErrors = [...userConfigModelParser.errors]; return userConfigModelParser; - } + }, + initCallback: isBrowserEnvironment ? () => { + initRes(); + } : undefined, }); this._register(this.userConfigModelWatcher); @@ -37,6 +44,10 @@ export class UserConfiguration extends Disposable { return this.userConfigModelWatcher.getConfig().configurationModel; } + get loaded(): Promise { + return this.loadPromise; + } + reload(): TPromise { return new TPromise(c => this.userConfigModelWatcher.reload(() => c(null))); } diff --git a/src/vs/platform/credentials/test/node/keytar.test.ts b/src/vs/platform/credentials/test/node/keytar.test.ts index f75689a147..6b3a63cb9b 100644 --- a/src/vs/platform/credentials/test/node/keytar.test.ts +++ b/src/vs/platform/credentials/test/node/keytar.test.ts @@ -16,7 +16,7 @@ suite('Keytar', () => { return; } (async () => { - const keytar = await import('keytar'); + const keytar = require('keytar'); const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`; try { await keytar.setPassword(name, 'foo', 'bar'); diff --git a/src/vs/platform/dialogs/node/dialogService.ts b/src/vs/platform/dialogs/node/dialogService.ts index 4304af5518..b12d48b18d 100644 --- a/src/vs/platform/dialogs/node/dialogService.ts +++ b/src/vs/platform/dialogs/node/dialogService.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as readline from 'readline'; +// import * as readline from 'readline'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { localize } from 'vs/nls'; import { canceled } from 'vs/base/common/errors'; +declare var readline: any; + export class CommandLineDialogService implements IDialogService { _serviceBrand: any; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ee381bbe35..698076bb89 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -188,6 +188,7 @@ export interface IGalleryExtension { publisherDisplayName: string; description: string; installCount: number; + stars: number; rating: number; ratingCount: number; assets: IGalleryExtensionAssets; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index ce457e5e9f..ee73e348a6 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -103,7 +103,7 @@ enum FilterType { ExcludeWithFlags = 12 } -const AssetType = { +export const AssetType = { Icon: 'Microsoft.VisualStudio.Services.Icons.Default', Details: 'Microsoft.VisualStudio.Services.Content.Details', Changelog: 'Microsoft.VisualStudio.Services.Content.Changelog', @@ -311,6 +311,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller publisherDisplayName: galleryExtension.publisher.displayName, description: galleryExtension.shortDescription || '', installCount: getStatistic(galleryExtension.statistics, 'install') + getStatistic(galleryExtension.statistics, 'updateCount'), + stars: getStatistic(galleryExtension.statistics, 'stars'), rating: getStatistic(galleryExtension.statistics, 'averagerating'), ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), assets, @@ -474,29 +475,104 @@ export class ExtensionGalleryService implements IExtensionGalleryService { private queryGallery(query: Query, token: CancellationToken): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { return this.commonHeadersPromise.then(commonHeaders => { const data = JSON.stringify(query.raw); - const headers = assign({}, commonHeaders, { + const headers = assign({}, {}, { 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1', 'Accept-Encoding': 'gzip', 'Content-Length': data.length }); + let ids: string[]; + try { + const criteria = query.raw.filters[0].criteria; + const newIds = []; + for (let i = 0; i < criteria.length; i++) { + const crit = criteria[i]; + if (crit.filterType === FilterType.ExtensionName) { + newIds.push(crit.value); + } + } + if (newIds.length > 0) { + ids = newIds; + } + } catch (ex) { + + } + + const queryString = `?${query.searchText ? `name=${query.searchText}&` : ""}page=${query.pageNumber - 1}&page_size=${query.pageSize}&sort=downloads${ids ? `&${ids.map((id) => `id=${id}`).join("&")}` : ""}` + return this.requestService.request({ - type: 'POST', - url: this.api('/extensionquery'), + type: 'GET', + url: this.api(`/search${queryString}`), data, - headers }, token).then(context => { if (context.res.statusCode >= 400 && context.res.statusCode < 500) { return { galleryExtensions: [], total: 0 }; } - return asJson(context).then(result => { - const r = result.results[0]; - const galleryExtensions = r.extensions; - const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; - const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; + return asJson(context).then((result: Array<{ + readonly author: string; + readonly category: string; + readonly downloads: number; + readonly forks: number; + readonly id: string; + readonly name: string; + readonly short_description: string; + readonly source_url: string; + readonly stars: number; + readonly version: string; + readonly watchers: number; + readonly metadata: { + readonly files: IRawGalleryExtensionFile[]; + readonly properties: IRawGalleryExtensionProperty[]; + } + }>) => { + + const galleryExtensions: IRawGalleryExtension[] = result.filter((ext) => ext.metadata).map((ext): IRawGalleryExtension => { + const assetUrl = ext.source_url.replace("/Microsoft.VisualStudio.Services.VSIXPackage", ""); + + return { + displayName: ext.name, + extensionId: ext.id, + extensionName: ext.id, + flags: "", + publisher: { + displayName: ext.author, + publisherId: undefined, + publisherName: ext.author, + }, + shortDescription: ext.short_description, + statistics: [{ + statisticName: 'stars', + value: ext.stars, + }, { + statisticName: 'forks', + value: ext.forks, + }, { + statisticName: 'watchers', + value: ext.watchers, + }, { + statisticName: 'install', + value: ext.downloads, + }], + versions: [{ + assetUri: assetUrl, + fallbackAssetUri: assetUrl, + files: [ + ...ext.metadata.files, + { + assetType: AssetType.VSIX, + source: ext.source_url, + }, + ], + properties: ext.metadata.properties, + version: ext.version, + lastUpdated: undefined, + }], + }; + }); + const total = result.filter(ext => ext.metadata).length; return { galleryExtensions, total }; }); @@ -667,7 +743,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): TPromise { return this.commonHeadersPromise.then(commonHeaders => { const baseOptions = { type: 'GET' }; - const headers = assign({}, commonHeaders, options.headers || {}); + const headers = assign({}, {}, options.headers || {}); options = assign({}, options, baseOptions, { headers }); const url = asset.uri; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 79c5558257..5bd7d0112d 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -12,7 +12,7 @@ import * as errors from 'vs/base/common/errors'; import { assign } from 'vs/base/common/objects'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { flatten } from 'vs/base/common/arrays'; -import { extract, buffer, ExtractError, zip, IFile } from 'vs/platform/node/zip'; +import { buffer, extract, tar, IFile } from 'vs/platform/node/nodeTar'; import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, @@ -80,8 +80,8 @@ function parseManifest(raw: string): TPromise<{ manifest: IExtensionManifest; me }); } -export function validateLocalExtension(zipPath: string): TPromise { - return buffer(zipPath, 'extension/package.json') +export function validateLocalExtension(tarPath: string): TPromise { + return buffer(tarPath, 'extension/package.json') .then(buffer => parseManifest(buffer.toString('utf8'))) .then(({ manifest }) => TPromise.as(manifest)); } @@ -162,7 +162,7 @@ export class ExtensionManagementService extends Disposable implements IExtension zip(extension: ILocalExtension): TPromise { return TPromise.wrap(this.collectFiles(extension)) - .then(files => zip(path.join(tmpdir(), generateUuid()), files)) + .then(files => tar(path.join(tmpdir(), generateUuid()), files)) .then(path => URI.file(path)); } @@ -471,15 +471,15 @@ export class ExtensionManagementService extends Disposable implements IExtension })); } - private extract(id: string, zipPath: string, extractPath: string, token: CancellationToken): TPromise { - this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`); + private extract(id: string, tarPath: string, extractPath: string, token: CancellationToken): TPromise { + this.logService.trace(`Started extracting the extension from ${tarPath} to ${extractPath}`); return pfs.rimraf(extractPath) .then( - () => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token) + () => extract(tarPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token) .then( () => this.logService.info(`Extracted extension to ${extractPath}:`, id), e => always(pfs.rimraf(extractPath), () => null) - .then(() => TPromise.wrapError(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))), + .then(() => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING)))), e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index e2d951ec24..b00a24ec11 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -15,6 +15,7 @@ import { startsWithIgnoreCase } from 'vs/base/common/strings'; import { IDisposable } from 'vs/base/common/lifecycle'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { ConflictResolution } from 'coder/common'; // NOTE@coder. export const IFileService = createDecorator('fileService'); @@ -22,6 +23,12 @@ export interface IResourceEncodings { getWriteEncoding(resource: URI, preferredEncoding?: string): string; } +export interface IContentProvider { + resolve: (uri: URI) => TPromise; + save: (uri: URI, accept?: ConflictResolution) => TPromise; + willHandle: (uri: URI) => boolean; +} + export interface IFileService { _serviceBrand: any; @@ -51,6 +58,13 @@ export interface IFileService { */ registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable; + /** + * Registers a content provider for a scheme. + * Multiple content providers can be registered for a scheme. + * The file service will use the first that resolves with content. + */ + registerContentProvider(scheme: string, provider: IContentProvider): IDisposable; + /** * Checks if this file service can handle the given resource. */ @@ -602,6 +616,17 @@ export interface IUpdateContentOptions { * Run mkdirp before saving. */ mkdirp?: boolean; + + /** + * NOTE@coder: which side to accept in case of conflict. + */ + accept?: ConflictResolution; + + /** + * NOTE@coder: whether we are creating the file, so we can bypass Clide since + * saving to Clide will not create files that don't exist. + */ + createFile?: boolean; } export interface IResolveFileOptions { diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 46bd114b4c..0b502acfcf 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -17,7 +17,7 @@ import product from 'vs/platform/node/product'; import { distinct, equals } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; -import { posix } from 'path'; +import * as path from 'path'; interface ILanguagePack { hash: string; @@ -107,7 +107,7 @@ class LanguagePacksCache extends Disposable { @ILogService private logService: ILogService ) { super(); - this.languagePacksFilePath = posix.join(environmentService.userDataPath, 'languagepacks.json'); + this.languagePacksFilePath = path.join(environmentService.userDataPath, 'languagepacks.json'); this.languagePacksFileLimiter = new Limiter(1); } @@ -152,7 +152,7 @@ class LanguagePacksCache extends Disposable { languagePack.extensions.push({ extensionIdentifier, version: extension.manifest.version }); } for (const translation of localizationContribution.translations) { - languagePack.translations[translation.id] = posix.join(extension.location.fsPath, translation.path); + languagePack.translations[translation.id] = path.join(extension.location.fsPath, translation.path); } } } diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index d2bd5c1965..da7bef4c00 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -107,4 +107,4 @@ class SpdLogService extends AbstractLogService implements ILogService { return result; } -} \ No newline at end of file +} diff --git a/src/vs/platform/node/package.ts b/src/vs/platform/node/package.ts index 93c32bc711..a66aa939ec 100644 --- a/src/vs/platform/node/package.ts +++ b/src/vs/platform/node/package.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +// import * as path from 'path'; +// import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IPackageConfiguration { name: string; version: string; } -const rootPath = path.dirname(getPathFromAmdModule(require, '')); -const packageJsonPath = path.join(rootPath, 'package.json'); -export default require.__$__nodeRequire(packageJsonPath) as IPackageConfiguration; +// TODO: obtain this version in a reasonable way +export default { name: "vscode", version: "1.28.0" } diff --git a/src/vs/platform/node/product.ts b/src/vs/platform/node/product.ts index dbb84fd473..3fba12657d 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/node/product.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +// import * as path from 'path'; +// import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { ExtensionHostExecutionEnvironment } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; +import { api } from 'coder/api'; export interface IProductConfiguration { nameShort: string; @@ -34,12 +36,14 @@ export interface IProductConfiguration { controlUrl: string; recommendationsUrl: string; }; + extensionExecutionEnvironments?: { [id: string]: ExtensionHostExecutionEnvironment }; extensionTips: { [id: string]: string; }; extensionImportantTips: { [id: string]: { name: string; pattern: string; }; }; exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: string[] }; }; extensionKeywords: { [extension: string]: string[]; }; extensionAllowedBadgeProviders: string[]; extensionAllowedProposedApi: string[]; + fetchUrl: string; keymapExtensionTips: string[]; crashReporter: { companyName: string; @@ -90,9 +94,24 @@ export interface ISurveyData { userProbability: number; } -const rootPath = path.dirname(getPathFromAmdModule(require, '')); -const productJsonPath = path.join(rootPath, 'product.json'); -const product = require.__$__nodeRequire(productJsonPath) as IProductConfiguration; +const product = { + nameShort: "VSCoder", + nameLong: "coder + vscode", + dataFolderName: "vscode", + extensionsGallery: { + "serviceUrl": undefined, + }, + extensionExecutionEnvironments: { + "wayou.vscode-todo-highlight": "worker", + "vscodevim.vim": "worker", + "coenraads.bracket-pair-colorizer": "worker", + }, + fetchUrl: "http://9080-untidyfeatheredmandrill.cdr-develop.co", +} as any as IProductConfiguration; + +if (typeof api !== "undefined") { + product.extensionsGallery.serviceUrl = api.environment.appURL("extensions-api"); +} if (process.env['VSCODE_DEV']) { product.nameShort += ' Dev'; diff --git a/src/vs/platform/output/node/outputAppender.ts b/src/vs/platform/output/node/outputAppender.ts index a7ffc9cd3d..72a251a3c9 100644 --- a/src/vs/platform/output/node/outputAppender.ts +++ b/src/vs/platform/output/node/outputAppender.ts @@ -21,4 +21,4 @@ export class OutputAppender { flush(): void { this.appender.flush(); } -} \ No newline at end of file +} diff --git a/src/vs/platform/request/electron-browser/requestService.ts b/src/vs/platform/request/electron-browser/requestService.ts index c66762f4d4..36b8c9fed6 100644 --- a/src/vs/platform/request/electron-browser/requestService.ts +++ b/src/vs/platform/request/electron-browser/requestService.ts @@ -32,6 +32,11 @@ export const xhrRequest: IRequestFunction = (options: IRequestOptions, token: Ca xhr.responseType = 'arraybuffer'; xhr.onerror = e => reject(new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText))); xhr.onload = (e) => { + if (!xhr.response) { + const { TextEncoder } = require("text-encoding"); + Object.defineProperty(xhr, "response", new TextEncoder().encode(xhr.responseText)); + } + resolve({ res: { statusCode: xhr.status, @@ -45,7 +50,7 @@ export const xhrRequest: IRequestFunction = (options: IRequestOptions, token: Ca constructor(arraybuffer: ArrayBuffer) { super(); - this._buffer = Buffer.from(new Uint8Array(arraybuffer)); + this._buffer = Buffer.from(arraybuffer); this._offset = 0; this._length = this._buffer.length; } diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 5238541eac..668a4c656e 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -66,6 +66,9 @@ export class FileStorage { } private loadSync(): object { + // Cannot load files sync + return {}; + try { return JSON.parse(fs.readFileSync(this.dbPath).toString()); // invalid JSON or permission issue can happen here } catch (error) { @@ -78,6 +81,9 @@ export class FileStorage { } private saveSync(): void { + // Cannot save sync + return; + try { writeFileAndFlushSync(this.dbPath, JSON.stringify(this.database, null, 4)); // permission issue can happen here } catch (error) { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 72b7d85693..a2c0831da8 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -164,11 +164,11 @@ Registry.as(Extensions.Configuration).registerConfigurat 'type': 'object', 'title': localize('telemetryConfigurationTitle', "Telemetry"), 'properties': { - 'telemetry.enableTelemetry': { - 'type': 'boolean', - 'description': localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to a Microsoft online service."), - 'default': true, - 'tags': ['usesOnlineServices'] - } + // 'telemetry.enableTelemetry': { + // 'type': 'boolean', + // 'description': localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to a Microsoft online service."), + // 'default': true, + // 'tags': ['usesOnlineServices'] + // } } }); \ No newline at end of file diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index 61fefc9506..6d4c9fe8ed 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -16,21 +16,21 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('updateConfigurationTitle', "Update"), 'type': 'object', 'properties': { - 'update.channel': { - 'type': 'string', - 'enum': ['none', 'default'], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change. The updates are fetched from a Microsoft online service."), - 'tags': ['usesOnlineServices'] - }, - 'update.enableWindowsBackgroundUpdates': { - 'type': 'boolean', - 'default': true, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates. The updates are fetched from a Microsoft online service."), - 'tags': ['usesOnlineServices'] - }, + // 'update.channel': { + // 'type': 'string', + // 'enum': ['none', 'default'], + // 'default': 'default', + // 'scope': ConfigurationScope.APPLICATION, + // 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change. The updates are fetched from a Microsoft online service."), + // 'tags': ['usesOnlineServices'] + // }, + // 'update.enableWindowsBackgroundUpdates': { + // 'type': 'boolean', + // 'default': true, + // 'scope': ConfigurationScope.APPLICATION, + // 'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates. The updates are fetched from a Microsoft online service."), + // 'tags': ['usesOnlineServices'] + // }, 'update.showReleaseNotes': { 'type': 'boolean', 'default': true, diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index ab83d870ea..321c7f0a17 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -67,6 +67,12 @@ export class WindowService implements IWindowService { } reloadWindow(args?: ParsedArgs): TPromise { + if (typeof location !== "undefined") { + location.reload(); + + return Promise.resolve(); + } + return this.windowsService.reloadWindow(this.windowId, args); } @@ -159,6 +165,7 @@ export class WindowService implements IWindowService { } showOpenDialog(options: Electron.OpenDialogOptions): TPromise { + console.log("ODKAJ(%(% GETTING CALLED OPEN", this.windowsService); return this.windowsService.showOpenDialog(this.windowId, options); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 0deb6db442..8c7f849e48 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -104,6 +104,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable } showOpenDialog(windowId: number, options: Electron.OpenDialogOptions): TPromise { + console.log("WE ARTE OUT HERE 092ti24t", windowId); this.logService.trace('windowsService#showOpenDialog', windowId); const codeWindow = this.windowsMainService.getWindowById(windowId); diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index f5546a0161..df12e2e5d5 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -114,7 +114,10 @@ export class WindowsChannel implements IWindowsChannel { case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); case 'pickWorkspaceAndOpen': return this.service.pickWorkspaceAndOpen(arg); - case 'showMessageBox': return this.service.showMessageBox(arg[0], arg[1]); + case 'showMessageBox': { + console.log("CAlling service showMsgBox", this.service); + return this.service.showMessageBox(arg[0], arg[1]); + } case 'showSaveDialog': return this.service.showSaveDialog(arg[0], arg[1]); case 'showOpenDialog': return this.service.showOpenDialog(arg[0], arg[1]); case 'reloadWindow': return this.service.reloadWindow(arg[0], arg[1]); @@ -185,6 +188,12 @@ export class WindowsChannel implements IWindowsChannel { } } +/** + * Instead of sending messages through the sharedProcess, we intercept this + * to provide our own client functionality. + * + * In most cases we'll access our electron filler. + */ export class WindowsChannelClient implements IWindowsService { _serviceBrand: any; @@ -215,11 +224,28 @@ export class WindowsChannelClient implements IWindowsService { } showMessageBox(windowId: number, options: MessageBoxOptions): TPromise { - return TPromise.wrap(this.channel.call('showMessageBox', [windowId, options])); + return new TPromise((resolve) => { + const { dialog } = require('electron'); + dialog.showMessageBox(undefined, options, (response: number, checked: boolean) => { + resolve({ + button: response, + checkboxChecked: checked, + }); + }); + }); + + // return TPromise.wrap(this.channel.call('showMessageBox', [windowId, options])); } showSaveDialog(windowId: number, options: SaveDialogOptions): TPromise { - return TPromise.wrap(this.channel.call('showSaveDialog', [windowId, options])); + return new TPromise((resolve) => { + const { dialog } = require('electron'); + dialog.showSaveDialog(undefined, options, (filename: string) => { + resolve(filename); + }); + }); + + // return TPromise.wrap(this.channel.call('showSaveDialog', [windowId, options])); } showOpenDialog(windowId: number, options: OpenDialogOptions): TPromise { @@ -255,7 +281,11 @@ export class WindowsChannelClient implements IWindowsService { } toggleFullScreen(windowId: number): TPromise { - return TPromise.wrap(this.channel.call('toggleFullScreen', windowId)); + return new TPromise((resolve) => { + const { toggleFullScreen } = require("electron"); + toggleFullScreen(); + resolve(undefined); + }); } setRepresentedFilename(windowId: number, fileName: string): TPromise { @@ -308,7 +338,11 @@ export class WindowsChannelClient implements IWindowsService { } focusWindow(windowId: number): TPromise { - return TPromise.wrap(this.channel.call('focusWindow', windowId)); + return new TPromise((resolve) => { + const { focusWindow } = require("electron"); + focusWindow(); + resolve(undefined); + }); } closeWindow(windowId: number): TPromise { diff --git a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts index b603f45eac..d62b599e34 100644 --- a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts @@ -30,6 +30,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { URI } from 'vs/base/common/uri'; +import { getFetchUri } from 'coder/api'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -174,6 +175,8 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { super(id, `${id}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } + + descriptor.icon = getFetchUri(descriptor.icon); const viewletDescriptor = new ViewletDescriptor( CustomViewlet, id, @@ -205,7 +208,7 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { // Generate CSS to show the icon in the activity bar const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${cssClass}`; - createCSSRule(iconClass, `-webkit-mask: url('${descriptor.icon}') no-repeat 50% 50%`); + createCSSRule(iconClass, `-webkit-mask: url('${descriptor.icon.toString(true)}') no-repeat 50% 50%`); } } diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index d95f314af6..0a034e4459 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -30,6 +30,8 @@ import './mainThreadEditor'; import './mainThreadEditors'; import './mainThreadErrors'; import './mainThreadExtensionService'; +import './mainThreadFastTime'; +import './mainThreadApi'; import './mainThreadFileSystem'; import './mainThreadFileSystemEventService'; import './mainThreadHeapService'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts index ce8dfa1af4..ed0d8c503b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts @@ -57,11 +57,14 @@ export class HeapService implements IHeapService { private _doTrackRecursive(obj: any): Promise { + // NOTE@coder: cannot control GC in the browser. + return Promise.resolve(obj); + if (isNullOrUndefined(obj)) { return Promise.resolve(obj); } - return import('gc-signals').then(({ GCSignal, consumeSignals }) => { + return Promise.resolve(require('gc-signals')).then(({ GCSignal, consumeSignals }) => { if (this._consumeHandle === void 0) { // ensure that there is one consumer of signals diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e8ba986c4b..6cdbd8a4bd 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -44,6 +44,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { TPromise } from 'vs/base/common/winjs.base'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as vscode from 'vscode'; +import * as coder from 'coder'; import * as paths from 'vs/base/common/paths'; import * as files from 'vs/platform/files/common/files'; import { MainContext, ExtHostContext, IInitData, IMainContext } from './extHost.protocol'; @@ -61,12 +62,19 @@ import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview'; import { ExtHostComments } from './extHostComments'; import { ExtHostSearch } from './extHostSearch'; import { ExtHostUrls } from './extHostUrls'; +import { ExtHostFastTime } from './extHostFastTime'; +import { ExtHostApi } from './extHostApi'; import { localize } from 'vs/nls'; +import { IInitData as ICoderInitData, isBrowserEnvironment } from 'coder/common'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; } +export interface ICoderExtensionApiFactory { + (extension: IExtensionDescription): typeof coder; +} + export function checkProposedApiEnabled(extension: IExtensionDescription): void { if (!extension.enableProposedApi) { throwProposedApiError(extension); @@ -85,6 +93,35 @@ function proposedApiFunction(extension: IExtensionDescription, fn: T): T { } } +/** + * Instantiate and return the coder extension API surface. + */ +export function createCoderApiFactory( + initData: ICoderInitData, + rpcProtocol: IMainContext, +): ICoderExtensionApiFactory { + const extHostFastTime = rpcProtocol.set(ExtHostContext.ExtHostFastTime, new ExtHostFastTime(rpcProtocol)); + const extHostApi = rpcProtocol.set(ExtHostContext.ExtHostApi, new ExtHostApi(rpcProtocol)); + + return function (_extension: IExtensionDescription): typeof coder { + return { + apiDetails: initData.api, + api: extHostApi.createApiProxy(), + extensions: { + isInstalled: (id) => extHostApi.extensionIsInstalled(id), + install: (id) => extHostApi.installExtension(id), + }, + fastTime: { + getUpdater: () => extHostFastTime.getUpdater(), + setUpdater: (updater) => { + extHostFastTime.setUpdater(updater); + }, + }, + }; + }; +} + + /** * This method instantiates and returns the extension API surface */ @@ -141,7 +178,9 @@ export function createApiFactory( const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); // Register an output channel for exthost log - extHostOutputService.createOutputChannelFromLogFile(localize('extensionsLog', "Extension Host"), extHostLogService.logFile); + // NOTE@coder: add suffix to distinguish between Node and web worker logs. + const suffix = typeof process === 'undefined' || typeof process.stdout === 'undefined' ? 'Worker' : 'Node'; + extHostOutputService.createOutputChannelFromLogFile(localize('extensionsLog' + suffix, suffix + " Extension Host"), extHostLogService.logFile); // Register API-ish commands ExtHostApiCommands.register(extHostCommands); @@ -832,30 +871,85 @@ class Extension implements vscode.Extension { } } -export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory): TPromise { - return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie)); +export function initializeExtensionApis( + extensionService: ExtHostExtensionService, vsFactory: IExtensionApiFactory, coderFactory: ICoderExtensionApiFactory, +): TPromise { + return extensionService.getExtensionPathIndex().then((trie) => { + defineCoderApi(coderFactory, trie); + defineVSApi(vsFactory, trie); + }); +} + +const extApiImpl = new Map(); +let lastFactory: IExtensionApiFactory; + +let lastCoderFactory: ICoderExtensionApiFactory; +const extCoderApiImpl = new Map(); + +/** + * Fetches the coder API for an extension. + */ +export function fetchCoderApi(description: IExtensionDescription): typeof coder { + return fetchApi(description, extCoderApiImpl, lastCoderFactory); +} + +/** + * Fetches the vscode api for an extension + */ +export function fetchVSApi(description: IExtensionDescription): typeof vscode { + return fetchApi(description, extApiImpl, lastFactory); +} + +function fetchApi(description: IExtensionDescription, _extApiImpl: Map, _lastFactory: (d: IExtensionDescription) => T): T { + if (!_lastFactory) { + throw new Error("the api has not been defined"); + } + + if (_extApiImpl.has(description.id)) { + return _extApiImpl.get(description.id); + } + + const newAPI = _lastFactory(description); + _extApiImpl.set(description.id, newAPI); + return newAPI; +} + +function defineCoderApi(factory: ICoderExtensionApiFactory, extensionPaths: TernarySearchTree): void { + if (isBrowserEnvironment()) { + lastCoderFactory = factory; + } else { + defineApi(factory, extensionPaths, extCoderApiImpl, "coder"); + } } -function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree): void { +function defineVSApi(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree): void { + if (isBrowserEnvironment()) { + lastFactory = factory; + } else { + defineApi(factory, extensionPaths, extApiImpl, "vscode"); + } +} - // each extension is meant to get its own api implementation - const extApiImpl = new Map(); - let defaultApiImpl: typeof vscode; +function defineApi( + factory: (d: IExtensionDescription) => T, extensionPaths: TernarySearchTree, + _extApiImpl: Map, moduleName: string, +): void { + let defaultApiImpl: T; const node_module = require.__$__nodeRequire('module'); const original = node_module._load; node_module._load = function load(request: string, parent: any, isMain: any) { - if (request !== 'vscode') { + if (request !== moduleName) { return original.apply(this, arguments); } // get extension id from filename and api for extension const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath); if (ext) { - let apiImpl = extApiImpl.get(ext.id); + let apiImpl = _extApiImpl.get(ext.id); if (!apiImpl) { apiImpl = factory(ext); - extApiImpl.set(ext.id, apiImpl); + _extApiImpl.set(ext.id, apiImpl); } return apiImpl; } @@ -864,7 +958,7 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT if (!defaultApiImpl) { let extensionPathsPretty = ''; extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.id}\n`); - console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); + console.warn(`Could not identify extension for '${moduleName}' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); defaultApiImpl = factory(nullExtensionDescription); } return defaultApiImpl; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 200aa028b5..39f600f5b7 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -42,6 +42,7 @@ import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/ import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { FastTimeState, IContainer, IUpdateContainer, IFastTimeBalanceResponse, IIDResponse } from 'coder/common'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -87,6 +88,20 @@ export interface IMainContext extends IRPCProtocol { // --- main thread +export interface MainThreadFastTimeShape extends IDisposable { + $setState(state: FastTimeState): void; +} + +export interface MainThreadApiShape extends IDisposable { + $getContainer(orgId: string, contId: string): Promise; + $getAvailableVolumes(orgId: string, contId: string): Promise; + $updateContainer(orgId: string, contId: string, update: IUpdateContainer): Promise; + $renewContainer(orgId: string, contId: string): Promise; + $getFastTime(orgId: string): Promise; + $extensionIsInstalled(extId: string): Promise; + $installExtension(extId: string): Promise; +} + export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; $unregisterCommand(id: string): void; @@ -593,6 +608,14 @@ export interface MainThreadWindowShape extends IDisposable { // -- extension host +export interface ExtHostFastTimeShape { + $updateState(state: FastTimeState): Thenable; +} + +export interface ExtHostApiShape { + // Nothing needed since requests are always one-way. +} + export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Thenable; $getContributedCommandHandlerDescriptions(): Thenable<{ [id: string]: string | ICommandHandlerDescription }>; @@ -1004,6 +1027,8 @@ export interface ExtHostCommentsShape { // --- proxy identifiers export const MainContext = { + MainThreadFastTime: createMainId('MainThreadFastTime'), + MainThreadApi: createMainId('MainThreadApi'), MainThreadCommands: >createMainId('MainThreadCommands'), MainThreadComments: createMainId('MainThreadComments'), MainThreadConfiguration: createMainId('MainThreadConfiguration'), @@ -1038,6 +1063,8 @@ export const MainContext = { }; export const ExtHostContext = { + ExtHostFastTime: createExtId('ExtHostFastTime'), + ExtHostApi: createExtId('ExtHostApi'), ExtHostCommands: createExtId('ExtHostCommands'), ExtHostConfiguration: createExtId('ExtHostConfiguration'), ExtHostDiagnostics: createExtId('ExtHostDiagnostics'), diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 36cdb4c70d..62fec93a61 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage'; -import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl'; +import { createCoderApiFactory, createApiFactory, fetchCoderApi, fetchVSApi, initializeExtensionApis } from 'vs/workbench/api/node/extHost.api.impl'; import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape, IMainContext } from './extHost.protocol'; import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent, ExtensionActivatedByAPI } from 'vs/workbench/api/node/extHostExtensionActivator'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; @@ -22,6 +22,12 @@ import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { URI } from 'vs/base/common/uri'; +import { IInitData as ICoderInitData, isBrowserEnvironment } from 'coder/common'; +// import * as path from 'path'; +import { RequireFS } from "../../../../../../../requirefs/src/index"; +// NOTE@coder for extensions to use the fetch URL. +import product from 'vs/platform/node/product'; +import * as resources from 'vs/base/common/resources'; class ExtensionMemento implements IExtensionMemento { @@ -136,6 +142,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { * This class is constructed manually because it is a service, so it doesn't use any ctor injection */ constructor(initData: IInitData, + coderInitData: ICoderInitData, extHostContext: IMainContext, extHostWorkspace: ExtHostWorkspace, extHostConfiguration: ExtHostConfiguration, @@ -151,9 +158,10 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._activator = null; // initialize API first (i.e. do not release barrier until the API is initialized) - const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, this._extHostLogService); + const coderFactory = createCoderApiFactory(coderInitData, extHostContext); + const vsFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, this._extHostLogService); - initializeExtensionApi(this, apiFactory).then(() => { + initializeExtensionApis(this, vsFactory, coderFactory).then(() => { this._activator = new ExtensionsActivator(this._registry, { showMessage: (severity: Severity, message: string): void => { @@ -315,7 +323,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { "${TelemetryActivationEvent}" ] } - */ + */ this._mainThreadTelemetry.$publicLog('activatePlugin', event); if (!extensionDescription.main) { // Treat the extension as being empty => NOT AN ERROR CASE @@ -326,7 +334,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return TPromise.join([ - loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder), + loadCommonJSModule(this._extHostLogService, extensionDescription, activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.id, values[0], values[1], activationTimesBuilder); @@ -358,9 +366,13 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { globalState, workspaceState, subscriptions: [], - get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, + get extensionPath() { + return extensionDescription.extensionLocation.path; + }, storagePath: this._storagePath.value(extensionDescription), - asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); }, + asAbsolutePath: (relativePath: string) => { + return resources.joinPath(extensionDescription.extensionLocation, relativePath).path; + }, logPath: that._extHostLogService.getLogDirectory(extensionDescription.id) }); }); @@ -407,16 +419,139 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } } -function loadCommonJSModule(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise { +// This variable is injected at the beginning of the output file. +// @ts-ignore +const extRequireMap = typeof externalRequireMap !== 'undefined' ? externalRequireMap : undefined; +if (extRequireMap) { + // This is required because of https://github.com/PowerShell/vscode-powershell/blob/master/src/features/Folding.ts#L588 + // Uncertain why this became acceptable instead of bundling its own version of vscode-textmate or having an api func for it + // but regardless this is a way to support it + extRequireMap['/./node_modules/vscode-textmate'] = require('vscode-textmate'); + extRequireMap['vscode-extension-telemetry'] = { + default: class TelemetryReporter { + + public sendTelemetryEvent(): void { + + } + + public async dispose(): Promise { + + } + + }, + }; +} + +const oldErr = global.console.error; + +function loadCommonJSModule(logService: ILogService, extensionDescription: IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise { let r: T = null; + const modulePath = extensionDescription.main; activationTimesBuilder.codeLoadingStart(); logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); - try { - r = require.__$__nodeRequire(modulePath); - } catch (e) { - return TPromise.wrapError(e); - } finally { - activationTimesBuilder.codeLoadingStop(); + if (isBrowserEnvironment()) { + return new Promise(async (res, rej) => { + const { fromTar } = require("../../../../../../../requirefs/src/index"); + /** + * Returns a requirefs archive + */ + const fetchArchive = async (): Promise => { + if (extensionDescription.download) { + return fetch(extensionDescription.download).then((resp) => { + if (resp.status !== 200) { + return Promise.reject("failed to download extension"); + } + return resp.arrayBuffer(); + }).then((arrayBuffer) => { + return fromTar(Buffer.from(arrayBuffer)); + }).then((requirefs) => { + requirefs.basedir("extension"); + return Promise.resolve(requirefs); + }); + } else { + const { wush } = require('coder/server'); + const resp = await wush.execute({ + command: `cd ${extensionDescription.extensionLocation.path} && tar cvf - ./*`, + }).done(undefined, true); + if (!resp.wasSuccessful()) { + throw new Error(`failed to tar: ${resp.stderr}`); + } + + return fromTar(resp.stdout); + } + }; + + const requireFs = await fetchArchive(); + const pathModule = require("path"); + const mainPath = pathModule.relative(extensionDescription.extensionLocation.path, modulePath); + pathModule.PATHS = { + VSIX_DIR: "/", + }; + pathModule.posix = pathModule; + const childProcessModule = require("child_process"); + childProcessModule.execSync = () => { + return ""; + }; + + self["process"] = { + // TODO: update this to users platform + argv: [], + browser: true, + binding: () => ({}), + env: { + HOME: "/root", + }, + on: () => { + // Nothing yet + }, + nextTick: function (cb, ...args) { + setTimeout(cb, 0, ...args); + }, + platform: "linux", + version: "1.0.0", + }; + + // @ts-ignore + self["window"] = self; + self["global"] = self; + self["Buffer"] = require("buffer"); + + requireFs.provide("coder", fetchCoderApi(extensionDescription)); + requireFs.provide("vscode", fetchVSApi(extensionDescription)); + requireFs.provide("assert", require("assert")); + requireFs.provide("buffer", require("buffer")); + requireFs.provide("child_process", childProcessModule); + requireFs.provide("fs", require("fs")); + requireFs.provide("http", require("http")); + requireFs.provide("os", require("os")); + requireFs.provide("path", pathModule); + requireFs.provide("util", require("util")); + requireFs.provide("stream", require("stream")); + requireFs.provide("events", require("events")); + requireFs.provide("tty", require("tty")); + + res(requireFs.require(mainPath)); + }); + } else { + try { + // This is done to remove extensions + // logging getting caught in stdio + Object.keys(global.console).forEach((k) => { + global.console[k] = () => { + // oldErr(...msg); + }; + }); + + // This variable is injected at the beginning of the output file. + // @ts-ignore + r = externalNodeRequire(modulePath); + } catch (e) { + logService.error("Module:", modulePath, e.stack); + oldErr(`Module: ${modulePath}\n` + e.stack + "\n"); + return TPromise.wrapError(e); + } finally { + activationTimesBuilder.codeLoadingStop(); + } } return TPromise.as(r); } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 1fd525bf8a..f2ff8ed7e7 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -7,7 +7,7 @@ import { MainContext, MainThreadOutputServiceShape, IMainContext } from './extHost.protocol'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { posix } from 'path'; +import * as path from 'path'; import { OutputAppender } from 'vs/platform/output/node/outputAppender'; import { toLocalISOString } from 'vs/base/common/date'; @@ -84,7 +84,7 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann constructor(name: string, outputDir: string, proxy: MainThreadOutputServiceShape) { const fileName = `${ExtHostOutputChannelBackedByFile._namePool++}-${name}`; - const file = URI.file(posix.join(outputDir, `${fileName}.log`)); + const file = URI.file(path.join(outputDir, `${fileName}.log`)); super(name, false, file, proxy); this._appender = new OutputAppender(fileName, file.fsPath); @@ -113,7 +113,7 @@ export class ExtHostOutputService { private _outputDir: string; constructor(logsLocation: URI, mainContext: IMainContext) { - this._outputDir = posix.join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + this._outputDir = path.join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); } diff --git a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts index 4cc7e4d150..543c47931c 100644 --- a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts +++ b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts @@ -682,10 +682,12 @@ export class FileIndexSearchManager { } private preventCancellation(promise: CancelablePromise): CancelablePromise { + // @ts-ignore return new class implements CancelablePromise { cancel() { // Do nothing } + // @ts-ignore then(resolve, reject) { return promise.then(resolve, reject); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 82b27ae509..02d90fe31d 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -14,6 +14,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration import { ILogService } from 'vs/platform/log/common/log'; import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal'; import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess'; +import { generate as generateTerminalId } from 'vs/workbench/parts/terminal/common/terminalId'; const RENDERER_NO_PROCESS_ID = -1; @@ -396,7 +397,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // Fork the process and listen for messages this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); - this._terminalProcesses[id] = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env); + this._terminalProcesses[id] = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, generateTerminalId()); this._terminalProcesses[id].onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid)); this._terminalProcesses[id].onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); this._terminalProcesses[id].onProcessData(data => this._proxy.$sendProcessData(id, data)); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 94b601a515..c989563804 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -34,6 +34,7 @@ import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/co import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { Upload } from 'coder/upload'; export interface IDraggedResource { resource: URI; @@ -164,14 +165,43 @@ export class ResourcesDropHandler { @IBackupFileService private backupFileService: IBackupFileService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IEditorService private editorService: IEditorService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private instantiationService: IInstantiationService ) { } handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup, afterDrop: (targetGroup: IEditorGroup) => void, targetIndex?: number): void { + // NOTE@coder: modified to work with browser uploads. + const handleBrowserUploads = (): void => { + const uploader = this.instantiationService.createInstance(Upload); + uploader.uploadDropped(event).then((paths) => { + // Add external ones to recently open list unless dropped resource is a workspace + const uris = paths.map((p) => URI.file(p)); + if (uris.length) { + this.windowsService.addRecentlyOpened(uris); + } + + const editors: IResourceEditor[] = uris.map(uri => ({ + resource: uri, + options: { + pinned: true, + index: targetIndex, + } + })); + + // Open in Editor + const targetGroup = resolveTargetGroup(); + return this.editorService.openEditors(editors, targetGroup).then(() => { + + // Finish with provided function + afterDrop(targetGroup); + }); + }); + }; + const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled); if (!untitledOrFileResources.length) { - return; + return handleBrowserUploads(); } // Make the window active to handle the drop properly within diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 2aee2d4e31..dfeb5cf5dc 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -211,6 +211,7 @@ export class ResourceLabel extends IconLabel { iconLabelOptions.extraClasses.push(...this.options.extraClasses); } + let element: HTMLElement; if (this.options && this.options.fileDecorations && resource) { const deco = this.decorationsService.getDecoration( resource, @@ -230,10 +231,14 @@ export class ResourceLabel extends IconLabel { if (this.options.fileDecorations.badges) { iconLabelOptions.extraClasses.push(deco.badgeClassName); } + + if (deco.element) { + element = deco.element; + } } } - this.setValue(label, this.label.description, iconLabelOptions); + this.setValue(label, this.label.description, iconLabelOptions, element); this._onDidRender.fire(); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 750950e953..3efcb01518 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -28,7 +28,7 @@ import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPart'; import { getZoomFactor } from 'vs/base/browser/browser'; -const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30; +const TITLE_BAR_HEIGHT = /*isMacintosh ? 22 : */30; const STATUS_BAR_HEIGHT = 22; const ACTIVITY_BAR_WIDTH = 50; diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index fc87a52eaf..e3d9e6bc5d 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -34,8 +34,8 @@ font-size: 11px; cursor: default; font-weight: normal; - -webkit-margin-before: 0; - -webkit-margin-after: 0; + margin-top: 0; + margin-bottom: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 02499b9f40..69102d2ea8 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -172,7 +172,7 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService); const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar - DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`); + DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl ? iconUrl.toString(true) : ''}') no-repeat 50% 50%`); } setActivity(activity: IActivity): void { @@ -253,4 +253,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 4ef749c3fd..45f003e2fd 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -32,6 +32,9 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { getFetchUri } from 'coder/api'; +import { ToggleFasttimeAction } from 'coder/contributions/fasttime'; +import { CreateNewTerminalAction, ToggleTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; interface IPlaceholderComposite { id: string; @@ -73,7 +76,11 @@ export class ActivitybarPart extends Part { getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)), - getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))], + getContextMenuActions: () => [ + this.instantiationService.createInstance(ToggleTerminalAction, ToggleTerminalAction.ID, nls.localize('createNewTerminal', "Toggle Terminal")), + this.instantiationService.createInstance(ToggleFasttimeAction, ToggleFasttimeAction.ID, nls.localize('toggleFasttime', "Toggle Fast Time")), + this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivityBar', "Hide Activity Bar")), + ], getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), hidePart: () => this.partService.setSideBarHidden(true), compositeSize: 50, @@ -85,7 +92,7 @@ export class ActivitybarPart extends Part { this.placeholderComposites = JSON.parse(previousState); this.placeholderComposites.forEach((s) => { if (typeof s.iconUrl === 'object') { - s.iconUrl = URI.revive(s.iconUrl); + s.iconUrl = getFetchUri(URI.revive(s.iconUrl)); } else { s.iconUrl = void 0; } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 5a92b2e1f5..1d3c735e75 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -4,25 +4,130 @@ *--------------------------------------------------------------------------------------------*/ .monaco-workbench > .part.activitybar { - width: 50px; + width: 50px; } .monaco-workbench > .activitybar > .content { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; } .monaco-workbench > .activitybar > .content .monaco-action-bar { - text-align: left; - background-color: inherit; + text-align: left; + background-color: inherit; } .monaco-workbench > .activitybar .action-item:focus { - outline: 0 !important; /* activity bar indicates focus custom */ + outline: 0 !important; /* activity bar indicates focus custom */ } .monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more { - -webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%; -} \ No newline at end of file + -webkit-mask: url("ellipsis-global.svg") no-repeat 50% 50%; +} + +.monaco-workbench .activitybar > .content > .extras-bar { + flex: 1; + display: flex; + flex-direction: column; + overflow: visible; +} + +.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal { + transition: 500ms color ease; + opacity: 0.65; + filter: brightness(115%); + padding-top: 10px; + padding-bottom: 10px; +} + +.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal:hover { + opacity: 1; +} + +.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal.disabled { + cursor: disabled; + opacity: 0.45 !important; +} + +.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal > .icon { + text-align: center; + display: block; +} + +.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal > .icon > svg { + width: 29px; + fill: currentColor; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime { + transition: 500ms color ease; + opacity: 0.65; + filter: brightness(115%); +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime:hover { + opacity: 1; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime.disabled { + cursor: disabled; + opacity: 0.45 !important; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .icon { + text-align: center; + display: block; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .icon > svg { + width: 22px; + fill: currentColor; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .text { + font-size: 12px; + text-align: center; +} + +.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .text.unknown { + font-size: 8px; + opacity: 0; +} + +.monaco-workbench .activitybar > .content > .extras-bar > .feedback { + transition: 500ms color ease; + padding-top: 10px; + padding-bottom: 10px; + margin-left: 0px; + margin-top: auto; + flex: 0; + cursor: default; +} + +.monaco-workbench .activitybar > .content > .extras-bar .feedback > .icon { + text-align: center; + display: block; + opacity: 0.65; + filter: brightness(115%); + cursor: pointer; +} + +.monaco-workbench .activitybar > .content > .extras-bar .feedback .feedback { + position: initial; + margin-left: 0px; +} + +.monaco-workbench .activitybar > .content > .extras-bar .feedback .feedback-dropdown { + bottom: -63px; +} + +.monaco-workbench .activitybar > .content > .extras-bar .feedback:hover > .icon { + opacity: 1; +} + +.monaco-workbench .activitybar > .content > .extras-bar .feedback > .icon > svg { + width: 29px; + fill: currentColor; +} diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 3cc6b830a8..51cf0560ac 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -22,7 +22,16 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Widget } from 'vs/base/browser/ui/widget'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IFasttimeService, ToggleFasttimeAction } from 'coder/contributions/fasttime'; +import { personalAccount } from 'coder/api'; +import { fasttimeIcon, feedbackIcon, terminalIcon } from 'coder/asset'; +import { Feedback, FastTimeState } from 'coder/common'; +import { errorForeground, descriptionForeground, focusBorder, inputValidationWarningBorder } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ToggleTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { IFeedbackService, ShowFeedbackAction, FeedbackState } from 'coder/contributions/feedback'; export interface ICompositeBarOptions { icon: boolean; @@ -58,7 +67,11 @@ export class CompositeBar extends Widget implements ICompositeBar { private options: ICompositeBarOptions, @IInstantiationService private instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IFasttimeService private fasttimeService: IFasttimeService, + @IFeedbackService private feedbackService: IFeedbackService, + @IThemeService private themeService: IThemeService, + @ICommandService private commandService: ICommandService, ) { super(); @@ -105,6 +118,101 @@ export class CompositeBar extends Widget implements ICompositeBar { } })); + if (this.options.orientation === ActionsOrientation.VERTICAL) { + const extrasDiv = parent.appendChild($('.extras-bar.monaco-action-bar')); + { + // Terminal Icon + const toggleItem = extrasDiv.appendChild($('.action-item.toggle-terminal')); + toggleItem.title = "Toggle Terminal"; + const toggleIcon = toggleItem.appendChild($('.icon')); + toggleIcon.innerHTML = terminalIcon; + toggleItem.addEventListener("click", () => { + this.instantiationService.createInstance(ToggleTerminalAction, ToggleTerminalAction.ID, "Toggle Terminal").run(); + }); + } + { + // Fasttime Icon + const toggleItem = extrasDiv.appendChild($('.action-item.fasttime')); + toggleItem.title = "Toggle Fast Time"; + const toggleIcon = toggleItem.appendChild($('.icon')); + toggleIcon.innerHTML = fasttimeIcon; + const toggleText = toggleItem.appendChild($('.text')); + toggleItem.addEventListener("click", async () => { + if (toggleItem.classList.contains("disabled")) { + return; + } + toggleItem.classList.add("disabled"); + try { + await this.commandService.executeCommand(ToggleFasttimeAction.ID); + } catch (ex) { + toggleItem.classList.remove("disabled"); + } + }); + + const updateLook = (state: FastTimeState): void => { + let fill: Color | string; + let text: string; + const theme = this.themeService.getTheme(); + switch (state) { + case FastTimeState.Enabled: + fill = "rgb(82, 139, 255)"; // theme.getColor(focusBorder); + text = "On"; + break; + case FastTimeState.Disabled: + fill = theme.getColor(descriptionForeground); + text = "Off"; + break; + case FastTimeState.Glowing: + fill = theme.getColor(inputValidationWarningBorder); + text = "Off"; + break; + case FastTimeState.Out: + fill = theme.getColor(errorForeground); + text = "Out"; + break; + case FastTimeState.NotSet: + fill = "transparent"; + text = "Unknown"; + break; + default: + throw new Error("not sure how to handle look for state"); + } + + if (state === FastTimeState.NotSet) { + toggleText.classList.add("unknown"); + } else { + toggleText.classList.remove("unknown"); + } + + toggleText.innerText = text; + toggleItem.classList.remove("disabled"); + toggleItem.style.color = fill.toString(); + }; + this.fasttimeService.onDidStateChange(updateLook); + this.themeService.onThemeChange(() => updateLook(this.fasttimeService.state)); + updateLook(this.fasttimeService.state); + } + + { + // Feedback Icon + const toggleItem = extrasDiv.appendChild($('.action-item.feedback')); + toggleItem.title = "Show/Hide Feedback"; + const toggleIcon = toggleItem.appendChild($('.icon')); + toggleIcon.innerHTML = feedbackIcon; + const feedback = new Feedback({ + account: personalAccount, + }); + toggleItem.addEventListener("click", async () => { + await this.commandService.executeCommand(ShowFeedbackAction.ID); + }); + toggleItem.appendChild(feedback.domNode); + + this.feedbackService.onDidShow(() => { + feedback.open(); + }); + } + } + return actionBarDiv; } @@ -285,7 +393,8 @@ export class CompositeBar extends Widget implements ICompositeBar { let overflows = false; let maxVisible = compositesToShow.length; let size = 0; - const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width; + // Subtract 100 from this so toggle terminal and fasttime show + const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height - 100 : this.dimension.width; for (let i = 0; i < compositesToShow.length && size <= limit; i++) { size += this.compositeSizeInBar.get(compositesToShow[i]); if (size > limit) { @@ -368,6 +477,7 @@ export class CompositeBar extends Widget implements ICompositeBar { () => this.getOverflowingComposites(), () => this.model.activeItem ? this.model.activeItem.id : void 0, (compositeId: string) => { + console.log("processing", compositeId); const item = this.model.findItem(compositeId); return item && item.activity[0] && item.activity[0].badge; }, diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 5363857d45..05f22f5d14 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -442,22 +442,23 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; } +const context = (require as any).context("./media/", true, /.*/); function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorToolItem, when: ContextKeyExpr, order: number): void { MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: primary.id, title: primary.title, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconDark}`)), - light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconLight}`)) + dark: URI.parse(context(`./${primary.iconDark}`)), + light: URI.parse(context(`./${primary.iconLight}`)) } }, alt: { id: alternative.id, title: alternative.title, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconDark}`)), - light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconLight}`)) + dark: URI.parse(context(`./${alternative.iconDark}`)), + light: URI.parse(context(`./${alternative.iconLight}`)) } }, group: 'navigation', diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index 204d01c7f1..46a3443c94 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -67,6 +67,7 @@ padding-right: 12px; margin-right: 5px; max-width: fit-content; + max-width: -moz-fit-content; } .monaco-shell.vs .screen-reader-detected-explanation .cancel { diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 020706de84..3657992030 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -49,6 +49,7 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit { width: 120px; min-width: fit-content; + min-width: -moz-fit-content; } .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { @@ -56,6 +57,7 @@ flex-basis: 0; /* all tabs are even */ flex-grow: 1; /* all tabs grow even */ max-width: fit-content; + max-width: -moz-fit-content; } .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after, diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index 24552a04bd..44b6ca9162 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -130,6 +130,7 @@ .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button { max-width: fit-content; + max-width: -moz-fit-content; padding: 5px 10px; margin-left: 10px; font-size: 12px; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index aae7522e58..733800a5bb 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -60,6 +60,84 @@ font-size: inherit; } +.monaco-workbench > .part.titlebar > .collaborators { + display: flex; + flex-direction: row; +} + +@keyframes collaborator-in { + from { + opacity: 0; + transform: translateX(15px); + } + to { + opacity: 0.75; + transform: translateX(0px); + } +} + +.monaco-workbench > .part.titlebar > .collaborators > .collaborator { + width: 27px; + height: 27px; + box-sizing: border-box; + border-radius: 50%; + border-color: currentColor; + border-width: 3px; + border-style: solid; + opacity: 0.75; + margin-right: 5px; + transition: 150ms opacity ease; + box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.5); + position: relative; +} + +@keyframes collaborator-active { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} + +.monaco-workbench > .part.titlebar > .collaborators > .collaborator.active { + animation-name: collaborator-active; + animation-duration: 0.3s; +} + +@keyframes collaborator-inner-active { + 0% { + transform: scale(0); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0; + } +} + +.monaco-workbench > .part.titlebar > .collaborators > .collaborator.active::before { + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + border-radius: 50%; + background-color: currentColor; + content: " "; + animation-name: collaborator-inner-active; + animation-fill-mode: forwards; + animation-duration: 0.3s; +} + +.monaco-workbench > .part.titlebar > .collaborators > .collaborator > img { + border-radius: 50%; + width: 100%; +} + .monaco-workbench.windows > .part.titlebar > .resizer, .monaco-workbench.linux > .part.titlebar > .resizer { -webkit-app-region: no-drag; @@ -76,17 +154,28 @@ .monaco-workbench > .part.titlebar > .window-appicon { - width: 35px; + width: 45px; + padding-top: 5px; + padding-left: 3px; + padding-right: 3px; height: 100%; position: relative; z-index: 99; - background-image: url('code-icon.svg'); - background-repeat: no-repeat; - background-position: center center; - background-size: 16px; + opacity: 0.5; + transition: 150ms opacity ease; + cursor: pointer; + /* fill: white; */ + /* background-image: url('code-icon.svg'); */ + /* background-repeat: no-repeat; */ + /* background-position: center center; */ + /* background-size: 16px; */ flex-shrink: 0; } +.monaco-workbench > .part.titlebar > .window-appicon:hover { + opacity: 1; +} + .monaco-workbench.fullscreen > .part.titlebar > .window-appicon { display: none; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index ec136a4539..1f45dfec44 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -141,9 +141,9 @@ export class MenubarControl extends Disposable { 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) }; - if (isMacintosh) { - this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); - } + // if (isMacintosh) { + // this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); + // } this.menuUpdater = this._register(new RunOnceScheduler(() => this.doSetupMenubar(), 200)); @@ -155,7 +155,7 @@ export class MenubarControl extends Disposable { this._onVisibilityChange = this._register(new Emitter()); this._onFocusStateChange = this._register(new Emitter()); - if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { + if (this.currentTitlebarStyleSetting !== 'custom') { for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { this._register(this.topLevelMenus[topLevelMenuName].onDidChange(() => this.setupMenubar())); } @@ -454,7 +454,7 @@ export class MenubarControl extends Disposable { this._register(this.keybindingService.onDidUpdateKeybindings(() => this.setupMenubar())); // These listeners only apply when the custom menubar is being used - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (this.currentTitlebarStyleSetting === 'custom') { // Listen to fullscreen changes this._register(browser.onDidChangeFullscreen(() => this.onDidChangeFullscreen())); @@ -467,7 +467,7 @@ export class MenubarControl extends Disposable { } private doSetupMenubar(): void { - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (this.currentTitlebarStyleSetting === 'custom') { this.setupCustomMenubar(); } @@ -652,7 +652,7 @@ export class MenubarControl extends Disposable { return; } - this.container.attributes['role'] = 'menubar'; + this.container.setAttribute('role', 'menubar'); const firstTimeSetup = this.customMenus === undefined; if (firstTimeSetup) { @@ -1136,7 +1136,7 @@ export class MenubarControl extends Disposable { if (this.container) { this.doSetupMenubar(); - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (this.currentTitlebarStyleSetting === 'custom') { this.setUnfocusedState(); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 8159c8e4eb..ad2d1ab6a9 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -35,6 +35,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { template, getBaseLabel } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event } from 'vs/base/common/event'; +import { logoArrow } from 'coder/asset'; +import { client } from 'coder/client'; +import { ICollaborator } from 'coder/common'; export class TitlebarPart extends Part implements ITitleService { @@ -48,6 +51,7 @@ export class TitlebarPart extends Part implements ITitleService { private titleContainer: HTMLElement; private title: HTMLElement; + private collaborators: HTMLElement; private dragRegion: HTMLElement; private windowControls: HTMLElement; private maxRestoreControl: HTMLElement; @@ -120,7 +124,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarVisibilityChanged(visible: boolean) { - if (isWindows || isLinux) { + if (isMacintosh || isWindows || isLinux) { // Hide title when toggling menu bar if (this.configurationService.getValue('window.menuBarVisibility') === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor @@ -133,7 +137,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (isWindows || isLinux) { + if (isMacintosh || isWindows || isLinux) { if (focused) { hide(this.dragRegion); } else { @@ -291,9 +295,15 @@ export class TitlebarPart extends Part implements ITitleService { this.dragRegion = append(this.titleContainer, $('div.titlebar-drag-region')); // App Icon (Windows/Linux) - if (!isMacintosh) { - this.appIcon = append(this.titleContainer, $('div.window-appicon')); - } + // if (!isMacintosh) { + // this.appIcon = append(this.titleContainer, $('div.window-appicon')); + // } + + this.appIcon = append(this.titleContainer, $('div.window-appicon')); + this.appIcon.innerHTML = logoArrow; + this.appIcon.addEventListener("click", () => { + window.location.href = "/projects"; + }); // Menubar: the menubar part which is responsible for populating both the custom and native menubars this.menubarPart = this.instantiationService.createInstance(MenubarControl); @@ -302,10 +312,10 @@ export class TitlebarPart extends Part implements ITitleService { this.menubarPart.create(this.menubar); - if (!isMacintosh) { + // if (!isMacintosh) { this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e))); - } + // } // Title this.title = append(this.titleContainer, $('div.window-title')); @@ -315,6 +325,50 @@ export class TitlebarPart extends Part implements ITitleService { this.doUpdateTitle(); } + this.collaborators = append(this.titleContainer, $('div.collaborators')); + const collaboratorDivs = new Map(); + + Promise.all([client.api, client.localCollaboratorsService]).then(([api, service]) => { + const registerCollaborator = (collaborator: ICollaborator) => { + const cd = append(this.collaborators, $('div.collaborator')); + cd.style.color = collaborator.color; + cd.id = collaborator.nonce; + const img = append(cd, $('img')) as HTMLImageElement; + img.src = api.avatars.get(collaborator.organizationId, 32); + + collaboratorDivs.set(collaborator.nonce, cd); + }; + service.collaborators.forEach((c) => registerCollaborator(c)); + const pulseActive = (id: string): void => { + const cd = collaboratorDivs.get(id); + if (cd) { + cd.classList.remove("active"); + setTimeout(() => { + cd.classList.add("active"); + }); + } + }; + this._register(service.onAdded(registerCollaborator)); + this._register(service.onRemoved((collaborator) => { + if (collaboratorDivs.has(collaborator.nonce)) { + collaboratorDivs.get(collaborator.nonce).remove(); + collaboratorDivs.delete(collaborator.nonce); + } + })); + this._register(service.onChangeActiveFile((event) => { + pulseActive(event.collaborator.nonce); + })); + this._register(service.onChangeCursorPosition((event) => { + pulseActive(event.collaborator.nonce); + })); + this._register(service.onOpenFile((event) => { + pulseActive(event.collaborator.nonce); + })); + this._register(service.onCloseFile((event) => { + pulseActive(event.collaborator.nonce); + })); + }); + // Maximize/Restore on doubleclick if (isMacintosh) { this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, e => { @@ -336,48 +390,48 @@ export class TitlebarPart extends Part implements ITitleService { }); // Window Controls (Windows/Linux) - if (!isMacintosh) { - this.windowControls = append(this.titleContainer, $('div.window-controls-container')); - - - // Minimize - const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); - const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); - addClass(minimizeIcon, 'window-minimize'); - this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { - this.windowService.minimizeWindow(); - })); - - // Restore - const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg')); - this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon')); - addClass(this.maxRestoreControl, 'window-max-restore'); - this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, e => { - this.windowService.isMaximized().then((maximized) => { - if (maximized) { - return this.windowService.unmaximizeWindow(); - } - - return this.windowService.maximizeWindow(); - }); - })); - - // Close - const closeIconContainer = append(this.windowControls, $('div.window-icon-bg')); - addClass(closeIconContainer, 'window-close-bg'); - const closeIcon = append(closeIconContainer, $('div.window-icon')); - addClass(closeIcon, 'window-close'); - this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { - this.windowService.closeWindow(); - })); - - // Resizer - this.resizer = append(this.titleContainer, $('div.resizer')); - - const isMaximized = this.windowService.getConfiguration().maximized ? true : false; - this.onDidChangeMaximized(isMaximized); - this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this); - } + // if (!isMacintosh) { + // this.windowControls = append(this.titleContainer, $('div.window-controls-container')); + + + // // Minimize + // const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); + // const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); + // addClass(minimizeIcon, 'window-minimize'); + // this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { + // this.windowService.minimizeWindow(); + // })); + + // // Restore + // const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg')); + // this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon')); + // addClass(this.maxRestoreControl, 'window-max-restore'); + // this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, e => { + // this.windowService.isMaximized().then((maximized) => { + // if (maximized) { + // return this.windowService.unmaximizeWindow(); + // } + + // return this.windowService.maximizeWindow(); + // }); + // })); + + // // Close + // const closeIconContainer = append(this.windowControls, $('div.window-icon-bg')); + // addClass(closeIconContainer, 'window-close-bg'); + // const closeIcon = append(closeIconContainer, $('div.window-icon')); + // addClass(closeIcon, 'window-close'); + // this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { + // this.windowService.closeWindow(); + // })); + + // // Resizer + // this.resizer = append(this.titleContainer, $('div.resizer')); + + // const isMaximized = this.windowService.getConfiguration().maximized ? true : false; + // this.onDidChangeMaximized(isMaximized); + // this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this); + // } // Since the title area is used to drag the window, we do not want to steal focus from the // currently active element. So we restore focus after a timeout back to where it was. @@ -529,14 +583,14 @@ export class TitlebarPart extends Part implements ITitleService { } // Only prevent zooming behavior on macOS or when the menubar is not visible - if (isMacintosh || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { + if (false && this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { // To prevent zooming we need to adjust the font size with the zoom factor const newHeight = this.initialSizing.titlebarHeight / getZoomFactor(); this.title.style.fontSize = `${this.initialSizing.titleFontSize / getZoomFactor()}px`; this.title.style.lineHeight = `${newHeight}px`; // Windows/Linux specific layout - if (isWindows || isLinux) { + if ((isWindows || isLinux) && this.appIcon) { if (typeof this.initialSizing.controlsWidth !== 'number') { this.initialSizing.controlsWidth = parseInt(getComputedStyle(this.windowControls).width, 10); } @@ -569,12 +623,16 @@ export class TitlebarPart extends Part implements ITitleService { this.title.style.fontSize = null; this.title.style.lineHeight = null; - this.appIcon.style.width = null; - this.appIcon.style.backgroundSize = null; - this.appIcon.style.paddingTop = null; - this.appIcon.style.paddingBottom = null; + if (this.appIcon) { + this.appIcon.style.width = null; + this.appIcon.style.backgroundSize = null; + this.appIcon.style.paddingTop = null; + this.appIcon.style.paddingBottom = null; + } - this.windowControls.style.width = null; + if (this.windowControls) { + this.windowControls.style.width = null; + } } if (this.menubarPart) { @@ -605,8 +663,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { collector.addRule(` - .monaco-workbench > .part.titlebar > .window-controls-container .window-icon { - background-color: ${titlebarActiveFg}; + .monaco-workbench > .part.titlebar > .window-appicon { + fill: ${titlebarActiveFg}; } `); } @@ -614,8 +672,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND); if (titlebarInactiveFg) { collector.addRule(` - .monaco-workbench > .part.titlebar.inactive > .window-controls-container .window-icon { - background-color: ${titlebarInactiveFg}; + .monaco-workbench > .part.titlebar.inactive > .window-appicon { + fill: ${titlebarInactiveFg}; } `); } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index c913fc9e6c..3b73e420a0 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -35,6 +35,7 @@ import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { localize } from 'vs/nls'; +import { getFetchUri } from 'coder/api'; export class CustomTreeViewPanel extends ViewletPanel { @@ -508,7 +509,7 @@ class TreeRenderer implements IRenderer { const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; const label = node.label ? node.label : resource ? basename(resource.path) : ''; const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; - const iconUrl = icon ? URI.revive(icon) : null; + const iconUrl = icon ? getFetchUri(URI.revive(icon)) : null; const title = node.tooltip ? node.tooltip : resource ? void 0 : label; // reset diff --git a/src/vs/workbench/browser/parts/views/media/panelviewlet.css b/src/vs/workbench/browser/parts/views/media/panelviewlet.css index 9691ecbdeb..0c34aee9c9 100644 --- a/src/vs/workbench/browser/parts/views/media/panelviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/panelviewlet.css @@ -8,6 +8,6 @@ text-overflow: ellipsis; overflow: hidden; font-size: 11px; - -webkit-margin-before: 0; - -webkit-margin-after: 0; + margin-top: 0; + margin-bottom: 0; } diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index b4a27fe133..e0fceac656 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -40,14 +40,14 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickS workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); const isLocal = FileDialogContext.isEqualTo('local'); -if (isMacintosh) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open...', fileCategory, isLocal); -} else { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open File...', fileCategory, isLocal); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, isLocal), 'File: Open Folder...', fileCategory, isLocal); -} - -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); +// if (isMacintosh) { +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open...', fileCategory, isLocal); +// } else { +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open File...', fileCategory, isLocal); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, isLocal), 'File: Open Folder...', fileCategory, isLocal); +// } + +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); if (!!product.reportIssueUrl) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenIssueReporterAction, OpenIssueReporterAction.ID, OpenIssueReporterAction.LABEL), 'Help: Open Issue Reporter', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory); @@ -73,7 +73,7 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTw workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); workbenchActionsRegistry.registerWorkbenchAction( new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { @@ -108,28 +108,28 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Increa workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, null), 'View: Decrease Current View Size', viewCategory); const workspacesCategory = nls.localize('workspaces', "Workspaces"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, isLocal); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, isLocal); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, isLocal); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); - -CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenWorkspaceConfigFileAction.ID, - title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, - }, - when: new RawContextKey('workbenchState', '').isEqualTo('workspace') -}); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, isLocal); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, isLocal); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, isLocal); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); + +// CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { +// serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); +// }); +// MenuRegistry.appendMenuItem(MenuId.CommandPalette, { +// command: { +// id: OpenWorkspaceConfigFileAction.ID, +// title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, +// }, +// when: new RawContextKey('workbenchState', '').isEqualTo('workspace') +// }); // Developer related actions const developerCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); @@ -155,92 +155,92 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Menu registration - file menu -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - command: { - id: NewWindowAction.ID, - title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileAction.ID, - title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") - }, - order: 1, - when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFolderAction.ID, - title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") - }, - order: 2, - when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileFolderAction.ID, - title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") - }, - order: 1, - when: ContextKeyExpr.and(IsMacContext, isLocal) -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenWorkspaceAction.ID, - title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") - }, - order: 3, - when: isLocal -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), - submenu: MenuId.MenubarRecentMenu, - group: '2_open', - order: 4 -}); - - -// More -MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { - group: 'y_more', - command: { - id: OpenRecentAction.ID, - title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: ADD_ROOT_FOLDER_COMMAND_ID, - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") - }, - order: 1, - when: isLocal -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: SaveWorkspaceAsAction.ID, - title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") - }, - order: 2, - when: isLocal -}); +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '1_new', +// command: { +// id: NewWindowAction.ID, +// title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") +// }, +// order: 2 +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '2_open', +// command: { +// id: OpenFileAction.ID, +// title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") +// }, +// order: 1, +// when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '2_open', +// command: { +// id: OpenFolderAction.ID, +// title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") +// }, +// order: 2, +// when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '2_open', +// command: { +// id: OpenFileFolderAction.ID, +// title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") +// }, +// order: 1, +// when: ContextKeyExpr.and(IsMacContext, isLocal) +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '2_open', +// command: { +// id: OpenWorkspaceAction.ID, +// title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") +// }, +// order: 3, +// when: isLocal +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), +// submenu: MenuId.MenubarRecentMenu, +// group: '2_open', +// order: 4 +// }); + + +// // More +// MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { +// group: 'y_more', +// command: { +// id: OpenRecentAction.ID, +// title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") +// }, +// order: 1 +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '3_workspace', +// command: { +// id: ADD_ROOT_FOLDER_COMMAND_ID, +// title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") +// }, +// order: 1, +// when: isLocal +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '3_workspace', +// command: { +// id: SaveWorkspaceAsAction.ID, +// title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") +// }, +// order: 2, +// when: isLocal +// }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), @@ -250,45 +250,45 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { when: IsMacContext.toNegated() }); -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), - precondition: new RawContextKey('workspaceFolderCount', 0).notEqualsTo('0') - }, - order: 3, - when: new RawContextKey('workbenchState', '').notEqualsTo('workspace') -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") - }, - order: 3, - when: new RawContextKey('workbenchState', '').isEqualTo('workspace') -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseCurrentWindowAction.ID, - title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") - }, - order: 4 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: 'z_Exit', - command: { - id: QUIT_ID, - title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") - }, - order: 1, - when: IsMacContext.toNegated() -}); +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '6_close', +// command: { +// id: CloseWorkspaceAction.ID, +// title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), +// precondition: new RawContextKey('workspaceFolderCount', 0).notEqualsTo('0') +// }, +// order: 3, +// when: new RawContextKey('workbenchState', '').notEqualsTo('workspace') +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '6_close', +// command: { +// id: CloseWorkspaceAction.ID, +// title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") +// }, +// order: 3, +// when: new RawContextKey('workbenchState', '').isEqualTo('workspace') +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: '6_close', +// command: { +// id: CloseCurrentWindowAction.ID, +// title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") +// }, +// order: 4 +// }); + +// MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { +// group: 'z_Exit', +// command: { +// id: QUIT_ID, +// title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") +// }, +// order: 1, +// when: IsMacContext.toNegated() +// }); // Appereance menu MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { @@ -450,25 +450,25 @@ MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { order: 1 }); -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.openProcessExplorer', - title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") - }, - order: 2 -}); +// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { +// group: '5_tools', +// command: { +// id: 'workbench.action.openProcessExplorer', +// title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") +// }, +// order: 2 +// }); // About -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: 'z_about', - command: { - id: 'workbench.action.showAboutDialog', - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") - }, - order: 1, - when: IsMacContext.toNegated() -}); +// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { +// group: 'z_about', +// command: { +// id: 'workbench.action.showAboutDialog', +// title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") +// }, +// order: 1, +// when: IsMacContext.toNegated() +// }); // Configuration: Workbench const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -629,13 +629,13 @@ configurationRegistry.registerConfiguration({ ], 'included': isMacintosh }, - 'workbench.settings.enableNaturalLanguageSearch': { - 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), - 'default': true, - 'scope': ConfigurationScope.WINDOW, - 'tags': ['usesOnlineServices'] - }, + // 'workbench.settings.enableNaturalLanguageSearch': { + // 'type': 'boolean', + // 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), + // 'default': true, + // 'scope': ConfigurationScope.WINDOW, + // 'tags': ['usesOnlineServices'] + // }, 'workbench.settings.settingsSearchTocBehavior': { 'type': 'string', 'enum': ['hide', 'filter'], @@ -658,12 +658,12 @@ configurationRegistry.registerConfiguration({ 'default': 'ui', 'scope': ConfigurationScope.WINDOW }, - 'workbench.enableExperiments': { - 'type': 'boolean', - 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), - 'default': true, - 'tags': ['usesOnlineServices'] - } + // 'workbench.enableExperiments': { + // 'type': 'boolean', + // 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), + // 'default': true, + // 'tags': ['usesOnlineServices'] + // } } }); @@ -675,59 +675,59 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('windowConfigurationTitle', "Window"), 'type': 'object', 'properties': { - 'window.openFilesInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off', 'default'], - 'enumDescriptions': [ - nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), - nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), - isMacintosh ? - nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : - nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") - ], - 'default': 'off', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': - isMacintosh ? - nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : - nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.openFoldersInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off', 'default'], - 'enumDescriptions': [ - nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), - nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), - nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.openWithoutArgumentsInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off'], - 'enumDescriptions': [ - nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."), - nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.") - ], - 'default': isMacintosh ? 'off' : 'on', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.restoreWindows': { - 'type': 'string', - 'enum': ['all', 'folders', 'one', 'none'], - 'enumDescriptions': [ - nls.localize('window.reopenFolders.all', "Reopen all windows."), - nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."), - nls.localize('window.reopenFolders.one', "Reopen the last active window."), - nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") - ], - 'default': 'one', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") - }, + // 'window.openFilesInNewWindow': { + // 'type': 'string', + // 'enum': ['on', 'off', 'default'], + // 'enumDescriptions': [ + // nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), + // nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), + // isMacintosh ? + // nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : + // nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") + // ], + // 'default': 'off', + // 'scope': ConfigurationScope.APPLICATION, + // 'markdownDescription': + // isMacintosh ? + // nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : + // nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + // }, + // 'window.openFoldersInNewWindow': { + // 'type': 'string', + // 'enum': ['on', 'off', 'default'], + // 'enumDescriptions': [ + // nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), + // nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), + // nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") + // ], + // 'default': 'default', + // 'scope': ConfigurationScope.APPLICATION, + // 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + // }, + // 'window.openWithoutArgumentsInNewWindow': { + // 'type': 'string', + // 'enum': ['on', 'off'], + // 'enumDescriptions': [ + // nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."), + // nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.") + // ], + // 'default': isMacintosh ? 'off' : 'on', + // 'scope': ConfigurationScope.APPLICATION, + // 'markdownDescription': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + // }, + // 'window.restoreWindows': { + // 'type': 'string', + // 'enum': ['all', 'folders', 'one', 'none'], + // 'enumDescriptions': [ + // nls.localize('window.reopenFolders.all', "Reopen all windows."), + // nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."), + // nls.localize('window.reopenFolders.one', "Reopen the last active window."), + // nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") + // ], + // 'default': 'one', + // 'scope': ConfigurationScope.APPLICATION, + // 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") + // }, 'window.restoreFullscreen': { 'type': 'boolean', 'default': false, @@ -745,19 +745,19 @@ configurationRegistry.registerConfiguration({ 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'title' }, "Controls the window title based on the active editor. Variables are substituted based on the context:\n- `\${activeEditorShort}`: the file name (e.g. myFile.txt).\n- `\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFile.txt).\n- `\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myProject/myFolder/myFile.txt).\n- `\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder).\n- `\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder).\n- `\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace).\n- `\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace).\n- `\${appName}`: e.g. VS Code.\n- `\${dirty}`: a dirty indicator if the active editor is dirty.\n- `\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") }, - 'window.newWindowDimensions': { - 'type': 'string', - 'enum': ['default', 'inherit', 'maximized', 'fullscreen'], - 'enumDescriptions': [ - nls.localize('window.newWindowDimensions.default', "Open new windows in the center of the screen."), - nls.localize('window.newWindowDimensions.inherit', "Open new windows with same dimension as last active one."), - nls.localize('window.newWindowDimensions.maximized', "Open new windows maximized."), - nls.localize('window.newWindowDimensions.fullscreen', "Open new windows in full screen mode.") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('newWindowDimensions', "Controls the dimensions of opening a new window when at least one window is already opened. Note that this setting does not have an impact on the first window that is opened. The first window will always restore the size and location as you left it before closing.") - }, + // 'window.newWindowDimensions': { + // 'type': 'string', + // 'enum': ['default', 'inherit', 'maximized', 'fullscreen'], + // 'enumDescriptions': [ + // nls.localize('window.newWindowDimensions.default', "Open new windows in the center of the screen."), + // nls.localize('window.newWindowDimensions.inherit', "Open new windows with same dimension as last active one."), + // nls.localize('window.newWindowDimensions.maximized', "Open new windows maximized."), + // nls.localize('window.newWindowDimensions.fullscreen', "Open new windows in full screen mode.") + // ], + // 'default': 'default', + // 'scope': ConfigurationScope.APPLICATION, + // 'description': nls.localize('newWindowDimensions', "Controls the dimensions of opening a new window when at least one window is already opened. Note that this setting does not have an impact on the first window that is opened. The first window will always restore the size and location as you left it before closing.") + // }, 'window.closeWhenEmpty': { 'type': 'boolean', 'default': false, @@ -792,8 +792,8 @@ configurationRegistry.registerConfiguration({ }, 'window.titleBarStyle': { 'type': 'string', - 'enum': ['native', 'custom'], - 'default': isLinux ? 'native' : 'custom', + 'enum': ['custom'], + 'default': 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. Changes require a full restart to apply.") }, diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 5323ed87b6..646c942be2 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -15,19 +15,19 @@ import * as errors from 'vs/base/common/errors'; import * as comparer from 'vs/base/common/comparers'; import * as platform from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; -import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +// import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import * as gracefulFs from 'graceful-fs'; +// import * as gracefulFs from 'graceful-fs'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { StorageService, inMemoryLocalStorageInstance, IStorage } from 'vs/platform/storage/common/storageService'; +// import { StorageService, inMemoryLocalStorageInstance, IStorage } from 'vs/platform/storage/common/storageService'; import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; import { webFrame } from 'electron'; import { UpdateChannelClient } from 'vs/platform/update/node/updateIpc'; @@ -37,7 +37,6 @@ import { IURLService } from 'vs/platform/url/common/url'; import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc'; import { IWorkspacesService, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; -import * as fs from 'fs'; import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc'; import { IIssueService } from 'vs/platform/issue/common/issue'; @@ -47,10 +46,11 @@ import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc'; import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/node/extfs'; +import { getStorageService } from 'coder/workbench'; -gracefulFs.gracefulify(fs); // enable gracefulFs +// gracefulFs.gracefulify(fs); // enable gracefulFs -export function startup(configuration: IWindowConfiguration): TPromise { +export function startup(configuration: IWindowConfiguration): TPromise { revive(configuration); @@ -92,7 +92,7 @@ function revive(workbench: IWindowConfiguration) { }); } -function openWorkbench(configuration: IWindowConfiguration): TPromise { +function openWorkbench(configuration: IWindowConfiguration): TPromise { const mainProcessClient = new ElectronIPCClient(`window:${configuration.windowId}`); const mainServices = createMainProcessServices(mainProcessClient, configuration); @@ -103,9 +103,11 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { // Since the configuration service is one of the core services that is used in so many places, we initialize it // right before startup of the workbench shell to have its data ready for consumers return createAndInitializeWorkspaceService(configuration, environmentService).then(workspaceService => { - const storageService = createStorageService(workspaceService, environmentService); - - return domContentLoaded().then(() => { + let storageService: IStorageService; + return getStorageService.then((gottenStorageService) => { + storageService = gottenStorageService; + return domContentLoaded(); + }).then(() => { // Open Shell perf.mark('willStartWorkbench'); @@ -119,6 +121,8 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { shell.open(); // Inform user about loading issues from the loader + return shell; + (self).require.config({ onError: (err: any) => { if (err.errorCode === 'load') { @@ -158,47 +162,47 @@ function validateFolderUri(folderUri: ISingleFolderWorkspaceIdentifier, verbose: }); } -function createStorageService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageService { - let workspaceId: string; - let secondaryWorkspaceId: number; - - switch (workspaceService.getWorkbenchState()) { - - // in multi root workspace mode we use the provided ID as key for workspace storage - case WorkbenchState.WORKSPACE: - workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString(); - break; - - // in single folder mode we use the path of the opened folder as key for workspace storage - // the ctime is used as secondary workspace id to clean up stale UI state if necessary - case WorkbenchState.FOLDER: - const workspace: Workspace = workspaceService.getWorkspace(); - workspaceId = workspace.folders[0].uri.toString(); - secondaryWorkspaceId = workspace.ctime; - break; - - // finaly, if we do not have a workspace open, we need to find another identifier for the window to store - // workspace UI state. if we have a backup path in the configuration we can use that because this - // will be a unique identifier per window that is stable between restarts as long as there are - // dirty files in the workspace. - // We use basename() to produce a short identifier, we do not need the full path. We use a custom - // scheme so that we can later distinguish these identifiers from the workspace one. - case WorkbenchState.EMPTY: - workspaceId = workspaceService.getWorkspace().id; - break; - } - - const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests! - - let storage: IStorage; - if (disableStorage) { - storage = inMemoryLocalStorageInstance; - } else { - storage = window.localStorage; - } - - return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId); -} +// function createStorageService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageService { +// let workspaceId: string; +// let secondaryWorkspaceId: number; + +// switch (workspaceService.getWorkbenchState()) { + +// // in multi root workspace mode we use the provided ID as key for workspace storage +// case WorkbenchState.WORKSPACE: +// workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString(); +// break; + +// // in single folder mode we use the path of the opened folder as key for workspace storage +// // the ctime is used as secondary workspace id to clean up stale UI state if necessary +// case WorkbenchState.FOLDER: +// const workspace: Workspace = workspaceService.getWorkspace(); +// workspaceId = workspace.folders[0].uri.toString(); +// secondaryWorkspaceId = workspace.ctime; +// break; + +// // finaly, if we do not have a workspace open, we need to find another identifier for the window to store +// // workspace UI state. if we have a backup path in the configuration we can use that because this +// // will be a unique identifier per window that is stable between restarts as long as there are +// // dirty files in the workspace. +// // We use basename() to produce a short identifier, we do not need the full path. We use a custom +// // scheme so that we can later distinguish these identifiers from the workspace one. +// case WorkbenchState.EMPTY: +// workspaceId = workspaceService.getWorkspace().id; +// break; +// } + +// const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests! + +// let storage: IStorage; +// if (disableStorage) { +// storage = inMemoryLocalStorageInstance; +// } else { +// storage = window.localStorage; +// } + +// return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId); +// } function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService { const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath); diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 55f91cfa0e..a4b6422562 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -24,7 +24,7 @@ import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { ElectronWindow } from 'vs/workbench/electron-browser/window'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; -import { IWindowsService, IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; import { IRequestService } from 'vs/platform/request/node/request'; import { RequestService } from 'vs/platform/request/electron-browser/requestService'; @@ -58,7 +58,8 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ICrashReporterService, NullCrashReporterService, CrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService'; import { getDelayedChannel, IPCClient } from 'vs/base/parts/ipc/node/ipc'; -import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; +// @ts-ignore +import { connect as connectNet, Client, Protocol } from 'vs/base/parts/ipc/node/ipc.net'; import { IExtensionManagementChannel, ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; import { IExtensionManagementService, IExtensionEnablementService, IExtensionManagementServerService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; @@ -100,6 +101,8 @@ import { DefaultURITransformer } from 'vs/base/common/uriIpc'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { ILabelService } from 'vs/platform/label/common/label'; +import { protocolPromise } from 'coder/workbench'; + /** * Services that we require for the Shell */ @@ -135,7 +138,7 @@ export class WorkbenchShell extends Disposable { private previousErrorTime: number; private configuration: IWindowConfiguration; - private workbench: Workbench; + public workbench: Workbench; constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, private mainProcessClient: IPCClient, configuration: IWindowConfiguration) { super(); @@ -344,8 +347,9 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, this.configuration.windowId, this.configuration)); - const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() - .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${this.configuration.windowId}`)); + const sharedProcess = protocolPromise.then((proto) => { + return new Client(proto, "1"); + }); sharedProcess.then(client => { client.registerChannel('download', new DownloadServiceChannel()); @@ -362,7 +366,7 @@ export class WorkbenchShell extends Disposable { // Telemetry if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); + const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender')) as any); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)), commonProperties: resolveWorkbenchCommonProperties(this.storageService, product.commit, pkg.version, this.configuration.machineId, this.environmentService.installSourcePath), @@ -394,7 +398,7 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IRequestService, new SyncDescriptor(RequestService)); serviceCollection.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - const extensionManagementChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('extensions'))); + const extensionManagementChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('extensions')) as any); const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel, DefaultURITransformer); serviceCollection.set(IExtensionManagementServerService, new SyncDescriptor(ExtensionManagementServerService, extensionManagementChannelClient)); serviceCollection.set(IExtensionManagementService, new SyncDescriptor(MulitExtensionManagementService)); @@ -440,7 +444,7 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IIntegrityService, new SyncDescriptor(IntegrityServiceImpl)); - const localizationsChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('localizations'))); + const localizationsChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('localizations')) as any); serviceCollection.set(ILocalizationsService, new SyncDescriptor(LocalizationsChannelClient, localizationsChannel)); return [instantiationService, serviceCollection]; diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 83cc52d8a4..09dca6d008 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -44,6 +44,33 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { nativeClipboard } from 'coder/workbench'; + +const pasteAction = new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), null, nativeClipboard.isEnabled, async () => { + if (nativeClipboard.isEnabled) { + try { + const element = document.activeElement as HTMLInputElement | HTMLTextAreaElement; + const start = element.selectionStart; + const end = element.selectionEnd; + const allText = element.value; + const newText = allText.substring(0, start) + (await nativeClipboard.instance.readText()) + allText.substring(end); + element.value = newText; + } catch (ex) { + document.execCommand('paste') && TPromise.as(true); + } + } else { + document.execCommand('paste') && TPromise.as(true); + } +}); + +nativeClipboard.onChange((v) => { + if (v) { + pasteAction.label = "Paste"; + } else { + pasteAction.label = "Paste (Must use keybind)"; + } + pasteAction.enabled = v; +}); const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && TPromise.as(true)), @@ -51,7 +78,7 @@ const TextInputActions: IAction[] = [ new Separator(), new Action('editor.action.clipboardCutAction', nls.localize('cut', "Cut"), null, true, () => document.execCommand('cut') && TPromise.as(true)), new Action('editor.action.clipboardCopyAction', nls.localize('copy', "Copy"), null, true, () => document.execCommand('copy') && TPromise.as(true)), - new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), null, true, () => document.execCommand('paste') && TPromise.as(true)), + pasteAction, new Separator(), new Action('editor.action.selectAll', nls.localize('selectAll', "Select All"), null, true, () => document.execCommand('selectAll') && TPromise.as(true)) ]; @@ -273,13 +300,14 @@ export class ElectronWindow extends Themable { private create(): void { + // Commented out (just use native window.open). // Handle window.open() calls - const $this = this; - (window).open = function (url: string, target: string, features: string, replace: boolean): any { - $this.windowsService.openExternal(url); + // const $this = this; + // (window).open = function (url: string, target: string, features: string, replace: boolean): any { + // $this.windowsService.openExternal(url); - return null; - }; + // return null; + // }; // Emit event when vscode has loaded this.lifecycleService.when(LifecyclePhase.Running).then(() => { @@ -292,8 +320,8 @@ export class ElectronWindow extends Themable { // Root warning this.lifecycleService.when(LifecyclePhase.Running).then(() => { let isAdminPromise: Promise; - if (isWindows) { - isAdminPromise = import('native-is-elevated').then(isElevated => isElevated()); + if (isWindows && false) { + isAdminPromise = require('native-is-elevated').then(isElevated => isElevated()); } else { isAdminPromise = Promise.resolve(isRootUser()); } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 04474dc883..4d55c884cd 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -115,6 +115,8 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { LabelService, ILabelService } from 'vs/platform/label/common/label'; +import { IFasttimeService, FasttimeService } from 'coder/contributions/fasttime'; +import { FeedbackService, IFeedbackService } from 'coder/contributions/feedback'; interface WorkbenchParams { configuration: IWindowConfiguration; @@ -186,7 +188,7 @@ export class Workbench extends Disposable implements IPartService { _serviceBrand: any; - private workbenchParams: WorkbenchParams; + public workbenchParams: WorkbenchParams; private workbench: HTMLElement; private workbenchStarted: boolean; private workbenchCreated: boolean; @@ -295,7 +297,7 @@ export class Workbench extends Disposable implements IPartService { private createWorkbench(): void { this.workbench = document.createElement('div'); this.workbench.id = Identifiers.WORKBENCH_CONTAINER; - DOM.addClasses(this.workbench, 'monaco-workbench', isWindows ? 'windows' : isLinux ? 'linux' : 'mac'); + DOM.addClasses(this.workbench, 'monaco-workbench', 'linux'); this._register(DOM.addDisposableListener(this.workbench, DOM.EventType.SCROLL, () => { this.workbench.scrollTop = 0; // Prevent workbench from scrolling #55456 @@ -360,12 +362,16 @@ export class Workbench extends Disposable implements IPartService { serviceCollection.set(IContextViewService, this.contextViewService); // Use themable context menus when custom titlebar is enabled to match custom menubar - if (!isMacintosh && this.getCustomTitleBarStyle() === 'custom') { + if (this.getCustomTitleBarStyle() === 'custom') { serviceCollection.set(IContextMenuService, new SyncDescriptor(HTMLContextMenuService, null)); } else { serviceCollection.set(IContextMenuService, new SyncDescriptor(NativeContextMenuService)); } + // Coder services + serviceCollection.set(IFasttimeService, new SyncDescriptor(FasttimeService)); + serviceCollection.set(IFeedbackService, new SyncDescriptor(FeedbackService)); + // Menus/Actions serviceCollection.set(IMenuService, new SyncDescriptor(MenuService)); @@ -1190,8 +1196,6 @@ export class Workbench extends Disposable implements IPartService { return false; } else if (!browser.isFullscreen()) { return true; - } else if (isMacintosh) { - return false; } else if (this.menubarVisibility === 'visible') { return true; } else if (this.menubarVisibility === 'toggle' || this.menubarVisibility === 'default') { @@ -1216,7 +1220,7 @@ export class Workbench extends Disposable implements IPartService { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { offset = this.workbenchLayout.partLayoutInfo.titlebar.height; - if (isMacintosh || this.menubarVisibility === 'hidden') { + if (this.menubarVisibility === 'hidden') { offset /= browser.getZoomFactor(); } } diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index a1ba3700a3..03ac5a2822 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -24,6 +24,15 @@ import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { timeout } from 'vs/base/common/async'; import { Counter } from 'vs/base/common/numbers'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IInitData as ICoderInitData } from 'coder/common'; + +if (!process.exit) { + process.exit = { + bind: () => { + + }, + } as any; +} const nativeExit = process.exit.bind(process); function patchProcess(allowExit: boolean) { @@ -65,7 +74,7 @@ export class ExtensionHostMain { private _searchRequestIdProvider: Counter; private _mainThreadWorkspace: MainThreadWorkspaceShape; - constructor(protocol: IMessagePassingProtocol, initData: IInitData) { + constructor(protocol: IMessagePassingProtocol, initData: IInitData, coderInitData: ICoderInitData) { const rpcProtocol = new RPCProtocol(protocol); // ensure URIs are transformed and revived @@ -87,7 +96,7 @@ export class ExtensionHostMain { this._extHostLogService.trace('initData', initData); this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration); - this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService); + this._extensionService = new ExtHostExtensionService(initData, coderInitData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService); // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) @@ -127,7 +136,9 @@ export class ExtensionHostMain { start(): TPromise { return this._extensionService.onExtensionAPIReady() - .then(() => this.handleEagerExtensions()) + .then(() => { + this.handleEagerExtensions(); + }) .then(() => this.handleExtensionTests()) .then(() => { this._extHostLogService.info(`eager extensions activated`); diff --git a/src/vs/workbench/node/extensionHostProcess.ts b/src/vs/workbench/node/extensionHostProcess.ts index ba0e95ec5c..676f5c4e41 100644 --- a/src/vs/workbench/node/extensionHostProcess.ts +++ b/src/vs/workbench/node/extensionHostProcess.ts @@ -2,19 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; +import { IInitData as ICoderInitData, isBrowserEnvironment, API } from 'coder/common'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionHostMain, exit } from 'vs/workbench/node/extensionHostMain'; import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; -import { createConnection } from 'net'; +// import { createConnection } from 'net'; import { Event, filterEvent } from 'vs/base/common/event'; import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol'; -import * as nativeWatchdog from 'native-watchdog'; import product from 'vs/platform/node/product'; +import connectToWush from '../../../../../../wush/src/index'; +import 'coder/setup'; +import { set as setApi } from 'coder/api'; +import { setWush } from 'coder/server'; // With Electron 2.x and node.js 8.x the "natives" module // can cause a native crash (see https://github.com/nodejs/node/issues/19891 and @@ -45,45 +48,61 @@ let onTerminate = function () { exit(); }; -function createExtHostProtocol(): Promise { +function createExtHostProtocol(): IMessagePassingProtocol { + const protocol = isBrowserEnvironment() + ? Protocol.fromWorker(self) + : Protocol.fromStream(process.stdin, process.stdout); - const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST; + return new class implements IMessagePassingProtocol { - return new Promise((resolve, reject) => { + private _terminating = false; - const socket = createConnection(pipeName, () => { - socket.removeListener('error', reject); - resolve(new Protocol(socket)); + readonly onMessage: Event = filterEvent(protocol.onMessage, msg => { + if (!isMessageOfType(msg, MessageType.Terminate)) { + return true; + } + this._terminating = true; + onTerminate(); + return false; }); - socket.once('error', reject); - - }).then(protocol => { - return new class implements IMessagePassingProtocol { + send(msg: any): void { + if (!this._terminating) { + protocol.send(msg); + } + } + }; +} - private _terminating = false; +function getCoderInitData(protocol: IMessagePassingProtocol): Promise { + return new Promise((resolve, reject) => { + const first = protocol.onMessage((raw) => { + first.dispose(); + const initData = JSON.parse(raw.toString()); - readonly onMessage: Event = filterEvent(protocol.onMessage, msg => { - if (!isMessageOfType(msg, MessageType.Terminate)) { - return true; - } - this._terminating = true; - onTerminate(); - return false; - }); + product.fetchUrl = initData.fetchUrl; - send(msg: any): void { - if (!this._terminating) { - protocol.send(msg); + if (isBrowserEnvironment()) { + if (initData.api.key) { + const api = new API(); + api.setAPIKey(initData.api.key); + setApi(api, { id_str: initData.api.organization } as any, { organization: { id_str: initData.api.organization} } as any, { id_str: initData.api.container } as any, undefined, undefined); } + connectToWush(initData.wush).then((wush) => { + setWush(wush, initData.wush); + resolve(initData); + }).catch((error) => { + reject(error); + }); + } else { + resolve(initData); } - }; + }); }); } function connectToRenderer(protocol: IMessagePassingProtocol): Promise { return new Promise((c, e) => { - // Listen init data message const first = protocol.onMessage(raw => { first.dispose(); @@ -129,25 +148,33 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { - // connect to main side - return connectToRenderer(protocol); -}).then(renderer => { - // setup things - const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData); - onTerminate = () => extensionHostMain.terminate(); - return extensionHostMain.start(); +const protocol = createExtHostProtocol(); +getCoderInitData(protocol).then((coderInitData) => { + return connectToRenderer(protocol).then((renderer) => { + // setup things + const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData, coderInitData); + if (!isBrowserEnvironment()) { + process.stdin.on('close', () => { + extensionHostMain.terminate(); + }); + } + onTerminate = () => extensionHostMain.terminate(); + return extensionHostMain.start(); + }); }).catch(err => console.error(err)); function patchExecArgv() { diff --git a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts index 11b46982bd..0ced196436 100644 --- a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts @@ -16,7 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { WorkbenchTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { renderViewTree, twistiePixels } from 'vs/workbench/parts/debug/browser/baseDebugView'; import { IAccessibilityProvider, ITree, IRenderer, IDataSource } from 'vs/base/parts/tree/browser/tree'; -import { IDebugSession, IDebugService, IModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -201,7 +201,7 @@ class RootFolderTreeItem extends BaseTreeItem { class RootTreeItem extends BaseTreeItem { - constructor(private _debugModel: IModel, private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService) { + constructor(private _debugModel: IDebugModel, private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService) { super(undefined, 'Root'); this._debugModel.getSessions().forEach(session => { this.add(session); diff --git a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css b/src/vs/workbench/parts/debug/browser/media/debugViewlet.css index 48ab4d9099..542b2026e4 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/parts/debug/browser/media/debugViewlet.css @@ -216,6 +216,7 @@ flex: 1; flex-shrink: 0; min-width: fit-content; + min-width: -moz-fit-content; } .debug-viewlet .debug-call-stack .stack-frame.subtle { diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index ba31334f87..528f843221 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -365,7 +365,7 @@ export interface IViewModel extends ITreeElement { onDidSelectExpression: Event; } -export interface IModel extends ITreeElement { +export interface IDebugModel extends ITreeElement { getSessions(): ReadonlyArray; getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): ReadonlyArray; areBreakpointsActivated(): boolean; @@ -776,7 +776,7 @@ export interface IDebugService { /** * Gets the current debug model. */ - getModel(): IModel; + getModel(): IDebugModel; /** * Gets the current view model. diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index ec1f0121bd..1d1db88a38 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -16,7 +16,7 @@ import { isObject, isString, isUndefinedOrNull } from 'vs/base/common/types'; import { distinct } from 'vs/base/common/arrays'; import { Range, IRange } from 'vs/editor/common/core/range'; import { - ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IModel, IReplElementSource, + ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; @@ -723,7 +723,7 @@ export class ThreadAndSessionIds implements ITreeElement { } } -export class Model implements IModel { +export class DebugModel implements IDebugModel { private sessions: IDebugSession[]; private toDispose: lifecycle.IDisposable[]; diff --git a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts index df72e464cf..dead98d5e6 100644 --- a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts @@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; import { TreeViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug'; -import { Thread, StackFrame, ThreadAndSessionIds, Model } from 'vs/workbench/parts/debug/common/debugModel'; +import { Thread, StackFrame, ThreadAndSessionIds, DebugModel } from 'vs/workbench/parts/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -313,14 +313,14 @@ class CallStackDataSource implements IDataSource { } public hasChildren(tree: ITree, element: any): boolean { - return element instanceof Model || element instanceof DebugSession || (element instanceof Thread && (element).stopped); + return element instanceof DebugModel || element instanceof DebugSession || (element instanceof Thread && (element).stopped); } public getChildren(tree: ITree, element: any): TPromise { if (element instanceof Thread) { return this.getThreadChildren(element); } - if (element instanceof Model) { + if (element instanceof DebugModel) { return TPromise.as(element.getSessions()); } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index f004ab8879..bef854be3f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -22,7 +22,7 @@ import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/file import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { Model, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; +import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; import * as debugactions from 'vs/workbench/parts/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/parts/debug/electron-browser/debugConfigurationManager'; @@ -47,7 +47,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IModel, IReplElementSource, IEnablement, IBreakpoint, IBreakpointData, IExpression, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IReplElementSource, IEnablement, IBreakpoint, IBreakpointData, IExpression, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; import { isExtensionHostDebugging } from 'vs/workbench/parts/debug/common/debugUtils'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; @@ -63,7 +63,7 @@ export class DebugService implements IDebugService { private readonly _onDidNewSession: Emitter; private readonly _onWillNewSession: Emitter; private readonly _onDidEndSession: Emitter; - private model: Model; + private model: DebugModel; private viewModel: ViewModel; private configurationManager: ConfigurationManager; private allSessions = new Map(); @@ -114,7 +114,7 @@ export class DebugService implements IDebugService { this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService); this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); - this.model = new Model(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(), + this.model = new DebugModel(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadWatchExpressions(), this.textFileService); this.toDispose.push(this.model); @@ -158,7 +158,7 @@ export class DebugService implements IDebugService { return this.allSessions.get(sessionId); } - getModel(): IModel { + getModel(): IDebugModel { return this.model; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts index 3118d64989..6d7c1c7bf2 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts @@ -16,7 +16,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, ActualBreakpoints, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; -import { Thread, ExpressionContainer, Model } from 'vs/workbench/parts/debug/common/debugModel'; +import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/parts/debug/common/debugModel'; import { RawDebugSession } from 'vs/workbench/parts/debug/electron-browser/rawDebugSession'; import product from 'vs/platform/node/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -49,7 +49,7 @@ export class DebugSession implements IDebugSession { constructor( private _configuration: { resolved: IConfig, unresolved: IConfig }, public root: IWorkspaceFolder, - private model: Model, + private model: DebugModel, @INotificationService private notificationService: INotificationService, @IDebugService private debugService: IDebugService, @ITelemetryService private telemetryService: ITelemetryService, diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 008bb89550..3dcd1f4fc3 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -15,7 +15,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults'; import { IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; -import { Model, RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel'; +import { DebugModel, RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel'; import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/browser/baseDebugView'; import { ClearReplAction, ReplCollapseAllAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; @@ -34,11 +34,11 @@ export class ReplExpressionsDataSource implements IDataSource { } public hasChildren(tree: ITree, element: any): boolean { - return element instanceof Model || (element).hasChildren; + return element instanceof DebugModel || (element).hasChildren; } public getChildren(tree: ITree, element: any): TPromise { - if (element instanceof Model) { + if (element instanceof DebugModel) { return TPromise.as(element.getReplElements()); } if (element instanceof RawObjectReplElement) { diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index 36de5bfdf7..9727aea0fa 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -12,7 +12,7 @@ import { IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider, import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { TreeViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/parts/debug/common/debug'; -import { Expression, Variable, Model } from 'vs/workbench/parts/debug/common/debugModel'; +import { Expression, Variable, DebugModel } from 'vs/workbench/parts/debug/common/debugModel'; import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -202,7 +202,7 @@ class WatchExpressionsDataSource implements IDataSource { } public hasChildren(tree: ITree, element: any): boolean { - if (element instanceof Model) { + if (element instanceof DebugModel) { return true; } @@ -211,7 +211,7 @@ class WatchExpressionsDataSource implements IDataSource { } public getChildren(tree: ITree, element: any): TPromise { - if (element instanceof Model) { + if (element instanceof DebugModel) { const viewModel = this.debugService.getViewModel(); return TPromise.join(element.getWatchExpressions().map(we => we.name ? we.evaluate(viewModel.focusedSession, viewModel.focusedStackFrame, 'watch').then(() => we) : TPromise.as(we))); @@ -344,7 +344,7 @@ class WatchExpressionsController extends BaseDebugController { const expression = element; this.debugService.getViewModel().setSelectedExpression(expression); return true; - } else if (element instanceof Model && event.detail === 2) { + } else if (element instanceof DebugModel && event.detail === 2) { // Double click in watch panel triggers to add a new watch expression this.debugService.addWatchExpression(); return true; @@ -376,8 +376,8 @@ class WatchExpressionsDragAndDrop extends DefaultDragAndDrop { return elements[0].name; } - public onDragOver(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): IDragOverReaction { - if (target instanceof Expression || target instanceof Model) { + public onDragOver(tree: ITree, data: IDragAndDropData, target: Expression | DebugModel, originalEvent: DragMouseEvent): IDragOverReaction { + if (target instanceof Expression || target instanceof DebugModel) { return { accept: true, autoExpand: false @@ -387,12 +387,12 @@ class WatchExpressionsDragAndDrop extends DefaultDragAndDrop { return DRAG_OVER_REJECT; } - public drop(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): void { + public drop(tree: ITree, data: IDragAndDropData, target: Expression | DebugModel, originalEvent: DragMouseEvent): void { const draggedData = data.getData(); if (Array.isArray(draggedData)) { const draggedElement = draggedData[0]; const watches = this.debugService.getModel().getWatchExpressions(); - const position = target instanceof Model ? watches.length - 1 : watches.indexOf(target); + const position = target instanceof DebugModel ? watches.length - 1 : watches.indexOf(target); this.debugService.moveWatchExpression(draggedElement.getId(), position); } } diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index cb6808251c..f88e404209 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -334,9 +334,10 @@ export class DebugAdapter extends StreamDebugAdapter { options.cwd = this.adapterExecutable.cwd; } const child = cp.fork(this.adapterExecutable.args[0], this.adapterExecutable.args.slice(1), options); - if (!child.pid) { - reject(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", this.adapterExecutable.args[0]))); - } + // NOTE@coder: our version doesn't provide a pid ATM. + // if (!child.pid) { + // reject(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", this.adapterExecutable.args[0]))); + // } this.serverProcess = child; resolve(null); } else { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index 3dcd2bfe3e..09d4b06ed4 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, ActualBreakpoints, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, ActualBreakpoints, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { ISuggestion } from 'vs/editor/common/modes'; @@ -109,7 +109,7 @@ export class MockDebugService implements IDebugService { return TPromise.as(null); } - public getModel(): IModel { + public getModel(): IDebugModel { return null; } diff --git a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts index 60007a799e..bc88b90a30 100644 --- a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts @@ -6,18 +6,18 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; -import { SimpleReplElement, Model, Expression, RawObjectReplElement, StackFrame, Thread } from 'vs/workbench/parts/debug/common/debugModel'; +import { SimpleReplElement, DebugModel, Expression, RawObjectReplElement, StackFrame, Thread } from 'vs/workbench/parts/debug/common/debugModel'; import * as sinon from 'sinon'; import { MockRawSession } from 'vs/workbench/parts/debug/test/common/mockDebug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; suite('Debug - Model', () => { - let model: Model; + let model: DebugModel; let rawSession: MockRawSession; setup(() => { - model = new Model([], true, [], [], [], { isDirty: (e: any) => false }); + model = new DebugModel([], true, [], [], [], { isDirty: (e: any) => false }); rawSession = new MockRawSession(); }); diff --git a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts index 208b281d85..9d833fc8a5 100644 --- a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts @@ -130,12 +130,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OPEN_NATIVE_CONSOLE_COMMAND_ID, - title: { value: nls.localize('globalConsoleAction', "Open New Terminal"), original: 'Open New Terminal' } - } -}); +// MenuRegistry.appendMenuItem(MenuId.CommandPalette, { +// command: { +// id: OPEN_NATIVE_CONSOLE_COMMAND_ID, +// title: { value: nls.localize('globalConsoleAction', "Open New Terminal"), original: 'Open New Terminal' } +// } +// }); const openConsoleCommand = { id: OPEN_IN_TERMINAL_COMMAND_ID, diff --git a/src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts index f8e86e08d8..7402bfcc6e 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts @@ -128,15 +128,16 @@ export class RatingsWidget implements IDisposable { return; } - const rating = Math.round(this.extension.rating * 2) / 2; + const rating = this.extension.stars; + // const rating = Math.round(this.extension.rating * 2) / 2; - if (this.extension.rating === null) { - return; - } + // if (this.extension.rating === null) { + // return; + // } - if (this.options.small && this.extension.ratingCount === 0) { - return; - } + // if (this.options.small && this.extension.ratingCount === 0) { + // return; + // } if (this.options.small) { append(this.container, $('span.full.star')); diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index ca6f8e8204..d9d82efe34 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -46,6 +46,7 @@ export interface IExtension { licenseUrl: string; installCount: number; rating: number; + stars: number; ratingCount: number; outdated: boolean; enablementState: EnablementState; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 487436a01e..1d4cb6ddf9 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -52,7 +52,7 @@ import { ShowCurrentReleaseNotesAction } from 'vs/workbench/parts/update/electro import { KeybindingParser } from 'vs/base/common/keybindingParser'; function renderBody(body: string): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://'); + const styleSheetPath = ""; // require.toUrl('css-loader!style-loader!./media/markdown.css').replace('file://', 'vscode-core-resource://'); return ` diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 89f50e43e3..2dc5fc09d4 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -443,7 +443,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // group ids by pattern, like {**/*.md} -> [ext.foo1, ext.bar2] this._availableRecommendations = Object.create(null); forEach(extensionTips, entry => { - let { key: id, value: pattern } = entry; + let { key: id, value: pattern }: { [key: string]: any } = entry; let ids = this._availableRecommendations[pattern]; if (!ids) { this._availableRecommendations[pattern] = [id.toLowerCase()]; @@ -454,7 +454,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe forEach(product.extensionImportantTips, entry => { let { key: id, value } = entry; - const { pattern } = value; + const { pattern }: any = value; let ids = this._availableRecommendations[pattern]; if (!ids) { this._availableRecommendations[pattern] = [id.toLowerCase()]; @@ -631,6 +631,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]')); const mimeTypes = result[1]; + // NOTE@coder we are handling these notifications. + fileExtensionSuggestionIgnoreList.push(...[ + "c", "cpp", "cc", "go", "php", "py", "rb", "rs", + ]); + if (fileExtension) { fileExtension = fileExtension.substr(1); // Strip the dot } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index a38def022f..216ef27590 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -171,8 +171,8 @@ actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: U const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); -const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); -actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel); +// const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); +// actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel); const disableAllAction = new SyncActionDescriptor(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL); actionRegistry.registerWorkbenchAction(disableAllAction, 'Extensions: Disable All Installed Extensions', ExtensionsLabel); diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index e8e1133868..3dc3c07c04 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -142,6 +142,7 @@ padding-left: 6px; flex: 1; min-width: fit-content; + min-width: -moz-fit-content; } .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) { diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css b/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css index 521ae95b08..9c0cfb035d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -body { +.vscode-body { padding: 10px 20px; line-height: 22px; } -img { +.vscode-body img { max-width: 100%; max-height: 100%; } -a { +.vscode-body a { text-decoration: none; } -a:hover { +.vscode-body a:hover { text-decoration: underline; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index fb9af88c96..cea8b08112 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -614,7 +614,7 @@ class SaveExtensionHostProfileAction extends Action { let dataToWrite: object = profileInfo.data; if (this._environmentService.isBuilt) { - const profiler = await import('v8-inspect-profiler'); + const profiler = require('v8-inspect-profiler'); // when running from a not-development-build we remove // absolute filenames because we don't want to reveal anything // about users. We also append the `.txt` suffix to make it diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index b4ab2011a4..7de8be820f 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -39,6 +39,8 @@ import { groupBy } from 'vs/base/common/collections'; import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { getFetchUrl } from 'coder/api'; +import { events } from 'coder/analytics'; interface IExtensionStateProvider { (extension: Extension): T; @@ -122,7 +124,7 @@ class Extension implements IExtension { } get iconUrl(): string { - return this.galleryIconUrl || this.localIconUrl || this.defaultIconUrl; + return this.galleryIconUrl || this.localIconUrl ? getFetchUrl(this.galleryIconUrl || this.localIconUrl) : this.defaultIconUrl; } get iconUrlFallback(): string { @@ -144,18 +146,20 @@ class Extension implements IExtension { return this.gallery && this.gallery.assets.icon.fallbackUri; } + // NOTE@coder: changed from require.toUrl to require.context. private get defaultIconUrl(): string { + const context = (require as any).context("../electron-browser/media/", true, /.*/); if (this.type === LocalExtensionType.System) { if (this.local.manifest && this.local.manifest.contributes) { if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) { - return require.toUrl('../electron-browser/media/theme-icon.png'); + return context('./theme-icon.png'); } if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) { - return require.toUrl('../electron-browser/media/language-icon.svg'); + return context('./language-icon.png'); } } } - return require.toUrl('../electron-browser/media/defaultIcon.png'); + return context('./defaultIcon.png'); } get repository(): string { @@ -176,6 +180,10 @@ class Extension implements IExtension { return this.gallery ? this.gallery.installCount : null; } + get stars(): number { + return this.gallery ? this.gallery.stars : null; + } + get rating(): number { return this.gallery ? this.gallery.rating : null; } @@ -621,11 +629,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, const ids = [], names = []; for (const installed of this.installed) { if (installed.type === LocalExtensionType.User) { - if (installed.uuid) { - ids.push(installed.uuid); - } else { + // if (installed.uuid) { + // ids.push(installed.uuid); + // } else { names.push(installed.id); - } + // } } } @@ -690,6 +698,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return TPromise.wrapError(new Error('Missing gallery')); } + events.ide.installExtension.track(extension.id); return this.progressService.withProgress({ location: ProgressLocation.Extensions, title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'), @@ -714,6 +723,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return TPromise.wrapError(new Error('Missing local')); } + events.ide.uninstallExtension.track(ext.id); this.logService.info(`Requested uninstalling the extension ${extension.id} from window ${this.windowService.getCurrentWindowId()}`); return this.progressService.withProgress({ location: ProgressLocation.Extensions, @@ -741,7 +751,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): TPromise { - const extension = this.local.filter(e => areSameExtensions(extensionIdentifier, e.local.identifier))[0]; + // NOTE@coder: When installing multiple extensions, sometimes other extensions can have an undefined `local`. + const extension = this.local.filter(e => e.local && areSameExtensions(extensionIdentifier, e.local.identifier))[0]; if (extension) { const disabledDepencies = this.getExtensionsRecursively([extension], this.local, EnablementState.Enabled, { dependencies: true, pack: false }); if (disabledDepencies.length) { @@ -1048,4 +1059,4 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, this.syncDelayer.cancel(); this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index 9db7cbedda..b9f65b0c2b 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -157,7 +157,9 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { } provideTextContent(resource: URI): TPromise { - const fileOnDiskResource = URI.file(resource.fsPath); + // NOTE@coder: keep using conflict scheme so we can load it differently. + // const fileOnDiskResource = URI.file(resource.fsPath); + const fileOnDiskResource = resource; // Make sure our file from disk is resolved up to date return this.resolveEditorModel(resource).then(codeEditorModel => { @@ -181,7 +183,9 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { } private resolveEditorModel(resource: URI, createAsNeeded = true): TPromise { - const fileOnDiskResource = URI.file(resource.fsPath); + // NOTE@coder: keep using conflict scheme so we can load it differently. + // const fileOnDiskResource = URI.file(resource.fsPath); + const fileOnDiskResource = resource; return this.textFileService.resolveTextContent(fileOnDiskResource).then(content => { let codeEditorModel = this.modelService.getModel(resource); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts index 4ed5466ae8..3c21272563 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -142,13 +142,15 @@ function appendEditorTitleContextMenuItem(id: string, title: string, when: Conte } // Editor Title Menu for Conflict Resolution +// NOTE@coder use context to resolve icons. +const context = (require as any).context("./media/", true, /.*/); appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite disk contents"), { - light: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/check.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/check-inverse.svg`)) + light: URI.parse(context(`./check.svg`)), + dark: URI.parse(context(`./check-inverse.svg`)) }, -10, acceptLocalChangesCommand); appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to content on disk"), { - light: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/undo.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/undo-inverse.svg`)) + light: URI.parse(context(`./undo.svg`)), + dark: URI.parse(context(`./undo-inverse.svg`)) }, -9, revertLocalChangesCommand); function appendSaveConflictEditorTitleAction(id: string, title: string, iconLocation: { dark: URI; light?: URI; }, order: number, command: ICommandHandler): void { diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 46d4ba300f..834f277486 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -546,7 +546,8 @@ class BaseDeleteFileAction extends BaseFileAction { super('moveFileToTrash', MOVE_FILE_TO_TRASH_LABEL, fileService, notificationService, textFileService); this.tree = tree; - this.useTrash = useTrash && elements.every(e => !paths.isUNC(e.resource.fsPath)); // on UNC shares there is no trash + // NOTE@coder: skip the trash for now. + this.useTrash = false; //useTrash && elements.every(e => !paths.isUNC(e.resource.fsPath)); // on UNC shares there is no trash this._updateEnablement(); } diff --git a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css index c3fc2504f7..8c7992e852 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css @@ -52,6 +52,7 @@ .explorer-viewlet .panel-header .count { min-width: fit-content; + min-width: -moz-fit-content; } .explorer-viewlet .panel-header .monaco-count-badge.hidden { diff --git a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts index 27a3136bda..21ec2a0c67 100644 --- a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts @@ -33,6 +33,7 @@ import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { once } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ConflictResolution } from 'coder/common'; // NOTE@coder. export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -80,8 +81,11 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I let isActiveEditorSaveConflictResolution = false; let activeConflictResolutionResource: URI; + // NOTE@coder: show buttons if the resource is a conflict resource. + // `originalInput` will *not* be `ResourceEditorInput` because we registered + // a provider for the conflict scheme which results in it becoming a `FileEditorInput`. const activeInput = this.editorService.activeEditor; - if (activeInput instanceof DiffEditorInput && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { + if (activeInput instanceof DiffEditorInput) { // && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { const resource = activeInput.originalInput.getResource(); if (resource && resource.scheme === CONFLICT_RESOLUTION_SCHEME) { isActiveEditorSaveConflictResolution = true; @@ -328,7 +332,8 @@ export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: modelService.updateModel(model.textEditorModel, createTextBufferFactoryFromSnapshot(localModelSnapshot)); // Trigger save - return model.save().then(() => { + // NOTE@coder: add conflict resolution. + return model.save({ accept: ConflictResolution.ACCEPT_LOCAL, force: true }).then(() => { // Reopen file input return editorService.openEditor({ resource: model.getResource() }, group).then(() => { @@ -357,15 +362,20 @@ export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions // Revert on model - return model.revert().then(() => { + // NOTE@coder: add conflict resolution. + return model.save({ accept: ConflictResolution.ACCEPT_DISK, force: true }).then(() => { - // Reopen file input - return editorService.openEditor({ resource: model.getResource() }, group).then(() => { + // NOTE@coder: revert so dirty indicator goes away. + return model.revert().then(() => { - // Clean up - group.closeEditor(editor); - editor.dispose(); - reference.dispose(); + // Reopen file input + return editorService.openEditor({ resource: model.getResource() }, group).then(() => { + + // Clean up + group.closeEditor(editor); + editor.dispose(); + reference.dispose(); + }); }); }); }); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index f64cf9a303..898b8269e4 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -56,6 +56,7 @@ import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { Upload } from 'coder/upload'; export class FileDataSource implements IDataSource { constructor( @@ -762,7 +763,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { @IInstantiationService instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IWindowService private windowService: IWindowService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, ) { super(stat => this.statToResource(stat), instantiationService); @@ -921,6 +922,11 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { } private handleExternalDrop(tree: ITree, data: DesktopDragAndDropData, target: ExplorerItem | Model, originalEvent: DragMouseEvent): TPromise { + // NOTE@coder: modified to work with browser uploads. + const uri = (target instanceof ExplorerItem ? target : target.roots[0]).resource; + const uploader = this.instantiationService.createInstance(Upload); + return uploader.uploadDropped(originalEvent.browserEvent as DragEvent, uri); + const droppedResources = extractResources(originalEvent.browserEvent as DragEvent, true); // Check for dropped external files to be folders diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts index b0df3ba7f1..7b68143490 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts @@ -26,7 +26,10 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { ) { super(); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true }); + // NOTE@coder: This channel only seems to be used when loading the app + // (electron-main/main.ts and electron-main/app.ts) and we skip all of that, + // so it is never actually created or written to. + // outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true }); outputChannelRegistry.registerChannel({ id: Constants.sharedLogChannelId, label: nls.localize('sharedLog', "Shared"), file: URI.file(join(environmentService.logsPath, `sharedprocess.log`)), log: true }); outputChannelRegistry.registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: URI.file(join(environmentService.logsPath, `renderer${windowService.getCurrentWindowId()}.log`)), log: true }); @@ -37,4 +40,4 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Starting); \ No newline at end of file +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Starting); diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/parts/output/common/outputLinkProvider.ts index 54d7402f8a..514e5e14ef 100644 --- a/src/vs/workbench/parts/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/parts/output/common/outputLinkProvider.ts @@ -78,6 +78,8 @@ export class OutputLinkProvider { } private provideLinks(modelUri: URI): TPromise { + // TODO@coder: get this working. + return TPromise.wrap([]); return this.getOrCreateWorker().withSyncedResources([modelUri]).then(linkComputer => { return linkComputer.computeLinks(modelUri.toString()); }); diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts index 37b449ca94..c488706bd0 100644 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -183,6 +183,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out // Use one rotating file to check for main file reset this.appender = new OutputAppender(this.id, this.file.fsPath); + this.rotatingFilePath = `${outputChannelDescriptor.id}.1.log`; this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index b694bf98a1..f258e1051e 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -955,8 +955,7 @@ export class DirtyDiffModel { private _editorModel: ITextModel, @ISCMService private scmService: ISCMService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, - @ITextModelService private textModelResolverService: ITextModelService, - @IConfigurationService private configurationService: IConfigurationService + @ITextModelService private textModelResolverService: ITextModelService ) { this.diffDelayer = new ThrottledDelayer(200); @@ -998,7 +997,7 @@ export class DirtyDiffModel { changes = []; } - const diff = sortedDiff(this._changes, changes, compareChanges); + const diff = sortedDiff(this._changes || [], changes || [], compareChanges); this._changes = changes; if (diff.length > 0) { @@ -1017,9 +1016,7 @@ export class DirtyDiffModel { return TPromise.as([]); // Files too large } - const ignoreTrimWhitespace = this.configurationService.getValue('diffEditor.ignoreTrimWhitespace'); - - return this.editorWorkerService.computeDirtyDiff(originalURI, this._editorModel.uri, ignoreTrimWhitespace); + return this.editorWorkerService.computeDirtyDiff(originalURI, this._editorModel.uri, false); }); } diff --git a/src/vs/workbench/parts/terminal/browser/terminalTab.ts b/src/vs/workbench/parts/terminal/browser/terminalTab.ts index 651f27fb81..a4132b433f 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/browser/terminalTab.ts @@ -9,6 +9,7 @@ import { Event, Emitter, anyEvent } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { IPartService, Position } from 'vs/workbench/services/part/common/partService'; +import { generate } from 'vs/workbench/parts/terminal/common/terminalId'; const SPLIT_PANE_MIN_SIZE = 120; @@ -224,6 +225,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper: ITerminalConfigHelper, private _container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, + initialId: number = generate(), @ITerminalService private readonly _terminalService: ITerminalService, @IPartService private readonly _partService: IPartService ) { @@ -236,6 +238,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper, undefined, shellLaunchConfig, + initialId, true); this._terminalInstances.push(instance); this._initInstanceListeners(instance); @@ -359,13 +362,15 @@ export class TerminalTab extends Disposable implements ITerminalTab { public split( terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, - shellLaunchConfig: IShellLaunchConfig + shellLaunchConfig: IShellLaunchConfig, + id: number, ): ITerminalInstance { const instance = this._terminalService.createInstance( terminalFocusContextKey, configHelper, undefined, shellLaunchConfig, + id, true); this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index f44f184e92..e687cea879 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -208,7 +208,7 @@ export interface ITerminalService { /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ - createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; + createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, id:number, doCreateProcess: boolean): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; getTabLabels(): string[]; @@ -233,6 +233,11 @@ export interface ITerminalService { selectDefaultWindowsShell(): TPromise; setWorkspaceShellAllowed(isAllowed: boolean): void; + /** + * Restores terminals stored + */ + restore(): void; + requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void; } @@ -258,7 +263,7 @@ export interface ITerminalTab { setVisible(visible: boolean): void; layout(width: number, height: number): void; addDisposable(disposable: IDisposable): void; - split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig, id: number): ITerminalInstance; } export interface ITerminalDimensions { @@ -483,7 +488,7 @@ export interface ITerminalInstance { /** * Focuses and pastes the contents of the clipboard into the terminal instance. */ - paste(): void; + paste(): Promise; /** * Send text to the terminal instance. The text is written to the stdin of the underlying pty @@ -594,7 +599,7 @@ export interface ITerminalProcessManager extends IDisposable { addDisposable(disposable: IDisposable); dispose(immediate?: boolean); - createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number); + createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, id: number); write(data: string): void; setDimensions(cols: number, rows: number): void; } diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 667e5e94f3..4cbebc3c8b 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -10,10 +10,19 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; +import { generate as generateTerminalId } from 'vs/workbench/parts/terminal/common/terminalId'; + +export type TerminalStorage = ReadonlyArray<{ + // The name of the socket + readonly terminals: ReadonlyArray; +}>; export abstract class TerminalService implements ITerminalService { + + private static readonly workbenchTerminalState = "workbench.terminal.state"; + public _serviceBrand: any; protected _isShuttingDown: boolean; @@ -23,7 +32,7 @@ export abstract class TerminalService implements ITerminalService { protected _terminalTabs: ITerminalTab[]; protected abstract _terminalInstances: ITerminalInstance[]; private _findState: FindReplaceState; - + private _restored: boolean; private _activeTabIndex: number; public get activeTabIndex(): number { return this._activeTabIndex; } @@ -48,6 +57,8 @@ export abstract class TerminalService implements ITerminalService { public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } protected readonly _onActiveInstanceChanged: Emitter = new Emitter(); public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } + protected readonly _onTabAdded: Emitter = new Emitter(); + public get onTabAdded(): Event { return this._onTabAdded.event; } protected readonly _onTabDisposed: Emitter = new Emitter(); public get onTabDisposed(): Event { return this._onTabDisposed.event; } @@ -58,7 +69,7 @@ export abstract class TerminalService implements ITerminalService { @IPanelService protected readonly _panelService: IPanelService, @IPartService private readonly _partService: IPartService, @ILifecycleService lifecycleService: ILifecycleService, - @IStorageService protected readonly _storageService: IStorageService + @IStorageService protected readonly _storageService: IStorageService, ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -70,6 +81,59 @@ export abstract class TerminalService implements ITerminalService { this.onTabDisposed(tab => this._removeTab(tab)); this._handleContextKeys(); + + } + + public restore(): void { + if (this._restored) { + return; + } + this._restored = true; + let beingRestored: boolean = false; + const updateStorage = () => { + if (beingRestored) { + return; + } + const storage: TerminalStorage = this._terminalTabs.map((tab) => { + return { + terminals: tab.terminalInstances.map((instance) => instance.id), + }; + }); + + this._storageService.store(TerminalService.workbenchTerminalState, JSON.stringify(storage), StorageScope.GLOBAL); + }; + + this.onTabAdded((tab) => { + tab.onInstancesChanged(() => { + updateStorage(); + }); + + updateStorage(); + }); + + try { + const values: TerminalStorage = JSON.parse(this._storageService.get(TerminalService.workbenchTerminalState, StorageScope.GLOBAL, "[]")); + + beingRestored = true; + values.forEach((value, index) => { + const terminals = value.terminals; + if (terminals.length > 0) { + let terminal: ITerminalInstance; + for (let i = 0; i < terminals.length; i++) { + const terminalId = terminals[i]; + if (!terminal) { + terminal = this.createTerminal(undefined, undefined, terminalId); + } else { + this.splitInstance(terminal, undefined, terminalId); + } + } + } + }); + beingRestored = false; + } catch (ex) { + console.error(`Failed to restore terminals: ${ex}`); + } + } private _handleContextKeys(): void { @@ -83,9 +147,9 @@ export abstract class TerminalService implements ITerminalService { } protected abstract _showTerminalCloseConfirmation(): TPromise; - public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; + public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean, id?: number): ITerminalInstance; public abstract createTerminalRenderer(name: string): ITerminalInstance; - public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; + public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, id: number, doCreateProcess: boolean): ITerminalInstance; public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; public abstract selectDefaultWindowsShell(): TPromise; public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; @@ -256,13 +320,13 @@ export abstract class TerminalService implements ITerminalService { this.setActiveTabByIndex(newIndex); } - public splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig: IShellLaunchConfig = {}): void { + public splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig: IShellLaunchConfig = {}, id?: number): void { const tab = this._getTabForInstance(instanceToSplit); if (!tab) { return; } - const instance = tab.split(this._terminalFocusContextKey, this.configHelper, shellLaunchConfig); + const instance = tab.split(this._terminalFocusContextKey, this.configHelper, shellLaunchConfig, id || generateTerminalId()); this._initInstanceListeners(instance); this._onInstancesChanged.fire(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 378144e25f..f3547a7a45 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -12,7 +12,7 @@ import * as nls from 'vs/nls'; import * as panel from 'vs/workbench/browser/panel'; import * as platform from 'vs/base/common/platform'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, DEFAULT_LINE_HEIGHT, DEFAULT_LETTER_SPACING, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, DEFAULT_LINE_HEIGHT, DEFAULT_LETTER_SPACING, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal'; import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from 'vs/workbench/parts/terminal/node/terminal'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -38,6 +38,7 @@ import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/term import { TerminalPickerHandler } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; import { setupTerminalMenu } from 'vs/workbench/parts/terminal/common/terminalMenu'; +import { nativeClipboard } from 'coder/workbench'; const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); @@ -404,9 +405,11 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTer actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V }, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V }, // Don't apply to Mac since cmd+v works mac: { primary: null } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); + // Conflicting expressions so never enabled +}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, nativeClipboard.contextKey)), 'Terminal: Paste into Active Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { // Don't use ctrl+a by default as that would override the common go to start // of prompt shell binding diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index c051ff13e4..f6876322aa 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -30,6 +30,7 @@ import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminal import { Command } from 'vs/editor/browser/editorExtensions'; import { timeout } from 'vs/base/common/async'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; +import { nativeClipboard } from 'coder/workbench'; export const TERMINAL_PICKER_PREFIX = 'term '; @@ -556,19 +557,31 @@ export class TerminalPasteAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.PASTE; public static readonly LABEL = nls.localize('workbench.action.terminal.paste', "Paste into Active Terminal"); - public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.paste.short', "Paste"); + public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.paste.short', nativeClipboard.instance ? "Paste" : "Paste (Must use keybind)"); constructor( id: string, label: string, @ITerminalService private terminalService: ITerminalService ) { super(id, label); + const update = (v: boolean) => { + if (v) { + this._setLabel("Paste"); + } else { + this._setLabel("Paste (Must use keybind)"); + } + this._setEnabled(v); + }; + update(nativeClipboard.isEnabled); + nativeClipboard.onChange((e) => { + update(e); + }); } public run(event?: any): TPromise { const instance = this.terminalService.getActiveOrCreateInstance(); if (instance) { - instance.paste(); + return instance.paste(); } return TPromise.as(void 0); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 4d4f6645ad..ede8ff0c32 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -214,7 +214,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's // safe to assume that this was used by accident as Sysnative does not // exist and will break the terminal in non-WoW64 environments. - if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { + if (false && platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase(); if (shell.executable.toLowerCase().indexOf(sysnativePath) === 0) { shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length)); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index f0ba1ed3a7..b38c87ab25 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -34,6 +34,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { TerminalCommandTracker } from 'vs/workbench/parts/terminal/node/terminalCommandTracker'; import { TerminalProcessManager } from './terminalProcessManager'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { nativeClipboard } from 'coder/workbench'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -117,6 +118,7 @@ export class TerminalInstance implements ITerminalInstance { private readonly _configHelper: TerminalConfigHelper, private _container: HTMLElement, private _shellLaunchConfig: IShellLaunchConfig, + id: number, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @INotificationService private readonly _notificationService: INotificationService, @@ -134,7 +136,7 @@ export class TerminalInstance implements ITerminalInstance { this._hadFocusOnExit = false; this._isVisible = false; this._isDisposed = false; - this._id = TerminalInstance._idCounter++; + this._id = typeof id !== "undefined" ? id : TerminalInstance._idCounter++; this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService); this.disableLayout = false; @@ -263,7 +265,7 @@ export class TerminalInstance implements ITerminalInstance { */ protected async _createXterm(): Promise { if (!Terminal) { - Terminal = (await import('vscode-xterm')).Terminal; + Terminal = (require('vscode-xterm')).Terminal; // Enable xterm.js addons Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); @@ -615,9 +617,19 @@ export class TerminalInstance implements ITerminalInstance { return this._xtermReadyPromise.then(() => this.focus(force)); } - public paste(): void { + public async paste(): Promise { this.focus(); - document.execCommand('paste'); + + if (nativeClipboard.instance) { + try { + const text = await nativeClipboard.instance.readText(); + this.sendText(text, false); + } catch (ex) { + this._notificationService.error(`You must allow clipboard access to paste!`); + } + } else { + document.execCommand('paste'); + } } public write(text: string): void { @@ -730,7 +742,7 @@ export class TerminalInstance implements ITerminalInstance { this._messageTitleDisposable = this._processManager.onProcessTitle(title => this.setTitle(title ? title : '', true)); } - if (platform.isWindows) { + if (false && platform.isWindows) { this._processManager.ptyProcessReady.then(() => { this._xtermReadyPromise.then(() => { if (!this._isDisposed) { @@ -743,7 +755,7 @@ export class TerminalInstance implements ITerminalInstance { // Create the process asynchronously to allow the terminal's container // to be created so dimensions are accurate setTimeout(() => { - this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows); + this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this.id); }, 0); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index d5ccc52fb7..bed7794981 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -114,7 +114,8 @@ export class TerminalPanel extends Panel { this._updateTheme(); } else { return super.setVisible(visible).then(() => { - // Check if instances were already restored as part of workbench restore + this._terminalService.restore(); + if (this._terminalService.terminalInstances.length === 0) { this._terminalService.createTerminal(); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts index 818b359ea9..d8645c3369 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts @@ -86,7 +86,8 @@ export class TerminalProcessManager implements ITerminalProcessManager { public createProcess( shellLaunchConfig: IShellLaunchConfig, cols: number, - rows: number + rows: number, + id: number, ): void { const extensionHostOwned = (this._configHelper.config).extHostProcess; if (extensionHostOwned) { @@ -120,7 +121,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { terminalEnvironment.addTerminalEnvironmentKeys(env, locale); this._logService.debug(`Terminal process launching`, shellLaunchConfig, this.initialCwd, cols, rows, env); - this._process = new TerminalProcess(shellLaunchConfig, this.initialCwd, cols, rows, env); + this._process = new TerminalProcess(shellLaunchConfig, this.initialCwd, cols, rows, env, id); } this.processState = ProcessState.LAUNCHING; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index fb61123763..b4b0e39d86 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -29,6 +29,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; +import { generate } from 'vs/workbench/parts/terminal/common/terminalId'; export class TerminalService extends AbstractTerminalService implements ITerminalService { private _configHelper: TerminalConfigHelper; @@ -71,13 +72,14 @@ export class TerminalService extends AbstractTerminalService implements ITermina }); } - public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { + public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean, id?: number): ITerminalInstance { const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalFocusContextKey, this._configHelper, this._terminalContainer, - shell); + shell, id || generate()); this._terminalTabs.push(terminalTab); + this._onTabAdded.fire(terminalTab); const instance = terminalTab.terminalInstances[0]; terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); @@ -95,8 +97,8 @@ export class TerminalService extends AbstractTerminalService implements ITermina return this.createTerminal({ name, isRendererOnly: true }); } - public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance { - const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); + public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, id: number, doCreateProcess: boolean): ITerminalInstance { + const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig, id); this._onInstanceCreated.fire(instance); return instance; } diff --git a/src/vs/workbench/parts/terminal/node/terminal.ts b/src/vs/workbench/parts/terminal/node/terminal.ts index 7eaadc133a..c6ea0a7fb7 100644 --- a/src/vs/workbench/parts/terminal/node/terminal.ts +++ b/src/vs/workbench/parts/terminal/node/terminal.ts @@ -33,7 +33,7 @@ export interface ITerminalChildProcess { let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string = null; export function getTerminalDefaultShellUnixLike(): string { if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) { - let unixLikeTerminal = 'sh'; + let unixLikeTerminal = 'eval $SHELL'; if (!platform.isWindows && process.env.SHELL) { unixLikeTerminal = process.env.SHELL; // Some systems have $SHELL set to /bin/false which breaks the terminal @@ -57,7 +57,8 @@ export function getTerminalDefaultShellWindows(): string { return _TERMINAL_DEFAULT_SHELL_WINDOWS; } -if (platform.isLinux) { +// Add this because we are always on a linux machine +if (true || platform.isLinux) { const file = '/etc/os-release'; fileExists(file).then(exists => { if (!exists) { diff --git a/src/vs/workbench/parts/terminal/node/terminalProcess.ts b/src/vs/workbench/parts/terminal/node/terminalProcess.ts index 03beafef97..c48cd6528b 100644 --- a/src/vs/workbench/parts/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/parts/terminal/node/terminalProcess.ts @@ -6,16 +6,19 @@ import * as os from 'os'; import * as path from 'path'; import * as platform from 'vs/base/common/platform'; -import * as pty from 'node-pty'; +import * as ptyTypes from 'node-pty'; import { Event, Emitter } from 'vs/base/common/event'; import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import { toString } from 'vs/workbench/parts/terminal/common/terminalId'; +import { dtachLocation } from 'coder/server'; export class TerminalProcess implements ITerminalChildProcess, IDisposable { private _exitCode: number; private _closeTimeout: number; - private _ptyProcess: pty.IPty; + // @ts-ignore + private _ptyProcess: ptyTypes.IPty; private _currentTitle: string = ''; private readonly _onProcessData: Emitter = new Emitter(); @@ -32,7 +35,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { cwd: string, cols: number, rows: number, - env: platform.IProcessEnvironment + env: platform.IProcessEnvironment, + id: number, ) { let shellName: string; if (os.platform() === 'win32') { @@ -43,7 +47,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { shellName = 'xterm-256color'; } - const options: pty.IPtyForkOptions = { + // @ts-ignore + const options: ptyTypes.IPtyForkOptions = { name: shellName, cwd, env, @@ -51,7 +56,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { rows }; - this._ptyProcess = pty.spawn(shellLaunchConfig.executable, shellLaunchConfig.args, options); + // This is here so the extensionHostProcess doesn't require node-pty + const pty = require("node-pty"); + this._ptyProcess = pty.spawn(`${dtachLocation} -A /tmp/${toString(id)} bash -c '${shellLaunchConfig.executable.replace(/'/g, "\\'")}'`, shellLaunchConfig.args, options); this._ptyProcess.on('data', (data) => { this._onProcessData.fire(data); if (this._closeTimeout) { @@ -97,7 +104,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { if (this._closeTimeout) { clearTimeout(this._closeTimeout); } - this._closeTimeout = setTimeout(() => this._kill(), 250); + this._closeTimeout = setTimeout(() => this._kill(), 250) as any; } private _kill(): void { diff --git a/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts index 246480377f..6d5002d6bd 100644 --- a/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts @@ -42,7 +42,7 @@ export class WindowsShellHelper { this._isDisposed = false; - (import('windows-process-tree')).then(mod => { + Promise.resolve(require('windows-process-tree')).then(mod => { if (this._isDisposed) { return; } diff --git a/src/vs/workbench/parts/update/electron-browser/media/markdown.css b/src/vs/workbench/parts/update/electron-browser/media/markdown.css index 2c2e0bb924..6f5b832a07 100644 --- a/src/vs/workbench/parts/update/electron-browser/media/markdown.css +++ b/src/vs/workbench/parts/update/electron-browser/media/markdown.css @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -body { +.vscode-body { padding: 10px 20px; line-height: 22px; } -img { +.vscode-body img { max-width: 100%; max-height: 100%; } -a { +.vscode-body a { text-decoration: none; } -a:hover { +.vscode-body a:hover { text-decoration: underline; } diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index 0d7075217d..fd32d7a3a3 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -33,7 +33,7 @@ function renderBody( body: string, css: string ): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://'); + const styleSheetPath = ""; // require.toUrl('css-loader!style-loader!./media/markdown.css').replace('file://', 'vscode-core-resource://'); return ` diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts index 4e79126712..a406c9c6b5 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts @@ -20,7 +20,7 @@ const workbench = Registry.as(WorkbenchExtensio workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Running); -if (platform.isWindows) { +if (platform.isWindows && false) { if (process.arch === 'ia32') { workbench.registerWorkbenchContribution(Win3264BitContribution, LifecyclePhase.Running); } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 53ba1b4913..51c6b5d0a8 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -377,7 +377,7 @@ export class UpdateContribution implements IGlobalActivity { // windows fast updates private onUpdateUpdating(update: IUpdate): void { - if (isWindows && product.target === 'user') { + if (false && isWindows && product.target === 'user') { return; } diff --git a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js index 6cfbf3f08f..070ce0c803 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js +++ b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js @@ -7,7 +7,23 @@ 'use strict'; // @ts-ignore - const ipcRenderer = require('electron').ipcRenderer; + // const ipcRenderer = require('electron').ipcRenderer; + const ipcRenderer = { + on: (channel, callback) => { + window.addEventListener("message", (event) => { + if (event.data.channel === channel) { + callback(event.data.channel, ...event.data.data); + } + }); + }, + sendToHost: (channel, ...args) => { + window.parent.postMessage({ + channel, + data: args, + id: document.body.id, + }, "*"); + }, + }; const registerVscodeResourceScheme = (function () { let hasRegistered = false; @@ -19,13 +35,13 @@ hasRegistered = true; // @ts-ignore - require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { - secure: true, - bypassCSP: false, - allowServiceWorkers: false, - supportFetchAPI: true, - corsEnabled: true - }); + // require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { + // secure: true, + // bypassCSP: false, + // allowServiceWorkers: false, + // supportFetchAPI: true, + // corsEnabled: true + // }); }; }()); @@ -115,66 +131,65 @@ }); }; - document.addEventListener('DOMContentLoaded', () => { - ipcRenderer.on('baseUrl', (_event, value) => { - initData.baseUrl = value; - }); - - ipcRenderer.on('styles', (_event, variables, activeTheme) => { - initData.styles = variables; - initData.activeTheme = activeTheme; + ipcRenderer.on('baseUrl', (_event, value) => { + initData.baseUrl = value; + }); - // webview - var target = getActiveFrame(); - if (!target) { - return; - } - var body = target.contentDocument.getElementsByTagName('body'); - styleBody(body[0]); + ipcRenderer.on('styles', (_event, variables, activeTheme) => { + initData.styles = variables; + initData.activeTheme = activeTheme; - // iframe - Object.keys(variables).forEach((variable) => { - target.contentDocument.documentElement.style.setProperty(`--${variable}`, variables[variable]); - }); - }); + // webview + var target = getActiveFrame(); + if (!target) { + return; + } + var body = target.contentDocument.getElementsByTagName('body'); + styleBody(body[0]); - // propagate focus - ipcRenderer.on('focus', () => { - const target = getActiveFrame(); - if (target) { - target.contentWindow.focus(); - } + // iframe + Object.keys(variables).forEach((variable) => { + target.contentDocument.documentElement.style.setProperty(`--${variable}`, variables[variable]); }); + }); - // update iframe-contents - ipcRenderer.on('content', (_event, data) => { - const options = data.options; - enableWrappedPostMessage = options && options.enableWrappedPostMessage; + // propagate focus + ipcRenderer.on('focus', () => { + const target = getActiveFrame(); + if (target) { + target.contentWindow.focus(); + } + }); - if (enableWrappedPostMessage) { - registerVscodeResourceScheme(); - } + // update iframe-contents + ipcRenderer.on('content', (_event, data) => { + const options = data.options; + enableWrappedPostMessage = options && options.enableWrappedPostMessage; - const text = data.contents; - const newDocument = new DOMParser().parseFromString(text, 'text/html'); + if (enableWrappedPostMessage) { + registerVscodeResourceScheme(); + } - newDocument.querySelectorAll('a').forEach(a => { - if (!a.title) { - a.title = a.href; - } - }); + const text = data.contents; + const newDocument = new DOMParser().parseFromString(text, 'text/html'); - // set base-url if applicable - if (initData.baseUrl && newDocument.head.getElementsByTagName('base').length === 0) { - const baseElement = newDocument.createElement('base'); - baseElement.href = initData.baseUrl; - newDocument.head.appendChild(baseElement); + newDocument.querySelectorAll('a').forEach(a => { + if (!a.title) { + a.title = a.href; } + }); + + // set base-url if applicable + if (initData.baseUrl && newDocument.head.getElementsByTagName('base').length === 0) { + const baseElement = newDocument.createElement('base'); + baseElement.href = initData.baseUrl; + newDocument.head.appendChild(baseElement); + } - // apply default script - if (enableWrappedPostMessage && options.allowScripts) { - const defaultScript = newDocument.createElement('script'); - defaultScript.textContent = ` + // apply default script + if (enableWrappedPostMessage && options.allowScripts) { + const defaultScript = newDocument.createElement('script'); + defaultScript.textContent = ` const acquireVsCodeApi = (function() { const originalPostMessage = window.parent.postMessage.bind(window.parent); let acquired = false; @@ -206,21 +221,21 @@ delete window.frameElement; `; - if (newDocument.head.hasChildNodes()) { - newDocument.head.insertBefore(defaultScript, newDocument.head.firstChild); - } else { - newDocument.head.appendChild(defaultScript); - } + if (newDocument.head.hasChildNodes()) { + newDocument.head.insertBefore(defaultScript, newDocument.head.firstChild); + } else { + newDocument.head.appendChild(defaultScript); } + } - // apply default styles - const defaultStyles = newDocument.createElement('style'); - defaultStyles.id = '_defaultStyles'; + // apply default styles + const defaultStyles = newDocument.createElement('style'); + defaultStyles.id = '_defaultStyles'; - const vars = Object.keys(initData.styles || {}).map(variable => { - return `--${variable}: ${initData.styles[variable].replace(/[^\#\"\'\,\. a-z0-9\-\(\)]/gi, '')};`; - }); - defaultStyles.innerHTML = ` + const vars = Object.keys(initData.styles || {}).map(variable => { + return `--${variable}: ${initData.styles[variable].replace(/[^\#\"\'\,\. a-z0-9\-\(\)]/gi, '')};`; + }); + defaultStyles.innerHTML = ` :root { ${vars.join('\n')} } body { @@ -298,143 +313,145 @@ background-color: rgba(111, 195, 223, 0.8); } `; - if (newDocument.head.hasChildNodes()) { - newDocument.head.insertBefore(defaultStyles, newDocument.head.firstChild); - } else { - newDocument.head.appendChild(defaultStyles); - } + if (newDocument.head.hasChildNodes()) { + newDocument.head.insertBefore(defaultStyles, newDocument.head.firstChild); + } else { + newDocument.head.appendChild(defaultStyles); + } - styleBody(newDocument.body); + styleBody(newDocument.body); - const frame = getActiveFrame(); + const frame = getActiveFrame(); - // keep current scrollY around and use later - var setInitialScrollPosition; - if (firstLoad) { - firstLoad = false; - setInitialScrollPosition = (body, window) => { - if (!isNaN(initData.initialScrollProgress)) { - if (window.scrollY === 0) { - window.scroll(0, body.clientHeight * initData.initialScrollProgress); - } - } - }; - } else { - const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0; - setInitialScrollPosition = (body, window) => { + // keep current scrollY around and use later + var setInitialScrollPosition; + if (firstLoad) { + firstLoad = false; + setInitialScrollPosition = (body, window) => { + if (!isNaN(initData.initialScrollProgress)) { if (window.scrollY === 0) { - window.scroll(0, scrollY); + window.scroll(0, body.clientHeight * initData.initialScrollProgress); } - }; - } - - // Clean up old pending frames and set current one as new one - const previousPendingFrame = getPendingFrame(); - if (previousPendingFrame) { - previousPendingFrame.setAttribute('id', ''); - document.body.removeChild(previousPendingFrame); - } - pendingMessages = []; - - const newFrame = document.createElement('iframe'); - newFrame.setAttribute('id', 'pending-frame'); - newFrame.setAttribute('frameborder', '0'); - newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-same-origin'); - newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; - document.body.appendChild(newFrame); - - // write new content onto iframe - newFrame.contentDocument.open('text/html', 'replace'); - newFrame.contentWindow.onbeforeunload = () => { - if (isInDevelopmentMode) { // Allow reloads while developing a webview - ipcRenderer.sendToHost('do-reload'); - return false; } - - // Block navigation when not in development mode - console.log('prevented webview navigation'); - return false; }; - - var onLoad = (contentDocument, contentWindow) => { - if (contentDocument.body) { - // Workaround for https://github.com/Microsoft/vscode/issues/12865 - // check new scrollY and reset if neccessary - setInitialScrollPosition(contentDocument.body, contentWindow); + } else { + const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0; + setInitialScrollPosition = (body, window) => { + if (window.scrollY === 0) { + window.scroll(0, scrollY); } + }; + } - const newFrame = getPendingFrame(); - if (newFrame && newFrame.contentDocument === contentDocument) { - const oldActiveFrame = getActiveFrame(); - if (oldActiveFrame) { - document.body.removeChild(oldActiveFrame); - } - newFrame.setAttribute('id', 'active-frame'); - newFrame.style.visibility = 'visible'; - newFrame.contentWindow.focus(); + // Clean up old pending frames and set current one as new one + const previousPendingFrame = getPendingFrame(); + if (previousPendingFrame) { + previousPendingFrame.setAttribute('id', ''); + document.body.removeChild(previousPendingFrame); + } + pendingMessages = []; + + const newFrame = document.createElement('iframe'); + newFrame.onerror = () => { + // Suppress errors + }; + newFrame.setAttribute('id', 'pending-frame'); + newFrame.setAttribute('frameborder', '0'); + newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-scripts allow-same-origin'); + newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; + document.body.appendChild(newFrame); + + // write new content onto iframe + newFrame.contentDocument.open('text/html', 'replace'); + newFrame.contentWindow.onbeforeunload = () => { + if (isInDevelopmentMode) { // Allow reloads while developing a webview + ipcRenderer.sendToHost('do-reload'); + return false; + } + + // Block navigation when not in development mode + console.log('prevented webview navigation'); + return false; + }; - contentWindow.addEventListener('scroll', handleInnerScroll); + var onLoad = (contentDocument, contentWindow) => { + if (contentDocument.body) { + // Workaround for https://github.com/Microsoft/vscode/issues/12865 + // check new scrollY and reset if neccessary + setInitialScrollPosition(contentDocument.body, contentWindow); + } - pendingMessages.forEach((data) => { - contentWindow.postMessage(data, '*'); - }); - pendingMessages = []; + const newFrame = getPendingFrame(); + if (newFrame && newFrame.contentDocument === contentDocument) { + const oldActiveFrame = getActiveFrame(); + if (oldActiveFrame) { + document.body.removeChild(oldActiveFrame); } - }; + newFrame.setAttribute('id', 'active-frame'); + newFrame.style.visibility = 'visible'; + // newFrame.contentWindow.focus(); + + contentWindow.addEventListener('scroll', handleInnerScroll); + + pendingMessages.forEach((data) => { + contentWindow.postMessage(data, '*'); + }); + pendingMessages = []; + } + }; + clearTimeout(loadTimeout); + loadTimeout = undefined; + loadTimeout = setTimeout(() => { clearTimeout(loadTimeout); loadTimeout = undefined; - loadTimeout = setTimeout(() => { + onLoad(newFrame.contentDocument, newFrame.contentWindow); + }, 200); + + newFrame.contentWindow.addEventListener('load', function (e) { + if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(newFrame.contentDocument, newFrame.contentWindow); - }, 200); - - newFrame.contentWindow.addEventListener('load', function (e) { - if (loadTimeout) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(e.target, this); - } - }); + onLoad(e.target, this); + } + }); - // Bubble out link clicks - newFrame.contentWindow.addEventListener('click', handleInnerClick); + // Bubble out link clicks + newFrame.contentWindow.addEventListener('click', handleInnerClick); - // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off - // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden - newFrame.contentDocument.write(''); - newFrame.contentDocument.write(newDocument.documentElement.innerHTML); - newFrame.contentDocument.close(); + // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off + // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden + newFrame.contentDocument.write(''); + newFrame.contentDocument.write(newDocument.documentElement.innerHTML); + newFrame.contentDocument.close(); - ipcRenderer.sendToHost('did-set-content'); - }); + ipcRenderer.sendToHost('did-set-content'); + }); - // Forward message to the embedded iframe - ipcRenderer.on('message', (_event, data) => { - const pending = getPendingFrame(); - if (pending) { - pendingMessages.push(data); - } else { - const target = getActiveFrame(); - if (target) { - target.contentWindow.postMessage(data, '*'); - } + // Forward message to the embedded iframe + ipcRenderer.on('message', (_event, data) => { + const pending = getPendingFrame(); + if (pending) { + pendingMessages.push(data); + } else { + const target = getActiveFrame(); + if (target) { + target.contentWindow.postMessage(data, '*'); } - }); + } + }); - ipcRenderer.on('initial-scroll-position', (_event, progress) => { - initData.initialScrollProgress = progress; - }); + ipcRenderer.on('initial-scroll-position', (_event, progress) => { + initData.initialScrollProgress = progress; + }); - ipcRenderer.on('devtools-opened', () => { - isInDevelopmentMode = true; - }); + ipcRenderer.on('devtools-opened', () => { + isInDevelopmentMode = true; + }); - // Forward messages from the embedded iframe - window.onmessage = onMessage; + // Forward messages from the embedded iframe + // window.onmessage = onMessage; - // signal ready - ipcRenderer.sendToHost('webview-ready', process.pid); - }); + // signal ready + ipcRenderer.sendToHost('webview-ready'); }()); \ No newline at end of file diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts index 898a782901..0da931ed6a 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts @@ -54,8 +54,8 @@ export class WebviewElement extends Disposable { this._webview.style.height = '0'; this._webview.style.outline = '0'; - this._webview.preload = require.toUrl('./webview-pre.js'); - this._webview.src = this._options.useSameOriginForRoot ? require.toUrl('./webview.html') : 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; + this._webview.preload = require('!!file-loader!./webview-pre.js'); + // this._webview.src = this._options.useSameOriginForRoot ? require.toUrl('./webview.html') : 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; this._ready = new Promise(resolve => { const subscription = this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts b/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts index 56ffc97bcb..d5624d748d 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts @@ -13,38 +13,151 @@ export function used() { export default () => `
-
-

${escape(localize('welcomePage.vscode', "Visual Studio Code"))}

-

${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}

-
-
+ +
+
+
+

${escape(localize('welcomePage.customize', "Cloud Computing"))}

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

${escape(localize('welcomePage.customize', "Keep Coding"))}

+
    +
  • + +
  • +
  • + +
  • +
  • + +

diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css index ee97160653..20793fde44 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css @@ -22,6 +22,13 @@ flex-flow: row; } + +@media(max-width:1000px) { + .monaco-workbench > .part.editor > .content .welcomePage .row { + display: unset; + } +} + .monaco-workbench > .part.editor > .content .welcomePage .row .section { overflow: hidden; } @@ -76,6 +83,13 @@ display: block; } +.monaco-workbench > .part.editor > .content .welcomePage .description { + font-size: 1.8em; + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + .hc-black .monaco-workbench > .part.editor > .content .welcomePage .subtitle { font-weight: 200; } @@ -94,7 +108,7 @@ } .monaco-workbench > .part.editor > .content .welcomePage .splash .section { - margin-bottom: 5em; + margin-bottom: 2em; } .monaco-workbench > .part.editor > .content .welcomePage .splash ul { @@ -214,6 +228,65 @@ display: inline; } +.monaco-workbench > .part.editor > .content .welcomePage .commands { + margin-right: 5px; +} + +.monaco-workbench > .part.editor > .content .welcomePage .toprow { + display: flex; + flex-direction: row; +} + +@media (max-width:1000px) { + .monaco-workbench > .part.editor > .content .welcomePage .toprow { + display: unset; + } +} + +.monaco-workbench > .part.editor > .content .welcomePage .toprow .splash { + display: flex; + flex-direction: row; + max-width: fit-content; + max-width: -moz-fit-content; + margin-left: auto; +} + +@media (max-width:1000px) { + .monaco-workbench > .part.editor > .content .welcomePage .toprow .splash { + margin-left: 0; + } +} + +.monaco-workbench > .part.editor > .content .welcomePage .toprow .splash .section { + margin-left: 4em; +} + +@media (max-width:1000px) { + .monaco-workbench > .part.editor > .content .welcomePage .toprow .splash .section { + margin-left: 0; + margin-right: 8em; + } +} + +.monaco-workbench > .part.editor > .content .welcomePage .toprow .splash .section h2 { + margin-top: 0px; +} + +@keyframes fasttime-fade { + from {fill:#6c5829;} + to {fill:#f1d183;} +} + +.monaco-workbench > .part.editor > .content .welcomePage .commands .resources svg { + /* animation: fasttime-fade 1.5s 0s linear infinite alternate; */ + width: 25px; + margin-right: 10px; + float: left; + opacity: 0.5; + fill: currentColor; + height: 100%; +} + .monaco-workbench > .part.editor > .content .welcomePageContainer.max-height-685px .title { display: none; } diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts index 5599f8459e..d5c481f1af 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts @@ -18,7 +18,7 @@ const typeId = 'workbench.editors.walkThroughInput'; const inputOptions: WalkThroughInputOptions = { typeId, name: localize('editorWalkThrough.title', "Interactive Playground"), - resource: URI.parse(require.toUrl('./vs_code_editor_walkthrough.md')) + resource: URI.parse(require('file-loader!./vs_code_editor_walkthrough.md')) .with({ scheme: Schemas.walkThrough }), telemetryFrom: 'walkThrough' }; diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts index 412a4cd9ba..791ae4a41c 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts @@ -16,6 +16,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { Range } from 'vs/editor/common/core/range'; +import 'vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page'; export class WalkThroughContentProvider implements ITextModelContentProvider, IWorkbenchContribution { @@ -31,13 +32,22 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW public provideTextContent(resource: URI): TPromise { const query = resource.query ? JSON.parse(resource.query) : {}; const content: TPromise = (query.moduleId ? new TPromise((resolve, reject) => { - require([query.moduleId], content => { - try { - resolve(content.default()); - } catch (err) { - reject(err); - } + import("vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page").then((content) => { + resolve(content.default()); + }).catch((err) => { + reject(err); }); + // require([query.moduleId], content => { + // try { + // resolve(content.default()); + // } catch (err) { + // reject(err); + // } + // }); + }) : resource.scheme !== "file" ? new TPromise((resolve, reject) => { + return fetch(resource.path).then((res) => res.text()).then((str) => { + resolve(str); + }).catch((err) => reject(err)); }) : this.textFileService.resolveTextContent(URI.file(resource.fsPath)).then(content => content.value)); return content.then(content => { let codeEditorModel = this.modelService.getModel(resource); @@ -64,7 +74,11 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi } public provideTextContent(resource: URI): TPromise { - return this.textFileService.resolveTextContent(URI.file(resource.fsPath)).then(content => { + return (new TPromise((resolve, reject) => { + return fetch(resource.path).then((res) => res.text()).then((str) => { + resolve(str); + }).catch((err) => reject(err)); + })).then(content => { let codeEditorModel = this.modelService.getModel(resource); if (!codeEditorModel) { const j = parseInt(resource.fragment); @@ -81,17 +95,17 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi return ''; }; - const textBuffer = content.value.create(DefaultEndOfLine.LF); - const lineCount = textBuffer.getLineCount(); - const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); - const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); - marked(markdown, { renderer }); + // const textBuffer = content.value.create(DefaultEndOfLine.LF); + // const lineCount = textBuffer.getLineCount(); + // const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); + // const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); + marked(content, { renderer }); const modeId = this.modeService.getModeIdForLanguageName(languageName); const mode = this.modeService.getOrCreateMode(modeId); codeEditorModel = this.modelService.createModel(codeSnippet, mode, resource); } else { - this.modelService.updateModel(codeEditorModel, content.value); + this.modelService.updateModel(codeEditorModel, content); } return codeEditorModel; diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 782c8d13dc..252fbb35e1 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -349,7 +349,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return stat(folder.fsPath) .then(workspaceStat => { let ctime: number; - if (isLinux) { + // Adding this because the stat will always be a linux stat + if (true || isLinux) { ctime = workspaceStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! } else if (isMacintosh) { ctime = workspaceStat.birthtime.getTime(); // macOS: birthtime is fine to use as is @@ -461,23 +462,25 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat this.cachedFolderConfigs = new ResourceMap(); const folders = this.workspace.folders; - return this.loadFolderConfigurations(folders) - .then((folderConfigurations) => { - - let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); - const folderConfigurationModels = new ResourceMap(); - folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - - const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null - - if (currentConfiguration) { - const changedKeys = this._configuration.compare(currentConfiguration); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE); - } else { - this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); - } - }); + return Promise.all([ + this.loadFolderConfigurations(folders), + this.userConfiguration.loaded, + ]).then((values) => { + const folderConfigurations = values[0]; + let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); + const folderConfigurationModels = new ResourceMap(); + folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); + + const currentConfiguration = this._configuration; + this._configuration = new Configuration(this.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null + + if (currentConfiguration) { + const changedKeys = this._configuration.compare(currentConfiguration); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE); + } else { + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + } + }); } private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel { diff --git a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts b/src/vs/workbench/services/configurationResolver/node/variableResolver.ts index b91edb9639..e63dc5863b 100644 --- a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/node/variableResolver.ts @@ -36,7 +36,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe private _context: IVariableResolveContext, private _envVariables: IProcessEnvironment = process.env ) { - if (isWindows) { + if (false && isWindows) { this._envVariables = Object.create(null); Object.keys(_envVariables).forEach(key => { this._envVariables[key.toLowerCase()] = _envVariables[key]; @@ -56,11 +56,11 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe const result = objects.deepClone(config) as any; // hoist platform specific attributes to top level - if (isWindows && result.windows) { + if (false && isWindows && result.windows) { Object.keys(result.windows).forEach(key => result[key] = result.windows[key]); } else if (isMacintosh && result.osx) { Object.keys(result.osx).forEach(key => result[key] = result.osx[key]); - } else if (isLinux && result.linux) { + } else if ((isWindows || isLinux) && result.linux) { Object.keys(result.linux).forEach(key => result[key] = result.linux[key]); } diff --git a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts index 71239496da..f7ed8c56ab 100644 --- a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts +++ b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts @@ -33,12 +33,12 @@ configurationRegistry.registerConfiguration({ title: nls.localize('telemetryConfigurationTitle', "Telemetry"), 'type': 'object', 'properties': { - 'telemetry.enableCrashReporter': { - 'type': 'boolean', - 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), - 'default': true, - 'tags': ['usesOnlineServices'] - } + // 'telemetry.enableCrashReporter': { + // 'type': 'boolean', + // 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), + // 'default': true, + // 'tags': ['usesOnlineServices'] + // } } }); diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index 3b5e58e218..5112c14575 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -20,12 +20,15 @@ export interface IDecorationData { readonly tooltip?: string; readonly bubble?: boolean; readonly source?: string; + + readonly element?: HTMLElement; } export interface IDecoration { readonly tooltip: string; readonly labelClassName: string; readonly badgeClassName: string; + readonly element?: HTMLElement; update(data: IDecorationData): IDecoration; } diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index b2f151ec7b..4597ed60a6 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -130,6 +130,17 @@ class DecorationStyles { let labelClassName = rule.itemColorClassName; let badgeClassName = rule.itemBadgeClassName; let tooltip = data.filter(d => !isFalsyOrWhitespace(d.tooltip)).map(d => d.tooltip).join(' • '); + let wrapper: HTMLElement; + data.forEach((d) => { + if (d.element) { + if (!wrapper) { + wrapper = document.createElement("div"); + wrapper.classList.add("decorations-wrapper"); + } + + wrapper.append(d.element); + } + }); if (onlyChildren) { // show items from its children only @@ -140,6 +151,7 @@ class DecorationStyles { return { labelClassName, badgeClassName, + element: wrapper, tooltip, update: (replace) => { let newData = data.slice(); diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index c2c1ee52f5..d7e7090cb8 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -110,7 +110,7 @@ export class DialogService implements IDialogService { // Linux: order of buttons is reverse // macOS: also reverse, but the OS handles this for us! - if (isLinux) { + if (isLinux || isWindows) { buttons = buttons.reverse(); buttonIndexMap = buttonIndexMap.reverse(); } @@ -126,7 +126,7 @@ export class DialogService implements IDialogService { // macOS/Linux: the cancel button should always be to the left of the primary action // if we see more than 2 buttons, move the cancel one to the left of the primary - if (!isWindows && buttons.length > 2 && cancelId !== 1) { + if (/*!isWindows && */buttons.length > 2 && cancelId !== 1) { const cancelButton = buttons[cancelId]; buttons.splice(cancelId, 1); buttons.splice(1, 0, cancelButton); diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 0a5f2cfcea..5624c60241 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { ExtensionHostExecutionEnvironment } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; export interface IExtensionDescription { readonly id: string; @@ -20,6 +21,7 @@ export interface IExtensionDescription { readonly publisher: string; readonly isBuiltin: boolean; readonly isUnderDevelopment: boolean; + readonly executionEnvironment?: ExtensionHostExecutionEnvironment; readonly extensionLocation: URI; readonly extensionDependencies?: string[]; readonly activationEvents?: string[]; @@ -33,6 +35,7 @@ export interface IExtensionDescription { url: string; }; enableProposedApi?: boolean; + download?: string; } export const IExtensionService = createDecorator('extensionService'); @@ -116,7 +119,9 @@ export class ExtensionPointContribution { } } -export const ExtensionHostLogFileName = 'exthost'; +// NOTE@coder: add suffix to distinguish between Node and web worker logs. +const suffix = typeof process === 'undefined' || typeof process.stdout === 'undefined' ? 'worker' : 'node'; +export const ExtensionHostLogFileName = 'exthost.' + suffix; export interface IExtensionService { _serviceBrand: any; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index a9017211fd..e574656333 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -5,6 +5,7 @@ 'use strict'; +import { Buffer } from "buffer"; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as objects from 'vs/base/common/objects'; @@ -15,13 +16,13 @@ import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/l import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ChildProcess, fork } from 'child_process'; +import { ChildProcess } from 'child_process'; import { ipcRenderer as ipc } from 'electron'; import product from 'vs/platform/node/product'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; -import { generateRandomPipeName, Protocol } from 'vs/base/parts/ipc/node/ipc.net'; -import { createServer, Server, Socket } from 'net'; +import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; +import { Socket } from 'net'; import { Event, Emitter, debounceEvent, mapEvent, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event'; import { IInitData, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; @@ -35,13 +36,16 @@ import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; import { getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { timeout } from 'vs/base/common/async'; import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/common/extensionHostProtocol'; import { ILabelService } from 'vs/platform/label/common/label'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { EventEmitter } from "events"; +import { IInitData as ICoderInitData } from 'coder/common'; +import { api, personalAccount, container } from 'coder/api'; +import { nodePath, bootstrapForkLocation, wush, wushCredentials } from 'coder/server'; export interface IExtensionHostStarter { readonly onCrashed: Event<[number, string]>; @@ -50,6 +54,11 @@ export interface IExtensionHostStarter { dispose(): void; } +export enum ExtensionHostExecutionEnvironment { + Worker = "worker", + Node = "node", +} + export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>(); @@ -67,7 +76,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private _terminating: boolean; // Resources, in order they get acquired/created when .start() is called: - private _namedPipeServer: Server; private _inspectPort: number; private _extensionHostProcess: ChildProcess; private _extensionHostConnection: Socket; @@ -76,6 +84,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { constructor( private readonly _extensions: TPromise, private readonly _extensionHostLogsLocation: URI, + private readonly _executionEnvironment: ExtensionHostExecutionEnvironment, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, @IWindowsService private readonly _windowsService: IWindowsService, @@ -84,6 +93,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService, + // @ts-ignore @ITelemetryService private readonly _telemetryService: ITelemetryService, @ICrashReporterService private readonly _crashReporterService: ICrashReporterService, @ILogService private readonly _logService: ILogService, @@ -100,7 +110,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._lastExtensionHostError = null; this._terminating = false; - this._namedPipeServer = null; this._extensionHostProcess = null; this._extensionHostConnection = null; this._messageProtocol = null; @@ -147,16 +156,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } if (!this._messageProtocol) { - this._messageProtocol = TPromise.join([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => { - const pipeName = data[0]; - const portData = data[1]; + this._messageProtocol = TPromise.join([this._tryFindDebugPort()]).then(async (data) => { + const portData = data[0]; const opts = { env: objects.mixin(objects.deepClone(process.env), { AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess', PIPE_LOGGING: 'true', VERBOSE_LOGGING: true, - VSCODE_IPC_HOOK_EXTHOST: pipeName, VSCODE_HANDLES_UNCAUGHT_ERRORS: true, VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || product.quality !== 'stable' || this._environmentService.verbose), VSCODE_LOG_LEVEL: this._environmentService.verbose ? 'trace' : this._environmentService.log @@ -188,7 +195,121 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } // Run Extension Host as fork of current process - this._extensionHostProcess = fork(getPathFromAmdModule(require, 'bootstrap-fork'), ['--type=extensionHost'], opts); + // Here we decide to run the extension process as a command via wush + // or in a web worker + + const setExtensionHostProcess = (dataListener: (cb: (data: Uint8Array) => void) => void, exitListener: (cb: (code: number) => void) => void): void => { + this._extensionHostProcess = new (class { + + public readonly stdout: EventEmitter; + public readonly stderr: EventEmitter; + private readonly emitter: EventEmitter; + + public constructor() { + this.emitter = new EventEmitter(); + + this.stdout = new EventEmitter(); + this.stderr = new EventEmitter(); + + [this.stdout, this.stderr].forEach((e) => { + (e as any).setEncoding = () => { + + }; + }); + + exitListener((code: number) => { + this.emitter.emit("exit", code); + }); + + dataListener((msg: Uint8Array) => { + this.stderr.emit("data", msg); + }); + } + + public on(event: string, listener: () => void) { + this.emitter.on(event, listener); + } + + }) as any; + }; + + let protocol: Protocol; + let shouldSendApiKey: boolean = false; + + switch (this._executionEnvironment) { + case ExtensionHostExecutionEnvironment.Worker: { + const ExtensionProcessWorker = require('worker-loader!vs/workbench/node/extensionHostProcess'); + const extensionProcessWorker = new ExtensionProcessWorker() as Worker; + extensionProcessWorker.onerror = (err) => { + console.log("Got worker err", err); + }; + setExtensionHostProcess((cb) => { + extensionProcessWorker.addEventListener("message", (event) => { + cb(event.data); + }); + }, (cb) => { + // We cannot tell if a worker dies + }); + shouldSendApiKey = true; + protocol = Protocol.fromWorker(extensionProcessWorker); + break; + } + case ExtensionHostExecutionEnvironment.Node: { + // The flag is to help identify it for debugging. + const sess = wush.execute({ + command: `bash -c 'VSCODE_ALLOW_IO=true` + + ` AMD_ENTRYPOINT=vs/workbench/node/extensionHostProcess` + + ` nice -n -18 ${nodePath} ${bootstrapForkLocation} --host'`, + }); + sess.onStderr((err) => { + console.error("Got stderr from node extension host:", err); + }); + protocol = Protocol.fromStdio({ + onMessage: (cb) => { + sess.onStdout((data: Buffer) => { + cb(Buffer.from(data)); + }, true); + }, + sendMessage: (data: Uint8Array) => { + while (data.length > 32000) { + const part = data.slice(0, 32000); + sess.sendStdin(part); + data = data.slice(32000); + } + sess.sendStdin(data); + }, + }); + setExtensionHostProcess((cb) => { + sess.onStdout((data) => { + cb(data); + }, true); + }, (cb) => { + sess.onDone((exitCode) => { + console.log("The session exited", exitCode); + cb(exitCode); + }); + }); + break; + } + default: { + throw new Error("invalid execution environment provided"); + } + } + + const initMessage: ICoderInitData = { + api: { + organization: personalAccount.organization.id_str, + container: container.id_str, + isProduction: api.environment.isProduction(), + key: shouldSendApiKey ? api.apiKey.key : undefined, + }, + // Only the worker needs Wush credentials. + wush: this._executionEnvironment === ExtensionHostExecutionEnvironment.Worker + ? wushCredentials + : undefined, + fetchUrl: product.fetchUrl, + }; + protocol.send(Buffer.from(JSON.stringify(initMessage))); // Catch all output coming from the extension host process type Output = { data: string, format: string[] }; @@ -214,9 +335,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { if (inspectorUrlMatch) { console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color: black'); } else { - console.group('Extension Host'); - console.log(output.data, ...output.format); - console.groupEnd(); + // console.group('Extension Host'); + // console.log(output.data, ...output.format); + // console.groupEnd(); } }); @@ -257,11 +378,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { run: () => this._windowService.reloadWindow() }] ); - }, 10000); + }, 10000) as any as number; } // Initialize extension host process with hand shakes - return this._tryExtHostHandshake().then((protocol) => { + return this._tryExtHostHandshake(protocol).then((protocol) => { clearTimeout(startupTimeoutHandle); return protocol; }); @@ -271,22 +392,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { return this._messageProtocol; } - /** - * Start a server (`this._namedPipeServer`) that listens on a named pipe and return the named pipe name. - */ - private _tryListenOnPipe(): Promise { - return new Promise((resolve, reject) => { - const pipeName = generateRandomPipeName(); - - this._namedPipeServer = createServer(); - this._namedPipeServer.on('error', reject); - this._namedPipeServer.listen(pipeName, () => { - this._namedPipeServer.removeListener('error', reject); - resolve(pipeName); - }); - }); - } - /** * Find a free port if extension host debugging is enabled. */ @@ -317,28 +422,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { }); } - private _tryExtHostHandshake(): Promise { + private _tryExtHostHandshake(protocol: Protocol): Promise { return new Promise((resolve, reject) => { - - // Wait for the extension host to connect to our named pipe - // and wrap the socket in the message passing protocol - let handle = setTimeout(() => { - this._namedPipeServer.close(); - this._namedPipeServer = null; - reject('timeout'); - }, 60 * 1000); - - this._namedPipeServer.on('connection', socket => { - clearTimeout(handle); - this._namedPipeServer.close(); - this._namedPipeServer = null; - this._extensionHostConnection = socket; - resolve(new Protocol(this._extensionHostConnection)); - }); - + resolve(protocol); }).then((protocol) => { - // 1) wait for the incoming `ready` event and send the initialization data. // 2) wait for the incoming `initialized` event. return new Promise((resolve, reject) => { @@ -348,10 +436,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { }, 60 * 1000); const disposable = protocol.onMessage(msg => { - if (isMessageOfType(msg, MessageType.Ready)) { // 1) Extension Host is ready to receive messages, initialize it - this._createExtHostInitData().then(data => protocol.send(Buffer.from(JSON.stringify(data)))); + this._createExtHostInitData().then(data => { + protocol.send(Buffer.from(JSON.stringify(data))); + }); return; } @@ -380,8 +469,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } private _createExtHostInitData(): TPromise { - return TPromise.join([this._telemetryService.getTelemetryInfo(), this._extensions]) - .then(([telemetryInfo, extensionDescriptions]) => { + return TPromise.join([this._extensions]) + .then(([extensionDescriptions]) => { const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} }; const workspace = this._contextService.getWorkspace(); const r: IInitData = { @@ -403,7 +492,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { extensions: extensionDescriptions, // Send configurations scopes only in development mode. configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData, - telemetryInfo, + telemetryInfo: { + instanceId: "1", + machineId: "1", + sessionId: "1", + }, logLevel: this._logService.getLevel(), logsLocation: this._extensionHostLogsLocation }; @@ -504,10 +597,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } private _cleanResources(): void { - if (this._namedPipeServer) { - this._namedPipeServer.close(); - this._namedPipeServer = null; - } + // if (this._namedPipeServer) { + // this._namedPipeServer.close(); + // this._namedPipeServer = null; + // } if (this._extensionHostConnection) { this._extensionHostConnection.end(); this._extensionHostConnection = null; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index cff2054ab5..5302b01786 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -17,7 +17,7 @@ export class ExtensionHostProfiler { } public start(): TPromise { - return TPromise.wrap(import('v8-inspect-profiler')).then(profiler => { + return TPromise.wrap(require('v8-inspect-profiler')).then(profiler => { return profiler.startProfiling({ port: this._port }).then(session => { return { stop: () => { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index de147469de..e6d86ea493 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ExtensionHostProcessWorker, IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; +import { ExtensionHostProcessWorker, IExtensionHostStarter, ExtensionHostExecutionEnvironment } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; @@ -47,6 +47,9 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { isEqualOrParent } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; +import { AssetType as ExtensionAssetType } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { api } from 'coder/api'; +import { systemExtensionsLocation } from 'coder/workbench'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; @@ -55,7 +58,9 @@ const LOG_USE_COLORS = true; let _SystemExtensionsRoot: string = null; function getSystemExtensionsRoot(): string { if (!_SystemExtensionsRoot) { - _SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions')); + // TODO: update this to a realistic path + _SystemExtensionsRoot = systemExtensionsLocation; + // _SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions')); } return _SystemExtensionsRoot; } @@ -297,7 +302,7 @@ export class ExtensionService extends Disposable implements IExtensionService { @IExtensionManagementService private extensionManagementService: IExtensionManagementService ) { super(); - this._extensionHostLogsLocation = URI.file(path.posix.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`)); + this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`)); this._registry = null; this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; @@ -392,11 +397,27 @@ export class ExtensionService extends Disposable implements IExtensionService { private _startExtensionHostProcess(initialActivationEvents: string[]): void { this._stopExtensionHostProcess(); - const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this.getExtensions(), this._extensionHostLogsLocation); - const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, initialActivationEvents); - extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal)); - extHostProcessManager.onDidChangeResponsiveState((responsiveState) => this._onResponsiveStateChanged(responsiveState)); - this._extensionHostProcessManagers.push(extHostProcessManager); + Object.keys(ExtensionHostExecutionEnvironment).forEach((key) => { + const executionValue = ExtensionHostExecutionEnvironment[key]; + + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this.getExtensions().then((extensions) => { + const validExts = extensions.filter((extension) => { + let env = extension.executionEnvironment || ExtensionHostExecutionEnvironment.Node; + if (product.extensionExecutionEnvironments && product.extensionExecutionEnvironments[extension.id]) { + env = product.extensionExecutionEnvironments[extension.id]; + } + + return env === executionValue; + }); + + return validExts; + }), this._extensionHostLogsLocation, executionValue); + const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, initialActivationEvents); + extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal)); + extHostProcessManager.onDidChangeResponsiveState((responsiveState) => this._onResponsiveStateChanged(responsiveState)); + this._extensionHostProcessManagers.push(extHostProcessManager); + }); + } private _onExtensionHostCrashed(code: number, signal: string): void { @@ -583,9 +604,10 @@ export class ExtensionService extends Disposable implements IExtensionService { // --- impl private _scanAndHandleExtensions(): void { - this._scanExtensions() - .then(allExtensions => this._getRuntimeExtensions(allExtensions)) + .then(allExtensions => { + return this._getRuntimeExtensions(allExtensions); + }) .then(allExtensions => { this._registry = new ExtensionDescriptionRegistry(allExtensions); @@ -620,6 +642,7 @@ export class ExtensionService extends Disposable implements IExtensionService { if (result.hasOwnProperty(userExtension.id)) { log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath)); } + userExtension.download = api.environment.publicBucket(`extensions/${userExtension.id}/${userExtension.version}/${ExtensionAssetType.VSIX}`); result[userExtension.id] = userExtension; }); development.forEach(developedExtension => { @@ -654,7 +677,7 @@ export class ExtensionService extends Disposable implements IExtensionService { }); } - const enableProposedApiForAll = !this._environmentService.isBuilt || + const enableProposedApiForAll = true || !this._environmentService.isBuilt || (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong.indexOf('Insiders') >= 0) || (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); @@ -686,14 +709,14 @@ export class ExtensionService extends Disposable implements IExtensionService { if (extensionsToDisable.length) { return this.extensionManagementService.getInstalled(LocalExtensionType.User) - .then(installed => { - const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e))); - return TPromise.join(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled))); - }) - .then(() => { - this._storageService.store(BetterMergeDisabledNowKey, true); - return runtimeExtensions; - }); + .then(installed => { + const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e))); + return TPromise.join(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled))); + }) + .then(() => { + this._storageService.store(BetterMergeDisabledNowKey, true); + return runtimeExtensions; + }); } else { return runtimeExtensions; } @@ -958,7 +981,6 @@ export class ExtensionService extends Disposable implements IExtensionService { new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionDevelopmentLocationURI.fsPath, false, true, translations), log ); } - return TPromise.join([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => { const system = extensionDescriptions[0]; const user = extensionDescriptions[1]; diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 4e45241c0b..cee4b45ed3 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -10,7 +10,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as crypto from 'crypto'; import * as assert from 'assert'; -import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider } from 'vs/platform/files/common/files'; +import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IContentProvider } from 'vs/platform/files/common/files'; import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/files'; import { isEqualOrParent } from 'vs/base/common/paths'; import { ResourceMap } from 'vs/base/common/map'; @@ -28,7 +28,7 @@ import * as pfs from 'vs/base/node/pfs'; import * as encoding from 'vs/base/node/encoding'; import * as flow from 'vs/base/node/flow'; import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcherService'; -import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService'; +// import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService'; import { toFileChangesEvent, normalize, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { Event, Emitter } from 'vs/base/common/event'; import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/watcherService'; @@ -45,6 +45,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import product from 'vs/platform/node/product'; import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/electron-browser/encoding'; import { createReadableOfSnapshot } from 'vs/workbench/services/files/electron-browser/streams'; +import { ConflictResolution } from 'coder/common'; // NOTE@coder. class BufferPool { @@ -76,6 +77,12 @@ export interface IFileServiceTestOptions { encodingOverride?: IEncodingOverride[]; } +let instantiationListeners: Array<(fileService: FileService) => void> = []; + +export function onInstantiation(cb: (fileService: FileService) => void): void { + instantiationListeners.push(cb); +} + export class FileService extends Disposable implements IFileService { _serviceBrand: any; @@ -104,6 +111,7 @@ export class FileService extends Disposable implements IFileService { private undeliveredRawFileChangesEvents: IRawFileChange[]; private _encoding: ResourceEncodings; + private readonly contentProviders: Map; constructor( private contextService: IWorkspaceContextService, @@ -120,10 +128,13 @@ export class FileService extends Disposable implements IFileService { this.activeFileChangesWatchers = new ResourceMap<{ unwatch: Function, count: number }>(); this.fileChangesWatchDelayer = new ThrottledDelayer(FileService.FS_EVENT_DELAY); this.undeliveredRawFileChangesEvents = []; + this.contentProviders = new Map(); this._encoding = new ResourceEncodings(textResourceConfigurationService, environmentService, contextService, this.options.encodingOverride); this.registerListeners(); + + instantiationListeners.forEach((i) => i(this)); } get encoding(): ResourceEncodings { @@ -214,18 +225,39 @@ export class FileService extends Disposable implements IFileService { // legacy watcher else { - let watcherIgnoredPatterns: string[] = []; - if (configuration.files && configuration.files.watcherExclude) { - watcherIgnoredPatterns = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]); - } - - if (isWindows) { - const legacyWindowsWatcher = new WindowsWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose); - this.activeWorkspaceFileChangeWatcher = toDisposable(legacyWindowsWatcher.startWatching()); - } else { - const legacyUnixWatcher = new UnixWatcherService(this.contextService, this.configurationService, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose); - this.activeWorkspaceFileChangeWatcher = toDisposable(legacyUnixWatcher.startWatching()); - } + // NOTE@coder: no windows. + // let watcherIgnoredPatterns: string[] = []; + // if (configuration.files && configuration.files.watcherExclude) { + // watcherIgnoredPatterns = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]); + // } + + // if (isWindows) { + // const legacyWindowsWatcher = new WindowsWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose); + // this.activeWorkspaceFileChangeWatcher = toDisposable(legacyWindowsWatcher.startWatching()); + // } else { + const legacyUnixWatcher = new UnixWatcherService( + this.contextService, + this.configurationService, + e => this._onFileChanges.fire(e), + (err, final) => { + this.handleError(err); + if (final) { + this.notificationService.prompt( + Severity.Error, + "File watcher failed to start after retrying for some time.", + [{ + label: "Restart File Watcher", + run: () => { + legacyUnixWatcher.startWatching(); + }, + }], + ); + } + }, + this.environmentService.verbose, + ); + this.activeWorkspaceFileChangeWatcher = toDisposable(legacyUnixWatcher.startWatching()); + // } } } @@ -233,6 +265,24 @@ export class FileService extends Disposable implements IFileService { throw new Error('not implemented'); } + registerContentProvider(scheme: string, provider: IContentProvider): IDisposable { + if (this.contentProviders.has(scheme)) { + this.contentProviders.get(scheme).push(provider); + } else { + this.contentProviders.set(scheme, [provider]); + } + + return { + dispose: (): void => { + const arr = this.contentProviders.get(scheme); + const index = arr.indexOf(provider); + if (index !== -1) { + arr.splice(index, 1); + } + }, + }; + } + canHandleResource(resource: uri): boolean { return resource.scheme === Schemas.file; } @@ -268,13 +318,15 @@ export class FileService extends Disposable implements IFileService { streamContent.value.on('error', err => reject(err)); streamContent.value.on('end', _ => resolve(result)); + // NOTE@coder: internally we wait for the ready event. + (streamContent.value as any).emit('ready'); + return result; }); }); } resolveStreamContent(resource: uri, options?: IResolveContentOptions): TPromise { - // Guard early against attempts to resolve an invalid file path if (resource.scheme !== Schemas.file || !resource.fsPath) { return TPromise.wrapError(new FileOperationError( @@ -415,6 +467,16 @@ export class FileService extends Disposable implements IFileService { } private resolveFileData(resource: uri, options: IResolveContentOptions, token: CancellationToken): TPromise { + if (this.contentProviders.has(resource.scheme)) { + const providers = this.contentProviders.get(resource.scheme); + for (let i = 0; i < providers.length; i++) { + const provider = providers[i]; + const data = provider.resolve(resource); + if (typeof data !== "undefined") { + return data; + } + } + } const chunkBuffer = BufferPool._64K.acquire(); @@ -572,61 +634,56 @@ export class FileService extends Disposable implements IFileService { return this.doUpdateContent(resource, value, options); } + // NOTE@coder: restructured for better performance. private doUpdateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): TPromise { const absolutePath = this.toAbsolutePath(resource); + const encodingToWrite = this._encoding.getWriteEncoding(resource, options.encoding); - // 1.) check file for writing - return this.checkFileBeforeWriting(absolutePath, options).then(exists => { - let createParentsPromise: TPromise; - if (exists) { - createParentsPromise = TPromise.as(null); - } else { - createParentsPromise = pfs.mkdirp(paths.dirname(absolutePath)); - } - - // 2.) create parents as needed - return createParentsPromise.then(() => { + // NOTE@coder: our provider doesn't use the encoding and has its own + // conflict resolution, so we can skip checking both here. + let checkPromise: TPromise; + if (!options.createFile && this.contentProviders.has(resource.scheme)) { + checkPromise = TPromise.as(false); + } else { + checkPromise = this.checkFileBeforeWriting(absolutePath, options).then((exists) => { const encodingToWrite = this._encoding.getWriteEncoding(resource, options.encoding); - let addBomPromise: TPromise = TPromise.as(false); - // UTF_16 BE and LE as well as UTF_8 with BOM always have a BOM if (encodingToWrite === encoding.UTF16be || encodingToWrite === encoding.UTF16le || encodingToWrite === encoding.UTF8_with_bom) { - addBomPromise = TPromise.as(true); + return TPromise.as(true); } // Existing UTF-8 file: check for options regarding BOM else if (exists && encodingToWrite === encoding.UTF8) { if (options.overwriteEncoding) { - addBomPromise = TPromise.as(false); // if we are to overwrite the encoding, we do not preserve it if found + return TPromise.as(false); // if we are to overwrite the encoding, we do not preserve it if found } else { - addBomPromise = encoding.detectEncodingByBOM(absolutePath).then(enc => enc === encoding.UTF8); // otherwise preserve it if found + return encoding.detectEncodingByBOM(absolutePath).then(enc => enc === encoding.UTF8); // otherwise preserve it if found } } - // 3.) check to add UTF BOM - return addBomPromise.then(addBom => { - - // 4.) set contents and resolve - if (!exists || !isWindows) { - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite); - } + return TPromise.as(false); + }); + } - // On Windows and if the file exists, we use a different strategy of saving the file - // by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows - // (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams - // (see https://github.com/Microsoft/vscode/issues/6363) - else { + const doUpdate = (addBom: boolean, createFile: boolean): TPromise => { + // NOTE@coder: add conflict resolution with options.accept. + // Also the Windows check has been entirely removed. + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, undefined, options.accept, createFile); + }; - // 4.) truncate - return pfs.truncate(absolutePath, 0).then(() => { + // NOTE@coder: Save first and then handle errors afterward. This lets us + // skip a bit of work for most saves. + return checkPromise.then((addBom) => { + return doUpdate(addBom, options.createFile).then(null, (error) => { + if (error.code === 'ENOENT') { + return pfs.mkdirp(paths.dirname(absolutePath)).then(() => { + return doUpdate(addBom, true); + }); + } - // 5.) set contents (with r+ mode) and resolve - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }); - }); - } - }); + return TPromise.wrapError(error); }); - }).then(null, error => { + }).then(null, (error) => { if (error.code === 'EACCES' || error.code === 'EPERM') { return TPromise.wrapError(new FileOperationError( nls.localize('filePermission', "Permission denied writing to file ({0})", resource.toString(true)), @@ -639,7 +696,12 @@ export class FileService extends Disposable implements IFileService { }); } - private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string | ITextSnapshot, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): TPromise { + // NOTE@coder: add conflict resolution. + private doSetContentsAndResolve( + resource: uri, absolutePath: string, value: string | ITextSnapshot, + addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }, + accept?: ConflictResolution, createFile?: boolean, + ): TPromise { let writeFilePromise: TPromise; // Configure encoding related options as needed @@ -651,10 +713,24 @@ export class FileService extends Disposable implements IFileService { }; } - if (typeof value === 'string') { - writeFilePromise = pfs.writeFile(absolutePath, value, writeFileOptions); - } else { - writeFilePromise = pfs.writeFile(absolutePath, createReadableOfSnapshot(value), writeFileOptions); + // NOTE@coder: use custom content provider for saving if one exists. + if (!createFile && this.contentProviders.has(resource.scheme)) { + const providers = this.contentProviders.get(resource.scheme); + for (let i = 0; i < providers.length; i++) { + const provider = providers[i]; + writeFilePromise = provider.save(resource, accept); + if (typeof writeFilePromise !== "undefined") { + break; + } + } + } + + if (typeof writeFilePromise === "undefined") { + if (typeof value === 'string') { + writeFilePromise = pfs.writeFile(absolutePath, value, writeFileOptions); + } else { + writeFilePromise = pfs.writeFile(absolutePath, createReadableOfSnapshot(value), writeFileOptions); + } } // set contents @@ -679,7 +755,7 @@ export class FileService extends Disposable implements IFileService { return this.updateContent(uri.file(tmpPath), value, writeOptions).then(() => { // 3.) invoke our CLI as super user - return TPromise.wrap(import('sudo-prompt')).then(sudoPrompt => { + return TPromise.wrap(require('sudo-prompt')).then(sudoPrompt => { return new TPromise((c, e) => { const promptOptions = { name: this.environmentService.appNameLong.replace('-', ''), @@ -748,7 +824,7 @@ export class FileService extends Disposable implements IFileService { } // Create file - return this.updateContent(resource, content).then(result => { + return this.updateContent(resource, content, { createFile: true }).then(result => { // Events this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, result)); @@ -776,8 +852,9 @@ export class FileService extends Disposable implements IFileService { } private checkFileBeforeWriting(absolutePath: string, options: IUpdateContentOptions = Object.create(null), ignoreReadonly?: boolean): TPromise { - return pfs.exists(absolutePath).then(exists => { - if (exists) { + // NOTE@coder: we can just use the stat to see if the file exists. + // return pfs.exists(absolutePath).then(exists => { + // if (exists) { return pfs.stat(absolutePath).then(stat => { if (stat.isDirectory()) { return TPromise.wrapError(new Error('Expected file is actually a directory')); @@ -795,7 +872,17 @@ export class FileService extends Disposable implements IFileService { // check for size is a weaker check because it can return a false negative if the file has changed // but to the same length. This is a compromise we take to avoid having to produce checksums of // the file content for comparison which would be much slower to compute. - if (typeof options.mtime === 'number' && typeof options.etag === 'string' && options.mtime < stat.mtime.getTime() && options.etag !== etag(stat.size, options.mtime)) { + const providers = this.contentProviders.get("file"); + let handled = false; + for (let i = 0; i < providers.length; i++) { + const provider = providers[i]; + if (provider.willHandle(uri.file(absolutePath))) { + handled = true; + break; + } + } + + if (!handled && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.mtime < stat.mtime.getTime() && options.etag !== etag(stat.size, options.mtime)) { return TPromise.wrapError(new FileOperationError(nls.localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE, options)); } @@ -816,17 +903,22 @@ export class FileService extends Disposable implements IFileService { return this.readOnlyError(options); } - return exists; + return true; }); }); } - return TPromise.as(exists); + return TPromise.as(true); + }, (error) => { + if (error.code === "ENOENT") { + return TPromise.as(false); + } + return TPromise.wrapError(error); }); - } + // } - return TPromise.as(exists); - }); + // return TPromise.as(exists); + // }); } private readOnlyError(options: IUpdateContentOptions): TPromise { @@ -916,11 +1008,11 @@ export class FileService extends Disposable implements IFileService { return this.doDelete(resource, options && options.recursive); } - private doMoveItemToTrash(resource: uri): TPromise { + private async doMoveItemToTrash(resource: uri): TPromise { const absolutePath = resource.fsPath; const shell = (require('electron') as Electron.RendererInterface).shell; // workaround for being able to run tests out of VSCode debugger - const result = shell.moveItemToTrash(absolutePath); + const result = await shell.moveItemToTrash(absolutePath); if (!result) { return TPromise.wrapError(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath)))); } diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts index 7c91d6c73e..d3346595c1 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; import { WatcherChannel } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { ChokidarWatcherService } from 'vs/workbench/services/files/node/watcher/unix/chokidarWatcherService'; +import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; +import { ChannelServer } from 'vs/base/parts/ipc/node/ipc'; -const server = new Server(); +const protocol = Protocol.fromStream(process.stdin, process.stdout); +const server = new ChannelServer(protocol); const service = new ChokidarWatcherService(); const channel = new WatcherChannel(service); -server.registerChannel('watcher', channel); \ No newline at end of file +server.registerChannel('watcher', channel); diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index 1c9aff61c6..30b561464f 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -5,7 +5,7 @@ 'use strict'; -import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc'; +import { getNextTickChannel, ChannelClient } from 'vs/base/parts/ipc/node/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; @@ -16,7 +16,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { Schemas } from 'vs/base/common/network'; import { filterEvent } from 'vs/base/common/event'; import { IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { nodePath, bootstrapForkLocation, wush } from 'coder/server'; +import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -30,7 +31,7 @@ export class FileWatcher { private contextService: IWorkspaceContextService, private configurationService: IConfigurationService, private onFileChanges: (changes: FileChangesEvent) => void, - private errorLogger: (msg: string) => void, + private errorLogger: (msg: string, final?: boolean) => void, private verboseLogging: boolean ) { this.isDisposed = false; @@ -39,23 +40,12 @@ export class FileWatcher { } public startWatching(): () => void { - const args = ['--type=watcherService']; - - const client = new Client( - getPathFromAmdModule(require, 'bootstrap-fork'), - { - serverName: 'File Watcher (chokidar)', - args, - env: { - AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/unix/watcherApp', - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: this.verboseLogging - } - } - ); - this.toDispose.push(client); - - client.onDidProcessExit(() => { + const protocol = Protocol.fromSession(wush.execute({ + // The flag is to help identify it for debugging. + command: `bash -c 'VSCODE_ALLOW_IO=true` + + ` AMD_ENTRYPOINT=vs/workbench/services/files/node/watcher/unix/watcherApp` + + ` ${nodePath} ${bootstrapForkLocation} --watcher'`, + }), () => { // our watcher app should never be completed because it keeps on watching. being in here indicates // that the watcher process died and we want to restart it here. we only do it a max number of times if (!this.isDisposed) { @@ -64,10 +54,14 @@ export class FileWatcher { this.restartCounter++; this.startWatching(); } else { - this.errorLogger('[FileWatcher] failed to start after retrying for some time, giving up. Please report this as a bug report!'); + this.errorLogger('[FileWatcher] failed to start after retrying for some time, giving up. Please report this as a bug report!', true); } } - }, null, this.toDispose); + + this.toDispose.forEach((d) => d.dispose()); + }); + const client = new ChannelClient(protocol); + this.toDispose.push(client); const channel = getNextTickChannel(client.getChannel('watcher')); this.service = new WatcherChannelClient(channel); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 0e3f6d5c06..f720bc57cd 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -446,16 +446,21 @@ export class SearchService implements IRawSearchService { * TODO@rob - Is this really needed? */ private preventCancellation(promise: CancelablePromise): CancelablePromise { + // @ts-ignore return new class implements CancelablePromise { cancel() { // Do nothing } + // @ts-ignore then(resolve, reject) { return promise.then(resolve, reject); } catch(reject?) { return this.then(undefined, reject); } + cancelableThen(resolve, reject) { + return createCancelablePromise(_ => this.then(resolve, reject)); + } }; } } diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 0d2c576b0d..bb8a9aac01 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import { rgPath } from 'vscode-ripgrep'; +import * as path from 'path'; +// import { rgPath } from 'vscode-ripgrep'; import { isMacintosh as isMac } from 'vs/base/common/platform'; import * as glob from 'vs/base/common/glob'; @@ -14,6 +15,9 @@ import { normalizeNFD } from 'vs/base/common/normalization'; import { IFolderSearch, IRawSearch } from './search'; import { foldersToIncludeGlobs, foldersToRgExcludeGlobs } from './ripgrepTextSearch'; +// NOTE@coder: use rg provided by our mount. +const rgPath = path.join(path.dirname(process.execPath), "rg"); + // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts index 94751dc40b..5f96dd74e1 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts @@ -18,9 +18,12 @@ import * as encoding from 'vs/base/node/encoding'; import * as extfs from 'vs/base/node/extfs'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IProgress, ITextSearchPreviewOptions, ITextSearchStats, TextSearchResult } from 'vs/platform/search/common/search'; -import { rgPath } from 'vscode-ripgrep'; +// import { rgPath } from 'vscode-ripgrep'; import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, ISerializedSearchSuccess } from './search'; +// NOTE@coder: use rg provided by our mount. +const rgPath = path.join(path.dirname(process.execPath), "rg"); + // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); @@ -63,7 +66,7 @@ export class RipgrepEngine { this.postProcessExclusions = glob.parseToAsync(rgArgs.siblingClauses, { trimForExclusions: true }); } - const cwd = platform.isWindows ? 'c:/' : '/'; + const cwd = /*platform.isWindows ? 'c:/' :*/ '/'; const escapedArgs = rgArgs.args .map(arg => arg.match(/^-/) ? arg : `'${arg}'`) .join(' '); diff --git a/src/vs/workbench/services/search/node/searchApp.ts b/src/vs/workbench/services/search/node/searchApp.ts index 293705f38b..a971b19658 100644 --- a/src/vs/workbench/services/search/node/searchApp.ts +++ b/src/vs/workbench/services/search/node/searchApp.ts @@ -5,11 +5,13 @@ 'use strict'; -import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; import { SearchChannel } from './searchIpc'; import { SearchService } from './rawSearchService'; +import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; +import { ChannelServer } from 'vs/base/parts/ipc/node/ipc'; -const server = new Server(); +const protocol = Protocol.fromStream(process.stdin, process.stdout); +const server = new ChannelServer(protocol); const service = new SearchService(); const channel = new SearchChannel(service); -server.registerChannel('search', channel); \ No newline at end of file +server.registerChannel('search', channel); diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index f1869966ca..252061caa3 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -18,8 +18,7 @@ import * as strings from 'vs/base/common/strings'; import { URI as uri } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as pfs from 'vs/base/node/pfs'; -import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc'; -import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; +import { getNextTickChannel, ChannelClient } from 'vs/base/parts/ipc/node/ipc'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -33,6 +32,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; +import { nodePath, bootstrapForkLocation, wush } from 'coder/server'; +import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; export class SearchService extends Disposable implements ISearchService { public _serviceBrand: any; @@ -409,34 +410,15 @@ export class DiskSearch implements ISearchResultProvider { private raw: IRawSearchService; constructor(verboseLogging: boolean, timeout: number = 60 * 60 * 1000, searchDebug?: IDebugParams) { - const opts: IIPCOptions = { - serverName: 'Search', - timeout: timeout, - args: ['--type=searchService'], - // See https://github.com/Microsoft/vscode/issues/27665 - // Pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`. - // e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host - // results in the forked process inheriting `--inspect-brk=xxx`. - freshExecArgv: true, - env: { - AMD_ENTRYPOINT: 'vs/workbench/services/search/node/searchApp', - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: verboseLogging - } - }; - - if (searchDebug) { - if (searchDebug.break && searchDebug.port) { - opts.debugBrk = searchDebug.port; - } else if (!searchDebug.break && searchDebug.port) { - opts.debug = searchDebug.port; - } - } - - const client = new Client( - getPathFromAmdModule(require, 'bootstrap-fork'), - opts); - + const protocol = Protocol.fromSession(wush.execute({ + // The flag is to help identify it for debugging. + command: `bash -c 'VSCODE_ALLOW_IO=true` + + ` AMD_ENTRYPOINT=vs/workbench/services/search/node/searchApp` + + ` ${nodePath} ${bootstrapForkLocation} --search'`, + }), () => { + // Nothin + }); + const client = new ChannelClient(protocol); const channel = getNextTickChannel(client.getChannel('search')); this.raw = new SearchChannelClient(channel); } diff --git a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts index 8dc3f78076..3c687de502 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts @@ -12,9 +12,9 @@ import { Event, Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ITokenizationSupport, TokenizationRegistry, IState, LanguageId, TokenMetadata } from 'vs/editor/common/modes'; +import { ITokenizationSupport, TokenizationRegistry, IState, LanguageId, TokenMetadata, StandardTokenType } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, ITokenTypeMap, StandardTokenType } from 'vscode-textmate'; +import { StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, ITokenTypeMap } from 'vscode-textmate'; import { IWorkbenchThemeService, ITokenColorizationRule } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { grammarsExtPoint, IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution } from 'vs/workbench/services/textMate/electron-browser/TMGrammars'; @@ -112,13 +112,13 @@ export class TMLanguageRegistration { const tokenType = tokenTypes[scope]; switch (tokenType) { case 'string': - this.tokenTypes[scope] = StandardTokenType.String; + this.tokenTypes[scope] = StandardTokenType.String as any; break; case 'other': - this.tokenTypes[scope] = StandardTokenType.Other; + this.tokenTypes[scope] = StandardTokenType.Other as any; break; case 'comment': - this.tokenTypes[scope] = StandardTokenType.Comment; + this.tokenTypes[scope] = StandardTokenType.Comment as any; break; } } @@ -211,7 +211,7 @@ export class TextMateService implements ITextMateService { private _getOrCreateGrammarRegistry(): TPromise<[Registry, StackElement]> { if (!this._grammarRegistry) { - this._grammarRegistry = TPromise.wrap(import('vscode-textmate')).then(({ Registry, INITIAL, parseRawGrammar }) => { + this._grammarRegistry = TPromise.wrap(require('vscode-textmate')).then(({ Registry, INITIAL, parseRawGrammar }) => { const grammarRegistry = new Registry({ loadGrammar: (scopeName: string) => { const location = this._scopeRegistry.getGrammarLocation(scopeName); @@ -228,7 +228,23 @@ export class TextMateService implements ITextMateService { }, getInjections: (scopeName: string) => { return this._injections[scopeName]; - } + }, + getOnigLib: () => { + return new Promise((res, rej) => { + const onigasm = require('onigasm'); + const wasmUrl = require('!!file-loader!onigasm/lib/onigasm.wasm'); + return fetch(wasmUrl).then((resp) => resp.arrayBuffer()).then((buffer) => { + return onigasm.loadWASM(buffer); + }).then(() => { + res({ + createOnigScanner: function (patterns) { return new onigasm.OnigScanner(patterns); }, + createOnigString: function (s) { return new onigasm.OnigString(s); } + }); + }).catch((reason) => { + rej(reason); + }); + }); + }, }); this._updateTheme(grammarRegistry); this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 5e727d770c..c8041e10f1 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -64,6 +64,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private bufferSavedVersionId: number; private lastResolvedDiskStat: IFileStat; private blockModelContentChange: boolean; + private updatingTextBuffer: boolean; private autoSaveAfterMillies: number; private autoSaveAfterMilliesEnabled: boolean; private autoSaveDisposable: IDisposable; @@ -425,9 +426,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Update model value in a block that ignores model content change events this.blockModelContentChange = true; + this.updatingTextBuffer = true; try { this.updateTextEditorModel(value); } finally { + this.updatingTextBuffer = false; this.blockModelContentChange = false; } @@ -687,7 +690,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // - the model is not dirty (otherwise we know there are changed which needs to go to the file) // - the model is not in orphan mode (because in that case we know the file does not exist on disk) // - the model version did not change due to save participants running - if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) { + // - NOTE@coder this is not a conflict resolution + if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId && typeof options.accept === "undefined") { return this.doTouch(newVersionId); } @@ -709,7 +713,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil mtime: this.lastResolvedDiskStat.mtime, encoding: this.getEncoding(), etag: this.lastResolvedDiskStat.etag, - writeElevated: options.writeElevated + writeElevated: options.writeElevated, + accept: options.accept, }).then(stat => { this.logService.trace(`doSave(${versionId}) - after updateContent()`, this.resource); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 56719225de..d96d9adff1 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -14,6 +14,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ConflictResolution } from 'coder/common'; // NOTE@coder. /** * The save error handler can be installed on the text text file editor model to install code that executes when save errors occur. @@ -207,6 +208,7 @@ export interface ISaveOptions { overwriteEncoding?: boolean; skipSaveParticipants?: boolean; writeElevated?: boolean; + accept?: ConflictResolution; } export interface ILoadOptions { diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index 90d988f493..b14745993a 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -87,8 +87,8 @@ export class TextFileService extends AbstractTextFileService { : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Don't Save"), nls.localize('cancel', "Cancel") ]; @@ -116,7 +116,7 @@ export class TextFileService extends AbstractTextFileService { const options: Electron.SaveDialogOptions = { defaultPath }; // Filters are only enabled on Windows where they work properly - if (!isWindows) { + if (!isWindows || true) { return options; } diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts index 81c979f3f8..d2806e9227 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts @@ -324,7 +324,7 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu let pListParser: Thenable<{ parse(content: string) }>; function getPListParser() { - return pListParser || import('fast-plist'); + return pListParser || Promise.resolve(require('fast-plist')); } function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): TPromise { diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts index b6d8dd31b1..4b62c4ccf1 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts @@ -13,6 +13,8 @@ import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbenc import { TPromise } from 'vs/base/common/winjs.base'; import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import product from 'vs/platform/node/product'; +import { api } from 'coder/api'; export class FileIconThemeData implements IFileIconTheme { id: string; @@ -113,7 +115,7 @@ export class FileIconThemeData implements IFileIconTheme { description: this.description, settingsId: this.settingsId, location: this.location, - styleSheetContent: this.styleSheetContent, + // styleSheetContent: this.styleSheetContent, hasFileIcons: this.hasFileIcons, hasFolderIcons: this.hasFolderIcons, hidesExplorerArrows: this.hidesExplorerArrows @@ -180,7 +182,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); function resolvePath(path: string) { - return resources.joinPath(iconThemeDocumentLocationDirname, path); + return URI.parse(product.fetchUrl + resources.joinPath(iconThemeDocumentLocationDirname, path).path) + `?api_key=${api.apiKey.key}`; } function collectSelectors(associations: IconsAssociation, baseThemeClassName?: string) { diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index ce9193eb9c..4da9379e26 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -5,6 +5,8 @@ 'use strict'; +import 'vs/loader'; + // Base import 'vs/base/common/strings'; import 'vs/base/common/errors'; @@ -65,9 +67,9 @@ import 'vs/workbench/parts/scm/electron-browser/scm.contribution'; import 'vs/workbench/parts/scm/electron-browser/scmViewlet'; // can be packaged separately import 'vs/workbench/parts/debug/electron-browser/debug.contribution'; -import 'vs/workbench/parts/debug/browser/debugQuickOpen'; -import 'vs/workbench/parts/debug/electron-browser/repl'; -import 'vs/workbench/parts/debug/browser/debugViewlet'; // can be packaged separately +// import 'vs/workbench/parts/debug/browser/debugQuickOpen'; +// import 'vs/workbench/parts/debug/electron-browser/repl'; +// import 'vs/workbench/parts/debug/browser/debugViewlet'; // can be packaged separately import 'vs/workbench/parts/markers/electron-browser/markers.contribution'; import 'vs/workbench/parts/comments/electron-browser/comments.contribution'; @@ -83,6 +85,8 @@ import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution'; import 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // can be packaged separately +import 'vs/workbench/parts/collaboration/electron-browser/collaboration.contribution'; + import 'vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution'; import 'vs/workbench/parts/output/electron-browser/output.contribution'; @@ -94,7 +98,7 @@ import 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; // can be p import 'vs/workbench/electron-browser/workbench'; -import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution'; +// import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution'; import 'vs/workbench/parts/tasks/electron-browser/task.contribution'; @@ -114,25 +118,25 @@ import 'vs/workbench/parts/snippets/electron-browser/tabCompletion'; import 'vs/workbench/parts/themes/electron-browser/themes.contribution'; -import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution'; +// import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution'; import 'vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; import 'vs/workbench/parts/update/electron-browser/update.contribution'; -import 'vs/workbench/parts/surveys/electron-browser/nps.contribution'; -import 'vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution'; +// import 'vs/workbench/parts/surveys/electron-browser/nps.contribution'; +// import 'vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution'; import 'vs/workbench/parts/performance/electron-browser/performance.contribution'; -import 'vs/workbench/parts/cli/electron-browser/cli.contribution'; +// import 'vs/workbench/parts/cli/electron-browser/cli.contribution'; import 'vs/workbench/api/electron-browser/extensionHost.contribution'; import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/electron-browser/main'; -import 'vs/workbench/parts/themes/test/electron-browser/themes.test.contribution'; +// import 'vs/workbench/parts/themes/test/electron-browser/themes.test.contribution'; import 'vs/workbench/parts/watermark/electron-browser/watermark'; @@ -145,3 +149,11 @@ import 'vs/workbench/parts/navigation/common/navigation.contribution'; // services import 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution'; + +import { URI } from 'vs/base/common/uri'; +import { startup } from 'vs/workbench/electron-browser/main'; + +export { + URI, + startup, +}; diff --git a/test/smoke/tsconfig.json b/test/smoke/tsconfig.json index 1b0b03c2d6..2b86b7ecc0 100644 --- a/test/smoke/tsconfig.json +++ b/test/smoke/tsconfig.json @@ -13,6 +13,9 @@ "lib": [ "es2016", "dom" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "exclude": [