Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f

This commit is contained in:
ADS Merger
2020-07-22 03:06:57 +00:00
parent 53ec7585a9
commit 1b7b54ce14
229 changed files with 5099 additions and 3188 deletions

View File

@@ -14,7 +14,7 @@ const nlsConfig = bootstrap.setupNLS();
// Bootstrap: Loader
loader.config({
baseUrl: bootstrap.uriFromPath(__dirname),
baseUrl: bootstrap.fileUriFromPath(__dirname),
catchError: true,
nodeRequire: require,
nodeMain: __filename,

View File

@@ -21,8 +21,9 @@
globalThis.MonacoBootstrapWindow = factory();
}
}(this, function () {
const path = require.__$__nodeRequire('path');
const bootstrap = globalThis.MonacoBootstrap;
const preloadGlobals = globals();
const sandbox = preloadGlobals.context.sandbox;
const safeProcess = sandbox ? preloadGlobals.process : process;
/**
* @param {string[]} modulePaths
@@ -43,29 +44,33 @@
const configuration = JSON.parse(args['config'] || '{}') || {};
// Error handler
process.on('uncaughtException', function (error) {
safeProcess.on('uncaughtException', function (error) {
onUnexpectedError(error, enableDeveloperTools);
});
// Developer tools
const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath;
const enableDeveloperTools = (safeProcess.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath;
let developerToolsUnbind;
if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) {
developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding);
}
// Correctly inherit the parent's environment
Object.assign(process.env, configuration.userEnv);
// Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only)
if (!sandbox) {
Object.assign(safeProcess.env, configuration.userEnv);
}
// Enable ASAR support
bootstrap.enableASARSupport(path.join(configuration.appRoot, 'node_modules'));
// Enable ASAR support (TODO@sandbox non-sandboxed only)
if (!sandbox) {
globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot);
}
if (options && typeof options.canModifyDOM === 'function') {
options.canModifyDOM(configuration);
}
// Get the nls configuration into the process.env as early as possible.
const nlsConfig = bootstrap.setupNLS();
// Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only)
const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS();
let locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
@@ -76,16 +81,20 @@
window.document.documentElement.setAttribute('lang', locale);
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs
window['define'] = undefined;
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs (TODO@sandbox non-sandboxed only)
if (!sandbox) {
window['define'] = undefined;
}
// replace the patched electron fs with the original node fs for all AMD code
require.define('fs', ['original-fs'], function (originalFS) { return originalFS; });
// replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only)
if (!sandbox) {
require.define('fs', ['original-fs'], function (originalFS) { return originalFS; });
}
window['MonacoEnvironment'] = {};
const loaderConfig = {
baseUrl: `${bootstrap.uriFromPath(configuration.appRoot)}/out`,
baseUrl: `${uriFromPath(configuration.appRoot)}/out`,
'vs/nls': nlsConfig,
amdModulesPattern: /^(vs|sql)\//, // {{SQL CARBON EDIT}} include sql in regex
};
@@ -150,7 +159,7 @@
* @returns {() => void}
*/
function registerDeveloperKeybindings(disallowReloadKeybinding) {
const ipcRenderer = globals().ipcRenderer;
const ipcRenderer = preloadGlobals.ipcRenderer;
const extractKey = function (e) {
return [
@@ -163,9 +172,9 @@
};
// Devtools & reload support
const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const TOGGLE_DEV_TOOLS_KB = (safeProcess.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12
const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
let listener = function (e) {
const key = extractKey(e);
@@ -192,7 +201,7 @@
*/
function onUnexpectedError(error, enableDeveloperTools) {
if (enableDeveloperTools) {
const ipcRenderer = globals().ipcRenderer;
const ipcRenderer = preloadGlobals.ipcRenderer;
ipcRenderer.send('vscode:openDevTools');
}
@@ -211,6 +220,31 @@
return window.vscode;
}
/**
* TODO@sandbox this should not use the file:// protocol at all
* and be consolidated with the fileUriFromPath() method in
* bootstrap.js.
*
* @param {string} path
* @returns {string}
*/
function uriFromPath(path) {
let pathName = path.replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = `/${pathName}`;
}
/** @type {string} */
let uri;
if (safeProcess.platform === 'win32' && pathName.startsWith('//')) { // specially handle Windows UNC paths
uri = encodeURI(`file:${pathName}`);
} else {
uri = encodeURI(`file://${pathName}`);
}
return uri.replace(/#/g, '%23');
}
return {
load,
globals

37
src/bootstrap.js vendored
View File

@@ -16,7 +16,11 @@
// Browser
else {
globalThis.MonacoBootstrap = factory();
try {
globalThis.MonacoBootstrap = factory();
} catch (error) {
console.warn(error); // expected when e.g. running with sandbox: true (TODO@sandbox eventually consolidate this)
}
}
}(this, function () {
const Module = require('module');
@@ -40,10 +44,10 @@
//#region Add support for using node_modules.asar
/**
* @param {string=} nodeModulesPath
* @param {string} appRoot
*/
function enableASARSupport(nodeModulesPath) {
let NODE_MODULES_PATH = nodeModulesPath;
function enableASARSupport(appRoot) {
let NODE_MODULES_PATH = appRoot ? path.join(appRoot, 'node_modules') : undefined;
if (!NODE_MODULES_PATH) {
NODE_MODULES_PATH = path.join(__dirname, '../node_modules');
} else {
@@ -83,7 +87,7 @@
* @param {string} _path
* @returns {string}
*/
function uriFromPath(_path) {
function fileUriFromPath(_path) {
let pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = `/${pathName}`;
@@ -132,7 +136,7 @@
}
const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`);
readFile(bundleFile).then(function (content) {
fs.promises.readFile(bundleFile, 'utf8').then(function (content) {
const json = JSON.parse(content);
bundles[bundle] = json;
@@ -140,7 +144,7 @@
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
@@ -152,23 +156,6 @@
return nlsConfig;
}
/**
* @param {string} file
* @returns {Promise<string>}
*/
function readFile(file) {
return fs.promises.readFile(file, 'utf8');
}
/**
* @param {string} file
* @param {string} content
* @returns {Promise<void>}
*/
function writeFile(file, content) {
return fs.promises.writeFile(file, content, 'utf8');
}
//#endregion
@@ -254,6 +241,6 @@
avoidMonkeyPatchFromAppInsights,
configurePortable,
setupNLS,
uriFromPath
fileUriFromPath
};
}));

View File

@@ -18,7 +18,11 @@ const bootstrap = require('./bootstrap');
const paths = require('./paths');
/** @type {any} */
const product = require('../product.json');
const { app, protocol } = require('electron');
const { app, protocol, crashReporter } = require('electron');
// Disable render process reuse, we still have
// non-context aware native modules in the renderer.
app.allowRendererProcessReuse = false;
// Enable portable support
const portable = bootstrap.configurePortable(product);
@@ -38,13 +42,13 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}}
const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);
// Set temp directory based on crash-reporter-directory CLI argument
// The crash reporter will store crashes in temp folder so we need
// to change that location accordingly.
// Configure static command line arguments
const argvConfig = configureCommandlineSwitchesSync(args);
// If a crash-reporter-directory is specified we setup the crash reporter
// right from the beginning as early as possible to monitor all processes.
// If a crash-reporter-directory is specified we store the crash reports
// in the specified directory and don't upload them to the crash server.
let crashReporterDirectory = args['crash-reporter-directory'];
let submitURL = '';
if (crashReporterDirectory) {
crashReporterDirectory = path.normalize(crashReporterDirectory);
@@ -62,23 +66,41 @@ if (crashReporterDirectory) {
}
}
// Crashes are stored in the temp directory by default, so we
// Crashes are stored in the crashDumps directory by default, so we
// need to change that directory to the provided one
console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`);
app.setPath('temp', crashReporterDirectory);
// Start crash reporter
const { crashReporter } = require('electron');
const productName = (product.crashReporter && product.crashReporter.productName) || product.nameShort;
const companyName = (product.crashReporter && product.crashReporter.companyName) || 'Microsoft';
crashReporter.start({
companyName: companyName,
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
submitURL: '',
uploadToServer: false
});
console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
app.setPath('crashDumps', crashReporterDirectory);
} else {
const appCenter = product.appCenter;
// Disable Appcenter crash reporting if
// * --crash-reporter-directory is specified
// * enable-crash-reporter runtime argument is set to 'false'
// * --disable-crash-reporter command line parameter is set
if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) {
const isWindows = (process.platform === 'win32');
const isLinux = (process.platform === 'linux');
const crashReporterId = argvConfig['crash-reporter-id'];
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (uuidPattern.test(crashReporterId)) {
submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin;
submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
// Send the id for child node process that are explicitly starting crash reporter.
// For vscode this is ExtensionHost process currently.
process.argv.push('--crash-reporter-id', crashReporterId);
}
}
}
// Start crash reporter for all processes
const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort;
const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft';
crashReporter.start({
companyName: companyName,
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
submitURL,
uploadToServer: !crashReporterDirectory
});
// Set logs path before app 'ready' event if running portable
// to ensure that no 'logs' folder is created on disk at a
// location outside of the portable directory
@@ -117,9 +139,6 @@ registerListeners();
// Cached data
const nodeCachedDataDir = getNodeCachedDir();
// Configure static command line arguments
const argvConfig = configureCommandlineSwitchesSync(args);
// Remove env set by snap https://github.com/microsoft/vscode/issues/85344
if (process.env['SNAP']) {
delete process.env['GDK_PIXBUF_MODULE_FILE'];
@@ -261,9 +280,6 @@ function configureCommandlineSwitchesSync(cliArgs) {
app.commandLine.appendSwitch('js-flags', jsFlags);
}
// TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873
app.commandLine.appendSwitch('disable-features', 'LayoutNG');
return argvConfig;
}

View File

@@ -476,6 +476,8 @@ export namespace Codicon {
export const record = new Codicon('record', { character: '\\eba7' });
export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' });
export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' });
export const cloud = new Codicon('cloud', { character: '\\ebaa' });
export const merge = new Codicon('merge', { character: '\\ebab' });
}

View File

@@ -72,6 +72,7 @@ export interface JSONScanner {
}
export interface ParseError {
error: ParseErrorCode;
offset: number;

View File

@@ -332,6 +332,12 @@ export class Scrollable extends Disposable {
this._setState(newState);
if (!this._smoothScrolling) {
// Looks like someone canceled the smooth scrolling
// from the scroll event handler
return;
}
if (update.isDone) {
this._smoothScrolling.dispose();
this._smoothScrolling = null;

View File

@@ -252,7 +252,7 @@ export class URI implements UriComponents {
return this;
}
return new CachingURI(scheme, authority, path, query, fragment);
return new Uri(scheme, authority, path, query, fragment);
}
// ---- parse & validate ------------------------
@@ -266,9 +266,9 @@ export class URI implements UriComponents {
static parse(value: string, _strict: boolean = false): URI {
const match = _regexp.exec(value);
if (!match) {
return new CachingURI(_empty, _empty, _empty, _empty, _empty);
return new Uri(_empty, _empty, _empty, _empty, _empty);
}
return new CachingURI(
return new Uri(
match[2] || _empty,
percentDecode(match[4] || _empty),
percentDecode(match[5] || _empty),
@@ -323,11 +323,11 @@ export class URI implements UriComponents {
}
}
return new CachingURI('file', authority, path, _empty, _empty);
return new Uri('file', authority, path, _empty, _empty);
}
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
return new CachingURI(
return new Uri(
components.scheme,
components.authority,
components.path,
@@ -388,7 +388,7 @@ export class URI implements UriComponents {
} else if (data instanceof URI) {
return data;
} else {
const result = new CachingURI(data);
const result = new Uri(data);
result._formatted = (<UriState>data).external;
result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath : null;
return result;
@@ -414,7 +414,7 @@ interface UriState extends UriComponents {
const _pathSepMarker = isWindows ? 1 : undefined;
// This class exists so that URI is compatibile with vscode.Uri (API).
class CachingURI extends URI {
class Uri extends URI {
_formatted: string | null = null;
_fsPath: string | null = null;

View File

@@ -61,6 +61,10 @@ export async function resolveTerminalEncoding(verbose?: boolean): Promise<string
exec('chcp', (err, stdout, stderr) => {
if (stdout) {
if (verbose) {
console.log(`Output from "chcp" command is: ${stdout}`);
}
const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings) as Array<keyof typeof windowsTerminalEncodings>;
for (const key of windowsTerminalEncodingKeys) {
if (stdout.indexOf(key) >= 0) {

View File

@@ -5,14 +5,14 @@
import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } from 'electron';
import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu';
import { withNullAsUndefined } from 'vs/base/common/types';
export function registerContextMenuListener(): void {
ipcMain.on(CONTEXT_MENU_CHANNEL, (event: IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => {
const menu = createMenu(event, onClickChannel, items);
const window = BrowserWindow.fromWebContents(event.sender);
menu.popup({
window: window ? window : undefined,
window: withNullAsUndefined(BrowserWindow.fromWebContents(event.sender)),
x: options ? options.x : undefined,
y: options ? options.y : undefined,
positioningItem: options ? options.positioningItem : undefined,

View File

@@ -209,37 +209,6 @@ export interface SaveDialogReturnValue {
bookmark?: string;
}
export interface CrashReporterStartOptions {
companyName: string;
/**
* URL that crash reports will be sent to as POST.
*/
submitURL: string;
/**
* Defaults to `app.name`.
*/
productName?: string;
/**
* Whether crash reports should be sent to the server. Default is `true`.
*/
uploadToServer?: boolean;
/**
* Default is `false`.
*/
ignoreSystemCrashHandler?: boolean;
/**
* An object you can define that will be sent along with the report. Only string
* properties are sent correctly. Nested objects are not supported. When using
* Windows, the property names and values must be fewer than 64 characters.
*/
extra?: Record<string, string>;
/**
* Directory to store the crash reports temporarily (only used when the crash
* reporter is started via `process.crashReporter.start`).
*/
crashesDirectory?: string;
}
export interface FileFilter {
// Docs: http://electronjs.org/docs/api/structures/file-filter
@@ -281,3 +250,62 @@ export interface MouseInputEvent extends InputEvent {
x: number;
y: number;
}
export interface CrashReporterStartOptions {
/**
* URL that crash reports will be sent to as POST.
*/
submitURL: string;
/**
* Defaults to `app.name`.
*/
productName?: string;
/**
* Deprecated alias for `{ globalExtra: { _companyName: ... } }`.
*
* @deprecated
*/
companyName?: string;
/**
* Whether crash reports should be sent to the server. If false, crash reports will
* be collected and stored in the crashes directory, but not uploaded. Default is
* `true`.
*/
uploadToServer?: boolean;
/**
* If true, crashes generated in the main process will not be forwarded to the
* system crash handler. Default is `false`.
*/
ignoreSystemCrashHandler?: boolean;
/**
* If true, limit the number of crashes uploaded to 1/hour. Default is `false`.
*
* @platform darwin,win32
*/
rateLimit?: boolean;
/**
* If true, crash reports will be compressed and uploaded with `Content-Encoding:
* gzip`. Not all collection servers support compressed payloads. Default is
* `false`.
*
* @platform darwin,win32
*/
compress?: boolean;
/**
* Extra string key/value annotations that will be sent along with crash reports
* that are generated in the main process. Only string values are supported.
* Crashes generated in child processes will not contain these extra parameters to
* crash reports generated from child processes, call `addExtraParameter` from the
* child process.
*/
extra?: Record<string, string>;
/**
* Extra string key/value annotations that will be sent along with any crash
* reports generated in any process. These annotations cannot be changed once the
* crash reporter has been started. If a key is present in both the global extra
* parameters and the process-specific extra parameters, then the global one will
* take precedence. By default, `productName` and the app version are included, as
* well as the Electron version.
*/
globalExtra?: Record<string, string>;
}

View File

@@ -7,16 +7,13 @@
(function () {
'use strict';
const { ipcRenderer, webFrame, crashReporter } = require('electron');
const { ipcRenderer, webFrame, crashReporter, contextBridge } = require('electron');
// @ts-ignore
window.vscode = {
const globals = {
/**
* A minimal set of methods exposed from ipcRenderer
* to support communication to electron-main
*
* @type {typeof import('../electron-sandbox/globals').ipcRenderer}
* A minimal set of methods exposed from Electron's `ipcRenderer`
* to support communication to main process.
*/
ipcRenderer: {
@@ -25,9 +22,9 @@
* @param {any[]} args
*/
send(channel, ...args) {
validateIPC(channel);
ipcRenderer.send(channel, ...args);
if (validateIPC(channel)) {
ipcRenderer.send(channel, ...args);
}
},
/**
@@ -35,9 +32,9 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
on(channel, listener) {
validateIPC(channel);
ipcRenderer.on(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.on(channel, listener);
}
},
/**
@@ -45,9 +42,9 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
once(channel, listener) {
validateIPC(channel);
ipcRenderer.once(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.once(channel, listener);
}
},
/**
@@ -55,16 +52,14 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
removeListener(channel, listener) {
validateIPC(channel);
ipcRenderer.removeListener(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.removeListener(channel, listener);
}
}
},
/**
* Support for methods of webFrame type.
*
* @type {typeof import('../electron-sandbox/globals').webFrame}
* Support for subset of methods of Electron's `webFrame` type.
*/
webFrame: {
@@ -72,26 +67,71 @@
* @param {number} level
*/
setZoomLevel(level) {
webFrame.setZoomLevel(level);
if (typeof level === 'number') {
webFrame.setZoomLevel(level);
}
}
},
/**
* Support for methods of crashReporter type.
*
* @type {typeof import('../electron-sandbox/globals').crashReporter}
* Support for subset of methods of Electron's `crashReporter` type.
*/
crashReporter: {
/**
* @param {Electron.CrashReporterStartOptions} options
* @param {string} key
* @param {string} value
*/
start(options) {
crashReporter.start(options);
addExtraParameter(key, value) {
crashReporter.addExtraParameter(key, value);
}
},
/**
* Support for a subset of access to node.js global `process`.
*/
process: {
platform: process.platform,
env: process.env,
on:
/**
* @param {string} type
* @param {() => void} callback
*/
function (type, callback) {
if (validateProcessEventType(type)) {
process.on(type, callback);
}
}
},
/**
* Some information about the context we are running in.
*/
context: {
sandbox: process.argv.includes('--enable-sandbox')
}
};
// Use `contextBridge` APIs to expose globals to VSCode
// only if context isolation is enabled, otherwise just
// add to the DOM global.
let useContextBridge = process.argv.includes('--context-isolation');
if (useContextBridge) {
try {
contextBridge.exposeInMainWorld('vscode', globals);
} catch (error) {
console.error(error);
useContextBridge = false;
}
}
if (!useContextBridge) {
// @ts-ignore
window.vscode = globals;
}
//#region Utilities
/**
@@ -101,6 +141,20 @@
if (!channel || !channel.startsWith('vscode:')) {
throw new Error(`Unsupported event IPC channel '${channel}'`);
}
return true;
}
/**
* @param {string} type
* @returns {type is 'uncaughtException'}
*/
function validateProcessEventType(type) {
if (type !== 'uncaughtException') {
throw new Error(`Unsupported process event '${type}'`);
}
return true;
}
//#endregion

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes';
export const ipcRenderer = (window as any).vscode.ipcRenderer as {
/**
@@ -54,30 +52,48 @@ export const webFrame = (window as any).vscode.webFrame as {
export const crashReporter = (window as any).vscode.crashReporter as {
/**
* You are required to call this method before using any other `crashReporter` APIs
* and in each process (main/renderer) from which you want to collect crash
* reports. You can pass different options to `crashReporter.start` when calling
* from different processes.
* Set an extra parameter to be sent with the crash report. The values specified
* here will be sent in addition to any values set via the `extra` option when
* `start` was called.
*
* **Note** Child processes created via the `child_process` module will not have
* access to the Electron modules. Therefore, to collect crash reports from them,
* use `process.crashReporter.start` instead. Pass the same options as above along
* with an additional one called `crashesDirectory` that should point to a
* directory to store the crash reports temporarily. You can test this out by
* calling `process.crash()` to crash the child process.
* Parameters added in this fashion (or via the `extra` parameter to
* `crashReporter.start`) are specific to the calling process. Adding extra
* parameters in the main process will not cause those parameters to be sent along
* with crashes from renderer or other child processes. Similarly, adding extra
* parameters in a renderer process will not result in those parameters being sent
* with crashes that occur in other renderer processes or in the main process.
*
* **Note:** If you need send additional/updated `extra` parameters after your
* first call `start` you can call `addExtraParameter` on macOS or call `start`
* again with the new/updated `extra` parameters on Linux and Windows.
*
* **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash
* collection and reporting. If you want to enable crash reporting, initializing
* `crashpad` from the main process using `crashReporter.start` is required
* regardless of which process you want to collect crashes from. Once initialized
* this way, the crashpad handler collects crashes from all processes. You still
* have to call `crashReporter.start` from the renderer or child process, otherwise
* crashes from them will get reported without `companyName`, `productName` or any
* of the `extra` information.
* **Note:** Parameters have limits on the length of the keys and values. Key names
* must be no longer than 39 bytes, and values must be no longer than 127 bytes.
* Keys with names longer than the maximum will be silently ignored. Key values
* longer than the maximum length will be truncated.
*/
start(options: CrashReporterStartOptions): void;
addExtraParameter(key: string, value: string): void;
};
export const process = (window as any).vscode.process as {
/**
* The process.platform property returns a string identifying the operating system platform
* on which the Node.js process is running.
*/
platform: 'win32' | 'linux' | 'darwin';
/**
* The process.env property returns an object containing the user environment. See environ(7).
*/
env: { [key: string]: string | undefined };
/**
* A listener on the process. Only a small subset of listener types are allowed.
*/
on: (type: string, callback: Function) => void;
};
export const context = (window as any).vscode.context as {
/**
* Wether the renderer runs with `sandbox` enabled or not.
*/
sandbox: boolean;
};

View File

@@ -12,7 +12,7 @@ import { request } from 'vs/base/parts/request/browser/request';
import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { isEqual } from 'vs/base/common/resources';
import { isStandalone } from 'vs/base/browser/browser';
import { mark } from 'vs/base/common/performance';
import { localize } from 'vs/nls';
interface ICredential {
service: string;
@@ -278,10 +278,6 @@ class WorkspaceProvider implements IWorkspaceProvider {
(function () {
// Mark start of workbench
mark('didLoadWorkbenchMain');
performance.mark('workbench-start');
// Find config by checking for DOM
const configElement = document.getElementById('vscode-workbench-web-configuration');
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
@@ -350,6 +346,11 @@ class WorkspaceProvider implements IWorkspaceProvider {
// Finally create workbench
create(document.body, {
...config,
homeIndicator: {
href: 'https://github.com/Microsoft/vscode',
icon: 'code',
title: localize('home', "Home")
},
workspaceProvider: new WorkspaceProvider(workspace, payload),
urlCallbackProvider: new PollingURLCallbackProvider(),
credentialsProvider: new LocalStorageCredentialsProvider()

View File

@@ -26,6 +26,6 @@ exports.collectModules = function () {
createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []),
createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []),
createModuleDescription('vs/platform/driver/node/driver', []),
createModuleDescription('vs/code/electron-browser/processExplorer/processExplorerMain', [])
createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain', [])
];
};

View File

@@ -82,6 +82,10 @@ import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMai
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { createServer, AddressInfo } from 'net';
import { IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
import { IFileService } from 'vs/platform/files/common/files';
import { stripComments } from 'vs/base/common/json';
import { generateUuid } from 'vs/base/common/uuid';
import { VSBuffer } from 'vs/base/common/buffer';
export class CodeApplication extends Disposable {
private windowsMainService: IWindowsMainService | undefined;
@@ -134,11 +138,6 @@ export class CodeApplication extends Disposable {
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
app.on('remote-get-guest-web-contents', event => {
this.logService.trace('App#on(remote-get-guest-web-contents): prevented');
event.preventDefault();
});
app.on('remote-require', (event, sender, module) => {
this.logService.trace('App#on(remote-require): prevented');
@@ -807,7 +806,7 @@ export class CodeApplication extends Disposable {
return { fileUri: URI.file(path) };
}
private afterWindowOpen(accessor: ServicesAccessor): void {
private async afterWindowOpen(accessor: ServicesAccessor): Promise<void> {
// Signal phase: after window open
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
@@ -820,6 +819,34 @@ export class CodeApplication extends Disposable {
if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) {
updateService.initialize();
}
// If enable-crash-reporter argv is undefined then this is a fresh start,
// based on telemetry.enableCrashreporter settings, generate a UUID which
// will be used as crash reporter id and also update the json file.
try {
const fileService = accessor.get(IFileService);
const argvContent = await fileService.readFile(this.environmentService.argvResource);
const argvString = argvContent.value.toString();
const argvJSON = JSON.parse(stripComments(argvString));
if (argvJSON['enable-crash-reporter'] === undefined) {
const enableCrashReporter = this.configurationService.getValue<boolean>('telemetry.enableCrashReporter') ?? true;
const additionalArgvContent = [
'',
' // Allows to disable crash reporting.',
' // Should restart the app if the value is changed.',
` "enable-crash-reporter": ${enableCrashReporter},`,
'',
' // Unique id used for correlating crash reports sent from this instance.',
' // Do not edit this value.',
` "crash-reporter-id": "${generateUuid()}"`,
'}'
];
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
}
} catch (error) {
this.logService.error(error);
}
}
private handleRemoteAuthorities(): void {

View File

@@ -60,10 +60,12 @@ export class ProxyAuthHandler extends Disposable {
title: 'VS Code',
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
enableWebSQL: false,
sandbox: true,
devTools: false,
contextIsolation: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
devTools: false,
v8CacheOptions: 'bypassHeatCheck'
}
};

View File

@@ -43,12 +43,13 @@ export class SharedProcess implements ISharedProcess {
backgroundColor: this.themeMainService.getBackgroundColor(),
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
images: false,
nodeIntegration: true,
webgl: false,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
images: false,
webgl: false,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});

View File

@@ -168,10 +168,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
webPreferences: {
preload: URI.parse(this.doGetPreloadUrl()).fsPath,
nodeIntegration: true,
webviewTag: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
webviewTag: true,
zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel)
}
};

View File

Before

Width:  |  Height:  |  Size: 139 B

After

Width:  |  Height:  |  Size: 139 B

View File

Before

Width:  |  Height:  |  Size: 118 B

After

Width:  |  Height:  |  Size: 118 B

View File

@@ -14,6 +14,6 @@ const bootstrapWindow = (() => {
return window.MonacoBootstrapWindow;
})();
bootstrapWindow.load(['vs/code/electron-browser/processExplorer/processExplorerMain'], function (processExplorer, configuration) {
processExplorer.startup(configuration.data);
bootstrapWindow.load(['vs/code/electron-sandbox/processExplorer/processExplorerMain'], function (processExplorer, configuration) {
processExplorer.startup(configuration.windowId, configuration.data);
}, { forceEnableDeveloperKeybindings: true });

View File

@@ -4,20 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/processExplorer';
import { clipboard } from 'electron';
import { totalmem } from 'os';
import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import product from 'vs/platform/product/common/product';
import { localize } from 'vs/nls';
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
@@ -40,7 +38,12 @@ class ProcessExplorer {
private listeners = new DisposableStore();
constructor(data: ProcessExplorerData) {
private electronService: IElectronService;
constructor(windowId: number, private data: ProcessExplorerData) {
const mainProcessService = new MainProcessService(windowId);
this.electronService = new ElectronService(windowId, mainProcessService) as IElectronService;
this.applyStyles(data.styles);
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
@@ -59,24 +62,24 @@ class ProcessExplorer {
ipcRenderer.send('vscode:listProcesses');
}
private getProcessList(rootProcess: ProcessItem, isLocal: boolean): FormattedProcessItem[] {
private getProcessList(rootProcess: ProcessItem, isLocal: boolean, totalMem: number): FormattedProcessItem[] {
const processes: FormattedProcessItem[] = [];
if (rootProcess) {
this.getProcessItem(processes, rootProcess, 0, isLocal);
this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem);
}
return processes;
}
private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean): void {
private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number): void {
const isRoot = (indent === 0);
const MB = 1024 * 1024;
let name = item.name;
if (isRoot) {
name = isLocal ? `${product.applicationName} main` : 'remote agent';
name = isLocal ? `${this.data.applicationName} main` : 'remote agent';
}
if (name === 'window') {
@@ -86,7 +89,7 @@ class ProcessExplorer {
// Format name with indent
const formattedName = isRoot ? name : `${' '.repeat(indent)} ${name}`;
const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100));
const memory = this.data.platform === 'win32' ? item.mem : (totalMem * (item.mem / 100));
processes.push({
cpu: item.load,
memory: (memory / MB),
@@ -100,7 +103,7 @@ class ProcessExplorer {
if (Array.isArray(item.children)) {
item.children.forEach(child => {
if (child) {
this.getProcessItem(processes, child, indent + 1, isLocal);
this.getProcessItem(processes, child, indent + 1, isLocal, totalMem);
}
});
}
@@ -258,7 +261,7 @@ class ProcessExplorer {
container.appendChild(body);
}
private updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]): void {
private async updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]): Promise<void> {
const container = document.getElementById('process-list');
if (!container) {
return;
@@ -271,19 +274,20 @@ class ProcessExplorer {
tableHead.innerHTML = `<tr>
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
<th scope="col" class="pid">${localize('pid', "pid")}</th>
<th scope="col" class="pid">${localize('pid', "PID")}</th>
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
</tr>`;
container.append(tableHead);
const hasMultipleMachines = Object.keys(processLists).length > 1;
const totalMem = await this.electronService.getTotalMem();
processLists.forEach((remote, i) => {
const isLocal = i === 0;
if (isRemoteDiagnosticError(remote.rootProcess)) {
this.renderProcessFetchError(remote.name, remote.rootProcess.errorMessage);
} else {
this.renderTableSection(remote.name, this.getProcessList(remote.rootProcess, isLocal), hasMultipleMachines, isLocal);
this.renderTableSection(remote.name, this.getProcessList(remote.rootProcess, isLocal, totalMem), hasMultipleMachines, isLocal);
}
});
}
@@ -322,15 +326,15 @@ class ProcessExplorer {
if (isLocal) {
items.push({
label: localize('killProcess', "Kill Process"),
click() {
process.kill(pid, 'SIGTERM');
click: () => {
this.electronService.killProcess(pid, 'SIGTERM');
}
});
items.push({
label: localize('forceKillProcess', "Force Kill Process"),
click() {
process.kill(pid, 'SIGKILL');
click: () => {
this.electronService.killProcess(pid, 'SIGKILL');
}
});
@@ -341,20 +345,20 @@ class ProcessExplorer {
items.push({
label: localize('copy', "Copy"),
click() {
click: () => {
const row = document.getElementById(pid.toString());
if (row) {
clipboard.writeText(row.innerText);
this.electronService.writeClipboardText(row.innerText);
}
}
});
items.push({
label: localize('copyAll', "Copy All"),
click() {
click: () => {
const processList = document.getElementById('process-list');
if (processList) {
clipboard.writeText(processList.innerText);
this.electronService.writeClipboardText(processList.innerText);
}
}
});
@@ -398,15 +402,15 @@ class ProcessExplorer {
export function startup(data: ProcessExplorerData): void {
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
export function startup(windowId: number, data: ProcessExplorerData): void {
const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac';
addClass(document.body, platformClass); // used by our fonts
applyZoom(data.zoomLevel);
const processExplorer = new ProcessExplorer(data);
const processExplorer = new ProcessExplorer(windowId, data);
document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
// Cmd/Ctrl + zooms in
if (cmdOrCtrlKey && e.keyCode === 187) {
@@ -421,7 +425,7 @@ export function startup(data: ProcessExplorerData): void {
// Cmd/Ctrl + w closes process explorer
window.addEventListener('keydown', e => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
if (cmdOrCtrlKey && e.keyCode === 87) {
processExplorer.dispose();
ipcRenderer.send('vscode:closeProcessExplorer');

View File

@@ -419,7 +419,7 @@ export function registerModelAndPositionCommand(id: string, handler: (model: ITe
const model = accessor.get(IModelService).getModel(resource);
if (model) {
const editorPosition = Position.lift(position);
return handler(model, editorPosition, args.slice(2));
return handler(model, editorPosition, ...args.slice(2));
}
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {

View File

@@ -381,7 +381,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
}
continue;
}
if (typeof baseValue === 'object' && typeof subsetValue === 'object') {
if (baseValue && typeof baseValue === 'object' && subsetValue && typeof subsetValue === 'object') {
if (!this._subsetEquals(baseValue, subsetValue)) {
return false;
}

View File

@@ -2576,9 +2576,9 @@ class EditorPixelRatio extends ComputedEditorOption<EditorOption.pixelRatio, num
* Configuration options for quick suggestions
*/
export interface IQuickSuggestionsOptions {
other: boolean;
comments: boolean;
strings: boolean;
other?: boolean;
comments?: boolean;
strings?: boolean;
}
export type ValidQuickSuggestionsOptions = boolean | Readonly<Required<IQuickSuggestionsOptions>>;

View File

@@ -153,9 +153,14 @@ export class LanguagesRegistry extends Disposable {
}
if (Array.isArray(lang.extensions)) {
if (lang.configuration) {
// insert first as this appears to be the 'primary' language definition
resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions);
} else {
resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions);
}
for (let extension of lang.extensions) {
mime.registerTextMime({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite);
resolvedLanguage.extensions.push(extension);
}
}

View File

@@ -101,6 +101,7 @@ export class ModesHoverController implements IEditorContribution {
this._toUnhook.add(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged()));
} else {
this._toUnhook.add(this._editor.onMouseMove(hideWidgetsEventHandler));
this._toUnhook.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));
}
this._toUnhook.add(this._editor.onMouseLeave(hideWidgetsEventHandler));

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-editor .on-type-rename-decoration {
background: rgba(255, 0, 0, 0.3);
border-left: 1px solid rgba(255, 0, 0, 0.3);
border-left: 1px solid transparent;
/* So border can be transparent */
background-clip: padding-box;
}

View File

@@ -26,6 +26,9 @@ import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('onTypeRenameInputVisible', false);
@@ -360,6 +363,13 @@ export function getOnTypeRenameRanges(model: ITextModel, position: Position, tok
}), result => !!result && arrays.isNonEmptyArray(result?.ranges));
}
export const editorOnTypeRenameBackground = registerColor('editor.onTypeRenameBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorOnTypeRenameBackground', 'Background color when the editor auto renames on type.'));
registerThemingParticipant((theme, collector) => {
const editorOnTypeRenameBackgroundColor = theme.getColor(editorOnTypeRenameBackground);
if (editorOnTypeRenameBackgroundColor) {
collector.addRule(`.monaco-editor .on-type-rename-decoration { background: ${editorOnTypeRenameBackgroundColor}; border-left-color: ${editorOnTypeRenameBackgroundColor}; }`);
}
});
registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None));

View File

@@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
@@ -33,6 +33,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, ConfigurationScope, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { assertType } from 'vs/base/common/types';
class RenameSkeleton {
@@ -350,11 +351,9 @@ registerEditorCommand(new RenameCommand({
// ---- api bridge command
registerDefaultLanguageCommand('_executeDocumentRenameProvider', function (model, position, args) {
let { newName } = args;
if (typeof newName !== 'string') {
throw illegalArgument('newName');
}
registerModelAndPositionCommand('_executeDocumentRenameProvider', function (model, position, ...args) {
const [newName] = args;
assertType(typeof newName === 'string');
return rename(model, position, newName);
});

View File

@@ -211,4 +211,15 @@ suite('Common Editor Config', () => {
strings: false
});
});
test('issue #102920: Can\'t snap or split view with JSON files', () => {
const config = new TestConfiguration({ quickSuggestions: null! });
config.updateOptions({ quickSuggestions: { strings: true } });
const actual = <Readonly<Required<IQuickSuggestionsOptions>>>config.options.get(EditorOption.quickSuggestions);
assert.deepEqual(actual, {
other: true,
comments: false,
strings: true
});
});
});

View File

@@ -221,6 +221,32 @@ suite('LanguagesRegistry', () => {
assert.deepEqual(registry.getExtensions('aName'), ['aExt', 'aExt2']);
});
test('extensions of primary language registration come first', () => {
let registry = new LanguagesRegistry(false);
registry._registerLanguages([{
id: 'a',
extensions: ['aExt3']
}]);
assert.deepEqual(registry.getExtensions('a')[0], 'aExt3');
registry._registerLanguages([{
id: 'a',
configuration: URI.file('conf.json'),
extensions: ['aExt']
}]);
assert.deepEqual(registry.getExtensions('a')[0], 'aExt');
registry._registerLanguages([{
id: 'a',
extensions: ['aExt2']
}]);
assert.deepEqual(registry.getExtensions('a')[0], 'aExt');
});
test('filenames', () => {
let registry = new LanguagesRegistry(false);

6
src/vs/monaco.d.ts vendored
View File

@@ -3561,9 +3561,9 @@ declare namespace monaco.editor {
* Configuration options for quick suggestions
*/
export interface IQuickSuggestionsOptions {
other: boolean;
comments: boolean;
strings: boolean;
other?: boolean;
comments?: boolean;
strings?: boolean;
}
export type ValidQuickSuggestionsOptions = boolean | Readonly<Required<IQuickSuggestionsOptions>>;

View File

@@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
@@ -354,10 +354,6 @@ export function getDefaultValues(): any {
return valueTreeRoot;
}
export function overrideIdentifierFromKey(key: string): string {
return key.substring(1, key.length - 1);
}
export function keyFromOverrideIdentifier(overrideIdentifier: string): string {
return `[${overrideIdentifier}]`;
}

View File

@@ -9,8 +9,8 @@ import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { Registry } from 'vs/platform/registry/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';

View File

@@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
import * as types from 'vs/base/common/types';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IStringDictionary } from 'vs/base/common/collections';
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -35,12 +35,12 @@ export interface IConfigurationRegistry {
/**
* Register multiple default configurations to the registry.
*/
registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
registerDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void;
/**
* Deregister multiple default configurations from the registry.
*/
deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
@@ -131,12 +131,6 @@ export interface IConfigurationNode {
extensionInfo?: IConfigurationExtensionInfo;
}
export interface IDefaultConfigurationExtension {
id: ExtensionIdentifier;
name: string;
defaults: { [key: string]: {} };
}
type SettingProperties = { [key: string]: any };
export const allSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} };
@@ -152,7 +146,8 @@ const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensio
class ConfigurationRegistry implements IConfigurationRegistry {
private readonly defaultOverridesConfigurationNode: IConfigurationNode;
private readonly defaultValues: IStringDictionary<any>;
private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode;
private readonly configurationContributors: IConfigurationNode[];
private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
@@ -166,12 +161,13 @@ class ConfigurationRegistry implements IConfigurationRegistry {
readonly onDidUpdateConfiguration: Event<string[]> = this._onDidUpdateConfiguration.event;
constructor() {
this.defaultOverridesConfigurationNode = {
this.defaultValues = {};
this.defaultLanguageConfigurationOverridesNode = {
id: 'defaultOverrides',
title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"),
title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),
properties: {}
};
this.configurationContributors = [this.defaultOverridesConfigurationNode];
this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode];
this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true };
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
@@ -202,29 +198,8 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (configuration.properties) {
for (const key in configuration.properties) {
properties.push(key);
delete this.configurationProperties[key];
// Delete from schema
delete allSettings.properties[key];
switch (configuration.properties[key].scope) {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.MACHINE:
delete machineSettings.properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
delete machineOverridableSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
delete resourceSettings.properties[key];
break;
}
this.removeFromSchema(key, configuration.properties[key]);
}
}
if (configuration.allOf) {
@@ -244,41 +219,60 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this._onDidUpdateConfiguration.fire(properties);
}
public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
public registerDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void {
const properties: string[] = [];
const overrideIdentifiers: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
const defaultValue = defaultConfiguration.defaults[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') {
const propertySchema: IConfigurationPropertySchema = {
for (const key in defaultConfiguration) {
properties.push(key);
this.defaultValues[key] = defaultConfiguration[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
const property: IConfigurationPropertySchema = {
type: 'object',
default: defaultValue,
description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key),
default: this.defaultValues[key],
description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key),
$ref: resourceLanguageSettingsSchemaId
};
allSettings.properties[key] = propertySchema;
this.defaultOverridesConfigurationNode.properties![key] = propertySchema;
this.configurationProperties[key] = propertySchema;
properties.push(key);
overrideIdentifiers.push(overrideIdentifierFromKey(key));
this.configurationProperties[key] = property;
this.defaultLanguageConfigurationOverridesNode.properties![key] = property;
} else {
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
this.updateSchema(key, property);
}
}
}
}
this.registerOverrideIdentifiers(overrideIdentifiers);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
public deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void {
const properties: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
for (const key in defaultConfiguration) {
properties.push(key);
delete allSettings.properties[key];
delete this.defaultOverridesConfigurationNode.properties![key];
delete this.configurationProperties[key];
delete this.defaultValues[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
delete this.configurationProperties[key];
delete this.defaultLanguageConfigurationOverridesNode.properties![key];
} else {
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
this.updateSchema(key, property);
}
}
}
}
this.updateOverridePropertyPatternKey();
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
@@ -291,7 +285,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
for (const overrideIdentifier of overrideIdentifiers) {
this.overrideIdentifiers.add(overrideIdentifier);
}
this.updateOverridePropertyPatternKey();
}
@@ -305,12 +298,13 @@ class ConfigurationRegistry implements IConfigurationRegistry {
delete properties[key];
continue;
}
// fill in default values
let property = properties[key];
let defaultValue = property.default;
if (types.isUndefined(defaultValue)) {
property.default = getDefaultValue(property.type);
}
const property = properties[key];
// update default value
this.updatePropertyDefaultValue(key, property);
// update scope
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
@@ -361,28 +355,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let properties = configuration.properties;
if (properties) {
for (const key in properties) {
allSettings.properties[key] = properties[key];
switch (properties[key].scope) {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = properties[key];
break;
case ConfigurationScope.MACHINE:
machineSettings.properties[key] = properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
machineOverridableSettings.properties[key] = properties[key];
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = properties[key];
break;
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = properties[key];
break;
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
resourceSettings.properties[key] = properties[key];
this.resourceLanguageSettingsSchema.properties![key] = properties[key];
break;
}
this.updateSchema(key, properties[key]);
}
}
let subNodes = configuration.allOf;
@@ -393,6 +366,53 @@ class ConfigurationRegistry implements IConfigurationRegistry {
register(configuration);
}
private updateSchema(key: string, property: IConfigurationPropertySchema): void {
allSettings.properties[key] = property;
switch (property.scope) {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = property;
break;
case ConfigurationScope.MACHINE:
machineSettings.properties[key] = property;
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
machineOverridableSettings.properties[key] = property;
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = property;
break;
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = property;
break;
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
resourceSettings.properties[key] = property;
this.resourceLanguageSettingsSchema.properties![key] = property;
break;
}
}
private removeFromSchema(key: string, property: IConfigurationPropertySchema): void {
delete allSettings.properties[key];
switch (property.scope) {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.MACHINE:
delete machineSettings.properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
delete machineOverridableSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
delete resourceSettings.properties[key];
break;
}
}
private updateOverridePropertyPatternKey(): void {
for (const overrideIdentifier of this.overrideIdentifiers.values()) {
const overrideIdentifierProperty = `[${overrideIdentifier}]`;
@@ -401,8 +421,8 @@ class ConfigurationRegistry implements IConfigurationRegistry {
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
$ref: resourceLanguageSettingsSchemaId,
default: this.defaultOverridesConfigurationNode.properties![overrideIdentifierProperty]?.default
};
this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);
allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
@@ -412,11 +432,26 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
this._onDidSchemaChange.fire();
}
private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void {
let defaultValue = this.defaultValues[key];
if (types.isUndefined(defaultValue)) {
defaultValue = property.default;
}
if (types.isUndefined(defaultValue)) {
defaultValue = getDefaultValue(property.type);
}
property.default = defaultValue;
}
}
const OVERRIDE_PROPERTY = '\\[.*\\]$';
export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY);
export function overrideIdentifierFromKey(key: string): string {
return key.substring(1, key.length - 1);
}
export function getDefaultValue(type: string | string[] | undefined): any {
const t = Array.isArray(type) ? (<string[]>type)[0] : <string>type;
switch (t) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
@@ -64,6 +64,10 @@ export interface ICommonElectronService {
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void>;
moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise<boolean>;
isAdmin(): Promise<boolean>;
getTotalMem(): Promise<number>;
// Process
killProcess(pid: number, code: string): Promise<void>;
// clipboard
readClipboardText(type?: 'selection' | 'clipboard'): Promise<string>;
@@ -94,7 +98,6 @@ export interface ICommonElectronService {
// Development
openDevTools(options?: OpenDevToolsOptions): Promise<void>;
toggleDevTools(): Promise<void>;
startCrashReporter(options: CrashReporterStartOptions): Promise<void>;
sendInputEvent(event: MouseInputEvent): Promise<void>;
// Connectivity

View File

@@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
import { OpenContext } from 'vs/platform/windows/node/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
@@ -20,9 +20,9 @@ import { dirExists } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { totalmem } from 'os';
export interface IElectronMainService extends AddFirstParameterToFunctions<ICommonElectronService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
@@ -37,8 +37,7 @@ export class ElectronMainService implements IElectronMainService {
@IDialogMainService private readonly dialogMainService: IDialogMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILogService private readonly logService: ILogService
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
}
@@ -313,6 +312,19 @@ export class ElectronMainService implements IElectronMainService {
return isAdmin;
}
async getTotalMem(): Promise<number> {
return totalmem();
}
//#endregion
//#region Process
async killProcess(windowId: number | undefined, pid: number, code: string): Promise<void> {
process.kill(pid, code);
}
//#endregion
@@ -465,12 +477,6 @@ export class ElectronMainService implements IElectronMainService {
}
}
async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise<void> {
this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options));
crashReporter.start(options);
}
async sendInputEvent(windowId: number | undefined, event: MouseInputEvent): Promise<void> {
const window = this.windowById(windowId);
if (window && (event.type === 'mouseDown' || event.type === 'mouseUp')) {

View File

@@ -64,18 +64,20 @@ export interface ParsedArgs {
'disable-updates'?: boolean;
'disable-crash-reporter'?: boolean;
'crash-reporter-directory'?: string;
'crash-reporter-id'?: string;
'skip-add-to-recently-opened'?: boolean;
'max-memory'?: string;
'file-write'?: boolean;
'file-chmod'?: boolean;
'driver'?: string;
'driver-verbose'?: boolean;
remote?: string;
'remote'?: string;
'disable-user-env-probe'?: boolean;
'force'?: boolean;
'do-not-sync'?: boolean;
'force-user-env'?: boolean;
'sync'?: 'on' | 'off';
'__sandbox'?: boolean;
// {{SQL CARBON EDIT}} Start
aad?: boolean;
@@ -190,6 +192,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'disable-updates': { type: 'boolean' },
'disable-crash-reporter': { type: 'boolean' },
'crash-reporter-directory': { type: 'string' },
'crash-reporter-id': { type: 'string' },
'disable-user-env-probe': { type: 'boolean' },
'skip-add-to-recently-opened': { type: 'boolean' },
'unity-launch': { type: 'boolean' },
@@ -204,6 +207,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'trace-options': { type: 'string' },
'force-user-env': { type: 'boolean' },
'open-devtools': { type: 'boolean' },
'__sandbox': { type: 'boolean' },
// {{SQL CARBON EDIT}} Start
'command': { type: 'string', alias: 'c', cat: 'o', args: 'command-name', description: localize('commandParameter', 'Name of command to run') },

View File

@@ -43,6 +43,8 @@ export interface INativeEnvironmentService extends IEnvironmentService {
driverVerbose: boolean;
disableUpdates: boolean;
sandbox: boolean;
}
export class EnvironmentService implements INativeEnvironmentService {
@@ -254,7 +256,7 @@ export class EnvironmentService implements INativeEnvironmentService {
get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); }
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
get crashReporterId(): string | undefined { return this._args['crash-reporter-id']; }
get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; }
get driverHandle(): string | undefined { return this._args['driver']; }
@@ -262,6 +264,8 @@ export class EnvironmentService implements INativeEnvironmentService {
get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; }
get sandbox(): boolean { return !!this._args['__sandbox']; }
constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) {
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');

View File

@@ -14,14 +14,15 @@ import { Event } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { rimraf } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export class ExtensionsLifecycle extends Disposable {
private processesLimiter: Limiter<void> = new Limiter(5); // Run max 5 processes in parallel
constructor(
private environmentService: INativeEnvironmentService,
private logService: ILogService
@IEnvironmentService private environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
}

View File

@@ -92,7 +92,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner));
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
@@ -102,9 +103,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.installingExtensions.clear();
this.uninstallingExtensions.clear();
}));
const extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService));
this._register(this.extensionsScanner.onDidRemoveExtension(extension => extensionLifecycle.postUninstall(extension)));
}
zip(extension: ILocalExtension): Promise<URI> {

View File

@@ -23,7 +23,6 @@ import { CancellationToken } from 'vscode';
import { extract, ExtractError } from 'vs/base/node/zip';
import { isWindows } from 'vs/base/common/platform';
import { flatten } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
@@ -41,10 +40,8 @@ export class ExtensionsScanner extends Disposable {
private readonly uninstalledPath: string;
private readonly uninstalledFileLimiter: Queue<any>;
private _onDidRemoveExtension = new Emitter<ILocalExtension>();
readonly onDidRemoveExtension = this._onDidRemoveExtension.event;
constructor(
private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IProductService private readonly productService: IProductService,
@@ -280,7 +277,7 @@ export class ExtensionsScanner extends Disposable {
await Promise.all(byExtension.map(async e => {
const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
if (!installed.has(latest.identifier.id.toLowerCase())) {
this._onDidRemoveExtension.fire(latest);
await this.beforeRemovingExtension(latest);
}
}));
const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);

View File

@@ -85,6 +85,8 @@ export interface ProcessExplorerStyles extends WindowStyles {
export interface ProcessExplorerData extends WindowData {
pid: number;
styles: ProcessExplorerStyles;
platform: string;
applicationName: string;
}
export interface ICommonIssueService {

View File

@@ -199,6 +199,7 @@ export class IssueMainService implements ICommonIssueService {
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel)
}
@@ -250,11 +251,23 @@ export class IssueMainService implements ICommonIssueService {
title: localize('processExplorer', "Process Explorer"),
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel)
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
...this.environmentService.sandbox ?
// Sandbox
{
sandbox: true,
contextIsolation: true
} :
// No Sandbox
{
nodeIntegration: true
}
}
});
@@ -270,7 +283,7 @@ export class IssueMainService implements ICommonIssueService {
};
this._processExplorerWindow.loadURL(
toLauchUrl('vs/code/electron-browser/processExplorer/processExplorer.html', windowConfiguration));
toLauchUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration));
this._processExplorerWindow.on('close', () => this._processExplorerWindow = null);

View File

@@ -70,14 +70,18 @@ export class LaunchMainService implements ILaunchMainService {
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
async start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
this.logService.trace('Received data from other instance: ', args, userEnv);
const urlsToOpen = parseOpenUrl(args);
// Since we now start to open a window, make sure the app has focus.
// Focussing a window will not ensure that the application itself
// has focus, so we use the `steal: true` hint to force focus.
app.focus({ steal: true });
// Check early for open-url which is handled in URL service
const urlsToOpen = parseOpenUrl(args);
if (urlsToOpen.length) {
let whenWindowReady: Promise<any> = Promise.resolve<any>(null);
let whenWindowReady: Promise<unknown> = Promise.resolve();
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
@@ -91,12 +95,12 @@ export class LaunchMainService implements ILaunchMainService {
this.urlService.open(url);
}
});
return Promise.resolve(undefined);
}
// Otherwise handle in windows service
return this.startOpenWindow(args, userEnv);
else {
return this.startOpenWindow(args, userEnv);
}
}
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
@@ -156,8 +160,6 @@ export class LaunchMainService implements ILaunchMainService {
else {
const lastActive = this.windowsMainService.getLastActiveWindow();
if (lastActive) {
// Force focus the app before requesting window focus
app.focus({ steal: true });
lastActive.focus();
usedWindows = [lastActive];

View File

@@ -10,7 +10,7 @@ import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/b
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
type Key = string;
type Value = string;
@@ -49,16 +49,6 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`);
}
// This is unique to the application instance and thereby
// should be written from the main process once.
//
// THIS SHOULD NEVER BE SENT TO TELEMETRY.
//
const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined);
if (crashReporterId === undefined) {
this.storageMainService.store(crashReporterIdStorageKey, generateUuid());
}
// Apply global telemetry values as part of the initialization
// These are global across all windows and thereby should be
// written from the main process once.

View File

@@ -55,4 +55,3 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate';
export const firstSessionDateStorageKey = 'telemetry.firstSessionDate';
export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
export const machineIdKey = 'telemetry.machineId';
export const crashReporterIdStorageKey = 'crashReporter.guid';

View File

@@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import {
SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService,
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview,
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
@@ -57,11 +57,11 @@ export interface IMergableResourcePreview extends IBaseResourcePreview {
readonly remoteContent: string | null;
readonly localContent: string | null;
readonly previewContent: string | null;
readonly acceptedContent: string | null;
readonly hasConflicts: boolean;
merged: boolean;
}
export type IResourcePreview = Omit<IMergableResourcePreview, 'merged'>;
export type IResourcePreview = Omit<IMergableResourcePreview, 'mergeState'>;
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
readonly remoteUserData: IRemoteUserData;
@@ -217,6 +217,19 @@ export abstract class AbstractSynchroniser extends Disposable {
return this._sync(manifest, false, headers);
}
async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
const status = await this.doApply(force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
}
private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
@@ -350,14 +363,18 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token));
}
if (apply) {
const preview = await this.syncPreviewPromise;
const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts);
return await this.updateConflictsAndApply(newConflicts, false);
} else {
return SyncStatus.Syncing;
const preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
if (apply) {
return await this.doApply(false);
}
return SyncStatus.Syncing;
} catch (error) {
// reset preview on error
@@ -367,76 +384,91 @@ export abstract class AbstractSynchroniser extends Disposable {
}
}
async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
async accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content);
return {
...updatedResourcePreview,
mergeState: MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async discard(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: MergeState.Preview
};
});
return this.syncPreviewPromise;
}
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise<IMergableResourcePreview>): Promise<void> {
if (!this.syncPreviewPromise) {
return null;
return;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token));
return this.merge(resource, force, headers);
} finally {
this.syncHeaders = {};
let preview = await this.syncPreviewPromise;
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (index === -1) {
return;
}
this.syncPreviewPromise = createCancelablePromise(async token => {
const resourcePreviews = [...preview.resourcePreviews];
resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]);
return {
...preview,
resourcePreviews
};
});
preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
this.setStatus(SyncStatus.HasConflicts);
} else {
this.setStatus(SyncStatus.Syncing);
}
}
async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
if (!this.syncPreviewPromise) {
return null;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (!resourcePreview) {
return preview;
}
/* mark merged */
resourcePreview.merged = true;
/* Add or remove the preview from conflicts */
const newConflicts = [...this._conflicts];
const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (resourcePreview.hasConflicts) {
if (newConflicts.indexOf(resourcePreview) === -1) {
newConflicts.push(resourcePreview);
}
} else {
if (index !== -1) {
newConflicts.splice(index, 1);
}
}
const status = await this.updateConflictsAndApply(newConflicts, force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise<IResourcePreview> {
return {
...resourcePreview,
acceptedContent
};
}
private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise<SyncStatus> {
private async doApply(force: boolean): Promise<SyncStatus> {
if (!this.syncPreviewPromise) {
return SyncStatus.Idle;
}
const preview = await this.syncPreviewPromise;
// update conflicts
this.updateConflicts(conflicts);
if (this._conflicts.length) {
// check for conflicts
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
// check if all are merged
if (preview.resourcePreviews.some(r => !r.merged)) {
// check if all are accepted
if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
return SyncStatus.Syncing;
}
@@ -452,39 +484,14 @@ export abstract class AbstractSynchroniser extends Disposable {
return SyncStatus.Idle;
}
private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<ISyncResourcePreview> {
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
(localChange !== Change.None || remoteChange !== Change.None)
&& (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)));
if (index !== -1) {
const resourcePreviews = [...preview.resourcePreviews];
const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token);
resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged };
preview = {
...preview,
resourcePreviews
};
}
return preview;
}
protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IResourcePreview> {
return {
...resourcePreview,
previewContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
};
}
private async clearPreviewFolder(): Promise<void> {
try {
await this.fileService.del(this.syncPreviewFolder, { recursive: true });
} catch (error) { /* Ignore */ }
}
private updateConflicts(conflicts: IMergableResourcePreview[]): void {
private updateConflicts(previews: IMergableResourcePreview[]): void {
const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict);
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
@@ -542,14 +549,14 @@ export abstract class AbstractSynchroniser extends Disposable {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) {
return resourcePreview.previewContent || '';
if (isEqual(resourcePreview.acceptedResource, uri)) {
return resourcePreview.acceptedContent;
}
if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent || '';
if (isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent;
}
if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent || '';
if (isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent;
}
}
}
@@ -562,21 +569,31 @@ export abstract class AbstractSynchroniser extends Disposable {
} catch (e) { /* ignore */ }
}
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
const machineId = await this.currentMachineIdPromise;
const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
// Mark merge
const mergableResourcePreviews = resourcePreviews.map(r => ({
...r,
merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */
}));
const resourcePreviews: IMergableResourcePreview[] = [];
for (const resourcePreview of result) {
if (token.isCancellationRequested) {
break;
}
if (!apply) {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
}
resourcePreviews.push({
...resourcePreview,
mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */
: apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted)
: MergeState.Preview
});
}
return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine };
return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine };
}
async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {

View File

@@ -46,8 +46,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
*/
protected readonly version: number = 3;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@@ -94,12 +96,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions);
const { added, removed, updated } = mergeResult;
return [{
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -131,12 +135,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = mergeResult;
return [{
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -177,14 +183,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
}
protected async updateResourcePreviewContent(resourcePreview: IExtensionResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IExtensionResourcePreview> {
if (isEqual(resource, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise<IExtensionResourcePreview> {
if (isEqual(resource, this.localResource)) {
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
return this.getPushPreview(remoteExtensions);
}
return {
...resourcePreview,
previewContent,
acceptedContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
@@ -195,10 +201,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const localResource = ExtensionsSynchroniser.EXTENSIONS_DATA_URI;
const localResource = this.localResource;
const localContent = this.format(localExtensions);
const remoteResource = this.remotePreviewResource;
const previewResource = this.localPreviewResource;
const remoteResource = this.remoteResource;
const previewResource = this.previewResource;
const acceptedResource = this.acceptedResource;
const previewContent = null;
if (remoteExtensions !== null) {
const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions);
@@ -210,6 +217,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
remoteContent: this.format(remoteExtensions),
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
added,
removed,
updated,
@@ -228,6 +237,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
remoteContent: null,
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [],
localChange: Change.None,
remoteChange: Change.None,
@@ -243,12 +254,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions);
const { added, removed, updated, remote } = mergeResult;
return {
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -266,13 +279,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
return this.format(localExtensions);
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}

View File

@@ -45,8 +45,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` });
protected readonly version: number = 1;
private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
constructor(
@IFileService fileService: IFileService,
@@ -93,12 +95,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
const { local, skipped } = mergeResult;
return [{
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localUserData),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local,
remote: syncGlobalState.storage,
localUserData,
@@ -125,12 +129,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const { local, remote, skipped } = mergeResult;
return [{
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localGloablState),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local,
remote,
localUserData: localGloablState,
@@ -172,11 +178,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
}
protected async updateResourcePreviewContent(resourcePreview: IGlobalStateResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IGlobalStateResourcePreview> {
if (GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, resource) {
protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise<IGlobalStateResourcePreview> {
if (isEqual(this.localResource, resource)) {
return this.getPushPreview(resourcePreview.remoteContent);
}
if (this.remotePreviewResource, resource) {
if (isEqual(this.remoteResource, resource)) {
return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys);
}
return resourcePreview;
@@ -184,10 +190,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise<IGlobalStateResourcePreview> {
const localGlobalState = await this.getLocalGlobalState();
const localResource = GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI;
const localResource = this.localResource;
const localContent = this.format(localGlobalState);
const remoteResource = this.remotePreviewResource;
const previewResource = this.localPreviewResource;
const remoteResource = this.remoteResource;
const previewResource = this.previewResource;
const acceptedResource = this.acceptedResource;
const previewContent = null;
if (remoteContent !== null) {
const remoteGlobalState: IGlobalState = JSON.parse(remoteContent);
@@ -200,6 +207,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
remoteContent: this.format(remoteGlobalState),
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
local,
remote,
localUserData: localGlobalState,
@@ -216,6 +225,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
remoteContent: null,
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
local: { added: {}, removed: [], updated: {} },
remote: null,
localUserData: localGlobalState,
@@ -231,12 +242,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const localUserData = await this.getLocalGlobalState();
const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null;
return {
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localUserData),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local: { added: {}, removed: [], updated: {} },
remote: localUserData.storage,
localUserData,
@@ -252,12 +265,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
const localGlobalState = await this.getLocalGlobalState();
return this.format(localGlobalState);
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}

View File

@@ -35,8 +35,10 @@ interface ISyncContent {
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
protected readonly version: number = 1;
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -58,13 +60,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: previewContent,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: Change.None,
hasConflicts: false,
@@ -76,13 +80,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent: string | null = fileContent ? fileContent.value.toString() : null;
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -94,13 +100,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content);
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -150,17 +158,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
if (previewContent && !token.isCancellationRequested) {
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent));
await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent));
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
hasConflicts,
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
@@ -168,7 +178,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
if (content !== null) {
if (this.hasErrors(content)) {
@@ -193,7 +203,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
// Delete the preview
try {
await this.fileService.del(this.localPreviewResource);
await this.fileService.del(this.previewResource);
} catch (e) { /* ignore */ }
} else {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
@@ -230,11 +240,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.file, uri)) {
const fileContent = await this.getLocalFileContent();
return fileContent ? fileContent.value.toString() : '';
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);

View File

@@ -39,8 +39,10 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
protected readonly version: number = 1;
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
constructor(
@IFileService fileService: IFileService,
@@ -66,19 +68,21 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
let previewContent: string | null = null;
if (remoteSettingsSyncContent !== null) {
if (remoteSettingsSyncContent) {
// Update ignored settings from local file content
previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: Change.None,
hasConflicts: false,
@@ -92,20 +96,22 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const ignoredSettings = await this.getIgnoredSettings();
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
let previewContent: string | null = null;
if (fileContent !== null) {
let previewContent: string | null = fileContent?.value.toString() || null;
if (previewContent) {
// Remove ignored settings
previewContent = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils);
previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -126,13 +132,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -146,6 +154,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
const ignoredSettings = await this.getIgnoredSettings();
let acceptedContent: string | null = null;
let previewContent: string | null = null;
let hasLocalChanged: boolean = false;
let hasRemoteChanged: boolean = false;
@@ -156,7 +165,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
this.validateContent(localContent);
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`);
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions);
previewContent = result.localContent || result.remoteContent;
acceptedContent = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
@@ -165,40 +174,43 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// First time syncing to remote
else if (fileContent) {
this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`);
previewContent = fileContent.value.toString();
acceptedContent = fileContent.value.toString();
hasRemoteChanged = true;
}
if (previewContent && !token.isCancellationRequested) {
if (acceptedContent && !token.isCancellationRequested) {
// Remove the ignored settings from the preview.
const content = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formattingOptions);
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent,
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
hasConflicts,
}];
}
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
const formatUtils = await this.getFormattingOptions();
// Add ignored settings from local file content
const ignoredSettings = await this.getIgnoredSettings();
previewContent = updateIgnoredSettings(previewContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
return super.updateResourcePreviewContent(resourcePreview, resource, previewContent, token) as Promise<IFileResourcePreview>;
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) {
const formatUtils = await this.getFormattingOptions();
// Add ignored settings from local file content
const ignoredSettings = await this.getIgnoredSettings();
acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
}
return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise<IFileResourcePreview>;
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
if (content !== null) {
@@ -225,7 +237,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// Delete the preview
try {
await this.fileService.del(this.localPreviewResource);
await this.fileService.del(this.previewResource);
} catch (e) { /* ignore */ }
} else {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
@@ -260,11 +272,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.file, uri)) {
const fileContent = await this.getLocalFileContent();
return fileContent ? fileContent.value.toString() : '';
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
@@ -289,7 +297,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
protected async resolvePreviewContent(resource: URI): Promise<string | null> {
let content = await super.resolvePreviewContent(resource);
if (content !== null) {
if (content) {
const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the preview content
const ignoredSettings = await this.getIgnoredSettings();

View File

@@ -5,7 +5,7 @@
import {
IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService,
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, UserDataSyncError, UserDataSyncErrorCode, Change
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, Change
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
@@ -20,7 +20,6 @@ import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/sn
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { deepClone } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
@@ -94,34 +93,93 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets);
const resourcePreviews = this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
for (const resourcePreview of resourcePreviews) {
if (resourcePreview.hasConflicts) {
if (!token.isCancellationRequested) {
await this.fileService.writeFile(resourcePreview.previewResource!, VSBuffer.fromString(resourcePreview.previewContent || ''));
}
}
}
return resourcePreviews;
return this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
}
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
return {
...resourcePreview,
previewContent: previewContent || null,
hasConflicts: false,
localChange: previewContent ? Change.Modified : Change.Deleted,
remoteChange: previewContent ? Change.Modified : Change.Deleted,
acceptedContent,
localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent),
remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent),
};
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
throw new UserDataSyncError(localize('unresolved conflicts', "Error while syncing {0}. Please resolve conflicts first.", this.syncResourceLogLabel), UserDataSyncErrorCode.UnresolvedConflicts, this.resource);
private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }));
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
const previewExists = acceptedContent !== null;
const remoteExists = resourcePreview.remoteContent !== null;
const localExists = resourcePreview.fileContent !== null;
if (isRemoteResourceAccepted) {
if (remoteExists && localExists) {
return Change.Modified;
}
if (remoteExists && !localExists) {
return Change.Added;
}
if (!remoteExists && localExists) {
return Change.Deleted;
}
return Change.None;
}
if (isPreviewResourceAccepted) {
if (previewExists && localExists) {
return Change.Modified;
}
if (previewExists && !localExists) {
return Change.Added;
}
if (!previewExists && localExists) {
return Change.Deleted;
}
return Change.None;
}
return Change.None;
}
private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }));
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
const previewExists = acceptedContent !== null;
const remoteExists = resourcePreview.remoteContent !== null;
const localExists = resourcePreview.fileContent !== null;
if (isLocalResourceAccepted) {
if (remoteExists && localExists) {
return Change.Modified;
}
if (remoteExists && !localExists) {
return Change.Deleted;
}
if (!remoteExists && localExists) {
return Change.Added;
}
return Change.None;
}
if (isPreviewResourceAccepted) {
if (previewExists && remoteExists) {
return Change.Modified;
}
if (previewExists && !remoteExists) {
return Change.Added;
}
if (!previewExists && remoteExists) {
return Change.Deleted;
}
return Change.None;
}
return Change.None;
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
}
@@ -158,13 +216,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets added remotely -> add locally */
for (const key of Object.keys(mergeResult.local.added)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: null,
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.local.added[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.local.added[key],
hasConflicts: false,
localChange: Change.Added,
remoteChange: Change.None
@@ -174,13 +234,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets updated remotely -> update locally */
for (const key of Object.keys(mergeResult.local.updated)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.local.updated[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.local.updated[key],
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.None
@@ -190,13 +252,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets removed remotely -> remove locally */
for (const key of mergeResult.local.removed) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: null,
hasConflicts: false,
localChange: Change.Deleted,
remoteChange: Change.None
@@ -206,13 +270,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets added locally -> add remotely */
for (const key of Object.keys(mergeResult.remote.added)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.remote.added[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.remote.added[key],
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Added
@@ -222,13 +288,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets updated locally -> update remotely */
for (const key of Object.keys(mergeResult.remote.updated)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.remote.updated[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.remote.updated[key],
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Modified
@@ -238,13 +306,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets removed locally -> remove remotely */
for (const key of mergeResult.remote.removed) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: null,
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: null,
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Deleted
@@ -254,13 +324,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets with conflicts */
for (const key of mergeResult.conflicts) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
hasConflicts: true,
localChange: localFileContent[key] ? Change.Modified : Change.Added,
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
@@ -271,13 +343,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
for (const key of Object.keys(localFileContent)) {
if (!resourcePreviews.has(key)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.None
@@ -308,17 +382,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqualOrParent(uri, this.snippetsFolder)) {
try {
const content = await this.fileService.readFile(uri);
return content ? content.value.toString() : null;
} catch (error) {
return '';
}
}
if (isEqualOrParent(uri.with({ scheme: this.syncPreviewFolder.scheme }), this.syncPreviewFolder)
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME }))) {
if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
return this.resolvePreviewContent(uri);
}
@@ -362,12 +428,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
// Do not update if there are conflicts
return;
}
for (const { fileContent, previewContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
if (localChange !== Change.None) {
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
const resource = joinPath(this.snippetsFolder, key);
@@ -397,15 +458,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
// Do not update if there are conflicts
return remoteUserData;
}
const currentSnippets: IStringDictionary<string> = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {};
const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
for (const { previewContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
if (remoteChange !== Change.None) {
const key = localResource ? basename(localResource) : basename(remoteResource!);
if (remoteChange === Change.Deleted) {

View File

@@ -15,6 +15,7 @@ import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { localize } from 'vs/nls';
import { toLocalISOString } from 'vs/base/common/date';
type AutoSyncClassification = {
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -96,18 +97,24 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
if (userDataSyncStoreService.userDataSyncStore) {
if (this.isEnabled()) {
this.logService.info('Auto Sync is enabled.');
} else {
this.logService.info('Auto Sync is disabled.');
}
this.updateAutoSync();
if (this.hasToDisableMachineEventually()) {
this.disableMachineEventually();
}
this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync()));
this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync()));
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));
this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false)));
}
}
private updateAutoSync(): void {
const { enabled, reason } = this.isAutoSyncEnabled();
const { enabled, message } = this.isAutoSyncEnabled();
if (enabled) {
if (this.autoSync.value === undefined) {
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService);
@@ -120,21 +127,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
} else {
this.syncTriggerDelayer.cancel();
if (this.autoSync.value !== undefined) {
this.logService.info('Auto Sync: Disabled because', reason);
if (message) {
this.logService.info(message);
}
this.autoSync.clear();
}
/* log message when auto sync is not disabled by user */
else if (message && this.isEnabled()) {
this.logService.info(message);
}
}
}
// For tests purpose only
protected startAutoSync(): boolean { return true; }
private isAutoSyncEnabled(): { enabled: boolean, reason?: string } {
private isAutoSyncEnabled(): { enabled: boolean, message?: string } {
if (!this.isEnabled()) {
return { enabled: false, reason: 'sync is disabled' };
return { enabled: false, message: 'Auto Sync: Disabled.' };
}
if (!this.userDataSyncAccountService.account) {
return { enabled: false, reason: 'token is not avaialable' };
return { enabled: false, message: 'Auto Sync: Suspended until auth token is available.' };
}
if (this.userDataSyncStoreService.donotMakeRequestsUntil) {
return { enabled: false, message: `Auto Sync: Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` };
}
return { enabled: true };
}

View File

@@ -122,16 +122,19 @@ export function isAuthenticationProvider(thing: any): thing is IAuthenticationPr
}
export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined {
const value = configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY];
const value = {
...(productService[CONFIGURATION_SYNC_STORE_KEY] || {}),
...(configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || {})
};
if (value
&& isString(value.url)
&& isObject(value.authenticationProviders)
&& Object.keys(value.authenticationProviders).every(authenticationProviderId => isArray(value.authenticationProviders[authenticationProviderId].scopes))
&& isString((value as any).url) // {{SQL CARBON EDIT}} strict-nulls
&& isObject((value as any).authenticationProviders) // {{SQL CARBON EDIT}} strict-nulls
&& Object.keys((value as any).authenticationProviders).every(authenticationProviderId => isArray((value as any).authenticationProviders[authenticationProviderId].scopes)) // {{SQL CARBON EDIT}} strict-nulls
) {
return {
url: joinPath(URI.parse(value.url), 'v1'),
authenticationProviders: Object.keys(value.authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
result.push({ id, scopes: value.authenticationProviders[id].scopes });
url: joinPath(URI.parse((value as any).url), 'v1'), // {{SQL CARBON EDIT}} strict-nulls
authenticationProviders: Object.keys((value as any).authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => { // {{SQL CARBON EDIT}} strict-nulls
result.push({ id, scopes: (value as any).authenticationProviders[id].scopes }); // {{SQL CARBON EDIT}} strict-nulls
return result;
}, [])
};
@@ -164,6 +167,9 @@ export interface IUserDataSyncStoreService {
readonly _serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
readonly donotMakeRequestsUntil: Date | undefined;
readonly onTokenFailed: Event<void>;
readonly onTokenSucceed: Event<void>;
setAuthToken(token: string, type: string): void;
@@ -207,6 +213,7 @@ export enum UserDataSyncErrorCode {
UpgradeRequired = 'UpgradeRequired', /* 426 */
PreconditionRequired = 'PreconditionRequired', /* 428 */
TooManyRequests = 'RemoteTooManyRequests', /* 429 */
TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */
// Local Errors
ConnectionRefused = 'ConnectionRefused',
@@ -317,13 +324,20 @@ export const enum Change {
Deleted,
}
export const enum MergeState {
Preview = 'preview',
Conflict = 'conflict',
Accepted = 'accepted',
}
export interface IResourcePreview {
readonly remoteResource: URI;
readonly localResource: URI;
readonly previewResource: URI;
readonly acceptedResource: URI;
readonly localChange: Change;
readonly remoteChange: Change;
readonly merged: boolean;
readonly mergeState: MergeState;
}
export interface ISyncResourcePreview {
@@ -345,18 +359,20 @@ export interface IUserDataSynchroniser {
pull(): Promise<void>;
push(): Promise<void>;
sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise<void>;
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
replace(uri: URI): Promise<boolean>;
stop(): Promise<void>;
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null>;
merge(resource: URI): Promise<ISyncResourcePreview | null>;
discard(resource: URI): Promise<ISyncResourcePreview | null>;
apply(force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
hasPreviouslySynced(): Promise<boolean>;
hasLocalData(): Promise<boolean>;
resetLocal(): Promise<void>;
resolveContent(resource: URI): Promise<string | null>;
acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
merge(resource: URI, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
getLocalSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>;
@@ -387,8 +403,10 @@ export interface IManualSyncTask extends IDisposable {
readonly manifest: IUserDataManifest | null;
readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>;
preview(): Promise<[SyncResource, ISyncResourcePreview][]>;
accept(uri: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]>;
merge(uri?: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
apply(): Promise<[SyncResource, ISyncResourcePreview][]>;
pull(): Promise<void>;
push(): Promise<void>;
stop(): Promise<void>;
@@ -422,7 +440,7 @@ export interface IUserDataSyncService {
hasLocalData(): Promise<boolean>;
hasPreviouslySynced(): Promise<boolean>;
resolveContent(resource: URI): Promise<string | null>;
acceptPreviewContent(resource: SyncResource, conflictResource: URI, content: string): Promise<void>;
accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise<void>;
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;

View File

@@ -53,7 +53,7 @@ export class UserDataSyncChannel implements IServerChannel {
case 'resetLocal': return this.service.resetLocal();
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
case 'hasLocalData': return this.service.hasLocalData();
case 'acceptPreviewContent': return this.service.acceptPreviewContent(args[0], URI.revive(args[1]), args[2]);
case 'accept': return this.service.accept(args[0], URI.revive(args[1]), args[2], args[3]);
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]);
case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]);
@@ -65,7 +65,7 @@ export class UserDataSyncChannel implements IServerChannel {
private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null }> {
const manualSyncTask = await this.service.createManualSyncTask();
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask);
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask, this.logService);
this.server.registerChannel(`manualSyncTask-${manualSyncTask.id}`, manualSyncTaskChannel);
return { id: manualSyncTask.id, manifest: manualSyncTask.manifest };
}
@@ -73,7 +73,10 @@ export class UserDataSyncChannel implements IServerChannel {
class ManualSyncTaskChannel implements IServerChannel {
constructor(private readonly manualSyncTask: IManualSyncTask) { }
constructor(
private readonly manualSyncTask: IManualSyncTask,
private readonly logService: ILogService
) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
@@ -83,10 +86,22 @@ class ManualSyncTaskChannel implements IServerChannel {
}
async call(context: any, command: string, args?: any): Promise<any> {
try {
const result = await this._call(context, command, args);
return result;
} catch (e) {
this.logService.error(e);
throw e;
}
}
private async _call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'preview': return this.manualSyncTask.preview();
case 'accept': return this.manualSyncTask.accept(URI.revive(args[0]), args[1]);
case 'merge': return this.manualSyncTask.merge(URI.revive(args[0]));
case 'discard': return this.manualSyncTask.discard(URI.revive(args[0]));
case 'apply': return this.manualSyncTask.apply();
case 'pull': return this.manualSyncTask.pull();
case 'push': return this.manualSyncTask.push();
case 'stop': return this.manualSyncTask.stop();

View File

@@ -5,7 +5,7 @@
import {
IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode,
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change
} from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -102,11 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await this.checkEnablement();
try {
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.pull();
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
}
await synchroniser.pull();
}
this.updateLastSyncTime();
} catch (error) {
@@ -121,11 +117,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await this.checkEnablement();
try {
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.push();
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
}
await synchroniser.push();
}
this.updateLastSyncTime();
} catch (error) {
@@ -264,10 +256,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
async acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string, executionId: string = generateUuid()): Promise<void> {
async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise<void> {
await this.checkEnablement();
const synchroniser = this.getSynchroniser(syncResource);
await synchroniser.acceptPreviewContent(resource, content, false, createSyncHeaders(executionId));
await synchroniser.accept(resource, content);
if (apply) {
await synchroniser.apply(false, createSyncHeaders(generateUuid()));
}
}
async resolveContent(resource: URI): Promise<string | null> {
@@ -399,6 +394,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
throw new UserDataSyncError(e.message, e.code, source);
case UserDataSyncErrorCode.TooManyRequests:
case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter:
case UserDataSyncErrorCode.LocalTooManyRequests:
case UserDataSyncErrorCode.Gone:
case UserDataSyncErrorCode.UpgradeRequired:
@@ -460,21 +456,21 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
return this.previews;
}
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.acceptPreviewContent(resource, content, force, this.syncHeaders));
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.accept(resource, content));
}
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
if (resource) {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.merge(resource, force, this.syncHeaders));
} else {
return this.mergeAll();
}
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.merge(resource));
}
private async mergeOrAccept(resource: URI, mergeOrAccept: (synchroniser: IUserDataSynchroniser, force: boolean) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.discard(resource));
}
private async performAction(resource: URI, action: (synchroniser: IUserDataSynchroniser) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging or accepting');
throw new Error('Missing preview. Create preview and try again.');
}
const index = this.previews.findIndex(([, preview]) => preview.resourcePreviews.some(({ localResource, previewResource, remoteResource }) =>
@@ -500,9 +496,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
}
const synchroniser = this.synchronisers.find(s => s.resource === this.previews![index][0])!;
/* force only if the resource is local or remote resource */
const force = isEqual(resource, resourcePreview.localResource) || isEqual(resource, resourcePreview.remoteResource);
const preview = await mergeOrAccept(synchroniser, force);
const preview = await action(synchroniser);
preview ? this.previews.splice(index, 1, this.toSyncResourcePreview(synchroniser.resource, preview)) : this.previews.splice(index, 1);
const i = this.synchronizingResources.findIndex(s => s[0] === syncResource);
@@ -515,25 +509,33 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
return this.previews;
}
private async mergeAll(): Promise<[SyncResource, ISyncResourcePreview][]> {
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging');
throw new Error('You need to create preview before applying');
}
if (this.synchronizingResources.length) {
throw new Error('Cannot merge while synchronizing resources');
throw new Error('Cannot pull while synchronizing resources');
}
const previews: [SyncResource, ISyncResourcePreview][] = [];
for (const [syncResource, preview] of this.previews) {
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
let syncResourcePreview = null;
/* merge those which are not yet merged */
for (const resourcePreview of preview.resourcePreviews) {
syncResourcePreview = await synchroniser.merge(resourcePreview.remoteResource, false, this.syncHeaders);
if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) {
await synchroniser.merge(resourcePreview.previewResource);
}
}
if (syncResourcePreview) {
previews.push([syncResource, syncResourcePreview]);
/* apply */
const newPreview = await synchroniser.apply(false, this.syncHeaders);
if (newPreview) {
previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview));
}
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -553,9 +555,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
for (const resourcePreview of preview.resourcePreviews) {
const content = await synchroniser.resolveContent(resourcePreview.remoteResource) || '';
await synchroniser.acceptPreviewContent(resourcePreview.remoteResource, content, true, this.syncHeaders);
const content = await synchroniser.resolveContent(resourcePreview.remoteResource);
await synchroniser.accept(resourcePreview.remoteResource, content);
}
await synchroniser.apply(true, this.syncHeaders);
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -574,9 +577,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
for (const resourcePreview of preview.resourcePreviews) {
const content = await synchroniser.resolveContent(resourcePreview.localResource) || '';
await synchroniser.acceptPreviewContent(resourcePreview.localResource, content, true, this.syncHeaders);
const content = await synchroniser.resolveContent(resourcePreview.localResource);
await synchroniser.accept(resourcePreview.localResource, content);
}
await synchroniser.apply(true, this.syncHeaders);
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -641,8 +645,9 @@ function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePr
localResource: resourcePreview.localResource,
previewResource: resourcePreview.previewResource,
remoteResource: resourcePreview.remoteResource,
acceptedResource: resourcePreview.acceptedResource,
localChange: resourcePreview.localChange,
remoteChange: resourcePreview.remoteChange,
merged: resourcePreview.merged,
mergeState: resourcePreview.mergeState,
};
}

View File

@@ -19,7 +19,9 @@ import { assign } from 'vs/base/common/objects';
import { generateUuid } from 'vs/base/common/uuid';
import { isWeb } from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/common/async';
const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until';
const USER_SESSION_ID_KEY = 'sync.user-session-id';
const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id';
const REQUEST_SESSION_LIMIT = 100;
@@ -40,6 +42,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
private _onTokenSucceed: Emitter<void> = this._register(new Emitter<void>());
readonly onTokenSucceed: Event<void> = this._onTokenSucceed.event;
private _donotMakeRequestsUntil: Date | undefined = undefined;
get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; }
private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter<void>());
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
constructor(
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@@ -66,12 +73,41 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
/* A requests session that limits requests per sessions */
this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService);
this.initDonotMakeRequestsUntil();
}
setAuthToken(token: string, type: string): void {
this.authToken = { token, type };
}
private initDonotMakeRequestsUntil(): void {
const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
this.setDonotMakeRequestsUntil(new Date(donotMakeRequestsUntil));
}
}
private resetDonotMakeRequestsUntilPromise: CancelablePromise<void> | undefined = undefined;
private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void {
if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) {
this._donotMakeRequestsUntil = donotMakeRequestsUntil;
if (this.resetDonotMakeRequestsUntilPromise) {
this.resetDonotMakeRequestsUntilPromise.cancel();
this.resetDonotMakeRequestsUntilPromise = undefined;
}
if (this._donotMakeRequestsUntil) {
this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL);
this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined)));
} else {
this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
}
this._onDidChangeDonotMakeRequestsUntil.fire();
}
}
async getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
@@ -244,6 +280,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, undefined);
}
if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined);
}
this.setDonotMakeRequestsUntil(undefined);
const commonHeaders = await this.commonHeadersPromise;
options.headers = assign(options.headers || {}, commonHeaders, {
'X-Account-Type': this.authToken.type,
@@ -299,7 +340,13 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
}
if (context.res.statusCode === 429) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
const retryAfter = context.res.headers['retry-after'];
if (retryAfter) {
this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000)));
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId);
} else {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
}
}
return context;

View File

@@ -83,4 +83,20 @@ suite('KeybindingsSync', () => {
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
});
test('test apply remote when keybindings file does not exist', async () => {
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
if (await fileService.exists(keybindingsResource)) {
await fileService.del(keybindingsResource);
}
const preview = (await testObject.preview(await client.manifest()))!;
server.reset();
const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource);
await testObject.accept(preview.resourcePreviews[0].remoteResource, content);
await testObject.apply(false);
assert.deepEqual(server.requests, []);
});
});

View File

@@ -287,7 +287,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
const conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet1, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet1);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -327,7 +328,7 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
let conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
conflicts = testObject.conflicts;
assert.equal(testObject.status, SyncStatus.HasConflicts);
@@ -346,8 +347,9 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
const conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.acceptPreviewContent(conflicts[1].previewResource, tsSnippet1, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
await testObject.accept(conflicts[1].previewResource, tsSnippet1);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -461,7 +463,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet3, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet2);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -565,7 +568,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet3, false);
await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet3);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -592,7 +596,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, '', false);
await testObject.accept(testObject.conflicts[0].previewResource, null);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -689,7 +694,8 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
let conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
await testObject.apply(false);
const fileService = testClient.instantiationService.get(IFileService);
assert.ok(!await fileService.exists(dirname(conflicts[0].previewResource)));
@@ -710,7 +716,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
@@ -736,8 +742,36 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('merge when there are multiple snippets and all snippets are merged and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet2, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -762,7 +796,37 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet1, client2);
await client2.sync();
await updateSnippet('html.json', htmlSnippet1, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -788,7 +852,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.equal(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews,
@@ -821,8 +885,8 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[1].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.merge(preview!.resourcePreviews[1].previewResource);
assert.equal(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews,
@@ -856,7 +920,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
@@ -886,8 +950,40 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[1].previewResource, tsSnippet2, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('accept when there are multiple snippets with conflicts and all snippets are accepted and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet1, client2);
await updateSnippet('typescript.json', tsSnippet1, client2);
await client2.sync();
await updateSnippet('html.json', htmlSnippet2, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
@@ -12,6 +12,8 @@ import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` });
@@ -47,27 +49,27 @@ class TestSynchroniser extends AbstractSynchroniser {
}
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
if (this.syncResult.hasError) {
throw new Error('failed');
}
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise<void> {
if (preview[0]?.previewContent) {
await this.applyRef(preview[0].previewContent);
if (preview[0]?.acceptedContent) {
await this.applyRef(preview[0].acceptedContent);
}
}
@@ -106,6 +108,7 @@ suite('TestSynchronizer', () => {
await client.setUp();
userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService);
disposableStore.add(toDisposable(() => userDataSyncStoreService.clear()));
client.instantiationService.get(IFileService).registerProvider(USER_DATA_SYNC_SCHEME, new InMemoryFileSystemProvider());
});
teardown(() => disposableStore.clear());
@@ -276,26 +279,70 @@ suite('TestSynchronizer', () => {
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle after merging if there are no conflicts', async () => {
test('preview: status is syncing after merging if there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle after merging and applying if there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
assertConflicts(testObject.conflicts, []);
});
test('preview: discarding the merge', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.discard(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is syncing after accepting when there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are no conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -319,35 +366,80 @@ suite('TestSynchronizer', () => {
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
const preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict);
assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].previewResource]);
});
test('preview: discarding the conflict', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
const preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource);
await testObject.discard(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is syncing after accepting when there are conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.deepEqual(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to syncing after accepting when there are conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);

View File

@@ -383,4 +383,22 @@ suite('UserDataAutoSyncService', () => {
assert.deepEqual((<UserDataSyncStoreError>e).code, UserDataSyncErrorCode.TooManyRequests);
});
test('test auto sync is suspended when server donot accepts requests', async () => {
const target = new UserDataSyncTestServer(5, 1);
// Set up and sync from the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService);
while (target.requests.length < 5) {
await testObject.sync();
}
target.reset();
await testObject.sync();
assert.deepEqual(target.requests, []);
});
});

View File

@@ -154,13 +154,13 @@ export class UserDataSyncTestServer implements IRequestService {
get responses(): { status: number }[] { return this._responses; }
reset(): void { this._requests = []; this._responses = []; this._requestsWithAllHeaders = []; }
constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER) { }
constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER, private readonly retryAfter?: number) { }
async resolveProxy(url: string): Promise<string | undefined> { return url; }
async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
if (this._requests.length === this.rateLimit) {
return this.toResponse(429);
return this.toResponse(429, this.retryAfter ? { 'retry-after': `${this.retryAfter}` } : undefined);
}
const headers: IHeaders = {};
if (options.headers) {

View File

@@ -9,12 +9,13 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
import { isWeb } from 'vs/base/common/platform';
import { RequestsSession } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { RequestsSession, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IRequestService } from 'vs/platform/request/common/request';
import { newWriteableBufferStream } from 'vs/base/common/buffer';
import { timeout } from 'vs/base/common/async';
import { NullLogService } from 'vs/platform/log/common/log';
import { Event } from 'vs/base/common/event';
suite('UserDataSyncStoreService', () => {
@@ -322,6 +323,71 @@ suite('UserDataSyncStoreService', () => {
assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined);
});
test('test rate limit on server with retry after', async () => {
const target = new UserDataSyncTestServer(1, 1);
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil);
try {
await testObject.manifest();
assert.fail('should fail');
} catch (e) {
assert.ok(e instanceof UserDataSyncStoreError);
assert.deepEqual((<UserDataSyncStoreError>e).code, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter);
await promise;
assert.ok(!!testObject.donotMakeRequestsUntil);
}
});
test('test donotMakeRequestsUntil is reset after retry time is finished', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil);
await timeout(300);
await promise;
assert.ok(!testObject.donotMakeRequestsUntil);
});
test('test donotMakeRequestsUntil is retrieved', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 1)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
const target = client.instantiationService.createInstance(UserDataSyncStoreService);
assert.equal(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime());
});
test('test donotMakeRequestsUntil is checked and reset after retreived', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
await timeout(300);
const target = client.instantiationService.createInstance(UserDataSyncStoreService);
assert.ok(!target.donotMakeRequestsUntil);
});
});
suite('UserDataSyncRequestsSession', () => {

View File

@@ -106,7 +106,6 @@ function normalizeRequestPath(requestUri: URI) {
return `${scheme}://`; // Url has own authority.
}
}));
console.log(requestUri, resourceUri);
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment

View File

@@ -174,7 +174,7 @@ export class WebviewProtocolProvider extends Disposable {
const fileService = {
readFileStream: async (resource: URI): Promise<VSBufferReadableStream> => {
if (uri.scheme === Schemas.file) {
if (resource.scheme === Schemas.file) {
return (await this.fileService.readFileStream(resource)).value;
}

5
src/vs/vscode.d.ts vendored
View File

@@ -9868,11 +9868,12 @@ declare module 'vscode' {
export interface ConfigurationChangeEvent {
/**
* Returns `true` if the given section is affected in the provided scope.
* Checks if the given section has changed.
* If scope is provided, checks if the section has changed for resources under the given scope.
*
* @param section Configuration name, supports _dotted_ names.
* @param scope A scope in which to check.
* @return `true` if the given section is affected in the provided scope.
* @return `true` if the given section has changed.
*/
affectsConfiguration(section: string, scope?: ConfigurationScope): boolean;
}

View File

@@ -18,7 +18,7 @@ declare module 'vscode' {
// #region auth provider: https://github.com/microsoft/vscode/issues/88309
export class AuthenticationSession {
export interface AuthenticationSession {
/**
* The identifier of the authentication session.
*/
@@ -39,8 +39,6 @@ declare module 'vscode' {
* are defined by the authentication provider.
*/
readonly scopes: ReadonlyArray<string>;
constructor(id: string, accessToken: string, account: AuthenticationSessionAccountInformation, scopes: string[]);
}
/**
@@ -104,7 +102,7 @@ declare module 'vscode' {
clearSessionPreference?: boolean;
}
export interface AuthenticationProviderAuthenticationSessionsChangeEvent extends AuthenticationSessionsChangeEvent {
export interface AuthenticationProviderAuthenticationSessionsChangeEvent {
/**
* The [authenticationProvider](#AuthenticationProvider) that has had its sessions change.
*/
@@ -212,17 +210,6 @@ declare module 'vscode' {
*/
export const providers: ReadonlyArray<AuthenticationProviderInformation>;
/**
* Returns whether a provider has any sessions matching the requested scopes. This request
* is transparent to the user, no UI is shown. Rejects if a provider with providerId is not
* registered.
* @param providerId The id of the provider
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication
* provider
* @returns A thenable that resolve to whether the provider has sessions with the requested scopes.
*/
export function hasSessions(providerId: string, scopes: string[]): Thenable<boolean>;
/**
* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
* registered, or if the user does not consent to sharing authentication information with
@@ -1436,6 +1423,11 @@ declare module 'vscode' {
Error = 4
}
export enum NotebookRunState {
Running = 1,
Idle = 2
}
export interface NotebookCellMetadata {
/**
* Controls if the content of a cell is editable or not.
@@ -1538,6 +1530,11 @@ declare module 'vscode' {
* Additional attributes of the document metadata.
*/
custom?: { [key: string]: any };
/**
* The document's current run state
*/
runState?: NotebookRunState;
}
export interface NotebookDocument {
@@ -1552,6 +1549,7 @@ declare module 'vscode' {
}
export interface NotebookConcatTextDocument {
uri: Uri;
isClosed: boolean;
dispose(): void;
onDidChange: Event<void>;
@@ -1605,6 +1603,11 @@ declare module 'vscode' {
*/
readonly onDidDispose: Event<void>;
/**
* Active kernel used in the editor
*/
readonly kernel?: NotebookKernel;
/**
* Fired when the output hosting webview posts a message.
*/
@@ -1834,10 +1837,27 @@ declare module 'vscode' {
}
export interface NotebookKernel {
readonly id?: string;
label: string;
description?: string;
isPreferred?: boolean;
preloads?: Uri[];
executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise<void>;
executeAllCells(document: NotebookDocument, token: CancellationToken): Promise<void>;
executeCell(document: NotebookDocument, cell: NotebookCell): void;
cancelCellExecution(document: NotebookDocument, cell: NotebookCell): void;
executeAllCells(document: NotebookDocument): void;
cancelAllCellsExecution(document: NotebookDocument): void;
}
export interface NotebookDocumentFilter {
viewType?: string;
filenamePattern?: GlobPattern;
excludeFileNamePattern?: GlobPattern;
}
export interface NotebookKernelProvider<T extends NotebookKernel = NotebookKernel> {
onDidChangeKernels?: Event<void>;
provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult<T[]>;
resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult<void>;
}
export namespace notebook {
@@ -1846,6 +1866,11 @@ declare module 'vscode' {
provider: NotebookContentProvider
): Disposable;
export function registerNotebookKernelProvider(
selector: NotebookDocumentFilter,
provider: NotebookKernelProvider
): Disposable;
export function registerNotebookKernel(
id: string,
selectors: GlobPattern[],
@@ -1882,6 +1907,8 @@ declare module 'vscode' {
* @param selector
*/
export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument;
export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined }>;
}
//#endregion

View File

@@ -17,6 +17,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { fromNow } from 'vs/base/common/date';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser'];
@@ -213,7 +214,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
@INotificationService private readonly notificationService: INotificationService,
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IQuickInputService private readonly quickInputService: IQuickInputService
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IExtensionService private readonly extensionService: IExtensionService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
@@ -292,7 +294,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
const session = await this.authenticationService.login(providerId, scopes);
await this.$setTrustedExtension(providerId, session.account.label, extensionId, extensionName);
await this.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this.$requestNewSession(providerId, scopes, extensionId, extensionName);
@@ -378,6 +380,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
await this.extensionService.activateByEvent(`onAuthenticationRequest:${providerId}`);
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
@@ -423,11 +427,13 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return choice === 0;
}
async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void> {
async $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL);
}
}

View File

@@ -3,14 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData } from '../common/extHost.protocol';
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol';
import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -19,9 +18,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IRelativePattern } from 'vs/base/common/glob';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { Emitter } from 'vs/base/common/event';
export class MainThreadNotebookDocument extends Disposable {
private _textModel: NotebookTextModel;
@@ -54,42 +53,8 @@ export class MainThreadNotebookDocument extends Disposable {
}));
}
async applyEdit(modelVersionId: number, edits: ICellEditOperation[], emitToExtHost: boolean, synchronous: boolean): Promise<boolean> {
await this.notebookService.transformEditsOutputs(this.textModel, edits);
if (synchronous) {
return this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, synchronous);
} else {
return new Promise(resolve => {
this._register(DOM.scheduleAtNextAnimationFrame(() => {
const ret = this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, true);
resolve(ret);
}));
});
}
}
async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) {
await this.notebookService.transformSpliceOutputs(this.textModel, splices);
this._textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
handleEdit(editId: number, label: string | undefined): void {
this.undoRedoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this._textModel.uri,
label: label ?? nls.localize('defaultEditLabel', "Edit"),
undo: async () => {
await this._proxy.$undoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty);
},
redo: async () => {
await this._proxy.$redoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty);
},
});
this._textModel.setDirty(true);
}
dispose() {
this._textModel.dispose();
// this._textModel.dispose();
super.dispose();
}
}
@@ -168,6 +133,7 @@ class DocumentAndEditorState {
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
@@ -201,20 +167,21 @@ class DocumentAndEditorState {
@extHostNamedCustomer(MainContext.MainThreadNotebook)
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
private readonly _notebookProviders = new Map<string, IMainNotebookController>();
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<void>, provider: IDisposable }>();
private readonly _notebookRenderers = new Map<string, MainThreadNotebookRenderer>();
private readonly _proxy: ExtHostNotebookShape;
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
private _currentState?: DocumentAndEditorState;
private _editorEventListenersMapping: Map<string, DisposableStore> = new Map();
constructor(
extHostContext: IExtHostContext,
@INotebookService private _notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
) {
super();
@@ -223,15 +190,23 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
return controller.tryApplyEdits(resource, modelVersionId, edits, renderers);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformEditsOutputs(textModel, edits);
return textModel.$applyEdit(modelVersionId, edits, true);
}
return false;
}
async removeNotebookTextModel(uri: URI): Promise<void> {
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString());
textModelDisposableStore?.dispose();
this._editorEventListenersMapping.delete(URI.from(uri).toString());
}
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) {
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
return false;
@@ -297,14 +272,39 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this._removeNotebookEditor(editors);
}));
this._register(this._notebookService.onNotebookDocumentAdd(() => {
this._register(this._notebookService.onNotebookDocumentAdd((documents) => {
documents.forEach(doc => {
if (!this._editorEventListenersMapping.has(doc.toString())) {
const disposableStore = new DisposableStore();
const textModel = this._notebookService.getNotebookTextModel(doc);
disposableStore.add(textModel!.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(textModel!.uri, e);
this._proxy.$acceptEditorPropertiesChanged(doc, { selections: { selections: textModel!.selections }, metadata: null });
}));
disposableStore.add(textModel!.onDidSelectionChange(e => {
const selectionsChange = e ? { selections: e } : null;
this._proxy.$acceptEditorPropertiesChanged(doc, { selections: selectionsChange, metadata: null });
}));
this._editorEventListenersMapping.set(textModel!.uri.toString(), disposableStore);
}
});
this._updateState();
}));
this._register(this._notebookService.onNotebookDocumentRemove(() => {
this._register(this._notebookService.onNotebookDocumentRemove((documents) => {
documents.forEach(doc => {
this._editorEventListenersMapping.get(doc.toString())?.dispose();
this._editorEventListenersMapping.delete(doc.toString());
});
this._updateState();
}));
this._register(this._notebookService.onDidChangeNotebookActiveKernel(e => {
this._proxy.$acceptNotebookActiveKernelChange(e);
}));
const updateOrder = () => {
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
this._proxy.$acceptDisplayOrder({
@@ -330,10 +330,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this._updateState(notebookEditor);
}
async addNotebookDocument(data: INotebookModelAddedData) {
this._updateState();
}
private _addNotebookEditor(e: IEditor) {
this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable(
e.onDidChangeModel(() => this._updateState()),
@@ -422,18 +418,92 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this._notebookService.unregisterNotebookRenderer(id);
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService, this._instantiationService);
this._notebookProviders.set(viewType, controller);
this._notebookService.registerNotebookController(viewType, extension, controller);
async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined): Promise<void> {
const controller: IMainNotebookController = {
kernel: _kernel,
supportBackup: _supportBackup,
reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => {
const data = await this._proxy.$resolveNotebookData(_viewType, mainthreadTextModel.uri);
if (!data) {
return;
}
mainthreadTextModel.languages = data.languages;
mainthreadTextModel.metadata = data.metadata;
const edits: ICellEditOperation[] = [
{ editType: CellEditType.Delete, count: mainthreadTextModel.cells.length, index: 0 },
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
];
await this._notebookService.transformEditsOutputs(mainthreadTextModel, edits);
await new Promise(resolve => {
DOM.scheduleAtNextAnimationFrame(() => {
const ret = mainthreadTextModel!.$applyEdit(mainthreadTextModel!.versionId, edits, true);
resolve(ret);
});
});
},
createNotebook: async (textModel: NotebookTextModel, backupId?: string) => {
// open notebook document
const data = await this._proxy.$resolveNotebookData(textModel.viewType, textModel.uri, backupId);
if (!data) {
return;
}
textModel.languages = data.languages;
textModel.metadata = data.metadata;
if (data.cells.length) {
textModel.initialize(data!.cells);
} else {
const mainCell = textModel.createCellTextModel([''], textModel.languages.length ? textModel.languages[0] : '', CellKind.Code, [], undefined);
textModel.insertTemplateCell(mainCell);
}
this._proxy.$acceptEditorPropertiesChanged(textModel.uri, { selections: null, metadata: textModel.metadata });
return;
},
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.executeNotebookByAttachedKernel(viewType, uri);
},
cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.cancelNotebookByAttachedKernel(viewType, uri);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
removeNotebookDocument: async (uri: URI) => {
return this.removeNotebookTextModel(uri);
},
executeNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$executeNotebookByAttachedKernel(_viewType, uri, handle);
},
cancelNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$cancelNotebookByAttachedKernel(_viewType, uri, handle);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(_viewType, uri, token);
},
saveAs: async (uri: URI, target: URI, token: CancellationToken) => {
return this._proxy.$saveNotebookAs(_viewType, uri, target, token);
},
backup: async (uri: URI, token: CancellationToken) => {
return this._proxy.$backup(_viewType, uri, token);
}
};
this._notebookProviders.set(_viewType, controller);
this._notebookService.registerNotebookController(_viewType, _extension, controller);
return;
}
async $onNotebookChange(viewType: string, uri: UriComponents): Promise<void> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
controller.handleNotebookChange(uri);
}
const textModel = this._notebookService.getNotebookTextModel(URI.from(uri));
textModel?.handleUnknownChange();
}
async $unregisterNotebookProvider(viewType: string): Promise<void> {
@@ -455,37 +525,85 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return;
}
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
let controller = this._notebookProviders.get(viewType);
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
const emitter = new Emitter<void>();
const that = this;
const provider = this._notebookService.registerNotebookKernelProvider({
providerExtensionId: extension.id.value,
providerDescription: extension.description,
onDidChangeKernels: emitter.event,
selector: documentFilter,
provideKernels: async (uri: URI, token: CancellationToken) => {
const kernels = await that._proxy.$provideNotebookKernels(handle, uri, token);
return kernels.map(kernel => {
return {
...kernel,
providerHandle: handle
};
});
},
resolveKernel: (editorId: string, uri: URI, kernelId: string, token: CancellationToken) => {
return that._proxy.$resolveNotebookKernel(handle, editorId, uri, kernelId, token);
},
executeNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => {
return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle);
}
});
this._notebookKernelProviders.set(handle, {
extension,
emitter,
provider
});
if (controller) {
controller.updateLanguages(resource, languages);
return;
}
async $unregisterNotebookKernelProvider(handle: number): Promise<void> {
const entry = this._notebookKernelProviders.get(handle);
if (entry) {
entry.emitter.dispose();
entry.provider.dispose();
this._notebookKernelProviders.delete(handle);
}
}
$onNotebookKernelChange(handle: number): void {
const entry = this._notebookKernelProviders.get(handle);
entry?.emitter.fire();
}
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateLanguages(languages);
}
async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
controller.updateNotebookMetadata(resource, metadata);
}
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookMetadata(metadata);
}
async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise<void> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
controller.updateNotebookCellMetadata(resource, handle, metadata);
}
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookCellMetadata(handle, metadata);
}
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
let controller = this._notebookProviders.get(viewType);
await controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformSpliceOutputs(textModel, splices);
textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
}
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, token);
async executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, undefined);
}
async cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, undefined);
}
async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean> {
@@ -499,218 +617,20 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void {
let controller = this._notebookProviders.get(viewType);
controller?.handleEdit(resource, editId, label);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
textModel.$handleEdit(label, () => {
return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
}, () => {
return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
});
}
}
$onContentChange(resource: UriComponents, viewType: string): void {
let controller = this._notebookProviders.get(viewType);
controller?.handleNotebookChange(resource);
}
}
export class MainThreadNotebookController implements IMainNotebookController {
private _mapping: Map<string, MainThreadNotebookDocument> = new Map();
static documentHandle: number = 0;
constructor(
private readonly _proxy: ExtHostNotebookShape,
private _mainThreadNotebook: MainThreadNotebooks,
private _viewType: string,
private _supportBackup: boolean,
readonly kernel: INotebookKernelInfoDto | undefined,
readonly notebookService: INotebookService,
readonly _instantiationService: IInstantiationService
) {
}
async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined> {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
if (mainthreadNotebook) {
if (forceReload) {
const data = await this._proxy.$resolveNotebookData(viewType, uri);
if (!data) {
return undefined; // {{SQL CARBON EDIT}}
}
mainthreadNotebook.textModel.languages = data.languages;
mainthreadNotebook.textModel.metadata = data.metadata;
await mainthreadNotebook.applyEdit(mainthreadNotebook.textModel.versionId, [
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
], true, false);
}
return mainthreadNotebook.textModel;
}
let document = this._instantiationService.createInstance(MainThreadNotebookDocument, this._proxy, MainThreadNotebookController.documentHandle++, viewType, this._supportBackup, uri);
this._mapping.set(document.uri.toString(), document);
if (backup) {
// trigger events
document.textModel.metadata = backup.metadata;
document.textModel.languages = backup.languages;
// restored from backup, update the text model without emitting any event to exthost
await document.applyEdit(document.textModel.versionId, [
{
editType: CellEditType.Insert,
index: 0,
cells: backup.cells || []
}
], false, true);
// create document in ext host with cells data
await this._mainThreadNotebook.addNotebookDocument({
viewType: document.viewType,
handle: document.handle,
uri: document.uri,
metadata: document.textModel.metadata,
versionId: document.textModel.versionId,
cells: document.textModel.cells.map(cell => ({
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
})),
attachedEditor: editorId ? {
id: editorId,
selections: document.textModel.selections
} : undefined
});
return document.textModel;
}
// open notebook document
const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId);
if (!data) {
return undefined; // {{SQL CARBON EDIT}}
}
document.textModel.languages = data.languages;
document.textModel.metadata = data.metadata;
if (data.cells.length) {
document.textModel.initialize(data!.cells);
} else {
const mainCell = document.textModel.createCellTextModel([''], document.textModel.languages.length ? document.textModel.languages[0] : '', CellKind.Code, [], undefined);
document.textModel.insertTemplateCell(mainCell);
}
await this._mainThreadNotebook.addNotebookDocument({
viewType: document.viewType,
handle: document.handle,
uri: document.uri,
metadata: document.textModel.metadata,
versionId: document.textModel.versionId,
cells: document.textModel.cells.map(cell => ({
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
})),
attachedEditor: editorId ? {
id: editorId,
selections: document.textModel.selections
} : undefined
});
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: null, metadata: document.textModel.metadata });
return document.textModel;
}
async resolveNotebookEditor(viewType: string, uri: URI, editorId: string) {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
}
async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
if (mainthreadNotebook) {
return await mainthreadNotebook.applyEdit(modelVersionId, edits, true, true);
}
return false;
}
async spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
await mainthreadNotebook?.spliceNotebookCellOutputs(cellHandle, splices);
}
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, token);
}
onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: unknown): void {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
}
async removeNotebookDocument(notebook: INotebookTextModel): Promise<void> {
let document = this._mapping.get(URI.from(notebook.uri).toString());
if (!document) {
return;
}
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
document.dispose();
this._mapping.delete(URI.from(notebook.uri).toString());
}
// Methods for ExtHost
handleNotebookChange(resource: UriComponents) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.handleUnknownChange();
}
handleEdit(resource: UriComponents, editId: number, label: string | undefined): void {
let document = this._mapping.get(URI.from(resource).toString());
document?.handleEdit(editId, label);
}
updateLanguages(resource: UriComponents, languages: string[]) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateLanguages(languages);
}
updateNotebookMetadata(resource: UriComponents, metadata: NotebookDocumentMetadata) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateNotebookMetadata(metadata);
}
updateNotebookCellMetadata(resource: UriComponents, handle: number, metadata: NotebookCellMetadata) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateNotebookCellMetadata(handle, metadata);
}
async executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, token);
}
async save(uri: URI, token: CancellationToken): Promise<boolean> {
return this._proxy.$saveNotebook(this._viewType, uri, token);
}
async saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean> {
return this._proxy.$saveNotebookAs(this._viewType, uri, target, token);
}
async backup(uri: URI, token: CancellationToken): Promise<string | undefined> {
const backupId = await this._proxy.$backup(this._viewType, uri, token);
return backupId;
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.handleUnknownChange();
}
}
@@ -726,8 +646,8 @@ export class MainThreadNotebookKernel implements INotebookKernelInfo {
) {
}
async executeNotebook(viewType: string, uri: URI, handle: number | undefined, token: CancellationToken): Promise<void> {
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle, token);
async executeNotebook(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle);
}
}

View File

@@ -613,6 +613,9 @@ export class MainThreadTask implements MainThreadTaskShape {
public $registerTaskSystem(key: string, info: TaskSystemInfoDTO): void {
let platform: Platform.Platform;
switch (info.platform) {
case 'Web':
platform = Platform.Platform.Web;
break;
case 'win32':
platform = Platform.Platform.Windows;
break;

View File

@@ -84,12 +84,19 @@ interface IUserFriendlyViewDescriptor {
icon?: string;
contextualTitle?: string;
visibility?: string;
// From 'remoteViewDescriptor' type
group?: string;
remoteName?: string | string[];
}
enum InitialVisibility {
Visible = 'visible',
Hidden = 'hidden',
Collapsed = 'collapsed'
}
const viewDescriptor: IJSONSchema = {
type: 'object',
properties: {
@@ -113,6 +120,20 @@ const viewDescriptor: IJSONSchema = {
description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"),
type: 'string'
},
visibility: {
description: localize('vscode.extension.contributes.view.initialState', "Initial state of the view when the extension is first installed. Once the user has changed the view state by collapsing, moving, or hiding the view, the initial state will not be used again."),
type: 'string',
enum: [
'visible',
'hidden',
'collapsed'
],
enumDescriptions: [
localize('vscode.extension.contributes.view.initialState.visible', "The default initial state for view. The view will be expanded. This may have different behavior when the view container that the view is in is built in."),
localize('vscode.extension.contributes.view.initialState.hidden', "The view will not be shown in the view container, but will be discoverable through the views menu and other view entry points and can be un-hidden by the user."),
localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.")
]
}
}
};
@@ -427,6 +448,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
: undefined;
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
const initialVisibility = this.convertInitialVisibility(item.visibility);
const viewDescriptor = <ICustomViewDescriptor>{
id: item.id,
name: item.name,
@@ -437,12 +459,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
canToggleVisibility: true,
canMoveView: true,
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
collapsed: this.showCollapsed(container),
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,
originalContainerId: entry.key,
group: item.group,
remoteAuthority: item.remoteName || (<any>item).remoteAuthority // TODO@roblou - delete after remote extensions are updated
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
hideByDefault: initialVisibility === InitialVisibility.Hidden
};
viewIds.add(viewDescriptor.id);
@@ -471,6 +494,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
}
private convertInitialVisibility(value: any): InitialVisibility | undefined {
if (Object.values(InitialVisibility).includes(value)) {
return value;
}
return undefined;
}
private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(viewDescriptors)) {
collector.error(localize('requirearray', "views must be an array"));
@@ -498,6 +528,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'contextualTitle'));
return false;
}
if (descriptor.visibility && !this.convertInitialVisibility(descriptor.visibility)) {
collector.error(localize('optenum', "property `{0}` can be omitted or must be one of {1}", 'visibility', Object.values(InitialVisibility).join(', ')));
return false;
}
}
return true;

View File

@@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { isObject } from 'vs/base/common/types';
@@ -105,20 +105,11 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<I
});
defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
if (removed.length) {
const removedDefaultConfigurations: IDefaultConfigurationExtension[] = removed.map(extension => {
const id = extension.description.identifier;
const name = extension.description.name;
const defaults = objects.deepClone(extension.value);
return <IDefaultConfigurationExtension>{
id, name, defaults
};
});
const removedDefaultConfigurations = removed.map<IStringDictionary<any>>(extension => objects.deepClone(extension.value));
configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations);
}
if (added.length) {
const addedDefaultConfigurations = added.map(extension => {
const id = extension.description.identifier;
const name = extension.description.name;
const addedDefaultConfigurations = added.map<IStringDictionary<any>>(extension => {
const defaults: IStringDictionary<any> = objects.deepClone(extension.value);
for (const key of Object.keys(defaults)) {
if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') {
@@ -126,9 +117,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
delete defaults[key];
}
}
return <IDefaultConfigurationExtension>{
id, name, defaults
};
return defaults;
});
configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations);
}

View File

@@ -208,9 +208,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
return extHostAuthentication.providers;
},
hasSessions(providerId: string, scopes: string[]): Thenable<boolean> {
return extHostAuthentication.hasSessions(providerId, scopes);
},
getSession(providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions) {
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
@@ -958,6 +955,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeVisibleNotebookEditors;
},
get onDidChangeActiveNotebookKernel() {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookKernel;
},
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);
@@ -966,6 +967,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernel(extension, id, selector, kernel);
},
registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
},
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
@@ -1127,7 +1132,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
CellKind: extHostTypes.CellKind,
CellOutputKind: extHostTypes.CellOutputKind,
NotebookCellRunState: extHostTypes.NotebookCellRunState,
AuthenticationSession: extHostTypes.AuthenticationSession
NotebookRunState: extHostTypes.NotebookRunState
};
};
}

View File

@@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
@@ -169,7 +169,7 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void>;
$setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void>;
$requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
@@ -601,6 +601,7 @@ export interface WebviewExtensionDescription {
export interface NotebookExtensionDescription {
readonly id: ExtensionIdentifier;
readonly location: UriComponents;
readonly description?: string;
}
export enum WebviewEditorCapabilities {
@@ -708,6 +709,9 @@ export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
$unregisterNotebookRenderer(id: string): Promise<void>;
$registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void>;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$onNotebookKernelChange(handle: number): void;
$unregisterNotebookKernel(id: string): Promise<void>;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean>;
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
@@ -1615,12 +1619,17 @@ export interface INotebookDocumentsAndEditorsDelta {
export interface ExtHostNotebookShape {
$resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto | undefined>;
$resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void>;
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
$executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void;
$renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>;
$renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;

View File

@@ -215,6 +215,20 @@ const newCommands: ApiCommand[] = [
[ApiCommandArgument.CallHierarchyItem],
new ApiCommandResult<IOutgoingCallDto[], types.CallHierarchyOutgoingCall[]>('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyOutgoingCall.to))
),
// --- rename
new ApiCommand(
'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('newName', 'The new symbol name', v => typeof v === 'string', v => v)],
new ApiCommandResult<IWorkspaceEditDto, types.WorkspaceEdit | undefined>('A promise that resolves to a WorkspaceEdit.', value => {
if (!value) {
return undefined;
}
if (value.rejectReason) {
throw new Error(value.rejectReason);
}
return typeConverters.WorkspaceEdit.to(value);
})
)
];
@@ -238,15 +252,6 @@ export class ExtHostApiCommands {
}
registerCommands() {
this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, {
description: 'Execute rename provider.',
args: [
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
{ name: 'newName', description: 'The new symbol name', constraint: String }
],
returns: 'A promise that resolves to a WorkspaceEdit.'
});
this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, {
description: 'Execute signature help provider.',
args: [
@@ -376,23 +381,6 @@ export class ExtHostApiCommands {
this._disposables.add(disposable);
}
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Promise<types.WorkspaceEdit> {
const args = {
resource,
position: position && typeConverters.Position.from(position),
newName
};
return this._commands.executeCommand<IWorkspaceEditDto>('_executeDocumentRenameProvider', args).then(value => {
if (!value) {
return undefined;
}
if (value.rejectReason) {
return Promise.reject<any>(new Error(value.rejectReason));
}
return typeConverters.WorkspaceEdit.to(value);
});
}
private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise<types.SignatureHelp | undefined> {
const args = {
resource,

View File

@@ -37,26 +37,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
}
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
return Object.freeze(this._providers);
}
private async resolveSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
const provider = this._authenticationProviders.get(providerId);
let sessions;
if (!provider) {
sessions = await this._proxy.$getSessions(providerId);
} else {
sessions = await provider.getSessions();
}
return sessions;
}
async hasSessions(providerId: string, scopes: string[]): Promise<boolean> {
const orderedScopes = scopes.sort().join(' ');
const sessions = await this.resolveSessions(providerId);
return !!(sessions.filter(session => session.scopes.slice().sort().join(' ') === orderedScopes).length);
return Object.freeze(this._providers.slice());
}
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise<vscode.AuthenticationSession>;
@@ -94,7 +75,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
}
const session = await provider.login(scopes);
await this._proxy.$setTrustedExtension(providerId, session.account.label, extensionId, extensionName);
await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);

View File

@@ -9,13 +9,14 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ISplice } from 'vs/base/common/sequence';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput, INotebookKernelInfoDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { NotImplementedProxy } from 'vs/base/common/types';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
@@ -24,7 +25,6 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { joinPath } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { hash } from 'vs/base/common/hash';
import { generateUuid } from 'vs/base/common/uuid';
import { Cache } from './cache';
interface IObservable<T> {
@@ -54,64 +54,85 @@ interface INotebookEventEmitter {
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
}
const addIdToOutput = (output: IRawOutput, id = generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich
const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich
? ({ ...output, outputId: id }) : output;
class DettachedCellDocumentData extends ExtHostDocumentData {
private static readonly _fakeProxy = new class extends NotImplementedProxy<MainThreadDocumentsShape>('document') {
$trySaveDocument() {
return Promise.reject('Cell-document cannot be saved');
}
};
constructor(cell: IMainCellDto) {
super(DettachedCellDocumentData._fakeProxy,
URI.revive(cell.uri),
cell.source,
cell.eol,
cell.language,
0,
false
);
}
}
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
// private originalSource: string[];
private _outputs: any[];
private _onDidChangeOutputs = new Emitter<ISplice<IProcessedOutput>[]>();
onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event;
// private _textDocument: vscode.TextDocument | undefined;
// private _initalVersion: number = -1;
private _outputMapping = new WeakMap<vscode.CellOutput, string | undefined /* output ID */>();
private _metadata: vscode.NotebookCellMetadata;
readonly onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event;
private _outputs: any[];
private _outputMapping = new WeakMap<vscode.CellOutput, string | undefined /* output ID */>();
private _metadata: vscode.NotebookCellMetadata;
private _metadataChangeListener: IDisposable;
private _documentData: ExtHostDocumentData;
readonly handle: number;
readonly uri: URI;
readonly cellKind: CellKind;
get document(): vscode.TextDocument {
return this._documentData.document;
}
get notebook(): vscode.NotebookDocument {
return this._notebook;
}
// todo@jrieken this is a little fish because we have
// vscode.TextDocument for which we never fired an onDidOpen
// event and which doesn't appear in the list of documents.
// this will change once the "real" document comes along. We
// should come up with a better approach here...
readonly defaultDocument: DettachedCellDocumentData;
constructor(
private readonly _notebook: ExtHostNotebookDocument,
readonly handle: number,
readonly uri: URI,
content: string,
public readonly cellKind: CellKind,
public language: string,
outputs: any[],
_metadata: vscode.NotebookCellMetadata | undefined,
private _proxy: MainThreadNotebookShape,
readonly notebook: ExtHostNotebookDocument,
private _extHostDocument: ExtHostDocumentsAndEditors,
cell: IMainCellDto,
) {
super();
this._documentData = new ExtHostDocumentData(
new class extends NotImplementedProxy<MainThreadDocumentsShape>('document') { },
uri,
content.split(/\r|\n|\r\n/g), '\n',
language, 0, false
);
this._outputs = outputs;
this.handle = cell.handle;
this.uri = URI.revive(cell.uri);
this.cellKind = cell.cellKind;
this.defaultDocument = new DettachedCellDocumentData(cell);
this._outputs = cell.outputs;
for (const output of this._outputs) {
this._outputMapping.set(output, output.outputId);
delete output.outputId;
}
const observableMetadata = getObservable(_metadata || {});
const observableMetadata = getObservable(cell.metadata ?? {});
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this.updateMetadata();
this._updateMetadata();
}));
}
get document(): vscode.TextDocument {
return this._extHostDocument.getDocument(this.uri)?.document ?? this.defaultDocument.document;
}
get language(): string {
return this.document.languageId;
}
get outputs() {
return this._outputs;
}
@@ -131,7 +152,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
start: diff.start,
toInsert: diff.toInsert.map((output): IProcessedOutput => {
if (output.outputKind === CellOutputKind.Rich) {
const uuid = generateUuid();
const uuid = UUID.generateUuid();
this._outputMapping.set(output, uuid);
return { ...output, outputId: uuid };
}
@@ -156,29 +177,14 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this.updateMetadata();
this._updateMetadata();
}));
this.updateMetadata();
this._updateMetadata();
}
private updateMetadata(): Promise<void> {
return this._proxy.$updateNotebookCellMetadata(this._notebook.viewType, this._notebook.uri, this.handle, this._metadata);
}
attachTextDocument(document: ExtHostDocumentData) {
this._documentData = document;
// this._initalVersion = this._documentData.version;
}
detachTextDocument() {
// no-op? keep stale document until new comes along?
// if (this._textDocument && this._textDocument.version !== this._initalVersion) {
// this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g);
// }
// this._textDocument = undefined;
// this._initalVersion = -1;
private _updateMetadata(): Promise<void> {
return this._proxy.$updateNotebookCellMetadata(this.notebook.viewType, this.notebook.uri, this.handle, this._metadata);
}
}
@@ -367,12 +373,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
splices.reverse().forEach(splice => {
let cellDtos = splice[2];
let newCells = cellDtos.map(cell => {
const extCell = new ExtHostCell(this, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy);
const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri));
if (documentData) {
extCell.attachTextDocument(documentData);
}
const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell);
if (!this._cellDisposableMapping.has(extCell.handle)) {
this._cellDisposableMapping.set(extCell.handle, new DisposableStore());
@@ -454,7 +456,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
private $changeCellLanguage(index: number, language: string): void {
const cell = this.cells[index];
cell.language = language;
cell.defaultDocument._acceptLanguageId(language);
const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language };
this._emitter.emitCellLanguageChange(event);
}
@@ -480,20 +482,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
getCell2(cellUri: UriComponents) {
return this.cells.find(cell => cell.uri.fragment === cellUri.fragment);
}
attachCellTextDocument(textDocument: ExtHostDocumentData) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
if (cell) {
cell.attachTextDocument(textDocument);
}
}
detachCellTextDocument(textDocument: ExtHostDocumentData) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
if (cell) {
cell.detachTextDocument();
}
}
}
export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit {
@@ -629,6 +617,16 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
this._active = value;
}
private _kernel?: vscode.NotebookKernel;
get kernel() {
return this._kernel;
}
set kernel(_kernel: vscode.NotebookKernel | undefined) {
throw readonly('kernel');
}
private _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _onDidReceiveMessage = new Emitter<any>();
@@ -641,31 +639,8 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
private _proxy: MainThreadNotebookShape,
private _webComm: vscode.NotebookCommunication,
public document: ExtHostNotebookDocument,
private _documentsAndEditors: ExtHostDocumentsAndEditors
) {
super();
this._register(this._documentsAndEditors.onDidAddDocuments(documents => {
for (const documentData of documents) {
let data = CellUri.parse(documentData.document.uri);
if (data) {
if (this.document.uri.fsPath === data.notebook.fsPath) {
document.attachCellTextDocument(documentData);
}
}
}
}));
this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => {
for (const documentData of documents) {
let data = CellUri.parse(documentData.document.uri);
if (data) {
if (this.document.uri.fsPath === data.notebook.fsPath) {
document.detachCellTextDocument(documentData);
}
}
}
}));
this._register(this._webComm.onDidReceiveMessage(e => {
this._onDidReceiveMessage.fire(e);
}));
@@ -727,6 +702,9 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
throw readonly('viewColumn');
}
updateActiveKernel(kernel?: vscode.NotebookKernel) {
this._kernel = kernel;
}
async postMessage(message: any): Promise<boolean> {
return this._webComm.postMessage(message);
}
@@ -780,10 +758,124 @@ export interface ExtHostNotebookOutputRenderingHandler {
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[];
}
export class ExtHostNotebookKernelProviderAdapter extends Disposable {
private _kernelToId = new Map<vscode.NotebookKernel, string>();
private _idToKernel = new Map<string, vscode.NotebookKernel>();
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _handle: number,
private readonly _extension: IExtensionDescription,
private readonly _provider: vscode.NotebookKernelProvider
) {
super();
if (this._provider.onDidChangeKernels) {
this._register(this._provider.onDidChangeKernels(() => {
this._proxy.$onNotebookKernelChange(this._handle);
}));
}
}
async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise<INotebookKernelInfoDto2[]> {
const data = await this._provider.provideKernels(document, token) || [];
const newMap = new Map<vscode.NotebookKernel, string>();
let kernel_unique_pool = 0;
let kernelIdCache = new Set<string>();
const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => {
let id = this._kernelToId.get(kernel);
if (id === undefined) {
if (kernel.id && kernelIdCache.has(kernel.id)) {
id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
} else {
id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
}
this._kernelToId.set(kernel, id);
}
newMap.set(kernel, id);
return {
id,
label: kernel.label,
extension: this._extension.identifier,
extensionLocation: this._extension.extensionLocation,
description: kernel.description,
isPreferred: kernel.isPreferred,
preloads: kernel.preloads
};
});
this._kernelToId = newMap;
this._idToKernel.clear();
this._kernelToId.forEach((value, key) => {
this._idToKernel.set(value, key);
});
return transformedData;
}
getKernel(kernelId: string) {
return this._idToKernel.get(kernelId);
}
async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) {
const kernel = this._idToKernel.get(kernelId);
if (kernel && this._provider.resolveKernel) {
return this._provider.resolveKernel(kernel, document, webview, token);
}
}
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
const kernel = this._idToKernel.get(kernelId);
if (!kernel) {
return;
}
if (cell) {
return withToken(token => (kernel.executeCell as any)(document, cell, token));
} else {
return withToken(token => (kernel.executeAllCells as any)(document, token));
}
}
async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
const kernel = this._idToKernel.get(kernelId);
if (!kernel) {
return;
}
if (cell) {
return kernel.cancelCellExecution(document, cell);
} else {
return kernel.cancelAllCellsExecution(document);
}
}
}
// TODO@roblou remove 'token' passed to all execute APIs once extensions are updated
async function withToken(cb: (token: CancellationToken) => any) {
const source = new CancellationTokenSource();
try {
await cb(source.token);
} finally {
source.dispose();
}
}
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
private static _notebookKernelProviderHandlePool: number = 0;
private readonly _proxy: MainThreadNotebookShape;
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernelProviders = new Map<number, ExtHostNotebookKernelProviderAdapter>();
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
private readonly _unInitializedDocuments = new Map<string, ExtHostNotebookDocument>();
private readonly _editors = new Map<string, { editor: ExtHostNotebookEditor }>();
@@ -820,6 +912,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private _onDidCloseNotebookDocument = new Emitter<vscode.NotebookDocument>();
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
visibleNotebookEditors: ExtHostNotebookEditor[] = [];
private _onDidChangeActiveNotebookKernel = new Emitter<{ document: ExtHostNotebookDocument, kernel: vscode.NotebookKernel | undefined }>();
onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event;
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
@@ -864,7 +958,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer);
this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer);
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, renderer.preloads || []);
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, type, filter, renderer.preloads || []);
return new extHostTypes.Disposable(() => {
this._notebookOutputRenderers.delete(extHostRenderer.type);
this._proxy.$unregisterNotebookRenderer(extHostRenderer.type);
@@ -989,7 +1083,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const supportBackup = !!provider.backupNotebook;
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined);
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined);
return new extHostTypes.Disposable(() => {
listener.dispose();
@@ -998,6 +1092,55 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
});
}
registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) {
const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++;
const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider);
this._notebookKernelProviders.set(handle, adapter);
this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, {
viewType: selector.viewType,
filenamePattern: selector.filenamePattern ? typeConverters.GlobPattern.from(selector.filenamePattern) : undefined,
excludeFileNamePattern: selector.excludeFileNamePattern ? typeConverters.GlobPattern.from(selector.excludeFileNamePattern) : undefined,
});
return new extHostTypes.Disposable(() => {
adapter.dispose();
this._notebookKernelProviders.delete(handle);
this._proxy.$unregisterNotebookKernelProvider(handle);
});
}
private _withAdapter<T>(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise<T>) {
const document = this._documents.get(URI.revive(uri).toString());
if (!document) {
return [];
}
const provider = this._notebookKernelProviders.get(handle);
if (!provider) {
return [];
}
return callback(provider, document);
}
async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]> {
return this._withAdapter<INotebookKernelInfoDto2[]>(handle, uri, (adapter, document) => {
return adapter.provideKernels(document, token);
});
}
async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void> {
await this._withAdapter<void>(handle, uri, async (adapter, document) => {
let webComm = this._webviewComm.get(editorId);
if (webComm) {
await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token);
}
});
}
registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable {
if (this._notebookKernels.has(id)) {
throw new Error(`Notebook kernel for '${id}' already registered`);
@@ -1006,7 +1149,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
this._notebookKernels.set(id, { kernel, extension });
const transformedSelectors = selectors.map(selector => typeConverters.GlobPattern.from(selector));
this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation }, id, kernel.label, transformedSelectors, kernel.preloads || []);
this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, id, kernel.label, transformedSelectors, kernel.preloads || []);
return new extHostTypes.Disposable(() => {
this._notebookKernels.delete(id);
this._proxy.$unregisterNotebookKernel(id);
@@ -1101,7 +1244,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
}
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
let document = this._documents.get(URI.revive(uri).toString());
if (!document) {
@@ -1112,17 +1255,46 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
const provider = this._notebookContentProviders.get(viewType)!.provider;
if (provider.kernel && useAttachedKernel) {
if (provider.kernel) {
if (cell) {
return provider.kernel.executeCell(document, cell, token);
return withToken(token => (provider.kernel!.executeCell as any)(document, cell, token));
} else {
return provider.kernel.executeAllCells(document, token);
return withToken(token => (provider.kernel!.executeAllCells as any)(document, token));
}
}
}
}
async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
async $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
const document = this._documents.get(URI.revive(uri).toString());
if (!document) {
return;
}
if (this._notebookContentProviders.has(viewType)) {
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
const provider = this._notebookContentProviders.get(viewType)!.provider;
if (provider.kernel) {
if (cell) {
return provider.kernel.cancelCellExecution(document, cell);
} else {
return provider.kernel.cancelAllCellsExecution(document);
}
}
}
}
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
await this._withAdapter(handle, uri, async (adapter, document) => {
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
return adapter.executeNotebook(kernelId, document, cell);
});
}
async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
let document = this._documents.get(URI.revive(uri).toString());
if (!document || document.viewType !== viewType) {
@@ -1138,9 +1310,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
if (cell) {
return kernelInfo.kernel.executeCell(document, cell, token);
return withToken(token => (kernelInfo!.kernel.executeCell as any)(document, cell, token));
} else {
return kernelInfo.kernel.executeAllCells(document, token);
return withToken(token => (kernelInfo!.kernel.executeAllCells as any)(document, token));
}
}
@@ -1209,6 +1381,20 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
this._outputDisplayOrder = displayOrder;
}
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }) {
if (event.providerHandle !== undefined) {
this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => {
const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined;
this._editors.forEach(editor => {
if (editor.editor.document === document) {
editor.editor.updateActiveKernel(kernel);
}
});
this._onDidChangeActiveNotebookKernel.fire({ document, kernel });
});
}
}
// TODO: remove document - editor one on one mapping
private _getEditorFromURI(uriComponents: UriComponents) {
const uriStr = URI.revive(uriComponents).toString();
@@ -1275,8 +1461,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
revivedUri,
this._proxy,
webComm.contentProviderComm,
document,
this._documentsAndEditors
document
);
const cells = editor.document.cells;

View File

@@ -13,6 +13,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { score } from 'vs/editor/common/modes/languageSelector';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument {
@@ -28,6 +30,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
readonly uri = URI.from({ scheme: 'vscode-concat-doc', path: generateUuid() });
constructor(
extHostNotebooks: ExtHostNotebookController,
extHostDocuments: ExtHostDocuments,

View File

@@ -696,13 +696,11 @@ export class WorkerExtHostTask extends ExtHostTaskBase {
@IExtHostApiDeprecationService deprecationService: IExtHostApiDeprecationService
) {
super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService, deprecationService);
if (initData.remote.isRemote && initData.remote.authority) {
this.registerTaskSystem(Schemas.vscodeRemote, {
scheme: Schemas.vscodeRemote,
authority: initData.remote.authority,
platform: Platform.PlatformToString(Platform.Platform.Web)
});
}
this.registerTaskSystem(Schemas.vscodeRemote, {
scheme: Schemas.vscodeRemote,
authority: '',
platform: Platform.PlatformToString(Platform.Platform.Web)
});
}
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {

View File

@@ -2739,6 +2739,11 @@ export enum NotebookCellRunState {
Error = 4
}
export enum NotebookRunState {
Running = 1,
Idle = 2
}
//#endregion
//#region Timeline
@@ -2774,13 +2779,6 @@ export enum ExtensionMode {
//#endregion ExtensionContext
//#region Authentication
export class AuthenticationSession implements vscode.AuthenticationSession {
constructor(public id: string, public accessToken: string, public account: { label: string, id: string }, public scopes: string[]) { }
}
//#endregion Authentication
export enum StandardTokenType {
Other = 0,
Comment = 1,

View File

@@ -40,7 +40,7 @@ namespace schema {
case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu;
case 'scm/title': return MenuId.SCMTitle;
case 'scm/sourceControl': return MenuId.SCMSourceControl;
case 'scm/resourceState/context': return MenuId.SCMResourceContext;//
case 'scm/resourceState/context': return MenuId.SCMResourceContext;
case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext;
case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext;
case 'scm/change/title': return MenuId.SCMChangeContext;//

View File

@@ -12,10 +12,13 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IPanel } from 'vs/workbench/common/panel';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Action2, MenuId, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { Direction } from 'vs/base/browser/ui/grid/grid';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
abstract class BaseNavigationAction extends Action {
@@ -257,12 +260,38 @@ export class FocusPreviousPart extends Action {
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
class GoHomeContributor implements IWorkbenchContribution {
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
const homeIndicator = environmentService.options?.homeIndicator;
if (homeIndicator) {
registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.actions.goHome`,
title: nls.localize('goHome', "Go Home"),
menu: { id: MenuId.MenubarWebNavigationMenu }
});
}
async run(): Promise<void> {
window.location.href = homeIndicator.href;
}
});
}
}
}
const actionsRegistry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
const viewCategory = nls.localize('view', "View");
registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateUpAction, undefined), 'View: Navigate to the View Above', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateDownAction, undefined), 'View: Navigate to the View Below', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAction, undefined), 'View: Navigate to the View on the Left', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateUpAction, undefined), 'View: Navigate to the View Above', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateDownAction, undefined), 'View: Navigate to the View Below', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAction, undefined), 'View: Navigate to the View on the Left', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', viewCategory);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', viewCategory);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(GoHomeContributor, LifecyclePhase.Ready);

View File

@@ -256,7 +256,7 @@ export class BreadcrumbsControl {
input = input.primary;
}
if (Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories).getFileEditorInputFactory().isFileEditorInput(input)) {
fileInfoUri = input.label;
fileInfoUri = input.preferredResource;
}
this.domNode.classList.toggle('hidden', false);

View File

@@ -32,7 +32,8 @@ import { Selection } from 'vs/editor/common/core/selection';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';

View File

@@ -163,6 +163,8 @@
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge {
margin-left: 8px;
display: flex;
align-items: center;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge .badge-content {
@@ -170,13 +172,14 @@
border-radius: 11px;
font-size: 11px;
min-width: 18px;
min-height: 18px;
height: 18px;
line-height: 11px;
font-weight: normal;
text-align: center;
display: inline-block;
box-sizing: border-box;
}
position: relative;
}
/* Rotate icons when panel is on right */
.monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal,

View File

@@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export class TreeViewPane extends ViewPane {
private treeView: ITreeView;
protected readonly treeView: ITreeView;
constructor(
options: IViewletViewOptions,
@@ -55,7 +55,7 @@ export class TreeViewPane extends ViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
this.treeView.show(container);
this.renderTreeView(container);
}
shouldShowWelcome(): boolean {
@@ -64,13 +64,21 @@ export class TreeViewPane extends ViewPane {
layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
this.treeView.layout(height, width);
this.layoutTreeView(height, width);
}
getOptimalWidth(): number {
return this.treeView.getOptimalWidth();
}
protected renderTreeView(container: HTMLElement): void {
this.treeView.show(container);
}
protected layoutTreeView(height: number, width: number): void {
this.treeView.layout(height, width);
}
private updateTreeVisibility(): void {
this.treeView.setVisibility(this.isBodyVisible());
}

View File

@@ -982,6 +982,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.updateViewHeaders();
}
});
this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => this._onTitleAreaUpdate.fire()));
}
getTitle(): string {

View File

@@ -45,7 +45,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
@IConfigurationService protected configurationService: IConfigurationService
) {
super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews)(() => {
this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews, viewPaneContainer.onTitleAreaUpdate)(() => {
// Update title area since there is no better way to update secondary actions
this.updateTitleArea();
}));

View File

@@ -22,7 +22,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
enum: ['default', 'large'],
enumDescriptions: [
nls.localize('workbench.editor.titleScrollbarSizing.default', "The default size."),
nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabed more easily with the mouse")
nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse")
],
description: nls.localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."),
default: 'default',

View File

@@ -167,7 +167,7 @@ export interface IFileEditorInputFactory {
/**
* Creates new new editor input capable of showing files.
*/
createFileEditorInput(resource: URI, label: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
createFileEditorInput(resource: URI, preferredResource: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
/**
* Check if the provided object is a file editor input.
@@ -649,20 +649,25 @@ export interface IModeSupport {
export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeSupport {
/**
* Gets the resource this file input is about.
* Gets the resource this file input is about. This will always be the
* canonical form of the resource, so it may differ from the original
* resource that was provided to create the input. Use `preferredResource`
* for the form as it was created.
*/
readonly resource: URI;
/**
* Gets the label of the editor. In most cases this will
* be identical to the resource.
* Gets the preferred resource of the editor. In most cases this will
* be identical to the resource. But in some cases the preferredResource
* may differ in path casing to the actual resource because we keep
* canonical forms of resources in-memory.
*/
readonly label: URI;
readonly preferredResource: URI;
/**
* Sets the preferred label to use for this file input.
* Sets the preferred resource to use for this file input.
*/
setLabel(label: URI): void;
setPreferredResource(preferredResource: URI): void;
/**
* Sets the preferred encoding to use for this file input.

View File

@@ -22,12 +22,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
private static readonly MEMOIZER = createMemoizer();
private _label: URI;
get label(): URI { return this._label; }
private _preferredResource: URI;
get preferredResource(): URI { return this._preferredResource; }
constructor(
public readonly resource: URI,
preferredLabel: URI | undefined,
preferredResource: URI | undefined,
@IEditorService protected readonly editorService: IEditorService,
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
@ITextFileService protected readonly textFileService: ITextFileService,
@@ -37,7 +37,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
) {
super();
this._label = preferredLabel || resource;
this._preferredResource = preferredResource || resource;
this.registerListeners();
}
@@ -51,7 +51,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
}
private onLabelEvent(scheme: string): void {
if (scheme === this._label.scheme) {
if (scheme === this._preferredResource.scheme) {
this.updateLabel();
}
}
@@ -65,25 +65,21 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
this._onDidChangeLabel.fire();
}
setLabel(label: URI): void {
if (!extUri.isEqual(label, this._label)) {
this._label = label;
setPreferredResource(preferredResource: URI): void {
if (!extUri.isEqual(preferredResource, this._preferredResource)) {
this._preferredResource = preferredResource;
this.updateLabel();
}
}
getLabel(): URI {
return this._label;
}
getName(): string {
return this.basename;
}
@AbstractTextResourceEditorInput.MEMOIZER
private get basename(): string {
return this.labelService.getUriBasenameLabel(this._label);
return this.labelService.getUriBasenameLabel(this._preferredResource);
}
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
@@ -100,17 +96,17 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
@AbstractTextResourceEditorInput.MEMOIZER
private get shortDescription(): string {
return this.labelService.getUriBasenameLabel(dirname(this._label));
return this.labelService.getUriBasenameLabel(dirname(this._preferredResource));
}
@AbstractTextResourceEditorInput.MEMOIZER
private get mediumDescription(): string {
return this.labelService.getUriLabel(dirname(this._label), { relative: true });
return this.labelService.getUriLabel(dirname(this._preferredResource), { relative: true });
}
@AbstractTextResourceEditorInput.MEMOIZER
private get longDescription(): string {
return this.labelService.getUriLabel(dirname(this._label));
return this.labelService.getUriLabel(dirname(this._preferredResource));
}
@AbstractTextResourceEditorInput.MEMOIZER
@@ -120,12 +116,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
@AbstractTextResourceEditorInput.MEMOIZER
private get mediumTitle(): string {
return this.labelService.getUriLabel(this._label, { relative: true });
return this.labelService.getUriLabel(this._preferredResource, { relative: true });
}
@AbstractTextResourceEditorInput.MEMOIZER
private get longTitle(): string {
return this.labelService.getUriLabel(this._label);
return this.labelService.getUriLabel(this._preferredResource);
}
getTitle(verbosity: Verbosity): string {

View File

@@ -618,6 +618,8 @@ export interface ITreeItemLabel {
highlights?: [number, number][];
strikethrough?: boolean;
}
export interface ITreeItem {

View File

@@ -160,7 +160,9 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
if (match(pattern, uri.toString())) {
for (const extensionId of extensionIds) {
// Add to recommendation to prompt if it is an important tip
if (this.importantExtensionTips[extensionId]) {
// Only prompt if the pattern matches the extensionImportantTips pattern
// Otherwise, assume pattern is from extensionTips, which means it should be a file based "passive" recommendation
if (this.importantExtensionTips[extensionId]?.pattern === pattern) {
recommendationsToPrompt.push(extensionId);
}
// Update file based recommendations

View File

@@ -34,7 +34,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding';
import { Schemas } from 'vs/base/common/network';
import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher';
import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig';
@@ -102,8 +102,8 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
// Register default file input factory
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({
createFileEditorInput: (resource, label, encoding, mode, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, label, encoding, mode);
createFileEditorInput: (resource, preferredResource, encoding, mode, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, preferredResource, encoding, mode);
},
isFileEditorInput: (obj): obj is IFileEditorInput => {
@@ -113,7 +113,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
interface ISerializedFileEditorInput {
resourceJSON: UriComponents;
labelJSON?: UriComponents;
preferredResourceJSON?: UriComponents;
encoding?: string;
modeId?: string;
}
@@ -128,10 +128,10 @@ class FileEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: EditorInput): string {
const fileEditorInput = <FileEditorInput>editorInput;
const resource = fileEditorInput.resource;
const label = fileEditorInput.getLabel();
const preferredResource = fileEditorInput.preferredResource;
const serializedFileEditorInput: ISerializedFileEditorInput = {
resourceJSON: resource.toJSON(),
labelJSON: extUri.isEqual(resource, label) ? undefined : label, // only storing label if it differs from the resource
preferredResourceJSON: extUri.isEqual(resource, preferredResource) ? undefined : preferredResource, // only storing preferredResource if it differs from the resource
encoding: fileEditorInput.getEncoding(),
modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data
};
@@ -143,13 +143,13 @@ class FileEditorInputFactory implements IEditorInputFactory {
return instantiationService.invokeFunction<FileEditorInput>(accessor => {
const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput);
const resource = URI.revive(serializedFileEditorInput.resourceJSON);
const label = URI.revive(serializedFileEditorInput.labelJSON);
const preferredResource = URI.revive(serializedFileEditorInput.preferredResourceJSON);
const encoding = serializedFileEditorInput.encoding;
const mode = serializedFileEditorInput.modeId;
const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput;
if (label) {
fileEditorInput.setLabel(label);
if (preferredResource) {
fileEditorInput.setPreferredResource(preferredResource);
}
return fileEditorInput;

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