mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f
This commit is contained in:
2
src/bootstrap-amd.js
vendored
2
src/bootstrap-amd.js
vendored
@@ -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,
|
||||
|
||||
72
src/bootstrap-window.js
vendored
72
src/bootstrap-window.js
vendored
@@ -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
37
src/bootstrap.js
vendored
@@ -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
|
||||
};
|
||||
}));
|
||||
|
||||
68
src/main.js
68
src/main.js
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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' });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface JSONScanner {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ParseError {
|
||||
error: ParseErrorCode;
|
||||
offset: number;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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', [])
|
||||
];
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 139 B After Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 118 B |
@@ -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 });
|
||||
@@ -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');
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
6
src/vs/monaco.d.ts
vendored
@@ -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>>;
|
||||
|
||||
@@ -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}]`;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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') },
|
||||
|
||||
@@ -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, '');
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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()]);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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[]>;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, []);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, []);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
5
src/vs/vscode.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
61
src/vs/vscode.proposed.d.ts
vendored
61
src/vs/vscode.proposed.d.ts
vendored
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;//
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -982,6 +982,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
this.updateViewHeaders();
|
||||
}
|
||||
});
|
||||
|
||||
this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => this._onTitleAreaUpdate.fire()));
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
|
||||
@@ -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();
|
||||
}));
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -618,6 +618,8 @@ export interface ITreeItemLabel {
|
||||
|
||||
highlights?: [number, number][];
|
||||
|
||||
strikethrough?: boolean;
|
||||
|
||||
}
|
||||
|
||||
export interface ITreeItem {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user