Archived
Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)
* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 * Bump distro * Upgrade GCC to 4.9 due to yarn install errors * Update build image * Fix bootstrap base url * Bump distro * Fix build errors * Update source map file * Disable checkbox for blocking migration issues (#15131) * disable checkbox for blocking issues * wip * disable checkbox fixes * fix strings * Remove duplicate tsec command * Default to off for tab color if settings not present * re-skip failing tests * Fix mocha error * Bump sqlite version & fix notebooks search view * Turn off esbuild warnings * Update esbuild log level * Fix overflowactionbar tests * Fix ts-ignore in dropdown tests * cleanup/fixes * Fix hygiene * Bundle in entire zone.js module * Remove extra constructor param * bump distro for web compile break * bump distro for web compile break v2 * Undo log level change * New distro * Fix integration test scripts * remove the "no yarn.lock changes" workflow * fix scripts v2 * Update unit test scripts * Ensure ads-kerberos2 updates in .vscodeignore * Try fix unit tests * Upload crash reports * remove nogpu * always upload crashes * Use bash script * Consolidate data/ext dir names * Create in tmp directory Co-authored-by: chlafreniere <hichise@gmail.com> Co-authored-by: Christopher Suh <chsuh@microsoft.com> Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
co-authored by
chlafreniere
Christopher Suh
chgagnon
parent
7e1c0076ba
commit
867a963882
Vendored
+2
@@ -8,6 +8,7 @@
|
||||
|
||||
const loader = require('./vs/loader');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const performance = require('./vs/base/common/performance');
|
||||
|
||||
// Bootstrap: NLS
|
||||
const nlsConfig = bootstrap.setupNLS();
|
||||
@@ -55,5 +56,6 @@ exports.load = function (entrypoint, onLoad, onError) {
|
||||
onLoad = onLoad || function () { };
|
||||
onError = onError || function (err) { console.error(err); };
|
||||
|
||||
performance.mark(`code/fork/willLoadCode`);
|
||||
loader([entrypoint], onLoad, onError);
|
||||
};
|
||||
|
||||
Vendored
+44
-12
@@ -6,6 +6,9 @@
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
const performance = require('./vs/base/common/performance');
|
||||
performance.mark('code/fork/start');
|
||||
|
||||
const bootstrap = require('./bootstrap');
|
||||
const bootstrapNode = require('./bootstrap-node');
|
||||
|
||||
@@ -20,7 +23,7 @@ if (process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']) {
|
||||
}
|
||||
|
||||
// Configure: pipe logging to parent process
|
||||
if (!!process.send && process.env.PIPE_LOGGING === 'true') {
|
||||
if (!!process.send && process.env['VSCODE_PIPE_LOGGING'] === 'true') {
|
||||
pipeLoggingToParent();
|
||||
}
|
||||
|
||||
@@ -38,7 +41,7 @@ if (process.env['VSCODE_PARENT_PID']) {
|
||||
configureCrashReporter();
|
||||
|
||||
// Load AMD entry point
|
||||
require('./bootstrap-amd').load(process.env['AMD_ENTRYPOINT']);
|
||||
require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']);
|
||||
|
||||
|
||||
//#region Helpers
|
||||
@@ -79,7 +82,7 @@ function pipeLoggingToParent() {
|
||||
|
||||
// Add the stack trace as payload if we are told so. We remove the message and the 2 top frames
|
||||
// to start the stacktrace where the console message was being written
|
||||
if (process.env.VSCODE_LOG_STACK === 'true') {
|
||||
if (process.env['VSCODE_LOG_STACK'] === 'true') {
|
||||
const stack = new Error().stack;
|
||||
if (stack) {
|
||||
argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') });
|
||||
@@ -135,18 +138,47 @@ function pipeLoggingToParent() {
|
||||
&& !(obj instanceof Date);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {'log' | 'warn' | 'error'} severity
|
||||
* @param {string} args
|
||||
*/
|
||||
function safeSendConsoleMessage(severity, args) {
|
||||
safeSend({ type: '__$console', severity, arguments: args });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {'log' | 'info' | 'warn' | 'error'} method
|
||||
* @param {'log' | 'warn' | 'error'} severity
|
||||
*/
|
||||
function wrapConsoleMethod(method, severity) {
|
||||
if (process.env['VSCODE_LOG_NATIVE'] === 'true') {
|
||||
const original = console[method];
|
||||
console[method] = function () {
|
||||
safeSendConsoleMessage(severity, safeToArray(arguments));
|
||||
|
||||
const stream = method === 'error' || method === 'warn' ? process.stderr : process.stdout;
|
||||
stream.write('\nSTART_NATIVE_LOG\n');
|
||||
original.apply(console, arguments);
|
||||
stream.write('\nEND_NATIVE_LOG\n');
|
||||
};
|
||||
} else {
|
||||
console[method] = function () { safeSendConsoleMessage(severity, safeToArray(arguments)); };
|
||||
}
|
||||
}
|
||||
|
||||
// Pass console logging to the outside so that we have it in the main side if told so
|
||||
if (process.env.VERBOSE_LOGGING === 'true') {
|
||||
console.log = function () { safeSend({ type: '__$console', severity: 'log', arguments: safeToArray(arguments) }); };
|
||||
console.info = function () { safeSend({ type: '__$console', severity: 'log', arguments: safeToArray(arguments) }); };
|
||||
console.warn = function () { safeSend({ type: '__$console', severity: 'warn', arguments: safeToArray(arguments) }); };
|
||||
} else {
|
||||
if (process.env['VSCODE_VERBOSE_LOGGING'] === 'true') {
|
||||
wrapConsoleMethod('info', 'log');
|
||||
wrapConsoleMethod('log', 'log');
|
||||
wrapConsoleMethod('warn', 'warn');
|
||||
wrapConsoleMethod('error', 'error');
|
||||
} else if (process.env['VSCODE_LOG_NATIVE'] !== 'true') {
|
||||
console.log = function () { /* ignore */ };
|
||||
console.warn = function () { /* ignore */ };
|
||||
console.info = function () { /* ignore */ };
|
||||
wrapConsoleMethod('error', 'error');
|
||||
}
|
||||
|
||||
console.error = function () { safeSend({ type: '__$console', severity: 'error', arguments: safeToArray(arguments) }); };
|
||||
}
|
||||
|
||||
function handleExceptions() {
|
||||
@@ -177,11 +209,11 @@ function terminateWhenParentTerminates() {
|
||||
}
|
||||
|
||||
function configureCrashReporter() {
|
||||
const crashReporterOptionsRaw = process.env['CRASH_REPORTER_START_OPTIONS'];
|
||||
const crashReporterOptionsRaw = process.env['VSCODE_CRASH_REPORTER_START_OPTIONS'];
|
||||
if (typeof crashReporterOptionsRaw === 'string') {
|
||||
try {
|
||||
const crashReporterOptions = JSON.parse(crashReporterOptionsRaw);
|
||||
if (crashReporterOptions) {
|
||||
if (crashReporterOptions && process['crashReporter'] /* Electron only */) {
|
||||
process['crashReporter'].start(crashReporterOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Vendored
+1
-1
@@ -62,7 +62,7 @@ exports.removeGlobalNodeModuleLookupPaths = function () {
|
||||
/**
|
||||
* Helper to enable portable mode.
|
||||
*
|
||||
* @param {{ portable?: string; applicationName: string; }} product
|
||||
* @param {Partial<import('./vs/platform/product/common/productService').IProductConfiguration>} product
|
||||
* @returns {{ portableDataPath: string; isPortable: boolean; }}
|
||||
*/
|
||||
exports.configurePortable = function (product) {
|
||||
|
||||
Vendored
+49
-68
@@ -27,6 +27,7 @@
|
||||
const webFrame = preloadGlobals.webFrame;
|
||||
const safeProcess = preloadGlobals.process;
|
||||
const configuration = parseWindowConfiguration();
|
||||
const useCustomProtocol = sandbox || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string';
|
||||
|
||||
// Start to resolve process.env before anything gets load
|
||||
// so that we can run loading and resolving in parallel
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
/**
|
||||
* @param {string[]} modulePaths
|
||||
* @param {(result, configuration: object) => any} resultCallback
|
||||
* @param {(result: unknown, configuration: object) => Promise<unknown> | undefined} resultCallback
|
||||
* @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options
|
||||
*/
|
||||
function load(modulePaths, resultCallback, options) {
|
||||
@@ -83,31 +84,32 @@
|
||||
|
||||
window.document.documentElement.setAttribute('lang', locale);
|
||||
|
||||
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs (TODO@sandbox non-sandboxed only)
|
||||
if (!sandbox) {
|
||||
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs
|
||||
if (!useCustomProtocol) {
|
||||
window['define'] = undefined;
|
||||
}
|
||||
|
||||
// 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; });
|
||||
require.define('fs', [], function () { return require.__$__nodeRequire('original-fs'); });
|
||||
}
|
||||
|
||||
window['MonacoEnvironment'] = {};
|
||||
|
||||
const baseUrl = sandbox ?
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` :
|
||||
const baseUrl = useCustomProtocol ?
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'azuredatastudio-file', fallbackAuthority: 'azuredatastudio-app' })}/out` :
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`;
|
||||
|
||||
const loaderConfig = {
|
||||
baseUrl: baseUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
amdModulesPattern: /^(vs|sql)\//, // {{SQL CARBON EDIT}} include sql in regex
|
||||
preferScriptTags: sandbox
|
||||
preferScriptTags: useCustomProtocol
|
||||
};
|
||||
|
||||
// use a trusted types policy when loading via script tags
|
||||
if (loaderConfig.preferScriptTags && window && window.trustedTypes) { // {{SQL CARBON EDIT}} fix uglify error
|
||||
loaderConfig.trustedTypesPolicy = window.trustedTypes.createPolicy('amdLoader', {
|
||||
if (loaderConfig.preferScriptTags) {
|
||||
loaderConfig.trustedTypesPolicy = window.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL(value) {
|
||||
if (value.startsWith(window.location.origin)) {
|
||||
return value;
|
||||
@@ -117,6 +119,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Enable loading of node modules:
|
||||
// - sandbox: we list paths of webpacked modules to help the loader
|
||||
// - non-sandbox: we signal that any module that does not begin with
|
||||
// `vs/` should be loaded using node.js require()
|
||||
if (sandbox) {
|
||||
loaderConfig.paths = {
|
||||
'vscode-textmate': `../node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `../node_modules/vscode-oniguruma/release/main`,
|
||||
'xterm': `../node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `../node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-webgl': `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'iconv-lite-umd': `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
||||
'jschardet': `../node_modules/jschardet/dist/jschardet.min.js`,
|
||||
};
|
||||
} else {
|
||||
loaderConfig.amdModulesPattern = /^(vs|sql)\//;
|
||||
}
|
||||
|
||||
// cached data config
|
||||
if (configuration.nodeCachedDataDir) {
|
||||
loaderConfig.nodeCachedData = {
|
||||
@@ -145,10 +166,9 @@
|
||||
try {
|
||||
|
||||
// Wait for process environment being fully resolved
|
||||
const perf = perfLib();
|
||||
perf.mark('willWaitForShellEnv');
|
||||
performance.mark('code/willWaitForShellEnv');
|
||||
await whenEnvResolved;
|
||||
perf.mark('didWaitForShellEnv');
|
||||
performance.mark('code/didWaitForShellEnv');
|
||||
|
||||
// Callback only after process environment is resolved
|
||||
const callbackResult = resultCallback(result, configuration);
|
||||
@@ -166,7 +186,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the contents of the `INativeWindowConfiguration` that
|
||||
* Parses the contents of the window condiguration that
|
||||
* is passed into the URL from the `electron-main` side.
|
||||
*
|
||||
* @returns {{
|
||||
@@ -195,22 +215,26 @@
|
||||
function registerDeveloperKeybindings(disallowReloadKeybinding) {
|
||||
const ipcRenderer = preloadGlobals.ipcRenderer;
|
||||
|
||||
const extractKey = function (e) {
|
||||
return [
|
||||
e.ctrlKey ? 'ctrl-' : '',
|
||||
e.metaKey ? 'meta-' : '',
|
||||
e.altKey ? 'alt-' : '',
|
||||
e.shiftKey ? 'shift-' : '',
|
||||
e.keyCode
|
||||
].join('');
|
||||
};
|
||||
const extractKey =
|
||||
/**
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
function (e) {
|
||||
return [
|
||||
e.ctrlKey ? 'ctrl-' : '',
|
||||
e.metaKey ? 'meta-' : '',
|
||||
e.altKey ? 'alt-' : '',
|
||||
e.shiftKey ? 'shift-' : '',
|
||||
e.keyCode
|
||||
].join('');
|
||||
};
|
||||
|
||||
// Devtools & reload support
|
||||
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 = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
|
||||
|
||||
/** @type {((e: any) => void) | undefined} */
|
||||
/** @type {((e: KeyboardEvent) => void) | undefined} */
|
||||
let listener = function (e) {
|
||||
const key = extractKey(e);
|
||||
if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) {
|
||||
@@ -252,7 +276,7 @@
|
||||
*/
|
||||
function bootstrap() {
|
||||
// @ts-ignore (defined in bootstrap.js)
|
||||
return globalThis.MonacoBootstrap;
|
||||
return window.MonacoBootstrap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,51 +287,8 @@
|
||||
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 {{ mark: (name: string) => void }}
|
||||
*/
|
||||
function perfLib() {
|
||||
globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
mark(name) {
|
||||
globalThis.MonacoPerformanceMarks.push(name, Date.now());
|
||||
performance.mark(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
load,
|
||||
globals,
|
||||
perfLib
|
||||
globals
|
||||
};
|
||||
}));
|
||||
|
||||
Vendored
+8
-93
@@ -16,11 +16,7 @@
|
||||
|
||||
// Browser
|
||||
else {
|
||||
try {
|
||||
globalThis.MonacoBootstrap = factory();
|
||||
} catch (error) {
|
||||
console.warn(error); // expected when e.g. running with sandbox: true (TODO@sandbox eventually consolidate this)
|
||||
}
|
||||
globalThis.MonacoBootstrap = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
const Module = typeof require === 'function' ? require('module') : undefined;
|
||||
@@ -174,94 +170,9 @@
|
||||
return nlsConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Portable helpers
|
||||
|
||||
/**
|
||||
* @param {{ portable: string | undefined; applicationName: string; }} product
|
||||
* @returns {{ portableDataPath: string; isPortable: boolean; } | undefined}
|
||||
* @returns {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals') | undefined}
|
||||
*/
|
||||
function configurePortable(product) {
|
||||
if (!path || !fs || typeof process === 'undefined') {
|
||||
console.warn('configurePortable() is only available in node.js environments'); // TODO@sandbox Portable is currently non-sandboxed only
|
||||
return;
|
||||
}
|
||||
|
||||
const appRoot = path.dirname(__dirname);
|
||||
|
||||
/**
|
||||
* @param {import('path')} path
|
||||
*/
|
||||
function getApplicationPath(path) {
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return appRoot;
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
return path.dirname(path.dirname(path.dirname(appRoot)));
|
||||
}
|
||||
|
||||
return path.dirname(path.dirname(appRoot));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('path')} path
|
||||
*/
|
||||
function getPortableDataPath(path) {
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
return process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
return path.join(getApplicationPath(path), 'data');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
|
||||
return path.join(path.dirname(getApplicationPath(path)), portableDataName);
|
||||
}
|
||||
|
||||
const portableDataPath = getPortableDataPath(path);
|
||||
const isPortable = !('target' in product) && fs.existsSync(portableDataPath);
|
||||
const portableTempPath = path.join(portableDataPath, 'tmp');
|
||||
const isTempPortable = isPortable && fs.existsSync(portableTempPath);
|
||||
|
||||
if (isPortable) {
|
||||
process.env['VSCODE_PORTABLE'] = portableDataPath;
|
||||
} else {
|
||||
delete process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
if (isTempPortable) {
|
||||
if (process.platform === 'win32') {
|
||||
process.env['TMP'] = portableTempPath;
|
||||
process.env['TEMP'] = portableTempPath;
|
||||
} else {
|
||||
process.env['TMPDIR'] = portableTempPath;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
portableDataPath,
|
||||
isPortable
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region ApplicationInsights
|
||||
|
||||
// Prevents appinsights from monkey patching modules.
|
||||
// This should be called before importing the applicationinsights module
|
||||
function avoidMonkeyPatchFromAppInsights() {
|
||||
// @ts-ignore
|
||||
process.env['APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL'] = true; // Skip monkey patching of 3rd party modules by appinsights
|
||||
global['diagnosticsSource'] = {}; // Prevents diagnostic channel (which patches "require") from initializing entirely
|
||||
}
|
||||
|
||||
function safeGlobals() {
|
||||
const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {});
|
||||
|
||||
@@ -269,7 +180,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {NodeJS.Process | undefined}
|
||||
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').IPartialNodeProcess | NodeJS.Process}
|
||||
*/
|
||||
function safeProcess() {
|
||||
if (typeof process !== 'undefined') {
|
||||
@@ -280,16 +191,20 @@
|
||||
if (globals) {
|
||||
return globals.process; // Native environment (sandboxed)
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Electron.IpcRenderer | undefined}
|
||||
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/electronTypes').IpcRenderer | undefined}
|
||||
*/
|
||||
function safeIpcRenderer() {
|
||||
const globals = safeGlobals();
|
||||
if (globals) {
|
||||
return globals.ipcRenderer;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ function entrypoint(name) {
|
||||
exports.base = [{
|
||||
name: 'vs/base/common/worker/simpleWorker',
|
||||
include: ['vs/editor/common/services/editorSimpleWorker'],
|
||||
prepend: ['vs/loader.js'],
|
||||
prepend: ['vs/loader.js', 'vs/nls.js'],
|
||||
append: ['vs/base/worker/workerMain'],
|
||||
dest: 'vs/base/worker/workerMain.js'
|
||||
}];
|
||||
|
||||
+43
-24
@@ -7,17 +7,16 @@
|
||||
'use strict';
|
||||
|
||||
const perf = require('./vs/base/common/performance');
|
||||
perf.mark('code/didStartMain');
|
||||
|
||||
const lp = require('./vs/base/node/languagePacks');
|
||||
|
||||
perf.mark('main:started');
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const bootstrapNode = require('./bootstrap-node');
|
||||
const paths = require('./paths');
|
||||
/** @type {any} */
|
||||
/** @type {Partial<import('./vs/platform/product/common/productService').IProductConfiguration> & { applicationName: string}} */
|
||||
const product = require('../product.json');
|
||||
const { app, protocol, crashReporter } = require('electron');
|
||||
|
||||
@@ -201,14 +200,14 @@ function startup(cachedDataDir, nlsConfig) {
|
||||
process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
|
||||
|
||||
// Load main in AMD
|
||||
perf.mark('willLoadMainBundle');
|
||||
perf.mark('code/willLoadMainBundle');
|
||||
require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
|
||||
perf.mark('didLoadMainBundle');
|
||||
perf.mark('code/didLoadMainBundle');
|
||||
});
|
||||
}
|
||||
|
||||
async function onReady() {
|
||||
perf.mark('main:appReady');
|
||||
perf.mark('code/mainAppReady');
|
||||
|
||||
try {
|
||||
const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
|
||||
@@ -220,9 +219,7 @@ async function onReady() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ [arg: string]: any; '--'?: string[]; _: string[]; }} NativeParsedArgs
|
||||
*
|
||||
* @param {NativeParsedArgs} cliArgs
|
||||
* @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs
|
||||
*/
|
||||
function configureCommandlineSwitchesSync(cliArgs) {
|
||||
const SUPPORTED_ELECTRON_SWITCHES = [
|
||||
@@ -246,7 +243,11 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
const SUPPORTED_MAIN_PROCESS_SWITCHES = [
|
||||
|
||||
// Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775
|
||||
'enable-proposed-api'
|
||||
'enable-proposed-api',
|
||||
|
||||
// TODO@bpasero remove me once testing is done on `vscode-file` protocol
|
||||
// (all traces of `enable-browser-code-loading` and `ENABLE_VSCODE_BROWSER_CODE_LOADING`)
|
||||
'enable-browser-code-loading'
|
||||
];
|
||||
|
||||
// Read argv config
|
||||
@@ -277,12 +278,20 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
|
||||
// Append main process flags to process.argv
|
||||
else if (SUPPORTED_MAIN_PROCESS_SWITCHES.indexOf(argvKey) !== -1) {
|
||||
if (argvKey === 'enable-proposed-api') {
|
||||
if (Array.isArray(argvValue)) {
|
||||
argvValue.forEach(id => id && typeof id === 'string' && process.argv.push('--enable-proposed-api', id));
|
||||
} else {
|
||||
console.error(`Unexpected value for \`enable-proposed-api\` in argv.json. Expected array of extension ids.`);
|
||||
}
|
||||
switch (argvKey) {
|
||||
case 'enable-proposed-api':
|
||||
if (Array.isArray(argvValue)) {
|
||||
argvValue.forEach(id => id && typeof id === 'string' && process.argv.push('--enable-proposed-api', id));
|
||||
} else {
|
||||
console.error(`Unexpected value for \`enable-proposed-api\` in argv.json. Expected array of extension ids.`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'enable-browser-code-loading':
|
||||
if (typeof argvValue === 'string') {
|
||||
process.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] = argvValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -293,6 +302,11 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
app.commandLine.appendSwitch('js-flags', jsFlags);
|
||||
}
|
||||
|
||||
// Support __sandbox flag
|
||||
if (cliArgs.__sandbox) {
|
||||
process.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] = 'bypassHeatCheck';
|
||||
}
|
||||
|
||||
return argvConfig;
|
||||
}
|
||||
|
||||
@@ -375,7 +389,7 @@ function getArgvConfigPath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NativeParsedArgs} cliArgs
|
||||
* @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function getJSFlags(cliArgs) {
|
||||
@@ -395,7 +409,7 @@ function getJSFlags(cliArgs) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NativeParsedArgs} cliArgs
|
||||
* @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
@@ -408,7 +422,7 @@ function getUserDataPath(cliArgs) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {NativeParsedArgs}
|
||||
* @returns {import('./vs/platform/environment/common/argv').NativeParsedArgs}
|
||||
*/
|
||||
function parseCLIArgs() {
|
||||
const minimist = require('minimist');
|
||||
@@ -457,11 +471,16 @@ function registerListeners() {
|
||||
* @type {string[]}
|
||||
*/
|
||||
const openUrls = [];
|
||||
const onOpenUrl = function (event, url) {
|
||||
event.preventDefault();
|
||||
const onOpenUrl =
|
||||
/**
|
||||
* @param {{ preventDefault: () => void; }} event
|
||||
* @param {string} url
|
||||
*/
|
||||
function (event, url) {
|
||||
event.preventDefault();
|
||||
|
||||
openUrls.push(url);
|
||||
};
|
||||
openUrls.push(url);
|
||||
};
|
||||
|
||||
app.on('will-finish-launching', function () {
|
||||
app.on('open-url', onOpenUrl);
|
||||
|
||||
@@ -159,7 +159,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
private createButtonMenuItem(title: string, command: HeaderFilterCommands, iconClass: string): Button {
|
||||
const buttonContainer = append(this.menu, $(''));
|
||||
const button = new Button(buttonContainer);
|
||||
button.icon = { classNames: `slick-header-menuicon ${iconClass}` };
|
||||
button.icon = { id: `slick-header-menuicon ${iconClass}` };
|
||||
button.label = title;
|
||||
button.onDidClick(async () => {
|
||||
await this.handleMenuItemClick(command, this.columnDef);
|
||||
|
||||
@@ -17,62 +17,59 @@ suite('Overflow Actionbar tests', () => {
|
||||
});
|
||||
|
||||
test('Verify moving actions from toolbar to overflow', () => {
|
||||
assert(overflowActionbar.actionsList.children.length === 0);
|
||||
assert(overflowActionbar.items.length === 0);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 0);
|
||||
assert.strictEqual(overflowActionbar.items.length, 0);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
let a1 = new Action('a1');
|
||||
let a2 = new Action('a1');
|
||||
let a3 = new Action('a3');
|
||||
overflowActionbar.pushAction([a1, a2, a3]);
|
||||
|
||||
assert(overflowActionbar.actionsList.children.length === 3);
|
||||
assert(overflowActionbar.items.length === 3);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
// more item element '...' is added when actions are moved to the overflow
|
||||
// and a placeholder undefined element is added to the items array for calculating focus
|
||||
overflowActionbar.createMoreItemElement();
|
||||
assert(overflowActionbar.actionsList.children.length === 4);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 4);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
// move a3 to overflow
|
||||
overflowActionbar.collapseItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 3);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 1);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 3);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move a2 to overflow
|
||||
overflowActionbar.collapseItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 2);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 1);
|
||||
assert(overflowActionbar.overflow.childElementCount === 2);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 2);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 1);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 2);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 2);
|
||||
|
||||
// move a2 to back to toolbar
|
||||
overflowActionbar.restoreItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 3);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 1);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 3);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 1);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move a3 to back to toolbar
|
||||
overflowActionbar.restoreItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 4);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 3);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 4);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 3);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
});
|
||||
|
||||
test('Verify moving actions and separators from toolbar to overflow', () => {
|
||||
assert(overflowActionbar.actionsList.children.length === 0);
|
||||
assert(overflowActionbar.items.length === 0);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 0);
|
||||
assert.strictEqual(overflowActionbar.items.length, 0);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
let a1 = new Action('a1');
|
||||
let a2 = new Action('a1');
|
||||
@@ -82,70 +79,70 @@ suite('Overflow Actionbar tests', () => {
|
||||
overflowActionbar.pushElement(separator);
|
||||
overflowActionbar.pushAction([a3]);
|
||||
|
||||
assert(overflowActionbar.actionsList.children.length === 4);
|
||||
assert(overflowActionbar.items.length === 3); // items only has focusable elements
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 4);
|
||||
assert.strictEqual(overflowActionbar.items.length, 3); // items only has focusable elements
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
// more item element '...' is added when actions are moved to the overflow
|
||||
// and a placeholder undefined element is added to the items array for calculating focus
|
||||
overflowActionbar.createMoreItemElement();
|
||||
assert(overflowActionbar.actionsList.children.length === 5);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 5);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
|
||||
// move a3 to overflow
|
||||
overflowActionbar.collapseItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 4);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 1);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 4);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 1);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move separator to overflow
|
||||
overflowActionbar.collapseItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 3);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 2);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 3);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 2);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move a2 to overflow
|
||||
overflowActionbar.collapseItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 2);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 1);
|
||||
assert(overflowActionbar.overflow.childElementCount === 3);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 2);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 1);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 3);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 2);
|
||||
|
||||
// move a2 to back to toolbar
|
||||
overflowActionbar.restoreItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 3);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 2);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 3);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 2);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move separator to back to toolbar
|
||||
overflowActionbar.restoreItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 4);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 2);
|
||||
assert(overflowActionbar.overflow.childElementCount === 1);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 4);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 1);
|
||||
verifyOverflowFocusedIndex(overflowActionbar, 3);
|
||||
|
||||
// move a3 to back to toolbar
|
||||
overflowActionbar.restoreItem();
|
||||
assert(overflowActionbar.actionsList.children.length === 5);
|
||||
assert(overflowActionbar.items.length === 4);
|
||||
assert(getMoreItemPlaceholderIndex(overflowActionbar.items) === 3);
|
||||
assert(overflowActionbar.overflow.childElementCount === 0);
|
||||
assert.strictEqual(overflowActionbar.actionsList.children.length, 5);
|
||||
assert.strictEqual(overflowActionbar.items.length, 4);
|
||||
assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 3);
|
||||
assert.strictEqual(overflowActionbar.overflow.childElementCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
function verifyOverflowFocusedIndex(overflowToolbar: OverflowActionBar, expectedIndex: number) {
|
||||
// click more item element to show overflow and set focus to first element
|
||||
overflowToolbar.moreElementOnClick(new MouseEvent('click'));
|
||||
assert(overflowToolbar.focusedItem === expectedIndex);
|
||||
assert.strictEqual(overflowToolbar.focusedItem, expectedIndex);
|
||||
// click to hide overflow
|
||||
overflowToolbar.moreElementOnClick(new MouseEvent('click'));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ suite('Editable dropdown tests', () => {
|
||||
|
||||
test('default value for editable dropdown is empty', () => {
|
||||
const dropdown = new Dropdown(container, undefined, options);
|
||||
assert(dropdown.value === '');
|
||||
assert.strictEqual(dropdown.value, '');
|
||||
});
|
||||
|
||||
test('changing value through code fires onValueChange event', () => {
|
||||
@@ -37,11 +37,11 @@ suite('Editable dropdown tests', () => {
|
||||
});
|
||||
dropdown.value = options.values[0];
|
||||
|
||||
assert(count === 1, 'onValueChange event was not fired');
|
||||
assert.strictEqual(count, 1, 'onValueChange event was not fired');
|
||||
dropdown.value = options.values[0];
|
||||
assert(count === 1, 'onValueChange event should not be fired for setting the same value again');
|
||||
assert.strictEqual(count, 1, 'onValueChange event should not be fired for setting the same value again');
|
||||
dropdown.value = options.values[1];
|
||||
assert(count === 2, 'onValueChange event was not fired for setting a new value of the dropdown');
|
||||
assert.strictEqual(count, 2, 'onValueChange event was not fired for setting a new value of the dropdown');
|
||||
});
|
||||
|
||||
test('changing value through input text fires onValue Change event', () => {
|
||||
@@ -54,18 +54,18 @@ suite('Editable dropdown tests', () => {
|
||||
dropdown.fireOnTextChange = true;
|
||||
dropdown.setDropdownVisibility(true);
|
||||
dropdown.input.value = options.values[0];
|
||||
assert(count === 1, 'onValueChange event was not fired for an option from the dropdown list');
|
||||
assert.strictEqual(count, 1, 'onValueChange event was not fired for an option from the dropdown list');
|
||||
dropdown.input.value = 'foo';
|
||||
assert(count === 2, 'onValueChange event was not fired for a value not in dropdown list');
|
||||
assert(dropdown.selectList.length === 4, 'list does not have all the values that are matching the input box text');
|
||||
assert(dropdown.value = 'foo');
|
||||
assert.strictEqual(count, 2, 'onValueChange event was not fired for a value not in dropdown list');
|
||||
assert.strictEqual(dropdown.selectList.length, 4, 'list does not have all the values that are matching the input box text');
|
||||
assert.strictEqual(dropdown.value, 'foo');
|
||||
dropdown.input.value = 'foobar';
|
||||
assert(count === 3, 'onValueChange event was not fired for a value not in dropdown list');
|
||||
assert(dropdown.selectList.length === 2, 'list does not have all the values that are matching the input box text');
|
||||
assert(dropdown.value = 'foobar');
|
||||
assert.strictEqual(count, 3, 'onValueChange event was not fired for a value not in dropdown list');
|
||||
assert.strictEqual(dropdown.selectList.length, 2, 'list does not have all the values that are matching the input box text');
|
||||
assert.strictEqual(dropdown.value, 'foobar');
|
||||
|
||||
dropdown.fireOnTextChange = false;
|
||||
dropdown.input.value = options.values[0];
|
||||
assert(count === 3, 'onValueChange event was fired with input box value change even after setting the fireOnTextChange to false');
|
||||
assert.strictEqual(count, 3, 'onValueChange event was fired with input box value change even after setting the fireOnTextChange to false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { FocusedViewContext, IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
// --- Toggle View with Command
|
||||
export abstract class ToggleViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private readonly viewId: string,
|
||||
protected viewsService: IViewsService,
|
||||
protected viewDescriptorService: IViewDescriptorService,
|
||||
protected contextKeyService: IContextKeyService,
|
||||
private layoutService: IWorkbenchLayoutService,
|
||||
cssClass?: string
|
||||
) {
|
||||
super(id, label, cssClass);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
|
||||
|
||||
if (focusedViewId === this.viewId) {
|
||||
if (this.viewDescriptorService.getViewLocationById(this.viewId) === ViewContainerLocation.Sidebar) {
|
||||
this.layoutService.setSideBarHidden(true);
|
||||
} else {
|
||||
this.layoutService.setPanelHidden(true);
|
||||
}
|
||||
} else {
|
||||
this.viewsService.openView(this.viewId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
const container = DOM.append(this._modalHeaderSection, DOM.$('.modal-go-back'));
|
||||
this._backButton = new Button(container, { secondary: true });
|
||||
this._backButton.icon = {
|
||||
classNames: 'backButtonIcon'
|
||||
id: 'backButtonIcon'
|
||||
};
|
||||
this._backButton.title = localize('modal.back', "Back");
|
||||
}
|
||||
@@ -262,21 +262,21 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
this._detailsButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button'));
|
||||
this._toggleMessageDetailButton = new Button(this._detailsButtonContainer);
|
||||
this._toggleMessageDetailButton.icon = {
|
||||
classNames: 'message-details-icon'
|
||||
id: 'message-details-icon'
|
||||
};
|
||||
this._toggleMessageDetailButton.label = SHOW_DETAILS_TEXT;
|
||||
this._register(this._toggleMessageDetailButton.onDidClick(() => this.toggleMessageDetail()));
|
||||
const copyMessageButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button'));
|
||||
this._copyMessageButton = new Button(copyMessageButtonContainer);
|
||||
this._copyMessageButton.icon = {
|
||||
classNames: 'copy-message-icon'
|
||||
id: 'copy-message-icon'
|
||||
};
|
||||
this._copyMessageButton.label = COPY_TEXT;
|
||||
this._register(this._copyMessageButton.onDidClick(() => this._clipboardService.writeText(this.getTextForClipboard())));
|
||||
const closeMessageButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button'));
|
||||
this._closeMessageButton = new Button(closeMessageButtonContainer);
|
||||
this._closeMessageButton.icon = {
|
||||
classNames: 'close-message-icon'
|
||||
id: 'close-message-icon'
|
||||
};
|
||||
this._closeMessageButton.label = CLOSE_TEXT;
|
||||
this._register(this._closeMessageButton.onDidClick(() => this.setError(undefined)));
|
||||
|
||||
@@ -177,7 +177,7 @@ export default class ButtonComponent extends ComponentWithIconBase<azdata.Button
|
||||
if (!this._iconClass) {
|
||||
super.updateIcon();
|
||||
this._button.icon = {
|
||||
classNames: this._iconClass + ' icon'
|
||||
id: this._iconClass + ' icon'
|
||||
};
|
||||
this.updateStyler();
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorOptions, EditorOption, InDiffEditorState } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
@@ -58,7 +58,7 @@ export class QueryTextEditor extends BaseTextEditor {
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
if (this.input) {
|
||||
options.inDiffEditor = InDiffEditorState.None;
|
||||
options.inDiffEditor = false;
|
||||
options.scrollBeyondLastLine = false;
|
||||
options.folding = false;
|
||||
options.renderIndentGuides = false;
|
||||
|
||||
@@ -62,7 +62,7 @@ export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implem
|
||||
if (elements) {
|
||||
for (const element of elements) {
|
||||
const resolvable = new ResolvableTreeComponentItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle, undefined);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
|
||||
@@ -24,7 +24,7 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
|
||||
import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { OpenerServiceStub } from 'sql/platform/opener/common/openerServiceStub';
|
||||
import { OpenerServiceStub } from 'sql/workbench/contrib/opener/common/openerServiceStub';
|
||||
import { SqlAssessmentTargetType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TestFileService, TestEnvironmentService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@@ -6,27 +6,14 @@
|
||||
import 'vs/css!./media/dataExplorer.contribution';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { DataExplorerViewletViewsContribution, OpenDataExplorerViewletAction } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DataExplorerContainerExtensionHandler } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { DataExplorerViewletViewsContribution } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet';
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting);
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(
|
||||
OpenDataExplorerViewletAction,
|
||||
OpenDataExplorerViewletAction.ID,
|
||||
OpenDataExplorerViewletAction.LABEL,
|
||||
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D }),
|
||||
'View: Show Data Explorer',
|
||||
localize('dataExplorer.view', "View")
|
||||
);
|
||||
|
||||
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
|
||||
@@ -22,30 +22,14 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.connections';
|
||||
|
||||
// Viewlet Action
|
||||
export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
||||
public static ID = VIEWLET_ID;
|
||||
public static LABEL = localize('showDataExplorer', "Show Connections");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DataExplorerViewletViewsContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor() {
|
||||
@@ -152,9 +136,15 @@ export const dataExplorerIconId = 'dataExplorer';
|
||||
|
||||
export const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: localize('dataexplorer.name', "Connections"),
|
||||
title: localize('dataexplorer.name', "Connections"),
|
||||
ctorDescriptor: new SyncDescriptor(DataExplorerViewPaneContainer),
|
||||
openCommandActionDescriptor: {
|
||||
id: VIEWLET_ID,
|
||||
mnemonicTitle: localize('showDataExplorer', "Show Connections"),
|
||||
keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D },
|
||||
order: 0
|
||||
},
|
||||
icon: { id: 'dataExplorer' },
|
||||
order: 0,
|
||||
storageId: `${VIEWLET_ID}.state`
|
||||
}, ViewContainerLocation.Sidebar, true);
|
||||
}, ViewContainerLocation.Sidebar, { isDefault: true });
|
||||
|
||||
@@ -71,7 +71,8 @@ export class NotebookFindModel extends Disposable implements INotebookFindModel
|
||||
|
||||
this._decorations = Object.create(null);
|
||||
|
||||
this._buffer = createTextBuffer('', NotebookFindModel.DEFAULT_CREATION_OPTIONS.defaultEOL);
|
||||
const { textBuffer, } = createTextBuffer('', NotebookFindModel.DEFAULT_CREATION_OPTIONS.defaultEOL);
|
||||
this._buffer = textBuffer;
|
||||
this._versionId = 1;
|
||||
this.id = '$model' + MODEL_ID;
|
||||
}
|
||||
@@ -287,7 +288,8 @@ export class NotebookFindModel extends Disposable implements INotebookFindModel
|
||||
*/
|
||||
private _validateRangeRelaxedNoAllocations(range: IRange): NotebookRange {
|
||||
if (range instanceof NotebookRange) {
|
||||
this._buffer = createTextBuffer(range.cell.source instanceof Array ? range.cell.source.join('\n') : range.cell.source, NotebookFindModel.DEFAULT_CREATION_OPTIONS.defaultEOL);
|
||||
const { textBuffer, } = createTextBuffer(range.cell.source instanceof Array ? range.cell.source.join('\n') : range.cell.source, NotebookFindModel.DEFAULT_CREATION_OPTIONS.defaultEOL);
|
||||
this._buffer = textBuffer;
|
||||
}
|
||||
|
||||
const linesCount = this._buffer.getLineCount();
|
||||
|
||||
@@ -260,7 +260,13 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
let errorWithAction = createErrorWithActions(toErrorMessage(error), {
|
||||
actions: [
|
||||
new Action('workbench.files.action.createMissingFile', localize('createFile', "Create File"), undefined, true, () => {
|
||||
return this.textFileService.create(this.notebookParams.notebookUri).then(() => this.editorService.openEditor({
|
||||
let operations = new Array(1);
|
||||
operations[0] = {
|
||||
resource: this.notebookParams.notebookUri,
|
||||
value: undefined,
|
||||
options: undefined
|
||||
};
|
||||
return this.textFileService.create(operations).then(() => this.editorService.openEditor({
|
||||
resource: this.notebookParams.notebookUri,
|
||||
options: {
|
||||
pinned: true // new file gets pinned by default
|
||||
|
||||
@@ -46,7 +46,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
|
||||
import { NotebookThemingContribution } from 'sql/workbench/contrib/notebook/browser/notebookThemingContribution';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode';
|
||||
import { NotebookExplorerViewletViewsContribution, OpenNotebookExplorerViewletAction } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet';
|
||||
import { NotebookExplorerViewletViewsContribution } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet';
|
||||
import 'vs/css!./media/notebook.contribution';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { SearchSortOrder } from 'vs/workbench/services/search/common/search';
|
||||
@@ -439,16 +439,6 @@ registerCellComponent(TextCellComponent);
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(NotebookExplorerViewletViewsContribution, LifecyclePhase.Starting);
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionsExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(
|
||||
OpenNotebookExplorerViewletAction,
|
||||
OpenNotebookExplorerViewletAction.ID,
|
||||
OpenNotebookExplorerViewletAction.LABEL,
|
||||
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }),
|
||||
'View: Show Notebook Explorer',
|
||||
localize('notebookExplorer.view', "View")
|
||||
);
|
||||
|
||||
// Configuration
|
||||
configurationRegistry.registerConfiguration({
|
||||
|
||||
+4
-21
@@ -21,10 +21,9 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { NotebookSearchWidget, INotebookExplorerSearchOptions } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget';
|
||||
import * as Constants from 'sql/workbench/common/constants';
|
||||
@@ -44,22 +43,6 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.notebooks';
|
||||
|
||||
// Viewlet Action
|
||||
export class OpenNotebookExplorerViewletAction extends ShowViewletAction {
|
||||
public static ID = VIEWLET_ID;
|
||||
public static LABEL = localize('showNotebookExplorer', "Show Notebooks");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookExplorerViewletViewsContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor() {
|
||||
@@ -450,7 +433,7 @@ export const notebookIconId = 'book';
|
||||
|
||||
export const NOTEBOOK_VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: localize('notebookExplorer.name', "Notebooks"),
|
||||
title: localize('notebookExplorer.name', "Notebooks"),
|
||||
ctorDescriptor: new SyncDescriptor(NotebookExplorerViewPaneContainer),
|
||||
icon: { id: notebookIconId },
|
||||
order: 6,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SearchView, SearchUIState } from 'vs/workbench/contrib/search/browser/searchView';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
@@ -43,6 +43,7 @@ import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchStop
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { SearchUIState } from 'vs/workbench/contrib/search/common/search';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
|
||||
@@ -119,10 +120,6 @@ export class NotebookSearchView extends SearchView {
|
||||
}
|
||||
|
||||
public updateActions(): void {
|
||||
if (!this.isVisible()) {
|
||||
this.updatedActionsWhileHidden = true;
|
||||
}
|
||||
|
||||
for (const action of this.viewActions) {
|
||||
action.update();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { nb } from 'azdata';
|
||||
import * as assert from 'assert';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as tempWrite from 'temp-write';
|
||||
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
|
||||
@@ -13,9 +16,9 @@ import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { IFileService, IReadFileOptions, IFileContent, IWriteFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { promisify } from 'util';
|
||||
|
||||
let expectedNotebookContent: nb.INotebookContents = {
|
||||
cells: [{
|
||||
@@ -53,7 +56,8 @@ suite('Local Content Manager', function (): void {
|
||||
const instantiationService = new TestInstantiationService();
|
||||
const fileService = new class extends TestFileService {
|
||||
async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise<IFileContent> {
|
||||
const content = await pfs.readFile(resource.fsPath);
|
||||
const content = await promisify(fs.readFile)(resource.fsPath);
|
||||
|
||||
return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource, ctime: 0 };
|
||||
}
|
||||
async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
|
||||
|
||||
+1
-1
@@ -977,7 +977,7 @@ suite('Notebook Editor Model', function (): void {
|
||||
await notebookModel.loadContents();
|
||||
}
|
||||
|
||||
async function createTextEditorModel(self: Mocha.ITestCallbackContext): Promise<NotebookEditorModel> {
|
||||
async function createTextEditorModel(self: Mocha.Context): Promise<NotebookEditorModel> {
|
||||
let textFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(self, defaultUri.toString()), 'utf8', undefined);
|
||||
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textFileEditorModel.resource, textFileEditorModel);
|
||||
await textFileEditorModel.load();
|
||||
|
||||
+10
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IOpenerService, IOpener, IValidator, IExternalUriResolver, IExternalOpener, ResolveExternalUriOptions, IResolvedExternalUri } from 'vs/platform/opener/common/opener';
|
||||
import { IExternalOpenerProvider } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
|
||||
@@ -25,4 +26,13 @@ export class OpenerServiceStub implements IOpenerService {
|
||||
}
|
||||
_serviceBrand: undefined;
|
||||
async open(resource: URI | string, options?: any): Promise<boolean> { return Promise.resolve(true); }
|
||||
setDefaultExternalOpener(opener: IExternalOpener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerExternalOpenerProvider(provider: IExternalOpenerProvider): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerExternalOpener(opener: IExternalOpener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorOptions, InDiffEditorState } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
@@ -60,7 +60,7 @@ export class ProfilerResourceEditor extends BaseTextEditor {
|
||||
const options = super.getConfigurationOverrides();
|
||||
options.readOnly = true;
|
||||
if (this.input) {
|
||||
options.inDiffEditor = InDiffEditorState.SideBySideLeft;
|
||||
options.inDiffEditor = false;
|
||||
options.scrollBeyondLastLine = false;
|
||||
options.folding = false;
|
||||
options.renderWhitespace = 'none';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as types from 'vs/base/common/types';
|
||||
@@ -34,8 +34,7 @@ export class BareResultsGridInfo extends BareFontInfo {
|
||||
cellPadding?: number | number[];
|
||||
}, zoomLevel: number): BareResultsGridInfo {
|
||||
let cellPadding = !types.isUndefinedOrNull(opts.cellPadding) ? opts.cellPadding : RESULTS_GRID_DEFAULTS.cellPadding;
|
||||
|
||||
return new BareResultsGridInfo(BareFontInfo.createFromRawSettings(opts, zoomLevel), { cellPadding });
|
||||
return new BareResultsGridInfo(BareFontInfo.createFromRawSettings(opts, zoomLevel, getPixelRatio()), { cellPadding });
|
||||
}
|
||||
|
||||
readonly cellPadding: number | number[];
|
||||
|
||||
@@ -16,7 +16,7 @@ import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { ToggleViewAction } from 'sql/workbench/browser/actions/layoutActions';
|
||||
|
||||
export class ToggleQueryHistoryAction extends ToggleViewAction {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IQueryHistoryService } from 'sql/workbench/services/queryHistory/common
|
||||
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
|
||||
import { QueryHistoryInfo } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
@@ -96,14 +96,11 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution
|
||||
// markers view container
|
||||
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: QUERY_HISTORY_CONTAINER_ID,
|
||||
name: localize('queryHistory', "Query History"),
|
||||
title: localize('queryHistory', "Query History"),
|
||||
hideIfEmpty: true,
|
||||
order: 20,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [QUERY_HISTORY_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
storageId: `${QUERY_HISTORY_CONTAINER_ID}.storage`,
|
||||
focusCommand: {
|
||||
id: ToggleQueryHistoryAction.ID
|
||||
}
|
||||
}, ViewContainerLocation.Panel);
|
||||
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
|
||||
@@ -68,7 +68,7 @@ function registerResourceViewerContainer() {
|
||||
const resourceViewerIcon = registerCodicon('reosurce-view', Codicon.database);
|
||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: RESOURCE_VIEWER_VIEW_CONTAINER_ID,
|
||||
name: localize('resourceViewer', "Resource Viewer"),
|
||||
title: localize('resourceViewer', "Resource Viewer"),
|
||||
ctorDescriptor: new SyncDescriptor(ResourceViewerViewlet),
|
||||
icon: resourceViewerIcon,
|
||||
alwaysUseContainerInfo: true
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
|
||||
@@ -75,14 +75,11 @@ registry.registerWorkbenchAction(
|
||||
// markers view container
|
||||
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: TASKS_CONTAINER_ID,
|
||||
name: localize('tasks', "Tasks"),
|
||||
title: localize('tasks', "Tasks"),
|
||||
hideIfEmpty: true,
|
||||
order: 20,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TASKS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
storageId: `${TASKS_CONTAINER_ID}.storage`,
|
||||
focusCommand: {
|
||||
id: ToggleTasksAction.ID
|
||||
}
|
||||
storageId: `${TASKS_CONTAINER_ID}.storage`
|
||||
}, ViewContainerLocation.Panel);
|
||||
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { ToggleViewAction } from 'sql/workbench/browser/actions/layoutActions';
|
||||
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { TASKS_VIEW_ID } from 'sql/workbench/contrib/tasks/common/tasks';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { DefaultFilter, DefaultDragAndDrop, DefaultAccessibilityProvider } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { localize } from 'vs/nls';
|
||||
import { hide, $, append } from 'vs/base/browser/dom';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
@@ -32,7 +32,8 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { IViewPaneOptions, ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { attachModalDialogStyler, attachPanelStyler } from 'sql/workbench/common/styler';
|
||||
import { IViewDescriptorService, IViewsRegistry, Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
@@ -53,7 +54,7 @@ export class AccountPaneContainer extends ViewPaneContainer {
|
||||
|
||||
export const ACCOUNT_VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: localize('accountExplorer.name', "Accounts"),
|
||||
title: localize('accountExplorer.name', "Accounts"),
|
||||
ctorDescriptor: new SyncDescriptor(AccountPaneContainer),
|
||||
storageId: `${VIEWLET_ID}.state`
|
||||
}, ViewContainerLocation.Dialog);
|
||||
|
||||
@@ -70,7 +70,7 @@ suite('ConnectionDialogService tests', () => {
|
||||
setup(() => {
|
||||
const viewInstantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
|
||||
const viewDescriptorService = viewInstantiationService.createInstance(ViewDescriptorService);
|
||||
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
|
||||
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
|
||||
viewInstantiationService.stub(IViewDescriptorService, viewDescriptorService);
|
||||
const viewDescriptor: ITreeViewDescriptor = {
|
||||
id: testTreeViewId,
|
||||
|
||||
@@ -42,7 +42,7 @@ suite('ConnectionDialogWidget tests', () => {
|
||||
setup(() => {
|
||||
const viewInstantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
|
||||
const viewDescriptorService = viewInstantiationService.createInstance(ViewDescriptorService);
|
||||
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
|
||||
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
|
||||
viewInstantiationService.stub(IViewDescriptorService, viewDescriptorService);
|
||||
const viewDescriptor: ITreeViewDescriptor = {
|
||||
id: testTreeViewId,
|
||||
|
||||
@@ -842,7 +842,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
this.addEventListener(DOM.EventType.MOUSE_MOVE, mouseMove, { passive: true });
|
||||
setTimeout(async () => {
|
||||
if (node instanceof ResolvableTreeItem) {
|
||||
await node.resolve();
|
||||
await node.resolve(undefined);
|
||||
}
|
||||
let tooltip: IMarkdownString | string | undefined = node.tooltip ?? label;
|
||||
if (isHovering && tooltip) {
|
||||
|
||||
@@ -81,7 +81,7 @@ export class ErrorMessageDialog extends Modal {
|
||||
}
|
||||
}, 'left', true);
|
||||
this._copyButton!.icon = {
|
||||
classNames: 'codicon scriptToClipboard'
|
||||
id: 'codicon scriptToClipboard'
|
||||
};
|
||||
this._copyButton!.element.title = copyButtonLabel;
|
||||
this._register(attachButtonStyler(this._copyButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND, buttonForeground: SIDE_BAR_FOREGROUND }));
|
||||
|
||||
@@ -39,7 +39,8 @@ import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ViewPane, IViewPaneOptions, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { attachPanelStyler, attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
@@ -61,7 +62,7 @@ export class InsightsDetailPaneContainer extends ViewPaneContainer { }
|
||||
|
||||
export const INSIGHTS_DETAIL_VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: nls.localize('insightsDetailView.name', "Insight Details"),
|
||||
title: nls.localize('insightsDetailView.name', "Insight Details"),
|
||||
ctorDescriptor: new SyncDescriptor(InsightsDetailPaneContainer),
|
||||
storageId: `${VIEWLET_ID}.state`
|
||||
}, ViewContainerLocation.Dialog);
|
||||
|
||||
@@ -32,7 +32,7 @@ const testColumns: string[] = [
|
||||
];
|
||||
|
||||
suite('Insights Dialog Controller Tests', () => {
|
||||
test('updates correctly with good input', async (done) => {
|
||||
test('updates correctly with good input', (done) => {
|
||||
|
||||
let model = new InsightsDialogModel();
|
||||
|
||||
@@ -82,19 +82,21 @@ suite('Insights Dialog Controller Tests', () => {
|
||||
options: {}
|
||||
};
|
||||
|
||||
await controller.update(<IInsightsConfigDetails>{ query: 'query' }, profile);
|
||||
// Once we update the controller, listen on when it changes the model and verify the data it
|
||||
// puts in is correct
|
||||
model.onDataChange(() => {
|
||||
for (let i = 0; i < testData.length; i++) {
|
||||
for (let j = 0; j < testData[i].length; j++) {
|
||||
equal(testData[i][j], model.rows[i][j]);
|
||||
controller.update(<IInsightsConfigDetails>{ query: 'query' }, profile).then(() => {
|
||||
// Once we update the controller, listen on when it changes the model and verify the data it
|
||||
// puts in is correct
|
||||
model.onDataChange(() => {
|
||||
for (let i = 0; i < testData.length; i++) {
|
||||
for (let j = 0; j < testData[i].length; j++) {
|
||||
equal(testData[i][j], model.rows[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
done();
|
||||
done();
|
||||
});
|
||||
// Fake the query Runner telling the controller the query is complete
|
||||
complete();
|
||||
});
|
||||
// Fake the query Runner telling the controller the query is complete
|
||||
complete();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as os from 'os';
|
||||
import { resolveQueryFilePath } from 'sql/workbench/services/insights/common/insightsUtils';
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { Workspace, toWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ConfigurationResolverService, BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
|
||||
@@ -45,7 +46,9 @@ suite('Insights Utils tests', function () {
|
||||
// Create test file - just needs to exist for verifying the path resolution worked correctly
|
||||
testRootPath = path.join(os.tmpdir(), 'adstests');
|
||||
queryFileDir = getRandomTestPath(testRootPath, 'insightsutils');
|
||||
await pfs.mkdirp(queryFileDir);
|
||||
|
||||
await fs.promises.mkdir(queryFileDir, { recursive: true });
|
||||
|
||||
queryFilePath = path.join(queryFileDir, 'test.sql');
|
||||
await pfs.writeFile(queryFilePath, '');
|
||||
});
|
||||
|
||||
@@ -59,9 +59,7 @@ export class TaskService implements ITaskService {
|
||||
this._taskQueue = new TaskNode('Root');
|
||||
this._onTaskComplete = new Emitter<TaskNode>();
|
||||
this._onAddNewTask = new Emitter<TaskNode>();
|
||||
|
||||
lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown()));
|
||||
|
||||
lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.taskservice'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "tsec",
|
||||
"exemptionConfig": "./tsec.exemptions.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"*/test/*",
|
||||
"**/*.test.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ban-trustedtypes-createpolicy": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
Vendored
+1
@@ -47,6 +47,7 @@ interface NodeRequire {
|
||||
onError: Function;
|
||||
__$__nodeRequire<T>(moduleName: string): T;
|
||||
getStats(): ReadonlyArray<LoaderEvent>;
|
||||
hasDependencyCycle(): boolean;
|
||||
define(amdModuleId: string, dependencies: string[], callback: (...args: any[]) => any): any;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,13 +110,13 @@ export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscree
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
export const isEdge = (userAgent.indexOf('Edge/') >= 0);
|
||||
export const isOpera = (userAgent.indexOf('Opera') >= 0);
|
||||
export const isEdgeLegacy = (userAgent.indexOf('Edge/') >= 0);
|
||||
export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
|
||||
export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
|
||||
export const isChrome = (userAgent.indexOf('Chrome') >= 0);
|
||||
export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
|
||||
export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
|
||||
export const isIPad = (userAgent.indexOf('iPad') >= 0 || (isSafari && navigator.maxTouchPoints > 0));
|
||||
export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
|
||||
export const isEdgeLegacyWebView = isEdgeLegacy && (userAgent.indexOf('WebView/') >= 0);
|
||||
export const isElectron = (userAgent.indexOf('Electron/') >= 0);
|
||||
export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const BrowserFeatures = {
|
||||
|| !!(navigator && navigator.clipboard && navigator.clipboard.readText)
|
||||
),
|
||||
richText: (() => {
|
||||
if (browser.isEdge) {
|
||||
if (browser.isEdgeLegacy) {
|
||||
let index = navigator.userAgent.indexOf('Edge/');
|
||||
let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface IContextMenuEvent {
|
||||
|
||||
export interface IContextMenuDelegate {
|
||||
getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; };
|
||||
getActions(): IAction[];
|
||||
getActions(): readonly IAction[];
|
||||
getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox';
|
||||
getActionViewItem?(action: IAction): IActionViewItem | undefined;
|
||||
getActionsContext?(event?: IContextMenuEvent): any;
|
||||
|
||||
+43
-42
@@ -20,7 +20,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export function clearNode(node: HTMLElement): void {
|
||||
while (node.firstChild) {
|
||||
node.removeChild(node.firstChild);
|
||||
node.firstChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,7 @@ export function trustedInnerHTML(node: Element, value: TrustedHTML): void {
|
||||
}
|
||||
|
||||
export function isInDOM(node: Node | null): boolean {
|
||||
while (node) {
|
||||
if (node === document.body) {
|
||||
return true;
|
||||
}
|
||||
node = node.parentNode || (node as ShadowRoot).host;
|
||||
}
|
||||
return false;
|
||||
return node?.isConnected ?? false;
|
||||
}
|
||||
|
||||
interface IDomClassList {
|
||||
@@ -357,7 +351,7 @@ export function modify(callback: () => void): IDisposable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a throttled listener. `handler` is fired at most every 16ms or with the next animation frame (if browser supports it).
|
||||
* Add a throttled listener. `handler` is fired at most every 8.33333ms or with the next animation frame (if browser supports it).
|
||||
*/
|
||||
export interface IEventMerger<R, E> {
|
||||
(lastEvent: R | null, currentEvent: E): R;
|
||||
@@ -368,7 +362,7 @@ export interface DOMEvent {
|
||||
stopPropagation(): void;
|
||||
}
|
||||
|
||||
const MINIMUM_TIME_MS = 16;
|
||||
const MINIMUM_TIME_MS = 8;
|
||||
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
|
||||
return currentEvent;
|
||||
};
|
||||
@@ -521,6 +515,8 @@ export interface IDimension {
|
||||
|
||||
export class Dimension implements IDimension {
|
||||
|
||||
static readonly None = new Dimension(0, 0);
|
||||
|
||||
constructor(
|
||||
public readonly width: number,
|
||||
public readonly height: number,
|
||||
@@ -912,7 +908,7 @@ export const EventType = {
|
||||
MOUSE_OUT: 'mouseout',
|
||||
MOUSE_ENTER: 'mouseenter',
|
||||
MOUSE_LEAVE: 'mouseleave',
|
||||
MOUSE_WHEEL: browser.isEdge ? 'mousewheel' : 'wheel',
|
||||
MOUSE_WHEEL: browser.isEdgeLegacy ? 'mousewheel' : 'wheel',
|
||||
POINTER_UP: 'pointerup',
|
||||
POINTER_DOWN: 'pointerdown',
|
||||
POINTER_MOVE: 'pointermove',
|
||||
@@ -1072,9 +1068,13 @@ export function after<T extends Node>(sibling: HTMLElement, child: T): T {
|
||||
return child;
|
||||
}
|
||||
|
||||
export function append<T extends Node>(parent: HTMLElement, ...children: T[]): T {
|
||||
children.forEach(child => parent.appendChild(child));
|
||||
return children[children.length - 1];
|
||||
export function append<T extends Node>(parent: HTMLElement, child: T): T;
|
||||
export function append<T extends Node>(parent: HTMLElement, ...children: (T | string)[]): void;
|
||||
export function append<T extends Node>(parent: HTMLElement, ...children: (T | string)[]): T | void {
|
||||
parent.append(...children);
|
||||
if (children.length === 1 && typeof children[0] !== 'string') {
|
||||
return <T>children[0];
|
||||
}
|
||||
}
|
||||
|
||||
export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
|
||||
@@ -1082,26 +1082,12 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all children from `parent` and appends `children`
|
||||
*/
|
||||
export function reset(parent: HTMLElement, ...children: Array<Node | string>): void {
|
||||
parent.innerText = '';
|
||||
appendChildren(parent, ...children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends `children` to `parent`
|
||||
*/
|
||||
export function appendChildren(parent: HTMLElement, ...children: Array<Node | string>): void {
|
||||
for (const child of children) {
|
||||
if (child instanceof Node) {
|
||||
parent.appendChild(child);
|
||||
} else if (typeof child === 'string') {
|
||||
parent.appendChild(document.createTextNode(child));
|
||||
}
|
||||
}
|
||||
append(parent, ...children);
|
||||
}
|
||||
|
||||
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;
|
||||
@@ -1155,13 +1141,7 @@ function _$<T extends Element>(namespace: Namespace, description: string, attrs?
|
||||
}
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
if (child instanceof Node) {
|
||||
result.appendChild(child);
|
||||
} else if (typeof child === 'string') {
|
||||
result.appendChild(document.createTextNode(child));
|
||||
}
|
||||
}
|
||||
result.append(...children);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
@@ -1281,9 +1261,10 @@ export function computeScreenAwareSize(cssPx: number): number {
|
||||
* See https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
export function windowOpenNoOpener(url: string): void {
|
||||
if (platform.isNative || browser.isEdgeWebView) {
|
||||
if (browser.isElectron || browser.isEdgeLegacyWebView) {
|
||||
// In VSCode, window.open() always returns null...
|
||||
// The same is true for a WebView (see https://github.com/microsoft/monaco-editor/issues/628)
|
||||
// Also call directly window.open in sandboxed Electron (see https://github.com/microsoft/monaco-editor/issues/2220)
|
||||
window.open(url);
|
||||
} else {
|
||||
let newTab = window.open();
|
||||
@@ -1316,10 +1297,14 @@ export function asCSSUrl(uri: URI): string {
|
||||
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
|
||||
}
|
||||
|
||||
export function asCSSPropertyValue(value: string) {
|
||||
return `'${value.replace(/'/g, '%27')}'`;
|
||||
}
|
||||
|
||||
export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void {
|
||||
|
||||
// If the data is provided as Buffer, we create a
|
||||
// blog URL out of it to produce a valid link
|
||||
// blob URL out of it to produce a valid link
|
||||
let url: string;
|
||||
if (URI.isUri(dataOrUri)) {
|
||||
url = dataOrUri.toString(true);
|
||||
@@ -1368,7 +1353,7 @@ export interface IDetectedFullscreen {
|
||||
mode: DetectedFullscreenMode;
|
||||
|
||||
/**
|
||||
* Wether we know for sure that we are in fullscreen mode or
|
||||
* Whether we know for sure that we are in fullscreen mode or
|
||||
* it is a guess.
|
||||
*/
|
||||
guess: boolean;
|
||||
@@ -1456,7 +1441,7 @@ export function safeInnerHtml(node: HTMLElement, value: string): void {
|
||||
}, ['class', 'id', 'role', 'tabindex']);
|
||||
|
||||
const html = _ttpSafeInnerHtml?.createHTML(value, options) ?? insane(value, options);
|
||||
node.innerHTML = html as unknown as string;
|
||||
node.innerHTML = html as string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1469,7 +1454,12 @@ function toBinary(str: string): string {
|
||||
for (let i = 0; i < codeUnits.length; i++) {
|
||||
codeUnits[i] = str.charCodeAt(i);
|
||||
}
|
||||
return String.fromCharCode(...new Uint8Array(codeUnits.buffer));
|
||||
let binary = '';
|
||||
const uint8array = new Uint8Array(codeUnits.buffer);
|
||||
for (let i = 0; i < uint8array.length; i++) {
|
||||
binary += String.fromCharCode(uint8array[i]);
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1516,7 +1506,7 @@ export namespace WebFileSystemAccess {
|
||||
}
|
||||
|
||||
export function supported(obj: any & Window): obj is FileSystemAccess {
|
||||
const candidate = obj as FileSystemAccess;
|
||||
const candidate = obj as FileSystemAccess | undefined;
|
||||
if (typeof candidate?.showDirectoryPicker === 'function') {
|
||||
return true;
|
||||
}
|
||||
@@ -1554,6 +1544,11 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
};
|
||||
|
||||
this._subscriptions.add(domEvent(document.body, 'keydown', true)(e => {
|
||||
// if keydown event is repeated, ignore it #112347
|
||||
if (e.repeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
if (e.altKey && !this._keyStatus.altKey) {
|
||||
@@ -1666,3 +1661,9 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
this._subscriptions.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function getCookieValue(name: string): string | undefined {
|
||||
const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531
|
||||
|
||||
return match ? match.pop() : undefined;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,12 @@ export interface CancellableEvent {
|
||||
stopPropagation(): void;
|
||||
}
|
||||
|
||||
export function stopEvent<T extends CancellableEvent>(event: T): T {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return event;
|
||||
}
|
||||
|
||||
export function stop<T extends CancellableEvent>(event: BaseEvent<T>): BaseEvent<T> {
|
||||
return BaseEvent.map(event, e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return e;
|
||||
});
|
||||
}
|
||||
return BaseEvent.map(event, stopEvent);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,14 @@ export async function sha1Hex(str: string): Promise<string> {
|
||||
|
||||
// Prefer to use browser's crypto module
|
||||
if (globalThis?.crypto?.subtle) {
|
||||
const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, VSBuffer.fromString(str).buffer);
|
||||
|
||||
// Careful to use `dontUseNodeBuffer` when passing the
|
||||
// buffer to the browser `crypto` API. Users reported
|
||||
// native crashes in certain cases that we could trace
|
||||
// back to passing node.js `Buffer` around
|
||||
// (https://github.com/microsoft/vscode/issues/114227)
|
||||
const buffer = VSBuffer.fromString(str, { dontUseNodeBuffer: true }).buffer;
|
||||
const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, buffer);
|
||||
|
||||
return toHexString(hash);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
|
||||
import { markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels';
|
||||
import { resolvePath } from 'vs/base/common/resources';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
|
||||
@@ -156,7 +156,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
};
|
||||
renderer.paragraph = (text): string => {
|
||||
if (markdown.supportThemeIcons) {
|
||||
const elements = renderCodicons(text);
|
||||
const elements = renderLabelWithIcons(text);
|
||||
text = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join('');
|
||||
}
|
||||
return `<p>${text}</p>`;
|
||||
@@ -233,7 +233,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// We always pass the output through insane after this so that we don't rely on
|
||||
// marked for sanitization.
|
||||
markedOptions.sanitizer = (html: string): string => {
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^>]+>)|(<\/\s*span>)$/) : undefined;
|
||||
return match ? html : '';
|
||||
};
|
||||
markedOptions.sanitize = true;
|
||||
@@ -248,13 +248,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
}
|
||||
// escape theme icons
|
||||
if (markdown.supportThemeIcons) {
|
||||
value = markdownEscapeEscapedCodicons(value);
|
||||
value = markdownEscapeEscapedIcons(value);
|
||||
}
|
||||
|
||||
const renderedMarkdown = marked.parse(value, markedOptions);
|
||||
|
||||
// sanitize with insane
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown);
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown) as string;
|
||||
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
@@ -276,13 +276,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
function sanitizeRenderedMarkdown(
|
||||
options: { isTrusted?: boolean },
|
||||
renderedMarkdown: string,
|
||||
): string {
|
||||
): string | TrustedHTML {
|
||||
const insaneOptions = getInsaneOptions(options);
|
||||
if (_ttpInsane) {
|
||||
return _ttpInsane.createHTML(renderedMarkdown, insaneOptions) as unknown as string;
|
||||
} else {
|
||||
return insane(renderedMarkdown, insaneOptions);
|
||||
}
|
||||
return _ttpInsane?.createHTML(renderedMarkdown, insaneOptions) ?? insane(renderedMarkdown, insaneOptions);
|
||||
}
|
||||
|
||||
function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOptions {
|
||||
@@ -317,12 +313,12 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti
|
||||
'td': ['align']
|
||||
},
|
||||
filter(token: { tag: string; attrs: { readonly [key: string]: string; }; }): boolean {
|
||||
if (token.tag === 'span' && options.isTrusted && (Object.keys(token.attrs).length === 1)) {
|
||||
if (token.attrs['style']) {
|
||||
if (token.tag === 'span' && options.isTrusted) {
|
||||
if (token.attrs['style'] && (Object.keys(token.attrs).length === 1)) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,3 @@
|
||||
.monaco-action-bar .action-item.action-dropdown-item > .action-label {
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item.action-dropdown-item > .monaco-dropdown {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@@ -20,27 +20,6 @@ export abstract class BreadcrumbsItem {
|
||||
abstract render(container: HTMLElement): void;
|
||||
}
|
||||
|
||||
export class SimpleBreadcrumbsItem extends BreadcrumbsItem {
|
||||
|
||||
constructor(
|
||||
readonly text: string,
|
||||
readonly title: string = text
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
equals(other: this) {
|
||||
return other === this || other instanceof SimpleBreadcrumbsItem && other.text === this.text && other.title === this.title;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
let node = document.createElement('div');
|
||||
node.title = this.title;
|
||||
node.innerText = this.text;
|
||||
container.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IBreadcrumbsWidgetStyles {
|
||||
breadcrumbsBackground?: Color;
|
||||
breadcrumbsForeground?: Color;
|
||||
@@ -333,7 +312,12 @@ export class BreadcrumbsWidget {
|
||||
private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void {
|
||||
dom.clearNode(container);
|
||||
container.className = '';
|
||||
item.render(container);
|
||||
try {
|
||||
item.render(container);
|
||||
} catch (err) {
|
||||
container.innerText = '<<RENDER ERROR>>';
|
||||
console.error(err);
|
||||
}
|
||||
container.tabIndex = -1;
|
||||
container.setAttribute('role', 'listitem');
|
||||
container.classList.add('monaco-breadcrumb-item');
|
||||
|
||||
@@ -11,15 +11,15 @@ import { mixin } from 'vs/base/common/objects';
|
||||
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom';
|
||||
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { CSSIcon, Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
readonly title?: boolean | string;
|
||||
readonly supportCodicons?: boolean;
|
||||
readonly supportIcons?: boolean;
|
||||
readonly secondary?: boolean;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const defaultOptions: IButtonStyles = {
|
||||
|
||||
export interface IButton extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
readonly onDidClick: BaseEvent<Event>;
|
||||
readonly onDidClick: BaseEvent<Event | undefined>;
|
||||
label: string;
|
||||
icon: CSSIcon;
|
||||
enabled: boolean;
|
||||
@@ -262,8 +262,8 @@ export class Button extends Disposable implements IButton {
|
||||
|
||||
set label(value: string) {
|
||||
this._element.classList.add('monaco-text-button');
|
||||
if (this.options.supportCodicons) {
|
||||
reset(this._element, ...renderCodicons(value));
|
||||
if (this.options.supportIcons) {
|
||||
reset(this._element, ...renderLabelWithIcons(value));
|
||||
} else {
|
||||
this._element.textContent = value;
|
||||
}
|
||||
@@ -278,7 +278,7 @@ export class Button extends Disposable implements IButton {
|
||||
// {{SQL CARBON EDIT}}
|
||||
set icon(icon: CSSIcon) {
|
||||
this.hasIcon = icon !== undefined;
|
||||
this._element.classList.add(...icon.classNames.split(' '));
|
||||
this._element.classList.add(...icon.id.split(' '));
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
@@ -317,10 +317,12 @@ export interface IButtonWithDropdownOptions extends IButtonOptions {
|
||||
export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
|
||||
private readonly button: Button;
|
||||
private readonly action: Action;
|
||||
private readonly dropdownButton: Button;
|
||||
|
||||
readonly element: HTMLElement;
|
||||
readonly onDidClick: BaseEvent<Event>;
|
||||
private readonly _onDidClick = this._register(new Emitter<Event | undefined>());
|
||||
readonly onDidClick = this._onDidClick.event;
|
||||
|
||||
constructor(container: HTMLElement, options: IButtonWithDropdownOptions) {
|
||||
super();
|
||||
@@ -330,15 +332,16 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
container.appendChild(this.element);
|
||||
|
||||
this.button = this._register(new Button(this.element, options));
|
||||
this.onDidClick = this.button.onDidClick;
|
||||
this._register(this.button.onDidClick(e => this._onDidClick.fire(e)));
|
||||
this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined)));
|
||||
|
||||
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportCodicons: true }));
|
||||
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
|
||||
this.dropdownButton.element.classList.add('monaco-dropdown-button');
|
||||
this.dropdownButton.icon = Codicon.dropDownButton;
|
||||
this._register(this.dropdownButton.onDidClick(() => {
|
||||
this._register(this.dropdownButton.onDidClick(e => {
|
||||
options.contextMenuProvider.showContextMenu({
|
||||
getAnchor: () => this.dropdownButton.element,
|
||||
getActions: () => options.actions,
|
||||
getActions: () => [this.action, ...options.actions],
|
||||
actionRunner: options.actionRunner,
|
||||
onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false')
|
||||
});
|
||||
@@ -348,6 +351,7 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
|
||||
set label(value: string) {
|
||||
this.button.label = value;
|
||||
this.action.label = value;
|
||||
}
|
||||
|
||||
set icon(icon: CSSIcon) {
|
||||
|
||||
@@ -101,12 +101,10 @@ export class Checkbox extends Widget {
|
||||
|
||||
const classes = ['monaco-custom-checkbox'];
|
||||
if (this._opts.icon) {
|
||||
classes.push(this._opts.icon.classNames);
|
||||
} else {
|
||||
classes.push('codicon'); // todo@aeschli: remove once codicon fully adopted
|
||||
classes.push(...CSSIcon.asClassNameArray(this._opts.icon));
|
||||
}
|
||||
if (this._opts.actionClassName) {
|
||||
classes.push(this._opts.actionClassName);
|
||||
classes.push(...this._opts.actionClassName.split(' '));
|
||||
}
|
||||
if (this._checked) {
|
||||
classes.push('checked');
|
||||
@@ -114,7 +112,7 @@ export class Checkbox extends Widget {
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.title = this._opts.title;
|
||||
this.domNode.className = classes.join(' ');
|
||||
this.domNode.classList.add(...classes);
|
||||
this.domNode.tabIndex = 0;
|
||||
this.domNode.setAttribute('role', 'checkbox');
|
||||
this.domNode.setAttribute('aria-checked', String(this._checked));
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.codicon-wrench-subaction {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.codicon-wrench-subaction {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes codicon-spin {
|
||||
100% {
|
||||
transform:rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin{
|
||||
/* Use steps to throttle FPS to reduce CPU usage */
|
||||
animation: codicon-spin 1.5s steps(30) infinite;
|
||||
}
|
||||
|
||||
.codicon-modifier-disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* custom speed & easing for loading icon */
|
||||
.codicon-loading,
|
||||
.codicon-tree-item-loading::before {
|
||||
animation-duration: 1s !important;
|
||||
animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important;
|
||||
}
|
||||
Binary file not shown.
@@ -4,8 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./codicon/codicon';
|
||||
import 'vs/css!./codicon/codicon-modifications';
|
||||
import 'vs/css!./codicon/codicon-animations';
|
||||
import 'vs/css!./codicon/codicon-modifiers';
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
|
||||
@@ -182,7 +182,9 @@ export class Dialog extends Disposable {
|
||||
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
|
||||
|
||||
this._register(button.onDidClick(e => {
|
||||
EventHelper.stop(e);
|
||||
if (e) {
|
||||
EventHelper.stop(e);
|
||||
}
|
||||
|
||||
resolve({
|
||||
button: buttonMap[index].index,
|
||||
@@ -307,7 +309,9 @@ export class Dialog extends Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
this.iconElement.classList.remove(...dialogErrorIcon.classNamesArray, ...dialogWarningIcon.classNamesArray, ...dialogInfoIcon.classNamesArray, ...Codicon.loading.classNamesArray);
|
||||
const spinModifierClassName = 'codicon-modifier-spin';
|
||||
|
||||
this.iconElement.classList.remove(...dialogErrorIcon.classNamesArray, ...dialogWarningIcon.classNamesArray, ...dialogInfoIcon.classNamesArray, ...Codicon.loading.classNamesArray, spinModifierClassName);
|
||||
|
||||
switch (this.options.type) {
|
||||
case 'error':
|
||||
@@ -317,7 +321,7 @@ export class Dialog extends Disposable {
|
||||
this.iconElement.classList.add(...dialogWarningIcon.classNamesArray);
|
||||
break;
|
||||
case 'pending':
|
||||
this.iconElement.classList.add(...Codicon.loading.classNamesArray, 'codicon-animation-spin');
|
||||
this.iconElement.classList.add(...Codicon.loading.classNamesArray, spinModifierClassName);
|
||||
break;
|
||||
case 'none':
|
||||
case 'info':
|
||||
|
||||
@@ -201,7 +201,7 @@ export class Dropdown extends BaseDropdown {
|
||||
}
|
||||
|
||||
export interface IActionProvider {
|
||||
getActions(): IAction[];
|
||||
getActions(): readonly IAction[];
|
||||
}
|
||||
|
||||
export interface IDropdownMenuOptions extends IBaseDropdownOptions {
|
||||
@@ -215,7 +215,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions {
|
||||
export class DropdownMenu extends BaseDropdown {
|
||||
private _contextMenuProvider: IContextMenuProvider;
|
||||
private _menuOptions: IMenuOptions | undefined;
|
||||
private _actions: IAction[] = [];
|
||||
private _actions: readonly IAction[] = [];
|
||||
private actionProvider?: IActionProvider;
|
||||
private menuClassName: string;
|
||||
private menuAsChild?: boolean;
|
||||
@@ -238,7 +238,7 @@ export class DropdownMenu extends BaseDropdown {
|
||||
return this._menuOptions;
|
||||
}
|
||||
|
||||
private get actions(): IAction[] {
|
||||
private get actions(): readonly IAction[] {
|
||||
if (this.actionProvider) {
|
||||
return this.actionProvider.getActions();
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export class DropdownMenu extends BaseDropdown {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
private set actions(actions: IAction[]) {
|
||||
private set actions(actions: readonly IAction[]) {
|
||||
this._actions = actions;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,13 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
|
||||
super.render(container);
|
||||
if (this.element) {
|
||||
this.element.classList.add('action-dropdown-item');
|
||||
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
|
||||
const menuActionsProvider = {
|
||||
getActions: () => {
|
||||
const actionsProvider = (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider;
|
||||
return [this._action, ...(Array.isArray(actionsProvider) ? actionsProvider : actionsProvider.getActions())];
|
||||
}
|
||||
};
|
||||
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
|
||||
this.dropdownMenuActionViewItem.render(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +258,10 @@ export class FindInput extends Widget {
|
||||
this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e));
|
||||
}
|
||||
|
||||
public get onDidChange(): Event<string> {
|
||||
return this.inputBox.onDidChange;
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
dom.removeClass(this.domNode, 'disabled');
|
||||
this.inputBox.enable();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
|
||||
export interface IHighlight {
|
||||
start: number;
|
||||
@@ -21,7 +21,7 @@ export class HighlightedLabel {
|
||||
private highlights: IHighlight[] = [];
|
||||
private didEverRender: boolean = false;
|
||||
|
||||
constructor(container: HTMLElement, private supportCodicons: boolean) {
|
||||
constructor(container: HTMLElement, private supportIcons: boolean) {
|
||||
this.domNode = document.createElement('span');
|
||||
this.domNode.className = 'monaco-highlighted-label';
|
||||
|
||||
@@ -61,12 +61,12 @@ export class HighlightedLabel {
|
||||
}
|
||||
if (pos < highlight.start) {
|
||||
const substring = this.text.substring(pos, highlight.start);
|
||||
children.push(dom.$('span', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring]));
|
||||
children.push(dom.$('span', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]));
|
||||
pos = highlight.end;
|
||||
}
|
||||
|
||||
const substring = this.text.substring(highlight.start, highlight.end);
|
||||
const element = dom.$('span.highlight', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring]);
|
||||
const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]);
|
||||
if (highlight.extraClasses) {
|
||||
element.classList.add(highlight.extraClasses);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class HighlightedLabel {
|
||||
|
||||
if (pos < this.text.length) {
|
||||
const substring = this.text.substring(pos,);
|
||||
children.push(dom.$('span', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring]));
|
||||
children.push(dom.$('span', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]));
|
||||
}
|
||||
|
||||
dom.reset(this.domNode, ...children);
|
||||
|
||||
@@ -45,10 +45,13 @@
|
||||
}
|
||||
|
||||
.monaco-hover hr {
|
||||
box-sizing: border-box;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: -4px;
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ export class HoverWidget extends Disposable {
|
||||
this.contentsDomNode = document.createElement('div');
|
||||
this.contentsDomNode.className = 'monaco-hover-content';
|
||||
|
||||
this._scrollbar = this._register(new DomScrollableElement(this.contentsDomNode, {}));
|
||||
this._scrollbar = this._register(new DomScrollableElement(this.contentsDomNode, {
|
||||
consumeMouseWheelIfScrollbarIsNeeded: true
|
||||
}));
|
||||
this.containerDomNode.appendChild(this._scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,20 @@ import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { isFunction, isString } from 'vs/base/common/types';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
supportDescriptionHighlights?: boolean;
|
||||
supportCodicons?: boolean;
|
||||
supportIcons?: boolean;
|
||||
hoverDelegate?: IHoverDelegate;
|
||||
}
|
||||
|
||||
export interface IIconLabelMarkdownString {
|
||||
markdown: IMarkdownString | string | undefined | Promise<IMarkdownString | string | undefined>;
|
||||
markdown: IMarkdownString | string | undefined | ((token: CancellationToken) => Promise<IMarkdownString | string | undefined>);
|
||||
markdownNotSupportedFallback: string | undefined;
|
||||
}
|
||||
|
||||
@@ -114,13 +116,13 @@ export class IconLabel extends Disposable {
|
||||
this.descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container'))));
|
||||
|
||||
if (options?.supportHighlights) {
|
||||
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
|
||||
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportIcons);
|
||||
} else {
|
||||
this.nameNode = new Label(nameContainer);
|
||||
}
|
||||
|
||||
if (options?.supportDescriptionHighlights) {
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportIcons);
|
||||
} else {
|
||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
|
||||
}
|
||||
@@ -190,24 +192,60 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private static adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean): IDisposable | undefined {
|
||||
if (hoverOptions && isHovering) {
|
||||
if (mouseX !== undefined) {
|
||||
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
|
||||
}
|
||||
return hoverDelegate.showHover(hoverOptions);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise<string | IMarkdownString | undefined> {
|
||||
if (isString(markdownTooltip)) {
|
||||
return async () => markdownTooltip;
|
||||
} else if (isFunction(markdownTooltip.markdown)) {
|
||||
return markdownTooltip.markdown;
|
||||
} else {
|
||||
const markdown = markdownTooltip.markdown;
|
||||
return async () => markdown;
|
||||
}
|
||||
}
|
||||
|
||||
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString): void {
|
||||
htmlElement.setAttribute('title', '');
|
||||
htmlElement.removeAttribute('title');
|
||||
let tooltip = isString(markdownTooltip) ? markdownTooltip : markdownTooltip.markdown;
|
||||
let tooltip = this.getTooltipForCustom(markdownTooltip);
|
||||
|
||||
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
|
||||
// On Mac, the delay is 1500.
|
||||
const hoverDelay = isMacintosh ? 1500 : 500;
|
||||
let hoverOptions: IHoverDelegateOptions | undefined;
|
||||
let mouseX: number | undefined;
|
||||
let isHovering = false;
|
||||
let tokenSource: CancellationTokenSource;
|
||||
function mouseOver(this: HTMLElement, e: MouseEvent): any {
|
||||
let isHovering = true;
|
||||
function mouseMove(this: HTMLElement, e: MouseEvent): any {
|
||||
mouseX = e.x;
|
||||
if (isHovering) {
|
||||
return;
|
||||
}
|
||||
tokenSource = new CancellationTokenSource();
|
||||
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
|
||||
isHovering = false;
|
||||
if ((<any>e).fromElement === htmlElement) {
|
||||
isHovering = false;
|
||||
hoverOptions = undefined;
|
||||
tokenSource.dispose(true);
|
||||
mouseLeaveDisposable.dispose();
|
||||
mouseDownDisposable.dispose();
|
||||
}
|
||||
}
|
||||
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeaveOrDown.bind(htmlElement));
|
||||
const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement));
|
||||
isHovering = true;
|
||||
|
||||
function mouseMove(this: HTMLElement, e: MouseEvent): any {
|
||||
mouseX = e.x;
|
||||
}
|
||||
const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement));
|
||||
setTimeout(async () => {
|
||||
if (isHovering && tooltip) {
|
||||
@@ -217,25 +255,29 @@ export class IconLabel extends Disposable {
|
||||
targetElements: [this],
|
||||
dispose: () => { }
|
||||
};
|
||||
const resolvedTooltip = await tooltip;
|
||||
hoverOptions = {
|
||||
text: localize('iconLabel.loading', "Loading..."),
|
||||
target,
|
||||
anchorPosition: AnchorPosition.BELOW
|
||||
};
|
||||
const hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
|
||||
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined);
|
||||
if (resolvedTooltip) {
|
||||
hoverOptions = {
|
||||
text: resolvedTooltip,
|
||||
target,
|
||||
anchorPosition: AnchorPosition.BELOW
|
||||
};
|
||||
// awaiting the tooltip could take a while. Make sure we're still hovering.
|
||||
IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
} else if (hoverDisposable) {
|
||||
hoverDisposable.dispose();
|
||||
}
|
||||
}
|
||||
if (hoverOptions) {
|
||||
if (mouseX !== undefined) {
|
||||
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
|
||||
}
|
||||
hoverDelegate.showHover(hoverOptions);
|
||||
}
|
||||
|
||||
}
|
||||
mouseMoveDisposable.dispose();
|
||||
mouseLeaveDisposable.dispose();
|
||||
mouseDownDisposable.dispose();
|
||||
}, hoverDelay);
|
||||
}
|
||||
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
|
||||
@@ -322,7 +364,7 @@ class LabelWithHighlights {
|
||||
private singleLabel: HighlightedLabel | undefined = undefined;
|
||||
private options: IIconLabelValueOptions | undefined;
|
||||
|
||||
constructor(private container: HTMLElement, private supportCodicons: boolean) { }
|
||||
constructor(private container: HTMLElement, private supportIcons: boolean) { }
|
||||
|
||||
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
|
||||
if (this.label === label && equals(this.options, options)) {
|
||||
@@ -336,7 +378,7 @@ class LabelWithHighlights {
|
||||
if (!this.singleLabel) {
|
||||
this.container.innerText = '';
|
||||
this.container.classList.remove('multiple');
|
||||
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
|
||||
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportIcons);
|
||||
}
|
||||
|
||||
this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines);
|
||||
@@ -354,7 +396,7 @@ class LabelWithHighlights {
|
||||
const id = options?.domId && `${options?.domId}_${i}`;
|
||||
|
||||
const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' });
|
||||
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
|
||||
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportIcons);
|
||||
highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines);
|
||||
|
||||
if (i < label.length - 1) {
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CSSIcon } from 'vs/base/common/codicons';
|
||||
|
||||
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
|
||||
|
||||
export function renderCodicons(text: string): Array<HTMLSpanElement | string> {
|
||||
const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g');
|
||||
export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | string> {
|
||||
const elements = new Array<HTMLSpanElement | string>();
|
||||
let match: RegExpMatchArray | null;
|
||||
|
||||
let textStart = 0, textStop = 0;
|
||||
while ((match = renderCodiconsRegex.exec(text)) !== null) {
|
||||
while ((match = labelWithIconsRegex.exec(text)) !== null) {
|
||||
textStop = match.index || 0;
|
||||
elements.push(text.substring(textStart, textStop));
|
||||
textStart = (match.index || 0) + match[0].length;
|
||||
|
||||
const [, escaped, codicon, name, animation] = match;
|
||||
elements.push(escaped ? `$(${codicon})` : renderCodicon(name, animation));
|
||||
const [, escaped, codicon] = match;
|
||||
elements.push(escaped ? `$(${codicon})` : renderIcon({ id: codicon }));
|
||||
}
|
||||
|
||||
if (textStart < text.length) {
|
||||
@@ -27,6 +27,8 @@ export function renderCodicons(text: string): Array<HTMLSpanElement | string> {
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function renderCodicon(name: string, animation: string): HTMLSpanElement {
|
||||
return dom.$(`span.codicon.codicon-${name}${animation ? `.codicon-animation-${animation}` : ''}`);
|
||||
export function renderIcon(icon: CSSIcon): HTMLSpanElement {
|
||||
const node = dom.$(`span`);
|
||||
node.classList.add(...CSSIcon.asClassNameArray(icon));
|
||||
return node;
|
||||
}
|
||||
@@ -55,6 +55,10 @@
|
||||
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
||||
}
|
||||
|
||||
.monaco-icon-label.nowrap > .monaco-icon-label-container > .monaco-icon-description-container > .label-description{
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.vs .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||
opacity: .95;
|
||||
}
|
||||
@@ -64,6 +68,16 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-icon-label.deprecated {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
/* make sure apply italic font style to decorations as well */
|
||||
.monaco-icon-label.italic::after {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
|
||||
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||
text-decoration: line-through;
|
||||
@@ -77,10 +91,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monaco-icon-label.italic::after {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* make sure selection color wins when a label is being selected */
|
||||
.monaco-list:focus .selected .monaco-icon-label, /* list */
|
||||
.monaco-list:focus .selected .monaco-icon-label::after
|
||||
|
||||
+3
-3
@@ -4,16 +4,16 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { reset } from 'vs/base/browser/dom';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
|
||||
export class CodiconLabel {
|
||||
export class SimpleIconLabel {
|
||||
|
||||
constructor(
|
||||
private readonly _container: HTMLElement
|
||||
) { }
|
||||
|
||||
set text(text: string) {
|
||||
reset(this._container, ...renderCodicons(text ?? ''));
|
||||
reset(this._container, ...renderLabelWithIcons(text ?? ''));
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
@@ -17,20 +17,20 @@
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > .input,
|
||||
.monaco-inputbox > .wrapper > .mirror {
|
||||
.monaco-inputbox > .ibwrapper > .input,
|
||||
.monaco-inputbox > .ibwrapper > .mirror {
|
||||
|
||||
/* Customizable */
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper {
|
||||
.monaco-inputbox > .ibwrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > .input {
|
||||
.monaco-inputbox > .ibwrapper > .input {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
@@ -43,26 +43,26 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > input {
|
||||
.monaco-inputbox > .ibwrapper > input {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input {
|
||||
.monaco-inputbox > .ibwrapper > textarea.input {
|
||||
display: block;
|
||||
-ms-overflow-style: none; /* IE 10+: hide scrollbars */
|
||||
scrollbar-width: none; /* Firefox: hide scrollbars */
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
|
||||
.monaco-inputbox > .ibwrapper > textarea.input::-webkit-scrollbar {
|
||||
display: none; /* Chrome + Safari: hide scrollbar */
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input.empty {
|
||||
.monaco-inputbox > .ibwrapper > textarea.input.empty {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > .mirror {
|
||||
.monaco-inputbox > .ibwrapper > .mirror {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
@@ -89,7 +89,6 @@
|
||||
padding: 0.4em;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
min-height: 34px;
|
||||
margin-top: -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ export class InputBox extends Widget {
|
||||
|
||||
let tagName = this.options.flexibleHeight ? 'textarea' : 'input';
|
||||
|
||||
let wrapper = dom.append(this.element, $('.wrapper'));
|
||||
let wrapper = dom.append(this.element, $('.ibwrapper'));
|
||||
this.input = dom.append(wrapper, $(tagName + '.input.empty'));
|
||||
this.input.setAttribute('autocorrect', 'off');
|
||||
this.input.setAttribute('autocapitalize', 'off');
|
||||
@@ -316,6 +316,9 @@ export class InputBox extends Widget {
|
||||
|
||||
if (range) {
|
||||
this.input.setSelectionRange(range.start, range.end);
|
||||
if (range.end === this.input.value.length) {
|
||||
this.input.scrollLeft = this.input.scrollWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +425,7 @@ export class InputBox extends Widget {
|
||||
return !!this.validation && !this.validation(this.value);
|
||||
}
|
||||
|
||||
public validate(): boolean {
|
||||
public validate(): boolean { // {{SQL CARBON EDIT}}
|
||||
let errorMsg: IMessage | null = null;
|
||||
|
||||
if (this.validation) {
|
||||
@@ -446,7 +449,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
return errorMsg ? errorMsg.type !== MessageType.ERROR : true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
.monaco-list-row {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -49,7 +49,9 @@
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
.monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple {
|
||||
.monaco-list.element-focused,
|
||||
.monaco-list.selection-single,
|
||||
.monaco-list.selection-multiple {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
@@ -64,6 +66,7 @@
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Type filter */
|
||||
|
||||
@@ -258,6 +258,10 @@ export class PagedList<T> implements IThemable, IDisposable {
|
||||
return this.list.getSelection();
|
||||
}
|
||||
|
||||
getSelectedElements(): T[] {
|
||||
return this.getSelection().map(i => this.model.get(i));
|
||||
}
|
||||
|
||||
layout(height?: number, width?: number): void {
|
||||
this.list.layout(height, width);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
|
||||
readonly transformOptimization?: boolean;
|
||||
readonly alwaysConsumeMouseWheel?: boolean;
|
||||
}
|
||||
|
||||
const DefaultOptions = {
|
||||
@@ -80,7 +81,8 @@ const DefaultOptions = {
|
||||
drop() { }
|
||||
},
|
||||
horizontalScrolling: false,
|
||||
transformOptimization: true
|
||||
transformOptimization: true,
|
||||
alwaysConsumeMouseWheel: true,
|
||||
};
|
||||
|
||||
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
|
||||
@@ -327,6 +329,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb));
|
||||
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel),
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
|
||||
@@ -432,8 +435,24 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const deleteRange = { start, end: start + deleteCount };
|
||||
const removeRange = Range.intersect(previousRenderRange, deleteRange);
|
||||
|
||||
// try to reuse rows, avoid removing them from DOM
|
||||
const rowsToDispose = new Map<string, [IRow, T, number, number][]>();
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(i);
|
||||
const item = this.items[i];
|
||||
item.dragStartDisposable.dispose();
|
||||
|
||||
if (item.row) {
|
||||
let rows = rowsToDispose.get(item.templateId);
|
||||
|
||||
if (!rows) {
|
||||
rows = [];
|
||||
rowsToDispose.set(item.templateId, rows);
|
||||
}
|
||||
|
||||
rows.push([item.row, item.element, i, item.size]);
|
||||
}
|
||||
|
||||
item.row = null;
|
||||
}
|
||||
|
||||
const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
|
||||
@@ -491,7 +510,34 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
for (const range of insertRanges) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
const item = this.items[i];
|
||||
const rows = rowsToDispose.get(item.templateId);
|
||||
const rowData = rows?.pop();
|
||||
|
||||
if (!rowData) {
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
} else {
|
||||
const [row, element, index, size] = rowData;
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
|
||||
if (renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(element, index, row.templateData, size);
|
||||
}
|
||||
|
||||
this.insertItemInDOM(i, beforeElement, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [templateId, rows] of rowsToDispose) {
|
||||
for (const [row, element, index, size] of rows) {
|
||||
const renderer = this.renderers.get(templateId);
|
||||
|
||||
if (renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(element, index, row.templateData, size);
|
||||
}
|
||||
|
||||
this.cache.release(row);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,24 +745,26 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
// DOM operations
|
||||
|
||||
private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void {
|
||||
private insertItemInDOM(index: number, beforeElement: HTMLElement | null, row?: IRow): void {
|
||||
const item = this.items[index];
|
||||
|
||||
if (!item.row) {
|
||||
item.row = this.cache.alloc(item.templateId);
|
||||
const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
|
||||
item.row!.domNode!.setAttribute('role', role);
|
||||
const checked = this.accessibilityProvider.isChecked(item.element);
|
||||
if (typeof checked !== 'undefined') {
|
||||
item.row!.domNode!.setAttribute('aria-checked', String(!!checked));
|
||||
}
|
||||
item.row = row ?? this.cache.alloc(item.templateId);
|
||||
}
|
||||
|
||||
if (!item.row.domNode!.parentElement) {
|
||||
const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
|
||||
item.row.domNode.setAttribute('role', role);
|
||||
|
||||
const checked = this.accessibilityProvider.isChecked(item.element);
|
||||
if (typeof checked !== 'undefined') {
|
||||
item.row.domNode.setAttribute('aria-checked', String(!!checked));
|
||||
}
|
||||
|
||||
if (!item.row.domNode.parentElement) {
|
||||
if (beforeElement) {
|
||||
this.rowsContainer.insertBefore(item.row.domNode!, beforeElement);
|
||||
this.rowsContainer.insertBefore(item.row.domNode, beforeElement);
|
||||
} else {
|
||||
this.rowsContainer.appendChild(item.row.domNode!);
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,10 +782,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const uri = this.dnd.getDragURI(item.element);
|
||||
item.dragStartDisposable.dispose();
|
||||
item.row.domNode!.draggable = !!uri;
|
||||
item.row.domNode.draggable = !!uri;
|
||||
|
||||
if (uri) {
|
||||
const onDragStart = domEvent(item.row.domNode!, 'dragstart');
|
||||
const onDragStart = domEvent(item.row.domNode, 'dragstart');
|
||||
item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event));
|
||||
}
|
||||
|
||||
@@ -768,36 +816,39 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
private updateItemInDOM(item: IItem<T>, index: number): void {
|
||||
item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
|
||||
item.row!.domNode.style.top = `${this.elementTop(index)}px`;
|
||||
|
||||
if (this.setRowHeight) {
|
||||
item.row!.domNode!.style.height = `${item.size}px`;
|
||||
item.row!.domNode.style.height = `${item.size}px`;
|
||||
}
|
||||
|
||||
if (this.setRowLineHeight) {
|
||||
item.row!.domNode!.style.lineHeight = `${item.size}px`;
|
||||
item.row!.domNode.style.lineHeight = `${item.size}px`;
|
||||
}
|
||||
|
||||
item.row!.domNode!.setAttribute('data-index', `${index}`);
|
||||
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
|
||||
item.row!.domNode!.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(item.element, index, this.length)));
|
||||
item.row!.domNode!.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index)));
|
||||
item.row!.domNode!.setAttribute('id', this.getElementDomId(index));
|
||||
item.row!.domNode.setAttribute('data-index', `${index}`);
|
||||
item.row!.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
|
||||
item.row!.domNode.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(item.element, index, this.length)));
|
||||
item.row!.domNode.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index)));
|
||||
item.row!.domNode.setAttribute('id', this.getElementDomId(index));
|
||||
|
||||
item.row!.domNode!.classList.toggle('drop-target', item.dropTarget);
|
||||
item.row!.domNode.classList.toggle('drop-target', item.dropTarget);
|
||||
}
|
||||
|
||||
private removeItemFromDOM(index: number): void {
|
||||
const item = this.items[index];
|
||||
item.dragStartDisposable.dispose();
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
if (item.row && renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, index, item.row.templateData, item.size);
|
||||
}
|
||||
if (item.row) {
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
|
||||
this.cache.release(item.row!);
|
||||
item.row = null;
|
||||
if (renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, index, item.row.templateData, item.size);
|
||||
}
|
||||
|
||||
this.cache.release(item.row);
|
||||
item.row = null;
|
||||
}
|
||||
|
||||
if (this.horizontalScrolling) {
|
||||
this.eventuallyUpdateScrollWidth();
|
||||
@@ -809,14 +860,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return scrollPosition.scrollTop;
|
||||
}
|
||||
|
||||
setScrollTop(scrollTop: number): void {
|
||||
setScrollTop(scrollTop: number, reuseAnimation?: boolean): void {
|
||||
if (this.scrollableElementUpdateDisposable) {
|
||||
this.scrollableElementUpdateDisposable.dispose();
|
||||
this.scrollableElementUpdateDisposable = null;
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
|
||||
}
|
||||
|
||||
this.scrollableElement.setScrollPosition({ scrollTop });
|
||||
this.scrollableElement.setScrollPosition({ scrollTop, reuseAnimation });
|
||||
}
|
||||
|
||||
getScrollLeft(): number {
|
||||
@@ -895,9 +946,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
|
||||
|
||||
if (this.supportDynamicHeights) {
|
||||
// Don't update scrollTop from within an scroll event
|
||||
// so we don't break smooth scrolling. #104144
|
||||
this._rerender(e.scrollTop, e.height, false);
|
||||
this._rerender(e.scrollTop, e.height, e.inSmoothScrolling);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Got bad scroll event:', e);
|
||||
@@ -1027,7 +1076,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const item = this.items[index]!;
|
||||
item.dropTarget = true;
|
||||
|
||||
if (item.row && item.row.domNode) {
|
||||
if (item.row) {
|
||||
item.row.domNode.classList.add('drop-target');
|
||||
}
|
||||
}
|
||||
@@ -1037,7 +1086,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const item = this.items[index]!;
|
||||
item.dropTarget = false;
|
||||
|
||||
if (item.row && item.row.domNode) {
|
||||
if (item.row) {
|
||||
item.row.domNode.classList.remove('drop-target');
|
||||
}
|
||||
}
|
||||
@@ -1167,7 +1216,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
* Given a stable rendered state, checks every rendered element whether it needs
|
||||
* to be probed for dynamic height. Adjusts scroll height and top if necessary.
|
||||
*/
|
||||
private _rerender(renderTop: number, renderHeight: number, updateScrollTop: boolean = true): void {
|
||||
private _rerender(renderTop: number, renderHeight: number, inSmoothScrolling?: boolean): void {
|
||||
const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
|
||||
|
||||
// Let's remember the second element's position, this helps in scrolling up
|
||||
@@ -1233,8 +1282,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (updateScrollTop && typeof anchorElementIndex === 'number') {
|
||||
this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!;
|
||||
if (typeof anchorElementIndex === 'number') {
|
||||
// To compute a destination scroll top, we need to take into account the current smooth scrolling
|
||||
// animation, and then reuse it with a new target (to avoid prolonging the scroll)
|
||||
// See https://github.com/microsoft/vscode/issues/104144
|
||||
// See https://github.com/microsoft/vscode/pull/104284
|
||||
// See https://github.com/microsoft/vscode/issues/107704
|
||||
const deltaScrollTop = this.scrollable.getFutureScrollPosition().scrollTop - renderTop;
|
||||
const newScrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta! + deltaScrollTop;
|
||||
this.setScrollTop(newScrollTop, inSmoothScrolling);
|
||||
}
|
||||
|
||||
this._onDidChangeContentHeight.fire(this.contentHeight);
|
||||
@@ -1256,7 +1312,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const size = item.size;
|
||||
|
||||
if (!this.setRowHeight && item.row && item.row.domNode) {
|
||||
if (!this.setRowHeight && item.row) {
|
||||
let newSize = item.row.domNode.offsetHeight;
|
||||
item.size = newSize;
|
||||
item.lastDynamicHeightWidth = this.renderWidth;
|
||||
@@ -1265,8 +1321,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const row = this.cache.alloc(item.templateId);
|
||||
|
||||
row.domNode!.style.height = '';
|
||||
this.rowsContainer.appendChild(row.domNode!);
|
||||
row.domNode.style.height = '';
|
||||
this.rowsContainer.appendChild(row.domNode);
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
if (renderer) {
|
||||
@@ -1277,14 +1333,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
item.size = row.domNode!.offsetHeight;
|
||||
item.size = row.domNode.offsetHeight;
|
||||
|
||||
if (this.virtualDelegate.setDynamicHeight) {
|
||||
this.virtualDelegate.setDynamicHeight(item.element, item.size);
|
||||
}
|
||||
|
||||
item.lastDynamicHeightWidth = this.renderWidth;
|
||||
this.rowsContainer.removeChild(row.domNode!);
|
||||
this.rowsContainer.removeChild(row.domNode);
|
||||
this.cache.release(row);
|
||||
|
||||
return item.size - size;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Gesture } from 'vs/base/browser/touch';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { domEvent, stopEvent } from 'vs/base/browser/event';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list';
|
||||
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -854,6 +854,7 @@ export interface IListOptions<T> {
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly alwaysConsumeMouseWheel?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -1148,35 +1149,26 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
get onTouchStart(): Event<IListTouchEvent<T>> { return this.view.onTouchStart; }
|
||||
get onTap(): Event<IListGestureEvent<T>> { return this.view.onTap; }
|
||||
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown'))
|
||||
const fromKeyboard = Event.chain(domEvent(this.view.domNode, 'keyup'))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
|
||||
.filter(e => { e.preventDefault(); e.stopPropagation(); return false; })
|
||||
.event as Event<any>;
|
||||
|
||||
const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup'))
|
||||
.filter(() => {
|
||||
const didJustPressContextMenuKey = this.didJustPressContextMenuKey;
|
||||
this.didJustPressContextMenuKey = false;
|
||||
return didJustPressContextMenuKey;
|
||||
})
|
||||
.filter(() => this.getFocus().length > 0 && !!this.view.domElement(this.getFocus()[0]))
|
||||
.map(browserEvent => {
|
||||
const index = this.getFocus()[0];
|
||||
const element = this.view.element(index);
|
||||
const anchor = this.view.domElement(index) as HTMLElement;
|
||||
.filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
|
||||
.map(stopEvent)
|
||||
.map(({ browserEvent }) => {
|
||||
const focus = this.getFocus();
|
||||
const index = focus.length ? focus[0] : undefined;
|
||||
const element = typeof index !== 'undefined' ? this.view.element(index) : undefined;
|
||||
const anchor = typeof index !== 'undefined' ? this.view.domElement(index) as HTMLElement : this.view.domNode;
|
||||
return { index, element, anchor, browserEvent };
|
||||
})
|
||||
.event;
|
||||
|
||||
const fromMouse = Event.chain(this.view.onContextMenu)
|
||||
.filter(() => !this.didJustPressContextMenuKey)
|
||||
.filter(e => !(e.browserEvent.button === 0 && e.browserEvent.buttons === 0 && !e.browserEvent.ctrlKey && !e.browserEvent.altKey && !e.browserEvent.metaKey))
|
||||
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent }))
|
||||
.event;
|
||||
|
||||
return Event.any<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
|
||||
return Event.any<IListContextMenuEvent<T>>(fromKeyboard, fromMouse);
|
||||
}
|
||||
|
||||
get onKeyDown(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keydown'); }
|
||||
@@ -1380,7 +1372,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
}
|
||||
|
||||
domFocus(): void {
|
||||
this.view.domNode.focus();
|
||||
this.view.domNode.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
layout(height?: number, width?: number): void {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
|
||||
export interface IRow {
|
||||
domNode: HTMLElement | null;
|
||||
domNode: HTMLElement;
|
||||
templateId: string;
|
||||
templateData: any;
|
||||
}
|
||||
@@ -84,7 +84,6 @@ export class RowCache<T> implements IDisposable {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.getRenderer(templateId);
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,11 +18,12 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { Codicon, registerCodicon, stripCodicons } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { stripIcons } from 'vs/base/common/iconLabels';
|
||||
|
||||
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
|
||||
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
|
||||
@@ -532,7 +533,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
|
||||
if (this.options.label) {
|
||||
clearNode(this.label);
|
||||
|
||||
let label = stripCodicons(this.getAction().label);
|
||||
let label = stripIcons(this.getAction().label);
|
||||
if (label) {
|
||||
const cleanLabel = cleanMnemonic(label);
|
||||
if (!this.options.enableMnemonics) {
|
||||
|
||||
@@ -126,9 +126,11 @@ export class MenuBar extends Disposable {
|
||||
let eventHandled = true;
|
||||
const key = !!e.key ? e.key.toLocaleLowerCase() : '';
|
||||
|
||||
if (event.equals(KeyCode.LeftArrow) || (isMacintosh && event.equals(KeyCode.Tab | KeyMod.Shift))) {
|
||||
const tabNav = isMacintosh && this.options.compactMode === undefined;
|
||||
|
||||
if (event.equals(KeyCode.LeftArrow) || (tabNav && event.equals(KeyCode.Tab | KeyMod.Shift))) {
|
||||
this.focusPrevious();
|
||||
} else if (event.equals(KeyCode.RightArrow) || (isMacintosh && event.equals(KeyCode.Tab))) {
|
||||
} else if (event.equals(KeyCode.RightArrow) || (tabNav && event.equals(KeyCode.Tab))) {
|
||||
this.focusNext();
|
||||
} else if (event.equals(KeyCode.Escape) && this.isFocused && !this.isOpen) {
|
||||
this.setUnfocusedState();
|
||||
|
||||
@@ -83,7 +83,7 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
* Creates the dom node for an arrow & adds it to the container
|
||||
*/
|
||||
protected _createArrow(opts: ScrollbarArrowOptions): void {
|
||||
let arrow = this._register(new ScrollbarArrow(opts));
|
||||
const arrow = this._register(new ScrollbarArrow(opts));
|
||||
this.domNode.domNode.appendChild(arrow.bgDomNode);
|
||||
this.domNode.domNode.appendChild(arrow.domNode);
|
||||
}
|
||||
@@ -186,10 +186,10 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
}
|
||||
|
||||
public delegateMouseDown(e: IMouseEvent): void {
|
||||
let domTop = this.domNode.domNode.getClientRects()[0].top;
|
||||
let sliderStart = domTop + this._scrollbarState.getSliderPosition();
|
||||
let sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();
|
||||
let mousePos = this._sliderMousePosition(e);
|
||||
const domTop = this.domNode.domNode.getClientRects()[0].top;
|
||||
const sliderStart = domTop + this._scrollbarState.getSliderPosition();
|
||||
const sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();
|
||||
const mousePos = this._sliderMousePosition(e);
|
||||
if (sliderStart <= mousePos && mousePos <= sliderStop) {
|
||||
// Act as if it was a mouse down on the slider
|
||||
if (e.leftButton) {
|
||||
@@ -263,7 +263,7 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
|
||||
private _setDesiredScrollPositionNow(_desiredScrollPosition: number): void {
|
||||
|
||||
let desiredScrollPosition: INewScrollPosition = {};
|
||||
const desiredScrollPosition: INewScrollPosition = {};
|
||||
this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);
|
||||
|
||||
this._scrollable.setScrollPositionNow(desiredScrollPosition);
|
||||
@@ -278,6 +278,10 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public isNeeded(): boolean {
|
||||
return this._scrollbarState.isNeeded();
|
||||
}
|
||||
|
||||
// ----------------- Overwrite these
|
||||
|
||||
protected abstract _renderDomNode(largeSize: number, smallSize: number): void;
|
||||
|
||||
@@ -38,8 +38,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
});
|
||||
|
||||
if (options.horizontalHasArrows) {
|
||||
let arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
let scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
const arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
const scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
|
||||
@@ -187,7 +187,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
this._onScroll.fire(e);
|
||||
}));
|
||||
|
||||
let scrollbarHost: ScrollbarHost = {
|
||||
const scrollbarHost: ScrollbarHost = {
|
||||
onMouseWheel: (mouseWheelEvent: StandardWheelEvent) => this._onMouseWheel(mouseWheelEvent),
|
||||
onDragStart: () => this._onDragStart(),
|
||||
onDragEnd: () => this._onDragEnd(),
|
||||
@@ -325,7 +325,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
// -------------------- mouse wheel scrolling --------------------
|
||||
|
||||
private _setListeningToMouseWheel(shouldListen: boolean): void {
|
||||
let isListening = (this._mouseWheelToDispose.length > 0);
|
||||
const isListening = (this._mouseWheelToDispose.length > 0);
|
||||
|
||||
if (isListening === shouldListen) {
|
||||
// No change
|
||||
@@ -337,7 +337,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
// Start listening (if necessary)
|
||||
if (shouldListen) {
|
||||
let onMouseWheel = (browserEvent: IMouseWheelEvent) => {
|
||||
const onMouseWheel = (browserEvent: IMouseWheelEvent) => {
|
||||
this._onMouseWheel(new StandardWheelEvent(browserEvent));
|
||||
};
|
||||
|
||||
@@ -426,7 +426,15 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._options.alwaysConsumeMouseWheel || didScroll) {
|
||||
let consumeMouseWheel = didScroll;
|
||||
if (!consumeMouseWheel && this._options.alwaysConsumeMouseWheel) {
|
||||
consumeMouseWheel = true;
|
||||
}
|
||||
if (!consumeMouseWheel && this._options.consumeMouseWheelIfScrollbarIsNeeded && (this._verticalScrollbar.isNeeded() || this._horizontalScrollbar.isNeeded())) {
|
||||
consumeMouseWheel = true;
|
||||
}
|
||||
|
||||
if (consumeMouseWheel) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -473,8 +481,8 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
if (this._options.useShadows) {
|
||||
const scrollState = this._scrollable.getCurrentScrollPosition();
|
||||
let enableTop = scrollState.scrollTop > 0;
|
||||
let enableLeft = scrollState.scrollLeft > 0;
|
||||
const enableTop = scrollState.scrollTop > 0;
|
||||
const enableLeft = scrollState.scrollLeft > 0;
|
||||
|
||||
this._leftShadowDomNode!.setClassName('shadow' + (enableLeft ? ' left' : ''));
|
||||
this._topShadowDomNode!.setClassName('shadow' + (enableTop ? ' top' : ''));
|
||||
@@ -549,8 +557,12 @@ export class SmoothScrollableElement extends AbstractScrollableElement {
|
||||
super(element, options, scrollable);
|
||||
}
|
||||
|
||||
public setScrollPosition(update: INewScrollPosition): void {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
public setScrollPosition(update: INewScrollPosition & { reuseAnimation?: boolean }): void {
|
||||
if (update.reuseAnimation) {
|
||||
this._scrollable.setScrollPositionSmooth(update, update.reuseAnimation);
|
||||
} else {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
}
|
||||
|
||||
public getScrollPosition(): IScrollPosition {
|
||||
@@ -593,12 +605,13 @@ export class DomScrollableElement extends ScrollableElement {
|
||||
}
|
||||
|
||||
function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableElementResolvedOptions {
|
||||
let result: ScrollableElementResolvedOptions = {
|
||||
const result: ScrollableElementResolvedOptions = {
|
||||
lazyRender: (typeof opts.lazyRender !== 'undefined' ? opts.lazyRender : false),
|
||||
className: (typeof opts.className !== 'undefined' ? opts.className : ''),
|
||||
useShadows: (typeof opts.useShadows !== 'undefined' ? opts.useShadows : true),
|
||||
handleMouseWheel: (typeof opts.handleMouseWheel !== 'undefined' ? opts.handleMouseWheel : true),
|
||||
flipAxes: (typeof opts.flipAxes !== 'undefined' ? opts.flipAxes : false),
|
||||
consumeMouseWheelIfScrollbarIsNeeded: (typeof opts.consumeMouseWheelIfScrollbarIsNeeded !== 'undefined' ? opts.consumeMouseWheelIfScrollbarIsNeeded : false),
|
||||
alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false),
|
||||
scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),
|
||||
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
|
||||
|
||||
@@ -40,6 +40,11 @@ export interface ScrollableElementCreationOptions {
|
||||
* Defaults to false.
|
||||
*/
|
||||
scrollYToX?: boolean;
|
||||
/**
|
||||
* Consume all mouse wheel events if a scrollbar is needed (i.e. scrollSize > size).
|
||||
* Defaults to false.
|
||||
*/
|
||||
consumeMouseWheelIfScrollbarIsNeeded?: boolean;
|
||||
/**
|
||||
* Always consume mouse wheel events, even when scrolling is no longer possible.
|
||||
* Defaults to false.
|
||||
@@ -136,6 +141,7 @@ export interface ScrollableElementResolvedOptions {
|
||||
handleMouseWheel: boolean;
|
||||
flipAxes: boolean;
|
||||
scrollYToX: boolean;
|
||||
consumeMouseWheelIfScrollbarIsNeeded: boolean;
|
||||
alwaysConsumeMouseWheel: boolean;
|
||||
mouseWheelScrollSensitivity: number;
|
||||
fastScrollSensitivity: number;
|
||||
|
||||
@@ -88,7 +88,7 @@ export class ScrollbarArrow extends Widget {
|
||||
}
|
||||
|
||||
private _arrowMouseDown(e: IMouseEvent): void {
|
||||
let scheduleRepeater = () => {
|
||||
const scheduleRepeater = () => {
|
||||
this._mousedownRepeatTimer.cancelAndSet(() => this._onActivate(), 1000 / 24);
|
||||
};
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export class ScrollbarState {
|
||||
}
|
||||
|
||||
public setVisibleSize(visibleSize: number): boolean {
|
||||
let iVisibleSize = Math.round(visibleSize);
|
||||
const iVisibleSize = Math.round(visibleSize);
|
||||
if (this._visibleSize !== iVisibleSize) {
|
||||
this._visibleSize = iVisibleSize;
|
||||
this._refreshComputedValues();
|
||||
@@ -95,7 +95,7 @@ export class ScrollbarState {
|
||||
}
|
||||
|
||||
public setScrollSize(scrollSize: number): boolean {
|
||||
let iScrollSize = Math.round(scrollSize);
|
||||
const iScrollSize = Math.round(scrollSize);
|
||||
if (this._scrollSize !== iScrollSize) {
|
||||
this._scrollSize = iScrollSize;
|
||||
this._refreshComputedValues();
|
||||
@@ -105,7 +105,7 @@ export class ScrollbarState {
|
||||
}
|
||||
|
||||
public setScrollPosition(scrollPosition: number): boolean {
|
||||
let iScrollPosition = Math.round(scrollPosition);
|
||||
const iScrollPosition = Math.round(scrollPosition);
|
||||
if (this._scrollPosition !== iScrollPosition) {
|
||||
this._scrollPosition = iScrollPosition;
|
||||
this._refreshComputedValues();
|
||||
@@ -198,7 +198,7 @@ export class ScrollbarState {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;
|
||||
const desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export class ScrollbarState {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let correctedOffset = offset - this._arrowSize; // compensate if has arrows
|
||||
const correctedOffset = offset - this._arrowSize; // compensate if has arrows
|
||||
let desiredScrollPosition = this._scrollPosition;
|
||||
if (correctedOffset < this._computedSliderPosition) {
|
||||
desiredScrollPosition -= this._visibleSize; // page up/left
|
||||
@@ -233,7 +233,7 @@ export class ScrollbarState {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let desiredSliderPosition = this._computedSliderPosition + delta;
|
||||
const desiredSliderPosition = this._computedSliderPosition + delta;
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ScrollbarVisibilityController extends Disposable {
|
||||
}
|
||||
|
||||
public setShouldBeVisible(rawShouldBeVisible: boolean): void {
|
||||
let shouldBeVisible = this.applyVisibilitySetting(rawShouldBeVisible);
|
||||
const shouldBeVisible = this.applyVisibilitySetting(rawShouldBeVisible);
|
||||
|
||||
if (this._shouldBeVisible !== shouldBeVisible) {
|
||||
this._shouldBeVisible = shouldBeVisible;
|
||||
@@ -105,4 +105,4 @@ export class ScrollbarVisibilityController extends Disposable {
|
||||
this._domNode.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ export class VerticalScrollbar extends AbstractScrollbar {
|
||||
});
|
||||
|
||||
if (options.verticalHasArrows) {
|
||||
let arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
let scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
const arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
const scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ToolBar extends Disposable {
|
||||
actionViewItemProvider: this.options.actionViewItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
keybindingProvider: this.options.getKeyBinding,
|
||||
classNames: (options.moreIcon ?? toolBarMoreIcon).classNames,
|
||||
classNames: CSSIcon.asClassNameArray(options.moreIcon ?? toolBarMoreIcon),
|
||||
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
|
||||
menuAsChild: !!this.options.renderDropdownAsChildElement
|
||||
}
|
||||
|
||||
@@ -1117,9 +1117,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
expandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick;
|
||||
}
|
||||
|
||||
const clickedOnFocus = this.tree.getFocus()[0] === node.element;
|
||||
|
||||
if (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2 && !(clickedOnFocus && !node.collapsed)) {
|
||||
if (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) {
|
||||
return super.onViewPointer(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, IObjectTreeSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { timeout, CancelablePromise, createCancelablePromise, Promises } from 'vs/base/common/async';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
@@ -279,6 +279,7 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
}
|
||||
|
||||
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
|
||||
export interface IAsyncDataTreeUpdateChildrenOptions<T> extends IObjectTreeSetChildrenOptions<T> { }
|
||||
|
||||
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
|
||||
readonly collapseByDefault?: { (e: T): boolean; };
|
||||
@@ -499,11 +500,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender);
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender, undefined, options);
|
||||
}
|
||||
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
@@ -514,7 +515,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const node = this.getDataNode(element);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext, options);
|
||||
|
||||
if (rerender) {
|
||||
try {
|
||||
@@ -704,9 +705,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
await this.refreshNode(node, recursive, viewStateContext);
|
||||
this.render(node, viewStateContext);
|
||||
this.render(node, viewStateContext, options);
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
@@ -739,7 +740,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
|
||||
node.stale = false;
|
||||
|
||||
await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
|
||||
await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
|
||||
} finally {
|
||||
done!();
|
||||
}
|
||||
@@ -768,8 +769,8 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
const children = await childrenPromise;
|
||||
return this.setChildren(node, children, recursive, viewStateContext);
|
||||
} catch (err) {
|
||||
if (node !== this.root) {
|
||||
this.tree.collapse(node === this.root ? null : node);
|
||||
if (node !== this.root && this.tree.hasElement(node)) {
|
||||
this.tree.collapse(node);
|
||||
}
|
||||
|
||||
if (isPromiseCanceledError(err)) {
|
||||
@@ -921,9 +922,18 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return childrenToRefresh;
|
||||
}
|
||||
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): void {
|
||||
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children);
|
||||
const objectTreeOptions: IObjectTreeSetChildrenOptions<IAsyncDataTreeNode<TInput, T>> | undefined = options && {
|
||||
...options,
|
||||
diffIdentityProvider: options!.diffIdentityProvider && {
|
||||
getId(node: IAsyncDataTreeNode<TInput, T>): { toString(): string; } {
|
||||
return options!.diffIdentityProvider!.getId(node.element as T);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.tree.setChildren(node === this.root ? null : node, children, objectTreeOptions);
|
||||
|
||||
if (node !== this.root) {
|
||||
this.tree.setCollapsible(node, node.hasChildren);
|
||||
@@ -980,16 +990,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
const expanded: string[] = [];
|
||||
const root = this.tree.getNode();
|
||||
const queue = [root];
|
||||
const stack = [root];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop()!;
|
||||
|
||||
if (node !== root && node.collapsible && !node.collapsed) {
|
||||
expanded.push(getId(node.element!.element as T));
|
||||
}
|
||||
|
||||
queue.push(...node.children);
|
||||
stack.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, expanded, scrollTop: this.scrollTop };
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel, IObjectTreeModelSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IIndexTreeModelSpliceOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
@@ -108,6 +109,12 @@ interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeM
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
const wrapIdentityProvider = <T>(base: IIdentityProvider<T>): IIdentityProvider<ICompressedTreeNode<T>> => ({
|
||||
getId(node) {
|
||||
return node.elements.map(e => base.getId(e).toString()).join('\0');
|
||||
}
|
||||
});
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
|
||||
@@ -120,6 +127,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
|
||||
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
|
||||
private enabled: boolean;
|
||||
private readonly identityProvider?: IIdentityProvider<ICompressedTreeNode<T>>;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
@@ -130,15 +138,21 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(),
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData>,
|
||||
): void {
|
||||
// Diffs must be deem, since the compression can affect nested elements.
|
||||
// @see https://github.com/microsoft/vscode/pull/114237#issuecomment-759425034
|
||||
|
||||
const diffIdentityProvider = options.diffIdentityProvider && wrapIdentityProvider(options.diffIdentityProvider);
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren);
|
||||
this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDepth: Infinity });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,7 +173,10 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
const parentChildren = parent.children
|
||||
.map(child => child === node ? recompressedElement : child);
|
||||
|
||||
this._setChildren(parent.element, parentChildren);
|
||||
this._setChildren(parent.element, parentChildren, {
|
||||
diffIdentityProvider,
|
||||
diffDepth: node.depth - parent.depth,
|
||||
});
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
@@ -177,22 +194,29 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
const rootChildren = root.children as ITreeNode<ICompressedTreeNode<T>>[];
|
||||
const decompressedRootChildren = Iterable.map(rootChildren, decompress);
|
||||
const recompressedRootChildren = Iterable.map(decompressedRootChildren, enabled ? compress : noCompress);
|
||||
this._setChildren(null, recompressedRootChildren);
|
||||
|
||||
// it should be safe to always use deep diff mode here if an identity
|
||||
// provider is available, since we know the raw nodes are unchanged.
|
||||
this._setChildren(null, recompressedRootChildren, {
|
||||
diffIdentityProvider: this.identityProvider,
|
||||
diffDepth: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
node: ICompressedTreeNode<T> | null,
|
||||
children: Iterable<ITreeElement<ICompressedTreeNode<T>>>
|
||||
children: Iterable<ITreeElement<ICompressedTreeNode<T>>>,
|
||||
options: IIndexTreeModelSpliceOptions<ICompressedTreeNode<T>, TFilterData>,
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const _onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
const onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
for (const element of node.element.elements) {
|
||||
insertedElements.add(element);
|
||||
this.nodes.set(element, node.element);
|
||||
}
|
||||
};
|
||||
|
||||
const _onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
const onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
for (const element of node.element.elements) {
|
||||
if (!insertedElements.has(element)) {
|
||||
this.nodes.delete(element);
|
||||
@@ -200,7 +224,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
}
|
||||
};
|
||||
|
||||
this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode);
|
||||
this.model.setChildren(node, children, { ...options, onDidCreateNode, onDidDeleteNode });
|
||||
}
|
||||
|
||||
has(element: T | null): boolean {
|
||||
@@ -363,16 +387,16 @@ function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilter
|
||||
function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwrapper<T>, options: ICompressibleObjectTreeModelOptions<T, TFilterData>): ICompressedObjectTreeModelOptions<T, TFilterData> {
|
||||
return {
|
||||
...options,
|
||||
sorter: options.sorter && {
|
||||
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
|
||||
return options.sorter!.compare(node.elements[0], otherNode.elements[0]);
|
||||
}
|
||||
},
|
||||
identityProvider: options.identityProvider && {
|
||||
getId(node: ICompressedTreeNode<T>): { toString(): string; } {
|
||||
return options.identityProvider!.getId(compressedNodeUnwrapper(node));
|
||||
}
|
||||
},
|
||||
sorter: options.sorter && {
|
||||
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
|
||||
return options.sorter!.compare(node.elements[0], otherNode.elements[0]);
|
||||
}
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(node: ICompressedTreeNode<T>, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData> {
|
||||
return options.filter!.filter(compressedNodeUnwrapper(node), parentVisibility);
|
||||
@@ -424,8 +448,12 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(),
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
|
||||
@@ -47,13 +47,19 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
return this.input;
|
||||
}
|
||||
|
||||
setInput(input: TInput, viewState?: IDataTreeViewState): void {
|
||||
setInput(input: TInput | undefined, viewState?: IDataTreeViewState): void {
|
||||
if (viewState && !this.identityProvider) {
|
||||
throw new TreeError(this.user, 'Can\'t restore tree view state without an identity provider');
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
|
||||
if (!input) {
|
||||
this.nodesByIdentity.clear();
|
||||
this.model.setChildren(null, Iterable.empty());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!viewState) {
|
||||
this._refresh(input);
|
||||
return;
|
||||
@@ -155,7 +161,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
};
|
||||
}
|
||||
|
||||
this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, onDidCreateNode, onDidDeleteNode);
|
||||
this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, { onDidCreateNode, onDidDeleteNode });
|
||||
}
|
||||
|
||||
private iterate(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined): { elements: Iterable<ITreeElement<T>>, size: number } {
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { splice, tail2 } from 'vs/base/common/arrays';
|
||||
import { LcsDiff } from 'vs/base/common/diff/diff';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
@@ -41,6 +43,34 @@ export interface IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
export interface IIndexTreeModelSpliceOptions<T, TFilterData> {
|
||||
/**
|
||||
* If set, child updates will recurse the given number of levels even if
|
||||
* items in the splice operation are unchanged. `Infinity` is a valid value.
|
||||
*/
|
||||
readonly diffDepth?: number;
|
||||
|
||||
/**
|
||||
* Identity provider used to optimize splice() calls in the IndexTree. If
|
||||
* this is not present, optimized splicing is not enabled.
|
||||
*
|
||||
* Warning: if this is present, calls to `setChildren()` will not replace
|
||||
* or update nodes if their identity is the same, even if the elements are
|
||||
* different. For this, you should call `rerender()`.
|
||||
*/
|
||||
readonly diffIdentityProvider?: IIdentityProvider<T>;
|
||||
|
||||
/**
|
||||
* Callback for when a node is created.
|
||||
*/
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
/**
|
||||
* Callback for when a node is deleted.
|
||||
*/
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
}
|
||||
|
||||
interface CollapsibleStateUpdate {
|
||||
readonly collapsible: boolean;
|
||||
}
|
||||
@@ -110,18 +140,95 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsert: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
options: IIndexTreeModelSpliceOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
if (location.length === 0) {
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
if (options.diffIdentityProvider) {
|
||||
this.spliceSmart(options.diffIdentityProvider, location, deleteCount, toInsert, options);
|
||||
} else {
|
||||
this.spliceSimple(location, deleteCount, toInsert, options);
|
||||
}
|
||||
}
|
||||
|
||||
private spliceSmart(
|
||||
identity: IIdentityProvider<T>,
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsertIterable: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
options: IIndexTreeModelSpliceOptions<T, TFilterData>,
|
||||
recurseLevels = options.diffDepth ?? 0,
|
||||
) {
|
||||
const { parentNode } = this.getParentNodeWithListIndex(location);
|
||||
const toInsert = [...toInsertIterable];
|
||||
const index = location[location.length - 1];
|
||||
const diff = new LcsDiff(
|
||||
{ getElements: () => parentNode.children.map(e => identity.getId(e.element).toString()) },
|
||||
{
|
||||
getElements: () => [
|
||||
...parentNode.children.slice(0, index),
|
||||
...toInsert,
|
||||
...parentNode.children.slice(index + deleteCount),
|
||||
].map(e => identity.getId(e.element).toString())
|
||||
},
|
||||
).ComputeDiff(false);
|
||||
|
||||
// if we were given a 'best effort' diff, use default behavior
|
||||
if (diff.quitEarly) {
|
||||
return this.spliceSimple(location, deleteCount, toInsert, options);
|
||||
}
|
||||
|
||||
const locationPrefix = location.slice(0, -1);
|
||||
const recurseSplice = (fromOriginal: number, fromModified: number, count: number) => {
|
||||
if (recurseLevels > 0) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
fromOriginal--;
|
||||
fromModified--;
|
||||
this.spliceSmart(
|
||||
identity,
|
||||
[...locationPrefix, fromOriginal, 0],
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
toInsert[fromModified].children,
|
||||
options,
|
||||
recurseLevels - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let lastStartO = Math.min(parentNode.children.length, index + deleteCount);
|
||||
let lastStartM = toInsert.length;
|
||||
for (const change of diff.changes.sort((a, b) => b.originalStart - a.originalStart)) {
|
||||
recurseSplice(lastStartO, lastStartM, lastStartO - (change.originalStart + change.originalLength));
|
||||
lastStartO = change.originalStart;
|
||||
lastStartM = change.modifiedStart - index;
|
||||
|
||||
this.spliceSimple(
|
||||
[...locationPrefix, lastStartO],
|
||||
change.originalLength,
|
||||
Iterable.slice(toInsert, lastStartM, lastStartM + change.modifiedLength),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
// at this point, startO === startM === count since any remaining prefix should match
|
||||
recurseSplice(lastStartO, lastStartM, lastStartO);
|
||||
}
|
||||
|
||||
private spliceSimple(
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsert: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
{ onDidCreateNode, onDidDeleteNode }: IIndexTreeModelSpliceOptions<T, TFilterData>,
|
||||
) {
|
||||
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
|
||||
const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
|
||||
const nodesToInsertIterator = Iterable.map(toInsert, el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode));
|
||||
|
||||
const lastIndex = location[location.length - 1];
|
||||
const lastHadChildren = parentNode.children.length > 0;
|
||||
|
||||
// figure out what's the visible child start index right before the
|
||||
// splice point
|
||||
@@ -190,6 +297,11 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
deletedNodes.forEach(visit);
|
||||
}
|
||||
|
||||
const currentlyHasChildren = parentNode.children.length > 0;
|
||||
if (lastHadChildren !== currentlyHasChildren) {
|
||||
this.setCollapsible(location.slice(0, -1), currentlyHasChildren);
|
||||
}
|
||||
|
||||
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
|
||||
|
||||
let node: IIndexTreeNode<T, TFilterData> | undefined = parentNode;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
@@ -17,6 +17,25 @@ export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTree
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export interface IObjectTreeSetChildrenOptions<T> {
|
||||
|
||||
/**
|
||||
* If set, child updates will recurse the given number of levels even if
|
||||
* items in the splice operation are unchanged. `Infinity` is a valid value.
|
||||
*/
|
||||
readonly diffDepth?: number;
|
||||
|
||||
/**
|
||||
* Identity provider used to optimize splice() calls in the IndexTree. If
|
||||
* this is not present, optimized splicing is not enabled.
|
||||
*
|
||||
* Warning: if this is present, calls to `setChildren()` will not replace
|
||||
* or update nodes if their identity is the same, even if the elements are
|
||||
* different. For this, you should call `rerender()`.
|
||||
*/
|
||||
readonly diffIdentityProvider?: IIdentityProvider<T>;
|
||||
}
|
||||
|
||||
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
|
||||
protected model!: IObjectTreeModel<T, TFilterData>;
|
||||
@@ -33,8 +52,8 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
super(user, container, delegate, renderers, options as IObjectTreeOptions<T | null, TFilterData>);
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> = Iterable.empty()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
@@ -189,8 +208,8 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider, options));
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList, IIndexTreeModelSpliceOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
@@ -13,11 +13,14 @@ import { mergeSort } from 'vs/base/common/arrays';
|
||||
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined): void;
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
updateElementHeight(element: T, height: number): void;
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelSetChildrenOptions<T, TFilterData> extends IIndexTreeModelSpliceOptions<T, TFilterData> {
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
@@ -63,23 +66,21 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
|
||||
this._setChildren(location, this.preserveCollapseState(children), options);
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
location: number[],
|
||||
children: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData>,
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
const _onDidCreateNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
const onDidCreateNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
@@ -95,12 +96,10 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
this.nodesByIdentity.set(id, tnode);
|
||||
}
|
||||
|
||||
if (onDidCreateNode) {
|
||||
onDidCreateNode(tnode);
|
||||
}
|
||||
options.onDidCreateNode?.(tnode);
|
||||
};
|
||||
|
||||
const _onDidDeleteNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
const onDidDeleteNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
@@ -118,17 +117,14 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
}
|
||||
|
||||
if (onDidDeleteNode) {
|
||||
onDidDeleteNode(tnode);
|
||||
}
|
||||
options.onDidDeleteNode?.(tnode);
|
||||
};
|
||||
|
||||
this.model.splice(
|
||||
[...location, 0],
|
||||
Number.MAX_VALUE,
|
||||
children,
|
||||
_onDidCreateNode,
|
||||
_onDidDeleteNode
|
||||
{ ...options, onDidCreateNode, onDidDeleteNode }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,7 +178,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
const location = this.getElementLocation(element);
|
||||
const node = this.model.getNode(location);
|
||||
|
||||
this._setChildren(location, this.resortChildren(node, recursive));
|
||||
this._setChildren(location, this.resortChildren(node, recursive), {});
|
||||
}
|
||||
|
||||
private resortChildren(node: ITreeNode<T | null, TFilterData>, recursive: boolean, first = true): Iterable<ITreeElement<T>> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user