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:
Karl Burtram
2021-04-27 14:01:59 -07:00
committed by GitHub
co-authored by chlafreniere Christopher Suh chgagnon
parent 7e1c0076ba
commit 867a963882
1817 changed files with 81812 additions and 50843 deletions
+2
View File
@@ -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);
};
+44 -12
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+49 -68
View File
@@ -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
};
}));
+8 -93
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
}
+4 -4
View File
@@ -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({
@@ -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> {
@@ -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();
@@ -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'));
}
/**
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"plugins": [
{
"name": "tsec",
"exemptionConfig": "./tsec.exemptions.json"
}
]
},
"exclude": [
"*/test/*",
"**/*.test.ts"
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"ban-trustedtypes-createpolicy": [
"**/*.ts"
]
}
+1
View File
@@ -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;
}
+3 -3
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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;
}
+8 -6
View File
@@ -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);
}
+8 -1
View File
@@ -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);
}
+11 -15
View File
@@ -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');
+16 -12
View File
@@ -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) {
+3 -5
View File
@@ -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';
+7 -3
View File
@@ -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':
+4 -4
View File
@@ -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);
+5 -2
View File
@@ -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;
}
+3 -1
View File
@@ -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());
}
+64 -22
View File
@@ -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;
}
+14 -4
View File
@@ -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
@@ -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) {
+9 -10
View File
@@ -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;
}
+6 -3
View File
@@ -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;
}
+5 -2
View File
@@ -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);
}
+102 -46
View File
@@ -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 -21
View File
@@ -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 {
+1 -2
View File
@@ -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;
}
});
+3 -2
View File
@@ -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 = /(&amp;)?(&amp;)([^\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) {
+4 -2
View File
@@ -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',
+1 -1
View File
@@ -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
}
+1 -3
View File
@@ -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);
}
+27 -17
View File
@@ -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 {
+8 -2
View File
@@ -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 } {
+114 -2
View File
@@ -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;
+24 -5
View File
@@ -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> {
+14 -18
View File
@@ -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