Update to VS Code 1.52.1
This commit is contained in:
6
lib/vscode/src/bootstrap-amd.js
vendored
6
lib/vscode/src/bootstrap-amd.js
vendored
@ -18,7 +18,9 @@ loader.config({
|
||||
catchError: true,
|
||||
nodeRequire: require,
|
||||
nodeMain: __filename,
|
||||
'vs/nls': nlsConfig
|
||||
'vs/nls': nlsConfig,
|
||||
amdModulesPattern: /^vs\//,
|
||||
recordStats: true
|
||||
});
|
||||
|
||||
// Running in Electron
|
||||
@ -29,7 +31,7 @@ if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) {
|
||||
}
|
||||
|
||||
// Pseudo NLS support
|
||||
if (nlsConfig.pseudo) {
|
||||
if (nlsConfig && nlsConfig.pseudo) {
|
||||
loader(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
|
10
lib/vscode/src/bootstrap-fork.js
vendored
10
lib/vscode/src/bootstrap-fork.js
vendored
@ -13,7 +13,7 @@ const bootstrapNode = require('./bootstrap-node');
|
||||
bootstrapNode.removeGlobalNodeModuleLookupPaths();
|
||||
|
||||
// Enable ASAR in our forked processes
|
||||
bootstrap.enableASARSupport();
|
||||
bootstrap.enableASARSupport(undefined);
|
||||
|
||||
if (process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']) {
|
||||
bootstrapNode.injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']);
|
||||
@ -81,7 +81,9 @@ function pipeLoggingToParent() {
|
||||
// to start the stacktrace where the console message was being written
|
||||
if (process.env.VSCODE_LOG_STACK === 'true') {
|
||||
const stack = new Error().stack;
|
||||
argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') });
|
||||
if (stack) {
|
||||
argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -114,7 +116,9 @@ function pipeLoggingToParent() {
|
||||
*/
|
||||
function safeSend(arg) {
|
||||
try {
|
||||
process.send(arg);
|
||||
if (process.send) {
|
||||
process.send(arg);
|
||||
}
|
||||
} catch (error) {
|
||||
// Can happen if the parent channel is closed meanwhile
|
||||
}
|
||||
|
70
lib/vscode/src/bootstrap-node.js
vendored
70
lib/vscode/src/bootstrap-node.js
vendored
@ -58,3 +58,73 @@ exports.removeGlobalNodeModuleLookupPaths = function () {
|
||||
return paths.slice(0, paths.length - commonSuffixLength);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to enable portable mode.
|
||||
*
|
||||
* @param {{ portable?: string; applicationName: string; }} product
|
||||
* @returns {{ portableDataPath: string; isPortable: boolean; }}
|
||||
*/
|
||||
exports.configurePortable = function (product) {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
|
110
lib/vscode/src/bootstrap-window.js
vendored
110
lib/vscode/src/bootstrap-window.js
vendored
@ -25,7 +25,12 @@
|
||||
const preloadGlobals = globals();
|
||||
const sandbox = preloadGlobals.context.sandbox;
|
||||
const webFrame = preloadGlobals.webFrame;
|
||||
const safeProcess = sandbox ? preloadGlobals.process : process;
|
||||
const safeProcess = preloadGlobals.process;
|
||||
const configuration = parseWindowConfiguration();
|
||||
|
||||
// Start to resolve process.env before anything gets load
|
||||
// so that we can run loading and resolving in parallel
|
||||
const whenEnvResolved = safeProcess.resolveEnv(configuration.userEnv);
|
||||
|
||||
/**
|
||||
* @param {string[]} modulePaths
|
||||
@ -33,18 +38,6 @@
|
||||
* @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) {
|
||||
const args = parseURLQueryArgs();
|
||||
/**
|
||||
* // configuration: INativeWindowConfiguration
|
||||
* @type {{
|
||||
* zoomLevel?: number,
|
||||
* extensionDevelopmentPath?: string[],
|
||||
* extensionTestsPath?: string,
|
||||
* userEnv?: { [key: string]: string | undefined },
|
||||
* appRoot?: string,
|
||||
* nodeCachedDataDir?: string
|
||||
* }} */
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
// Apply zoom level early to avoid glitches
|
||||
const zoomLevel = configuration.zoomLevel;
|
||||
@ -64,22 +57,15 @@
|
||||
developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding);
|
||||
}
|
||||
|
||||
// Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only)
|
||||
if (!sandbox) {
|
||||
Object.assign(safeProcess.env, configuration.userEnv);
|
||||
}
|
||||
|
||||
// Enable ASAR support (TODO@sandbox non-sandboxed only)
|
||||
if (!sandbox) {
|
||||
globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot);
|
||||
}
|
||||
// Enable ASAR support
|
||||
globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot);
|
||||
|
||||
if (options && typeof options.canModifyDOM === 'function') {
|
||||
options.canModifyDOM(configuration);
|
||||
}
|
||||
|
||||
// Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only)
|
||||
const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS();
|
||||
// Get the nls configuration into the process.env as early as possible
|
||||
const nlsConfig = globalThis.MonacoBootstrap.setupNLS();
|
||||
|
||||
let locale = nlsConfig.availableLanguages['*'] || 'en';
|
||||
if (locale === 'zh-tw') {
|
||||
@ -102,9 +88,14 @@
|
||||
|
||||
window['MonacoEnvironment'] = {};
|
||||
|
||||
const baseUrl = sandbox ?
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` :
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`;
|
||||
|
||||
const loaderConfig = {
|
||||
baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`,
|
||||
'vs/nls': nlsConfig
|
||||
baseUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
preferScriptTags: sandbox
|
||||
};
|
||||
|
||||
// Enable loading of node modules:
|
||||
@ -150,17 +141,23 @@
|
||||
options.beforeRequire();
|
||||
}
|
||||
|
||||
require(modulePaths, result => {
|
||||
require(modulePaths, async result => {
|
||||
try {
|
||||
|
||||
// Wait for process environment being fully resolved
|
||||
const perf = perfLib();
|
||||
perf.mark('willWaitForShellEnv');
|
||||
await whenEnvResolved;
|
||||
perf.mark('didWaitForShellEnv');
|
||||
|
||||
// Callback only after process environment is resolved
|
||||
const callbackResult = resultCallback(result, configuration);
|
||||
if (callbackResult && typeof callbackResult.then === 'function') {
|
||||
callbackResult.then(() => {
|
||||
if (developerToolsUnbind && options && options.removeDeveloperKeybindingsAfterLoad) {
|
||||
developerToolsUnbind();
|
||||
}
|
||||
}, error => {
|
||||
onUnexpectedError(error, enableDeveloperTools);
|
||||
});
|
||||
if (callbackResult instanceof Promise) {
|
||||
await callbackResult;
|
||||
|
||||
if (developerToolsUnbind && options && options.removeDeveloperKeybindingsAfterLoad) {
|
||||
developerToolsUnbind();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onUnexpectedError(error, enableDeveloperTools);
|
||||
@ -169,20 +166,30 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{[param: string]: string }}
|
||||
* Parses the contents of the `INativeWindowConfiguration` that
|
||||
* is passed into the URL from the `electron-main` side.
|
||||
*
|
||||
* @returns {{
|
||||
* zoomLevel?: number,
|
||||
* extensionDevelopmentPath?: string[],
|
||||
* extensionTestsPath?: string,
|
||||
* userEnv?: { [key: string]: string | undefined },
|
||||
* appRoot: string,
|
||||
* nodeCachedDataDir?: string
|
||||
* }}
|
||||
*/
|
||||
function parseURLQueryArgs() {
|
||||
const search = window.location.search || '';
|
||||
|
||||
return search.split(/[?&]/)
|
||||
function parseWindowConfiguration() {
|
||||
const rawConfiguration = (window.location.search || '').split(/[?&]/)
|
||||
.filter(function (param) { return !!param; })
|
||||
.map(function (param) { return param.split('='); })
|
||||
.filter(function (param) { return param.length === 2; })
|
||||
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
|
||||
|
||||
return JSON.parse(rawConfiguration['config'] || '{}') || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} disallowReloadKeybinding
|
||||
* @param {boolean | undefined} disallowReloadKeybinding
|
||||
* @returns {() => void}
|
||||
*/
|
||||
function registerDeveloperKeybindings(disallowReloadKeybinding) {
|
||||
@ -203,6 +210,7 @@
|
||||
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} */
|
||||
let listener = function (e) {
|
||||
const key = extractKey(e);
|
||||
if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) {
|
||||
@ -255,8 +263,26 @@
|
||||
return window.vscode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
globals,
|
||||
perfLib
|
||||
};
|
||||
}));
|
||||
|
121
lib/vscode/src/bootstrap.js
vendored
121
lib/vscode/src/bootstrap.js
vendored
@ -42,11 +42,11 @@
|
||||
//#region Add support for using node_modules.asar
|
||||
|
||||
/**
|
||||
* @param {string} appRoot
|
||||
* @param {string | undefined} appRoot
|
||||
*/
|
||||
function enableASARSupport(appRoot) {
|
||||
if (!path || !Module) {
|
||||
console.warn('enableASARSupport() is only available in node.js environments');
|
||||
if (!path || !Module || typeof process === 'undefined') {
|
||||
console.warn('enableASARSupport() is only available in node.js environments'); // TODO@sandbox ASAR is currently non-sandboxed only
|
||||
return;
|
||||
}
|
||||
|
||||
@ -124,17 +124,14 @@
|
||||
//#region NLS helpers
|
||||
|
||||
/**
|
||||
* @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean }}
|
||||
* @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean } | undefined}
|
||||
*/
|
||||
function setupNLS() {
|
||||
if (!path || !fs) {
|
||||
console.warn('setupNLS() is only available in node.js environments');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the nls configuration into the process.env as early as possible.
|
||||
// Get the nls configuration as early as possible.
|
||||
const process = safeProcess();
|
||||
let nlsConfig = { availableLanguages: {} };
|
||||
if (process.env['VSCODE_NLS_CONFIG']) {
|
||||
if (process && process.env['VSCODE_NLS_CONFIG']) {
|
||||
try {
|
||||
nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']);
|
||||
} catch (e) {
|
||||
@ -153,8 +150,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`);
|
||||
fs.promises.readFile(bundleFile, 'utf8').then(function (content) {
|
||||
safeReadNlsFile(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`).then(function (content) {
|
||||
const json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
|
||||
@ -162,7 +158,7 @@
|
||||
}).catch((error) => {
|
||||
try {
|
||||
if (nlsConfig._corruptedFile) {
|
||||
fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); });
|
||||
safeWriteNlsFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
|
||||
}
|
||||
} finally {
|
||||
cb(error, undefined);
|
||||
@ -174,77 +170,73 @@
|
||||
return nlsConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
function safeGlobals() {
|
||||
const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {});
|
||||
|
||||
|
||||
//#region Portable helpers
|
||||
return globals.vscode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ portable: string; applicationName: string; }} product
|
||||
* @returns {{ portableDataPath: string; isPortable: boolean; }}
|
||||
* @returns {NodeJS.Process | undefined}
|
||||
*/
|
||||
function configurePortable(product) {
|
||||
if (!path || !fs) {
|
||||
console.warn('configurePortable() is only available in node.js environments');
|
||||
return;
|
||||
function safeProcess() {
|
||||
if (typeof process !== 'undefined') {
|
||||
return process; // Native environment (non-sandboxed)
|
||||
}
|
||||
|
||||
const appRoot = path.dirname(__dirname);
|
||||
const globals = safeGlobals();
|
||||
if (globals) {
|
||||
return globals.process; // Native environment (sandboxed)
|
||||
}
|
||||
}
|
||||
|
||||
function getApplicationPath() {
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return appRoot;
|
||||
}
|
||||
/**
|
||||
* @returns {Electron.IpcRenderer | undefined}
|
||||
*/
|
||||
function safeIpcRenderer() {
|
||||
const globals = safeGlobals();
|
||||
if (globals) {
|
||||
return globals.ipcRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
return path.dirname(path.dirname(path.dirname(appRoot)));
|
||||
}
|
||||
|
||||
return path.dirname(path.dirname(appRoot));
|
||||
/**
|
||||
* @param {string[]} pathSegments
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function safeReadNlsFile(...pathSegments) {
|
||||
const ipcRenderer = safeIpcRenderer();
|
||||
if (ipcRenderer) {
|
||||
return ipcRenderer.invoke('vscode:readNlsFile', ...pathSegments);
|
||||
}
|
||||
|
||||
function getPortableDataPath() {
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
return process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
return path.join(getApplicationPath(), 'data');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
|
||||
return path.join(path.dirname(getApplicationPath()), portableDataName);
|
||||
if (fs && path) {
|
||||
return (await fs.promises.readFile(path.join(...pathSegments))).toString();
|
||||
}
|
||||
|
||||
const portableDataPath = getPortableDataPath();
|
||||
const isPortable = !('target' in product) && fs.existsSync(portableDataPath);
|
||||
const portableTempPath = path.join(portableDataPath, 'tmp');
|
||||
const isTempPortable = isPortable && fs.existsSync(portableTempPath);
|
||||
throw new Error('Unsupported operation (read NLS files)');
|
||||
}
|
||||
|
||||
if (isPortable) {
|
||||
process.env['VSCODE_PORTABLE'] = portableDataPath;
|
||||
} else {
|
||||
delete process.env['VSCODE_PORTABLE'];
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function safeWriteNlsFile(path, content) {
|
||||
const ipcRenderer = safeIpcRenderer();
|
||||
if (ipcRenderer) {
|
||||
return ipcRenderer.invoke('vscode:writeNlsFile', path, content);
|
||||
}
|
||||
|
||||
if (isTempPortable) {
|
||||
if (process.platform === 'win32') {
|
||||
process.env['TMP'] = portableTempPath;
|
||||
process.env['TEMP'] = portableTempPath;
|
||||
} else {
|
||||
process.env['TMPDIR'] = portableTempPath;
|
||||
}
|
||||
if (fs) {
|
||||
return fs.promises.writeFile(path, content);
|
||||
}
|
||||
|
||||
return {
|
||||
portableDataPath,
|
||||
isPortable
|
||||
};
|
||||
throw new Error('Unsupported operation (write NLS files)');
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
|
||||
//#region ApplicationInsights
|
||||
|
||||
@ -267,7 +259,6 @@
|
||||
return {
|
||||
enableASARSupport,
|
||||
avoidMonkeyPatchFromAppInsights,
|
||||
configurePortable,
|
||||
setupNLS,
|
||||
fileUriFromPath
|
||||
};
|
||||
|
@ -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'
|
||||
}];
|
||||
|
@ -7,16 +7,17 @@
|
||||
'use strict';
|
||||
|
||||
const bootstrap = require('./bootstrap');
|
||||
const bootstrapNode = require('./bootstrap-node');
|
||||
const product = require('../product.json');
|
||||
|
||||
// Avoid Monkey Patches from Application Insights
|
||||
bootstrap.avoidMonkeyPatchFromAppInsights();
|
||||
|
||||
// Enable portable support
|
||||
bootstrap.configurePortable(product);
|
||||
bootstrapNode.configurePortable(product);
|
||||
|
||||
// Enable ASAR support
|
||||
bootstrap.enableASARSupport();
|
||||
bootstrap.enableASARSupport(undefined);
|
||||
|
||||
// Load CLI through AMD loader
|
||||
require('./bootstrap-amd').load('vs/code/node/cli');
|
||||
|
@ -15,6 +15,7 @@ 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} */
|
||||
const product = require('../product.json');
|
||||
@ -25,10 +26,10 @@ const { app, protocol, crashReporter } = require('electron');
|
||||
app.allowRendererProcessReuse = false;
|
||||
|
||||
// Enable portable support
|
||||
const portable = bootstrap.configurePortable(product);
|
||||
const portable = bootstrapNode.configurePortable(product);
|
||||
|
||||
// Enable ASAR support
|
||||
bootstrap.enableASARSupport();
|
||||
bootstrap.enableASARSupport(undefined);
|
||||
|
||||
// Set userData path before app 'ready' event
|
||||
const args = parseCLIArgs();
|
||||
@ -107,7 +108,7 @@ crashReporter.start({
|
||||
// to ensure that no 'logs' folder is created on disk at a
|
||||
// location outside of the portable directory
|
||||
// (https://github.com/microsoft/vscode/issues/56651)
|
||||
if (portable.isPortable) {
|
||||
if (portable && portable.isPortable) {
|
||||
app.setAppLogsPath(path.join(userDataPath, 'logs'));
|
||||
}
|
||||
|
||||
@ -133,6 +134,15 @@ protocol.registerSchemesAsPrivileged([
|
||||
corsEnabled: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
scheme: 'vscode-file',
|
||||
privileges: {
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// Global app listeners
|
||||
@ -145,7 +155,7 @@ const nodeCachedDataDir = getNodeCachedDir();
|
||||
* Support user defined locale: load it early before app('ready')
|
||||
* to have more things running in parallel.
|
||||
*
|
||||
* @type {Promise<import('./vs/base/node/languagePacks').NLSConfiguration>} nlsConfig | undefined
|
||||
* @type {Promise<import('./vs/base/node/languagePacks').NLSConfiguration> | undefined}
|
||||
*/
|
||||
let nlsConfigurationPromise = undefined;
|
||||
|
||||
@ -359,7 +369,7 @@ function getArgvConfigPath() {
|
||||
|
||||
/**
|
||||
* @param {NativeParsedArgs} cliArgs
|
||||
* @returns {string}
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function getJSFlags(cliArgs) {
|
||||
const jsFlags = [];
|
||||
@ -387,7 +397,7 @@ function getUserDataPath(cliArgs) {
|
||||
return path.join(portable.portableDataPath, 'user-data');
|
||||
}
|
||||
|
||||
return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath(process.platform));
|
||||
return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -468,12 +478,14 @@ function getNodeCachedDir() {
|
||||
}
|
||||
|
||||
async ensureExists() {
|
||||
try {
|
||||
await mkdirp(this.value);
|
||||
if (typeof this.value === 'string') {
|
||||
try {
|
||||
await mkdirp(this.value);
|
||||
|
||||
return this.value;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
return this.value;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,25 +11,38 @@ const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
/**
|
||||
* @param {string} platform
|
||||
* @returns {string}
|
||||
*/
|
||||
function getAppDataPath(platform) {
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['VSCODE_APPDATA'] || process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return process.env['VSCODE_APPDATA'] || path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['VSCODE_APPDATA'] || process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
function getDefaultUserDataPath() {
|
||||
|
||||
// Support global VSCODE_APPDATA environment variable
|
||||
let appDataPath = process.env['VSCODE_APPDATA'];
|
||||
|
||||
// Otherwise check per platform
|
||||
if (!appDataPath) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
appDataPath = process.env['APPDATA'];
|
||||
if (!appDataPath) {
|
||||
const userProfile = process.env['USERPROFILE'];
|
||||
if (typeof userProfile !== 'string') {
|
||||
throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
|
||||
}
|
||||
appDataPath = path.join(userProfile, 'AppData', 'Roaming');
|
||||
}
|
||||
break;
|
||||
case 'darwin':
|
||||
appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
|
||||
break;
|
||||
case 'linux':
|
||||
appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
return path.join(appDataPath, pkg.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} platform
|
||||
* @returns {string}
|
||||
*/
|
||||
function getDefaultUserDataPath(platform) {
|
||||
return path.join(getAppDataPath(platform), pkg.name);
|
||||
}
|
||||
|
||||
exports.getAppDataPath = getAppDataPath;
|
||||
exports.getDefaultUserDataPath = getDefaultUserDataPath;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
export interface IContextMenuEvent {
|
||||
readonly shiftKey?: boolean;
|
||||
@ -26,6 +26,7 @@ export interface IContextMenuDelegate {
|
||||
actionRunner?: IActionRunner;
|
||||
autoSelectFirstItem?: boolean;
|
||||
anchorAlignment?: AnchorAlignment;
|
||||
anchorAxisAlignment?: AnchorAxisAlignment;
|
||||
domForShadowRoot?: HTMLElement;
|
||||
}
|
||||
|
||||
|
@ -661,6 +661,48 @@ export function isAncestor(testChild: Node | null, testAncestor: Node | null): b
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentFlowToDataKey = 'parentFlowToElementId';
|
||||
|
||||
/**
|
||||
* Set an explicit parent to use for nodes that are not part of the
|
||||
* regular dom structure.
|
||||
*/
|
||||
export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: Element): void {
|
||||
fromChildElement.dataset[parentFlowToDataKey] = toParentElement.id;
|
||||
}
|
||||
|
||||
function getParentFlowToElement(node: HTMLElement): HTMLElement | null {
|
||||
const flowToParentId = node.dataset[parentFlowToDataKey];
|
||||
if (typeof flowToParentId === 'string') {
|
||||
return document.getElementById(flowToParentId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `testAncestor` is an ancessor of `testChild`, observing the explicit
|
||||
* parents set by `setParentFlowTo`.
|
||||
*/
|
||||
export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): boolean {
|
||||
let node: Node | null = testChild;
|
||||
while (node) {
|
||||
if (node === testAncestor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node instanceof HTMLElement) {
|
||||
const flowToParentElement = getParentFlowToElement(node);
|
||||
if (flowToParentElement) {
|
||||
node = flowToParentElement;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null {
|
||||
while (node && node.nodeType === node.ELEMENT_NODE) {
|
||||
if (node.classList.contains(clazz)) {
|
||||
@ -1331,8 +1373,8 @@ export function safeInnerHtml(node: HTMLElement, value: string): void {
|
||||
const options = _extInsaneOptions({
|
||||
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
allowedAttributes: {
|
||||
'a': ['href'],
|
||||
'button': ['data-href'],
|
||||
'a': ['href', 'x-dispatch'],
|
||||
'button': ['data-href', 'x-dispatch'],
|
||||
'input': ['type', 'placeholder', 'checked', 'required'],
|
||||
'label': ['for'],
|
||||
'select': ['required'],
|
||||
|
@ -4,11 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IframeUtils } from 'vs/base/browser/iframe';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
|
||||
export interface IStandardMouseMoveEventData {
|
||||
leftButton: boolean;
|
||||
@ -26,7 +24,7 @@ export interface IMouseMoveCallback<R> {
|
||||
}
|
||||
|
||||
export interface IOnStopCallback {
|
||||
(): void;
|
||||
(browserEvent?: MouseEvent | KeyboardEvent): void;
|
||||
}
|
||||
|
||||
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
|
||||
@ -52,7 +50,7 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
|
||||
this._hooks.dispose();
|
||||
}
|
||||
|
||||
public stopMonitoring(invokeStopCallback: boolean): void {
|
||||
public stopMonitoring(invokeStopCallback: boolean, browserEvent?: MouseEvent | KeyboardEvent): void {
|
||||
if (!this.isMonitoring()) {
|
||||
// Not monitoring
|
||||
return;
|
||||
@ -66,7 +64,7 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
|
||||
this._onStopCallback = null;
|
||||
|
||||
if (invokeStopCallback && onStopCallback) {
|
||||
onStopCallback();
|
||||
onStopCallback(browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +88,8 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
|
||||
this._onStopCallback = onStopCallback;
|
||||
|
||||
const windowChain = IframeUtils.getSameOriginWindowChain();
|
||||
const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
|
||||
const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
|
||||
const mouseMove = 'mousemove';
|
||||
const mouseUp = 'mouseup';
|
||||
|
||||
const listenTo: (Document | ShadowRoot)[] = windowChain.map(element => element.window.document);
|
||||
const shadowRoot = dom.getShadowRoot(initialElement);
|
||||
|
25
lib/vscode/src/vs/base/browser/hash.ts
Normal file
25
lib/vscode/src/vs/base/browser/hash.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { StringSHA1, toHexString } from 'vs/base/common/hash';
|
||||
|
||||
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);
|
||||
|
||||
return toHexString(hash);
|
||||
}
|
||||
|
||||
// Otherwise fallback to `StringSHA1`
|
||||
else {
|
||||
const computer = new StringSHA1();
|
||||
computer.update(str);
|
||||
|
||||
return computer.digest();
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ export interface MarkedOptions extends marked.MarkedOptions {
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<HTMLElement>;
|
||||
codeBlockRenderCallback?: () => void;
|
||||
asyncRenderCallback?: () => void;
|
||||
baseUrl?: URI;
|
||||
}
|
||||
|
||||
@ -177,8 +177,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// ignore
|
||||
});
|
||||
|
||||
if (options.codeBlockRenderCallback) {
|
||||
promise.then(options.codeBlockRenderCallback);
|
||||
if (options.asyncRenderCallback) {
|
||||
promise.then(options.asyncRenderCallback);
|
||||
}
|
||||
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
@ -215,11 +215,15 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// Use our own sanitizer so that we can let through only spans.
|
||||
// Otherwise, we'd be letting all html be rendered.
|
||||
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
|
||||
// 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;
|
||||
return match ? html : '';
|
||||
};
|
||||
markedOptions.sanitize = true;
|
||||
markedOptions.silent = true;
|
||||
|
||||
markedOptions.renderer = renderer;
|
||||
|
||||
// values that are too long will freeze the UI
|
||||
@ -240,6 +244,17 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
|
||||
// signal size changes for image tags
|
||||
if (options.asyncRenderCallback) {
|
||||
for (const img of element.getElementsByTagName('img')) {
|
||||
const listener = DOM.addDisposableListener(img, 'load', () => {
|
||||
listener.dispose();
|
||||
options.asyncRenderCallback!();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@ -301,3 +316,76 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips all markdown from `markdown`. For example `# Header` would be output as `Header`.
|
||||
*/
|
||||
export function renderMarkdownAsPlaintext(markdown: IMarkdownString) {
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.code = (code: string): string => {
|
||||
return code;
|
||||
};
|
||||
renderer.blockquote = (quote: string): string => {
|
||||
return quote;
|
||||
};
|
||||
renderer.html = (_html: string): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.heading = (text: string, _level: 1 | 2 | 3 | 4 | 5 | 6, _raw: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.hr = (): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.list = (body: string, _ordered: boolean): string => {
|
||||
return body;
|
||||
};
|
||||
renderer.listitem = (text: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.paragraph = (text: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.table = (header: string, body: string): string => {
|
||||
return header + body + '\n';
|
||||
};
|
||||
renderer.tablerow = (content: string): string => {
|
||||
return content;
|
||||
};
|
||||
renderer.tablecell = (content: string, _flags: {
|
||||
header: boolean;
|
||||
align: 'center' | 'left' | 'right' | null;
|
||||
}): string => {
|
||||
return content + ' ';
|
||||
};
|
||||
renderer.strong = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.em = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.codespan = (code: string): string => {
|
||||
return code;
|
||||
};
|
||||
renderer.br = (): string => {
|
||||
return '\n';
|
||||
};
|
||||
renderer.del = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.image = (_href: string, _title: string, _text: string): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.text = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.link = (_href: string, _title: string, text: string): string => {
|
||||
return text;
|
||||
};
|
||||
// values that are too long will freeze the UI
|
||||
let value = markdown.value ?? '';
|
||||
if (value.length > 100_000) {
|
||||
value = `${value.substr(0, 100_000)}…`;
|
||||
}
|
||||
return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString();
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
protected focusedItem?: number;
|
||||
private focusTracker: DOM.IFocusTracker;
|
||||
|
||||
// Trigger Key Tracking
|
||||
private triggerKeyDown: boolean = false;
|
||||
|
||||
// Elements
|
||||
domNode: HTMLElement;
|
||||
protected actionsList: HTMLElement;
|
||||
@ -74,8 +77,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
private _onDidRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidRun = this._onDidRun.event;
|
||||
|
||||
private _onDidBeforeRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidBeforeRun = this._onDidBeforeRun.event;
|
||||
private _onBeforeRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onBeforeRun = this._onBeforeRun.event;
|
||||
|
||||
constructor(container: HTMLElement, options: IActionBarOptions = {}) {
|
||||
super();
|
||||
@ -96,7 +99,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
|
||||
this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e)));
|
||||
this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e)));
|
||||
this._register(this._actionRunner.onBeforeRun(e => this._onBeforeRun.fire(e)));
|
||||
|
||||
this._actionIds = [];
|
||||
this.viewItems = [];
|
||||
@ -148,6 +151,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
// Staying out of the else branch even if not triggered
|
||||
if (this._triggerKeys.keyDown) {
|
||||
this.doTrigger(event);
|
||||
} else {
|
||||
this.triggerKeyDown = true;
|
||||
}
|
||||
} else {
|
||||
eventHandled = false;
|
||||
@ -164,7 +169,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
|
||||
// Run action on Enter/Space
|
||||
if (this.isTriggerKeyEvent(event)) {
|
||||
if (!this._triggerKeys.keyDown) {
|
||||
if (!this._triggerKeys.keyDown && this.triggerKeyDown) {
|
||||
this.triggerKeyDown = false;
|
||||
this.doTrigger(event);
|
||||
}
|
||||
|
||||
@ -183,6 +189,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
if (DOM.getActiveElement() === this.domNode || !DOM.isAncestor(DOM.getActiveElement(), this.domNode)) {
|
||||
this._onDidBlur.fire();
|
||||
this.focusedItem = undefined;
|
||||
this.triggerKeyDown = false;
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
import 'vs/css!./breadcrumbsWidget';
|
||||
|
||||
export abstract class BreadcrumbsItem {
|
||||
@ -56,7 +56,7 @@ export interface IBreadcrumbsItemEvent {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
const breadcrumbSeparatorIcon = registerIcon('breadcrumb-separator', Codicon.chevronRight);
|
||||
const breadcrumbSeparatorIcon = registerCodicon('breadcrumb-separator', Codicon.chevronRight);
|
||||
|
||||
export class BreadcrumbsWidget {
|
||||
|
||||
|
@ -10,7 +10,6 @@
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
outline-offset: 2px !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@ -24,7 +23,15 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-button > .codicon {
|
||||
.monaco-text-button > .codicon {
|
||||
margin: 0 0.2em;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown > .monaco-dropdown-button {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
@ -9,10 +9,13 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
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 { 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 { CSSIcon, Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
readonly title?: boolean | string;
|
||||
@ -36,7 +39,18 @@ const defaultOptions: IButtonStyles = {
|
||||
buttonForeground: Color.white
|
||||
};
|
||||
|
||||
export class Button extends Disposable {
|
||||
export interface IButton extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
readonly onDidClick: BaseEvent<Event>;
|
||||
label: string;
|
||||
icon: CSSIcon;
|
||||
enabled: boolean;
|
||||
style(styles: IButtonStyles): void;
|
||||
focus(): void;
|
||||
hasFocus(): boolean;
|
||||
}
|
||||
|
||||
export class Button extends Disposable implements IButton {
|
||||
|
||||
private _element: HTMLElement;
|
||||
private options: IButtonOptions;
|
||||
@ -188,8 +202,8 @@ export class Button extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
set icon(iconClassName: string) {
|
||||
this._element.classList.add(iconClassName);
|
||||
set icon(icon: CSSIcon) {
|
||||
this._element.classList.add(...icon.classNames.split(' '));
|
||||
}
|
||||
|
||||
set enabled(value: boolean) {
|
||||
@ -217,47 +231,124 @@ export class Button extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonGroup extends Disposable {
|
||||
private _buttons: Button[] = [];
|
||||
export interface IButtonWithDropdownOptions extends IButtonOptions {
|
||||
readonly contextMenuProvider: IContextMenuProvider;
|
||||
readonly actions: IAction[];
|
||||
readonly actionRunner?: IActionRunner;
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, count: number, options?: IButtonOptions) {
|
||||
export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
|
||||
private readonly button: Button;
|
||||
private readonly dropdownButton: Button;
|
||||
|
||||
readonly element: HTMLElement;
|
||||
readonly onDidClick: BaseEvent<Event>;
|
||||
|
||||
constructor(container: HTMLElement, options: IButtonWithDropdownOptions) {
|
||||
super();
|
||||
|
||||
this.create(container, count, options);
|
||||
this.element = document.createElement('div');
|
||||
this.element.classList.add('monaco-button-dropdown');
|
||||
container.appendChild(this.element);
|
||||
|
||||
this.button = this._register(new Button(this.element, options));
|
||||
this.onDidClick = this.button.onDidClick;
|
||||
|
||||
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportCodicons: true }));
|
||||
this.dropdownButton.element.classList.add('monaco-dropdown-button');
|
||||
this.dropdownButton.icon = Codicon.dropDownButton;
|
||||
this._register(this.dropdownButton.onDidClick(() => {
|
||||
options.contextMenuProvider.showContextMenu({
|
||||
getAnchor: () => this.dropdownButton.element,
|
||||
getActions: () => options.actions,
|
||||
actionRunner: options.actionRunner,
|
||||
onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false')
|
||||
});
|
||||
this.dropdownButton.element.setAttribute('aria-expanded', 'true');
|
||||
}));
|
||||
}
|
||||
|
||||
get buttons(): Button[] {
|
||||
set label(value: string) {
|
||||
this.button.label = value;
|
||||
}
|
||||
|
||||
set icon(icon: CSSIcon) {
|
||||
this.button.icon = icon;
|
||||
}
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
this.button.enabled = enabled;
|
||||
this.dropdownButton.enabled = enabled;
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
return this.button.enabled;
|
||||
}
|
||||
|
||||
style(styles: IButtonStyles): void {
|
||||
this.button.style(styles);
|
||||
this.dropdownButton.style(styles);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
hasFocus(): boolean {
|
||||
return this.button.hasFocus() || this.dropdownButton.hasFocus();
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonBar extends Disposable {
|
||||
|
||||
private _buttons: IButton[] = [];
|
||||
|
||||
constructor(private readonly container: HTMLElement) {
|
||||
super();
|
||||
}
|
||||
|
||||
get buttons(): IButton[] {
|
||||
return this._buttons;
|
||||
}
|
||||
|
||||
private create(container: HTMLElement, count: number, options?: IButtonOptions): void {
|
||||
for (let index = 0; index < count; index++) {
|
||||
const button = this._register(new Button(container, options));
|
||||
this._buttons.push(button);
|
||||
|
||||
// Implement keyboard access in buttons if there are multiple
|
||||
if (count > 1) {
|
||||
this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let eventHandled = true;
|
||||
|
||||
// Next / Previous Button
|
||||
let buttonIndexToFocus: number | undefined;
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow)) {
|
||||
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
|
||||
} else {
|
||||
eventHandled = false;
|
||||
}
|
||||
|
||||
if (eventHandled && typeof buttonIndexToFocus === 'number') {
|
||||
this._buttons[buttonIndexToFocus].focus();
|
||||
EventHelper.stop(e, true);
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
}
|
||||
addButton(options?: IButtonOptions): IButton {
|
||||
const button = this._register(new Button(this.container, options));
|
||||
this.pushButton(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
addButtonWithDropdown(options: IButtonWithDropdownOptions): IButton {
|
||||
const button = this._register(new ButtonWithDropdown(this.container, options));
|
||||
this.pushButton(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
private pushButton(button: IButton): void {
|
||||
this._buttons.push(button);
|
||||
|
||||
const index = this._buttons.length - 1;
|
||||
this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let eventHandled = true;
|
||||
|
||||
// Next / Previous Button
|
||||
let buttonIndexToFocus: number | undefined;
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow)) {
|
||||
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
|
||||
} else {
|
||||
eventHandled = false;
|
||||
}
|
||||
|
||||
if (eventHandled && typeof buttonIndexToFocus === 'number') {
|
||||
this._buttons[buttonIndexToFocus].focus();
|
||||
EventHelper.stop(e, true);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,12 +11,12 @@ import { Color } from 'vs/base/common/color';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Codicon, CSSIcon } from 'vs/base/common/codicons';
|
||||
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
|
||||
export interface ICheckboxOpts extends ICheckboxStyles {
|
||||
readonly actionClassName?: string;
|
||||
readonly icon?: Codicon;
|
||||
readonly icon?: CSSIcon;
|
||||
readonly title: string;
|
||||
readonly isChecked: boolean;
|
||||
}
|
||||
|
Binary file not shown.
@ -7,18 +7,7 @@ import 'vs/css!./codicon/codicon';
|
||||
import 'vs/css!./codicon/codicon-modifications';
|
||||
import 'vs/css!./codicon/codicon-animations';
|
||||
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
|
||||
export const CodiconStyles = new class {
|
||||
onDidChange = iconRegistry.onDidRegister;
|
||||
public getCSS(): string {
|
||||
const rules = [];
|
||||
for (let c of iconRegistry.all) {
|
||||
rules.push(formatRule(c));
|
||||
}
|
||||
return rules.join('\n');
|
||||
}
|
||||
};
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export function formatRule(c: Codicon) {
|
||||
let def = c.definition;
|
||||
|
@ -31,6 +31,10 @@ export const enum AnchorPosition {
|
||||
BELOW, ABOVE
|
||||
}
|
||||
|
||||
export const enum AnchorAxisAlignment {
|
||||
VERTICAL, HORIZONTAL
|
||||
}
|
||||
|
||||
export interface IDelegate {
|
||||
getAnchor(): HTMLElement | IAnchor;
|
||||
render(container: HTMLElement): IDisposable | null;
|
||||
@ -38,6 +42,7 @@ export interface IDelegate {
|
||||
layout?(): void;
|
||||
anchorAlignment?: AnchorAlignment; // default: left
|
||||
anchorPosition?: AnchorPosition; // default: below
|
||||
anchorAxisAlignment?: AnchorAxisAlignment; // default: vertical
|
||||
canRelayout?: boolean; // default: true
|
||||
onDOMEvent?(e: Event, activeElement: HTMLElement): void;
|
||||
onHide?(data?: any): void;
|
||||
@ -66,9 +71,15 @@ export const enum LayoutAnchorPosition {
|
||||
After
|
||||
}
|
||||
|
||||
export enum LayoutAnchorMode {
|
||||
AVOID,
|
||||
ALIGN
|
||||
}
|
||||
|
||||
export interface ILayoutAnchor {
|
||||
offset: number;
|
||||
size: number;
|
||||
mode?: LayoutAnchorMode; // default: AVOID
|
||||
position: LayoutAnchorPosition;
|
||||
}
|
||||
|
||||
@ -78,25 +89,26 @@ export interface ILayoutAnchor {
|
||||
* @returns The view offset within the viewport.
|
||||
*/
|
||||
export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number {
|
||||
const anchorEnd = anchor.offset + anchor.size;
|
||||
const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
|
||||
const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
|
||||
|
||||
if (anchor.position === LayoutAnchorPosition.Before) {
|
||||
if (viewSize <= viewportSize - anchorEnd) {
|
||||
return anchorEnd; // happy case, lay it out after the anchor
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
|
||||
return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor
|
||||
}
|
||||
|
||||
if (viewSize <= anchor.offset) {
|
||||
return anchor.offset - viewSize; // ok case, lay it out before the anchor
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor
|
||||
}
|
||||
|
||||
return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor
|
||||
} else {
|
||||
if (viewSize <= anchor.offset) {
|
||||
return anchor.offset - viewSize; // happy case, lay it out before the anchor
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor
|
||||
}
|
||||
|
||||
if (viewSize <= viewportSize - anchorEnd) {
|
||||
return anchorEnd; // ok case, lay it out after the anchor
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
|
||||
return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor
|
||||
}
|
||||
|
||||
return 0; // sad case, lay it over the anchor
|
||||
@ -270,28 +282,36 @@ export class ContextView extends Disposable {
|
||||
|
||||
const anchorPosition = this.delegate!.anchorPosition || AnchorPosition.BELOW;
|
||||
const anchorAlignment = this.delegate!.anchorAlignment || AnchorAlignment.LEFT;
|
||||
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment || AnchorAxisAlignment.VERTICAL;
|
||||
|
||||
const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
let top: number;
|
||||
let left: number;
|
||||
|
||||
let horizontalAnchor: ILayoutAnchor;
|
||||
if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) {
|
||||
const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
if (anchorAlignment === AnchorAlignment.LEFT) {
|
||||
horizontalAnchor = { offset: around.left, size: 0, position: LayoutAnchorPosition.Before };
|
||||
} else {
|
||||
horizontalAnchor = { offset: around.left + around.width, size: 0, position: LayoutAnchorPosition.After };
|
||||
}
|
||||
top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
|
||||
|
||||
const top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
|
||||
|
||||
// if view intersects vertically with anchor, shift it horizontally
|
||||
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
|
||||
horizontalAnchor.size = around.width;
|
||||
if (anchorAlignment === AnchorAlignment.RIGHT) {
|
||||
horizontalAnchor.offset = around.left;
|
||||
// if view intersects vertically with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
|
||||
horizontalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
}
|
||||
|
||||
const left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
|
||||
left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
|
||||
} else {
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
|
||||
|
||||
// if view intersects horizontally with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
|
||||
verticalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
|
||||
top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
|
||||
}
|
||||
|
||||
this.view.classList.remove('top', 'bottom', 'left', 'right');
|
||||
this.view.classList.add(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
|
||||
|
@ -148,7 +148,8 @@
|
||||
width: fit-content;
|
||||
width: -moz-fit-content;
|
||||
padding: 5px 10px;
|
||||
margin: 4px 5px; /* allows button focus outline to be visible */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 4px 5px; /* allows button focus outline to be visible */
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
@ -11,13 +11,13 @@ import { domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button';
|
||||
import { ButtonBar, IButtonStyles } from 'vs/base/browser/ui/button/button';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
|
||||
export interface IDialogInputOptions {
|
||||
@ -60,10 +60,10 @@ interface ButtonMapEntry {
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
const dialogErrorIcon = registerIcon('dialog-error', Codicon.error);
|
||||
const dialogWarningIcon = registerIcon('dialog-warning', Codicon.warning);
|
||||
const dialogInfoIcon = registerIcon('dialog-info', Codicon.info);
|
||||
const dialogCloseIcon = registerIcon('dialog-close', Codicon.close);
|
||||
const dialogErrorIcon = registerCodicon('dialog-error', Codicon.error);
|
||||
const dialogWarningIcon = registerCodicon('dialog-warning', Codicon.warning);
|
||||
const dialogInfoIcon = registerCodicon('dialog-info', Codicon.info);
|
||||
const dialogCloseIcon = registerCodicon('dialog-close', Codicon.close);
|
||||
|
||||
export class Dialog extends Disposable {
|
||||
private readonly element: HTMLElement;
|
||||
@ -74,7 +74,7 @@ export class Dialog extends Disposable {
|
||||
private readonly iconElement: HTMLElement;
|
||||
private readonly checkbox: SimpleCheckbox | undefined;
|
||||
private readonly toolbarContainer: HTMLElement;
|
||||
private buttonGroup: ButtonGroup | undefined;
|
||||
private buttonBar: ButtonBar | undefined;
|
||||
private styles: IDialogStyles | undefined;
|
||||
private focusToReturn: HTMLElement | undefined;
|
||||
private readonly inputs: InputBox[];
|
||||
@ -173,11 +173,12 @@ export class Dialog extends Disposable {
|
||||
return new Promise<IDialogResult>((resolve) => {
|
||||
clearNode(this.buttonsContainer);
|
||||
|
||||
const buttonGroup = this.buttonGroup = this._register(new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true }));
|
||||
const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer));
|
||||
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
|
||||
|
||||
// Handle button clicks
|
||||
buttonGroup.buttons.forEach((button, index) => {
|
||||
buttonMap.forEach((entry, index) => {
|
||||
const button = this._register(buttonBar.addButton({ title: true }));
|
||||
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
|
||||
|
||||
this._register(button.onDidClick(e => {
|
||||
@ -237,8 +238,8 @@ export class Dialog extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.buttonGroup) {
|
||||
for (const button of this.buttonGroup.buttons) {
|
||||
if (this.buttonBar) {
|
||||
for (const button of this.buttonBar.buttons) {
|
||||
focusableElements.push(button);
|
||||
if (button.hasFocus()) {
|
||||
focusedIndex = focusableElements.length - 1;
|
||||
@ -349,7 +350,7 @@ export class Dialog extends Disposable {
|
||||
} else {
|
||||
buttonMap.forEach((value, index) => {
|
||||
if (value.index === 0) {
|
||||
buttonGroup.buttons[index].focus();
|
||||
buttonBar.buttons[index].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -371,8 +372,8 @@ export class Dialog extends Disposable {
|
||||
this.element.style.backgroundColor = bgColor?.toString() ?? '';
|
||||
this.element.style.border = border;
|
||||
|
||||
if (this.buttonGroup) {
|
||||
this.buttonGroup.buttons.forEach(button => button.style(style));
|
||||
if (this.buttonBar) {
|
||||
this.buttonBar.buttons.forEach(button => button.style(style));
|
||||
}
|
||||
|
||||
if (this.checkbox) {
|
||||
|
@ -12,3 +12,7 @@
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-label > .action-label.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { Emitter } from 'vs/base/common/event';
|
||||
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export interface IKeybindingProvider {
|
||||
(action: IAction): ResolvedKeybinding | undefined;
|
||||
@ -35,6 +36,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
private menuActionsOrProvider: readonly IAction[] | IActionProvider;
|
||||
private dropdownMenu: DropdownMenu | undefined;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItem: HTMLElement | null = null;
|
||||
|
||||
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
@ -56,6 +58,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
this.actionItem = container;
|
||||
|
||||
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
|
||||
this.element = append(el, $('a.action-label'));
|
||||
|
||||
@ -115,6 +119,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.updateEnabled();
|
||||
}
|
||||
|
||||
setActionContext(newContext: unknown): void {
|
||||
@ -134,6 +140,12 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
this.dropdownMenu.show();
|
||||
}
|
||||
}
|
||||
|
||||
protected updateEnabled(): void {
|
||||
const disabled = !this.getAction().enabled;
|
||||
this.actionItem?.classList.toggle('disabled', disabled);
|
||||
this.element?.classList.toggle('disabled', disabled);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IActionWithDropdownActionViewItemOptions extends IActionViewItemOptions {
|
||||
@ -158,7 +170,7 @@ 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-chevron-down', ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
|
||||
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
|
||||
this.dropdownMenuActionViewItem.render(this.element);
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ function getGridLocation(element: HTMLElement): number[] {
|
||||
}
|
||||
|
||||
const index = indexInParent(parentElement);
|
||||
const ancestor = parentElement.parentElement!.parentElement!.parentElement!;
|
||||
const ancestor = parentElement.parentElement!.parentElement!.parentElement!.parentElement!;
|
||||
return [...getGridLocation(ancestor), index];
|
||||
}
|
||||
|
||||
@ -215,6 +215,8 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; }
|
||||
|
||||
set edgeSnapping(edgeSnapping: boolean) { this.gridview.edgeSnapping = edgeSnapping; }
|
||||
|
||||
get element(): HTMLElement { return this.gridview.element; }
|
||||
|
||||
private didLayout = false;
|
||||
|
@ -170,6 +170,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
|
||||
private absoluteOffset: number = 0;
|
||||
private absoluteOrthogonalOffset: number = 0;
|
||||
private absoluteOrthogonalSize: number = 0;
|
||||
|
||||
private _styles: IGridViewStyles;
|
||||
get styles(): IGridViewStyles { return this._styles; }
|
||||
@ -270,6 +271,24 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private _edgeSnapping = false;
|
||||
get edgeSnapping(): boolean { return this._edgeSnapping; }
|
||||
set edgeSnapping(edgeSnapping: boolean) {
|
||||
if (this._edgeSnapping === edgeSnapping) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._edgeSnapping = edgeSnapping;
|
||||
|
||||
for (const child of this.children) {
|
||||
if (child instanceof BranchNode) {
|
||||
child.edgeSnapping = edgeSnapping;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSplitviewEdgeSnappingEnablement();
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly orientation: Orientation,
|
||||
readonly layoutController: ILayoutController,
|
||||
@ -277,6 +296,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
readonly proportionalLayout: boolean,
|
||||
size: number = 0,
|
||||
orthogonalSize: number = 0,
|
||||
edgeSnapping: boolean = false,
|
||||
childDescriptors?: INodeDescriptor[]
|
||||
) {
|
||||
this._styles = styles;
|
||||
@ -355,6 +375,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this._orthogonalSize = size;
|
||||
this.absoluteOffset = ctx.absoluteOffset + offset;
|
||||
this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
|
||||
this.absoluteOrthogonalSize = ctx.absoluteOrthogonalSize;
|
||||
|
||||
this.splitview.layout(ctx.orthogonalSize, {
|
||||
orthogonalSize: size,
|
||||
@ -364,9 +385,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
absoluteOrthogonalSize: ctx.absoluteSize
|
||||
});
|
||||
|
||||
// Disable snapping on views which sit on the edges of the grid
|
||||
this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0;
|
||||
this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize;
|
||||
this.updateSplitviewEdgeSnappingEnablement();
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
@ -607,6 +626,11 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
private updateSplitviewEdgeSnappingEnablement(): void {
|
||||
this.splitview.startSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset > 0;
|
||||
this.splitview.endSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const child of this.children) {
|
||||
child.dispose();
|
||||
@ -775,7 +799,7 @@ export interface INodeDescriptor {
|
||||
|
||||
function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number): T {
|
||||
if (node instanceof BranchNode) {
|
||||
const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize);
|
||||
const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize, node.edgeSnapping);
|
||||
|
||||
let totalSize = 0;
|
||||
|
||||
@ -863,6 +887,10 @@ export class GridView implements IDisposable {
|
||||
this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation);
|
||||
}
|
||||
|
||||
set edgeSnapping(edgeSnapping: boolean) {
|
||||
this.root.edgeSnapping = edgeSnapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first layout controller makes sure layout only propagates
|
||||
* to the views after the very first call to gridview.layout()
|
||||
@ -932,7 +960,7 @@ export class GridView implements IDisposable {
|
||||
|
||||
grandParent.removeChild(parentIndex);
|
||||
|
||||
const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize);
|
||||
const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize, grandParent.edgeSnapping);
|
||||
grandParent.addChild(newParent, parent.size, parentIndex);
|
||||
|
||||
const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size);
|
||||
@ -1205,7 +1233,7 @@ export class GridView implements IDisposable {
|
||||
} as INodeDescriptor;
|
||||
});
|
||||
|
||||
result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, children);
|
||||
result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, undefined, children);
|
||||
} else {
|
||||
result = new LeafNode(deserializer.fromJSON(node.data), orientation, this.layoutController, orthogonalSize, node.size);
|
||||
}
|
||||
|
@ -87,7 +87,6 @@
|
||||
|
||||
.monaco-hover .monaco-tokenized-source {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.monaco-hover .hover-row.status-bar {
|
||||
|
@ -14,7 +14,7 @@ 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';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
@ -24,8 +24,13 @@ export interface IIconLabelCreationOptions {
|
||||
hoverDelegate?: IHoverDelegate;
|
||||
}
|
||||
|
||||
export interface IIconLabelMarkdownString {
|
||||
markdown: IMarkdownString | string | undefined | (() => Promise<IMarkdownString | string | undefined>);
|
||||
markdownNotSupportedFallback: string | undefined;
|
||||
}
|
||||
|
||||
export interface IIconLabelValueOptions {
|
||||
title?: string | IMarkdownString | Promise<IMarkdownString | string | undefined>;
|
||||
title?: string | IIconLabelMarkdownString;
|
||||
descriptionTitle?: string;
|
||||
hideIcon?: boolean;
|
||||
extraClasses?: string[];
|
||||
@ -93,6 +98,8 @@ export class IconLabel extends Disposable {
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
private labelContainer: HTMLElement;
|
||||
|
||||
private hoverDelegate: IHoverDelegate | undefined = undefined;
|
||||
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
|
||||
|
||||
@ -101,10 +108,10 @@ export class IconLabel extends Disposable {
|
||||
|
||||
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
|
||||
|
||||
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
|
||||
this.labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
|
||||
|
||||
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
|
||||
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
|
||||
const nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container'));
|
||||
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);
|
||||
@ -144,7 +151,7 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
|
||||
this.domNode.className = classes.join(' ');
|
||||
this.setupHover(this.domNode.element, options?.title);
|
||||
this.setupHover(this.labelContainer, options?.title);
|
||||
|
||||
this.nameNode.setLabel(label, options);
|
||||
|
||||
@ -164,7 +171,7 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private setupHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
|
||||
private setupHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
|
||||
const previousCustomHover = this.customHovers.get(htmlElement);
|
||||
if (previousCustomHover) {
|
||||
previousCustomHover.dispose();
|
||||
@ -183,22 +190,39 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
|
||||
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString): void {
|
||||
htmlElement.removeAttribute('title');
|
||||
let tooltip: () => Promise<string | IMarkdownString | undefined>;
|
||||
if (isString(markdownTooltip)) {
|
||||
tooltip = async () => markdownTooltip;
|
||||
} else if (isFunction(markdownTooltip.markdown)) {
|
||||
tooltip = markdownTooltip.markdown;
|
||||
} else {
|
||||
const markdown = markdownTooltip.markdown;
|
||||
tooltip = async () => markdown;
|
||||
}
|
||||
// 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;
|
||||
function mouseOver(this: HTMLElement, e: MouseEvent): any {
|
||||
let isHovering = true;
|
||||
if (isHovering) {
|
||||
return;
|
||||
}
|
||||
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
|
||||
isHovering = false;
|
||||
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;
|
||||
}
|
||||
function mouseLeave(this: HTMLElement, e: MouseEvent): any {
|
||||
isHovering = false;
|
||||
}
|
||||
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeave.bind(htmlElement));
|
||||
const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement));
|
||||
setTimeout(async () => {
|
||||
if (isHovering && tooltip) {
|
||||
@ -208,7 +232,7 @@ export class IconLabel extends Disposable {
|
||||
targetElements: [this],
|
||||
dispose: () => { }
|
||||
};
|
||||
const resolvedTooltip = await tooltip;
|
||||
const resolvedTooltip = await tooltip();
|
||||
if (resolvedTooltip) {
|
||||
hoverOptions = {
|
||||
text: resolvedTooltip,
|
||||
@ -217,7 +241,8 @@ export class IconLabel extends Disposable {
|
||||
};
|
||||
}
|
||||
}
|
||||
if (hoverOptions) {
|
||||
// awaiting the tooltip could take a while. Make sure we're still hovering.
|
||||
if (hoverOptions && isHovering) {
|
||||
if (mouseX !== undefined) {
|
||||
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
|
||||
}
|
||||
@ -225,15 +250,20 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
mouseMoveDisposable.dispose();
|
||||
mouseLeaveDisposable.dispose();
|
||||
}, hoverDelay);
|
||||
}
|
||||
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
|
||||
this.customHovers.set(htmlElement, mouseOverDisposable);
|
||||
}
|
||||
|
||||
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
|
||||
htmlElement.title = isString(tooltip) ? tooltip : '';
|
||||
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
|
||||
let stringTooltip: string = '';
|
||||
if (isString(tooltip)) {
|
||||
stringTooltip = tooltip;
|
||||
} else if (tooltip?.markdownNotSupportedFallback) {
|
||||
stringTooltip = tooltip.markdownNotSupportedFallback;
|
||||
}
|
||||
htmlElement.title = stringTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,12 @@
|
||||
}
|
||||
|
||||
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
|
||||
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
|
||||
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
|
||||
.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description {
|
||||
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -327,7 +327,6 @@ 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: true,
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
|
||||
@ -1323,6 +1322,9 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
if (item.row) {
|
||||
const renderer = this.renderers.get(item.row.templateId);
|
||||
if (renderer) {
|
||||
if (renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, -1, item.row.templateData, undefined);
|
||||
}
|
||||
renderer.disposeTemplate(item.row.templateData);
|
||||
}
|
||||
}
|
||||
|
@ -318,10 +318,12 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private onEscape(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.list.setSelection([], e.browserEvent);
|
||||
this.view.domNode.focus();
|
||||
if (this.list.getSelection().length) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.list.setSelection([], e.browserEvent);
|
||||
this.view.domNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@ -1460,6 +1462,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex));
|
||||
|
||||
if (this.view.getScrollTop() !== previousScrollTop) {
|
||||
this.setFocus([]);
|
||||
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
|
||||
}
|
||||
@ -1492,6 +1496,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
this.view.setScrollTop(scrollTop - this.view.renderHeight);
|
||||
|
||||
if (this.view.getScrollTop() !== previousScrollTop) {
|
||||
this.setFocus([]);
|
||||
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ 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, registerIcon, stripCodicons } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon, stripCodicons } 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';
|
||||
@ -27,8 +27,8 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
|
||||
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
|
||||
|
||||
const menuSelectionIcon = registerIcon('menu-selection', Codicon.check);
|
||||
const menuSubmenuIcon = registerIcon('menu-submenu', Codicon.chevronRight);
|
||||
const menuSelectionIcon = registerCodicon('menu-selection', Codicon.check);
|
||||
const menuSubmenuIcon = registerCodicon('menu-submenu', Codicon.chevronRight);
|
||||
|
||||
export enum Direction {
|
||||
Right,
|
||||
@ -728,6 +728,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
|
||||
if (this.item) {
|
||||
this.item.classList.add('monaco-submenu-item');
|
||||
this.item.tabIndex = 0;
|
||||
this.item.setAttribute('aria-haspopup', 'true');
|
||||
this.updateAriaExpanded('false');
|
||||
this.submenuIndicator = append(this.item, $('span.submenu-indicator' + menuSubmenuIcon.cssSelector));
|
||||
@ -777,6 +778,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
}));
|
||||
}
|
||||
|
||||
updateEnabled(): void {
|
||||
// override on submenu entry
|
||||
// native menus do not observe enablement on sumbenus
|
||||
// we mimic that behavior
|
||||
}
|
||||
|
||||
open(selectFirst?: boolean): void {
|
||||
this.cleanupExistingSubmenu(false);
|
||||
this.createSubmenu(selectFirst);
|
||||
@ -1189,6 +1196,7 @@ ${formatRule(menuSubmenuIcon)}
|
||||
outline: 0;
|
||||
border: none;
|
||||
animation: fadeIn 0.083s linear;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.context-view.monaco-menu-container :focus,
|
||||
|
@ -21,11 +21,11 @@ import { asArray } from 'vs/base/common/arrays';
|
||||
import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
const menuBarMoreIcon = registerIcon('menubar-more', Codicon.more);
|
||||
const menuBarMoreIcon = registerCodicon('menubar-more', Codicon.more);
|
||||
|
||||
export interface IMenuBarOptions {
|
||||
enableMnemonics?: boolean;
|
||||
@ -115,7 +115,7 @@ export class MenuBar extends Disposable {
|
||||
this.menuUpdater = this._register(new RunOnceScheduler(() => this.update(), 200));
|
||||
|
||||
this.actionRunner = this._register(new ActionRunner());
|
||||
this._register(this.actionRunner.onDidBeforeRun(() => {
|
||||
this._register(this.actionRunner.onBeforeRun(() => {
|
||||
this.setUnfocusedState();
|
||||
}));
|
||||
|
||||
|
@ -42,7 +42,10 @@
|
||||
* The progress bit has a width: 2% (1/50) of the parent container. The animation moves it from 0% to 100% of
|
||||
* that container. Since translateX is relative to the progress bit size, we have to multiple it with
|
||||
* its relative size to the parent container:
|
||||
* 50%: 50 * 50 = 2500%
|
||||
* 100%: 50 * 100 - 50 (do not overflow): 4950%
|
||||
* parent width: 5000%
|
||||
* bit width: 100%
|
||||
* translateX should be as follow:
|
||||
* 50%: 5000% * 50% - 50% (set to center) = 2450%
|
||||
* 100%: 5000% * 100% - 100% (do not overflow) = 4900%
|
||||
*/
|
||||
@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
|
||||
@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4900%) scaleX(1) } }
|
||||
|
@ -58,6 +58,7 @@ export class ProgressBar extends Disposable {
|
||||
this.element = document.createElement('div');
|
||||
this.element.classList.add('monaco-progress-container');
|
||||
this.element.setAttribute('role', 'progressbar');
|
||||
this.element.setAttribute('aria-valuemin', '0');
|
||||
container.appendChild(this.element);
|
||||
|
||||
this.bit = document.createElement('div');
|
||||
|
@ -60,19 +60,48 @@
|
||||
height: var(--sash-size);
|
||||
}
|
||||
|
||||
.monaco-sash:not(.disabled).orthogonal-start::before, .monaco-sash:not(.disabled).orthogonal-end::after {
|
||||
content: ' ';
|
||||
.monaco-sash:not(.disabled).orthogonal-start::before,
|
||||
.monaco-sash:not(.disabled).orthogonal-end::after {
|
||||
content: " ";
|
||||
height: calc(var(--sash-size) * 2);
|
||||
width: calc(var(--sash-size) * 2);
|
||||
z-index: 100;
|
||||
display: block;
|
||||
cursor: all-scroll; position: absolute;
|
||||
cursor: all-scroll;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-start.vertical::before { left: -calc(var(--sash-size) / 2); top: calc(var(--sash-size) * -1); }
|
||||
.monaco-sash.orthogonal-end.vertical::after { left: -calc(var(--sash-size) / 2); bottom: calc(var(--sash-size) * -1); }
|
||||
.monaco-sash.orthogonal-start.horizontal::before { top: -calc(var(--sash-size) / 2); left: calc(var(--sash-size) * -1); }
|
||||
.monaco-sash.orthogonal-end.horizontal::after { top: -calc(var(--sash-size) / 2); right: calc(var(--sash-size) * -1); }
|
||||
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-start::before,
|
||||
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-end::after {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-end::after,
|
||||
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-start::before {
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-start.vertical::before {
|
||||
left: -calc(var(--sash-size) / 2);
|
||||
top: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.orthogonal-end.vertical::after {
|
||||
left: -calc(var(--sash-size) / 2);
|
||||
bottom: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.orthogonal-start.horizontal::before {
|
||||
top: -calc(var(--sash-size) / 2);
|
||||
left: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.orthogonal-end.horizontal::after {
|
||||
top: -calc(var(--sash-size) / 2);
|
||||
right: calc(var(--sash-size) * -1);
|
||||
}
|
||||
|
||||
.monaco-sash {
|
||||
transition: background-color 0.1s ease-out;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/** Debug **/
|
||||
|
||||
|
@ -37,11 +37,19 @@ export interface ISashEvent {
|
||||
altKey: boolean;
|
||||
}
|
||||
|
||||
export enum OrthogonalEdge {
|
||||
North = 'north',
|
||||
South = 'south',
|
||||
East = 'east',
|
||||
West = 'west'
|
||||
}
|
||||
|
||||
export interface ISashOptions {
|
||||
readonly orientation: Orientation;
|
||||
readonly orthogonalStartSash?: Sash;
|
||||
readonly orthogonalEndSash?: Sash;
|
||||
readonly size?: number;
|
||||
readonly orthogonalEdge?: OrthogonalEdge;
|
||||
}
|
||||
|
||||
export interface IVerticalSashOptions extends ISashOptions {
|
||||
@ -150,6 +158,10 @@ export class Sash extends Disposable {
|
||||
|
||||
this.el = append(container, $('.monaco-sash'));
|
||||
|
||||
if (options.orthogonalEdge) {
|
||||
this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`);
|
||||
}
|
||||
|
||||
if (isMacintosh) {
|
||||
this.el.classList.add('mac');
|
||||
}
|
||||
|
@ -38,12 +38,14 @@ export interface AbstractScrollbarOptions {
|
||||
visibility: ScrollbarVisibility;
|
||||
extraScrollbarClassName: string;
|
||||
scrollable: Scrollable;
|
||||
scrollByPage: boolean;
|
||||
}
|
||||
|
||||
export abstract class AbstractScrollbar extends Widget {
|
||||
|
||||
protected _host: ScrollbarHost;
|
||||
protected _scrollable: Scrollable;
|
||||
protected _scrollByPage: boolean;
|
||||
private _lazyRender: boolean;
|
||||
protected _scrollbarState: ScrollbarState;
|
||||
private _visibilityController: ScrollbarVisibilityController;
|
||||
@ -59,6 +61,7 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
this._lazyRender = opts.lazyRender;
|
||||
this._host = opts.host;
|
||||
this._scrollable = opts.scrollable;
|
||||
this._scrollByPage = opts.scrollByPage;
|
||||
this._scrollbarState = opts.scrollbarState;
|
||||
this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));
|
||||
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
||||
@ -210,7 +213,14 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
offsetX = e.posx - domNodePosition.left;
|
||||
offsetY = e.posy - domNodePosition.top;
|
||||
}
|
||||
this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(offsetX, offsetY)));
|
||||
|
||||
const offset = this._mouseDownRelativePosition(offsetX, offsetY);
|
||||
this._setDesiredScrollPositionNow(
|
||||
this._scrollByPage
|
||||
? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)
|
||||
: this._scrollbarState.getDesiredScrollPositionFromOffset(offset)
|
||||
);
|
||||
|
||||
if (e.leftButton) {
|
||||
e.preventDefault();
|
||||
this._sliderMouseDown(e, () => { /*nothing to do*/ });
|
||||
|
@ -9,11 +9,11 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
|
||||
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
|
||||
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
|
||||
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
|
||||
|
||||
const scrollbarButtonLeftIcon = registerIcon('scrollbar-button-left', Codicon.triangleLeft);
|
||||
const scrollbarButtonRightIcon = registerIcon('scrollbar-button-right', Codicon.triangleRight);
|
||||
const scrollbarButtonLeftIcon = registerCodicon('scrollbar-button-left', Codicon.triangleLeft);
|
||||
const scrollbarButtonRightIcon = registerCodicon('scrollbar-button-right', Codicon.triangleRight);
|
||||
|
||||
export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
|
||||
@ -33,7 +33,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
),
|
||||
visibility: options.horizontal,
|
||||
extraScrollbarClassName: 'horizontal',
|
||||
scrollable: scrollable
|
||||
scrollable: scrollable,
|
||||
scrollByPage: options.scrollByPage
|
||||
});
|
||||
|
||||
if (options.horizontalHasArrows) {
|
||||
|
@ -361,6 +361,8 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
// console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`);
|
||||
|
||||
let didScroll = false;
|
||||
|
||||
if (e.deltaY || e.deltaX) {
|
||||
let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity;
|
||||
let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity;
|
||||
@ -419,11 +421,12 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
} else {
|
||||
this._scrollable.setScrollPositionNow(desiredScrollPosition);
|
||||
}
|
||||
this._shouldRender = true;
|
||||
|
||||
didScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._options.alwaysConsumeMouseWheel || this._shouldRender) {
|
||||
if (this._options.alwaysConsumeMouseWheel || didScroll) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@ -614,7 +617,9 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme
|
||||
vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.Auto),
|
||||
verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10),
|
||||
verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false),
|
||||
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0)
|
||||
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0),
|
||||
|
||||
scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false)
|
||||
};
|
||||
|
||||
result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize);
|
||||
|
@ -114,6 +114,11 @@ export interface ScrollableElementCreationOptions {
|
||||
* Defaults to false.
|
||||
*/
|
||||
verticalHasArrows?: boolean;
|
||||
/**
|
||||
* Scroll gutter clicks move by page vs. jump to position.
|
||||
* Defaults to false.
|
||||
*/
|
||||
scrollByPage?: boolean;
|
||||
}
|
||||
|
||||
export interface ScrollableElementChangeOptions {
|
||||
@ -146,4 +151,5 @@ export interface ScrollableElementResolvedOptions {
|
||||
verticalScrollbarSize: number;
|
||||
verticalSliderSize: number;
|
||||
verticalHasArrows: boolean;
|
||||
scrollByPage: boolean;
|
||||
}
|
||||
|
@ -202,6 +202,28 @@ export class ScrollbarState {
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a desired `scrollPosition` from if offset is before or after the slider position.
|
||||
* If offset is before slider, treat as a page up (or left). If after, page down (or right).
|
||||
* `offset` and `_computedSliderPosition` are based on the same coordinate system.
|
||||
* `_visibleSize` corresponds to a "page" of lines in the returned coordinate system.
|
||||
*/
|
||||
public getDesiredScrollPositionFromOffsetPaged(offset: number): number {
|
||||
if (!this._computedIsNeeded) {
|
||||
// no need for a slider
|
||||
return 0;
|
||||
}
|
||||
|
||||
let correctedOffset = offset - this._arrowSize; // compensate if has arrows
|
||||
let desiredScrollPosition = this._scrollPosition;
|
||||
if (correctedOffset < this._computedSliderPosition) {
|
||||
desiredScrollPosition -= this._visibleSize; // page up/left
|
||||
} else {
|
||||
desiredScrollPosition += this._visibleSize; // page down/right
|
||||
}
|
||||
return desiredScrollPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
|
||||
*/
|
||||
|
@ -9,10 +9,10 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
|
||||
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
|
||||
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
|
||||
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
|
||||
const scrollbarButtonUpIcon = registerIcon('scrollbar-button-up', Codicon.triangleUp);
|
||||
const scrollbarButtonDownIcon = registerIcon('scrollbar-button-down', Codicon.triangleDown);
|
||||
const scrollbarButtonUpIcon = registerCodicon('scrollbar-button-up', Codicon.triangleUp);
|
||||
const scrollbarButtonDownIcon = registerCodicon('scrollbar-button-down', Codicon.triangleDown);
|
||||
|
||||
export class VerticalScrollbar extends AbstractScrollbar {
|
||||
|
||||
@ -33,7 +33,8 @@ export class VerticalScrollbar extends AbstractScrollbar {
|
||||
),
|
||||
visibility: options.vertical,
|
||||
extraScrollbarClassName: 'vertical',
|
||||
scrollable: scrollable
|
||||
scrollable: scrollable,
|
||||
scrollByPage: options.scrollByPage
|
||||
});
|
||||
|
||||
if (options.verticalHasArrows) {
|
||||
|
@ -46,6 +46,7 @@ export interface ISelectBoxOptions {
|
||||
// Utilize optionItem interface to capture all option parameters
|
||||
export interface ISelectOptionItem {
|
||||
text: string;
|
||||
detail?: string;
|
||||
decoratorRight?: string;
|
||||
description?: string;
|
||||
descriptionIsMarkdown?: boolean;
|
||||
|
@ -75,6 +75,15 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-detail {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-left: 3.5px;
|
||||
white-space: nowrap;
|
||||
float: left;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-decorator-right {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
@ -29,6 +29,7 @@ const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
|
||||
interface ISelectListTemplateData {
|
||||
root: HTMLElement;
|
||||
text: HTMLElement;
|
||||
detail: HTMLElement;
|
||||
decoratorRight: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
}
|
||||
@ -42,6 +43,7 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
|
||||
data.disposables = [];
|
||||
data.root = container;
|
||||
data.text = dom.append(container, $('.option-text'));
|
||||
data.detail = dom.append(container, $('.option-detail'));
|
||||
data.decoratorRight = dom.append(container, $('.option-decorator-right'));
|
||||
|
||||
return data;
|
||||
@ -49,12 +51,16 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
|
||||
|
||||
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
|
||||
const data: ISelectListTemplateData = templateData;
|
||||
|
||||
const text = element.text;
|
||||
const detail = element.detail;
|
||||
const decoratorRight = element.decoratorRight;
|
||||
|
||||
const isDisabled = element.isDisabled;
|
||||
|
||||
data.text.textContent = text;
|
||||
data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : '');
|
||||
data.detail.textContent = !!detail ? detail : '';
|
||||
data.decoratorRight.innerText = !!decoratorRight ? decoratorRight : '';
|
||||
|
||||
// pseudo-select disabled option
|
||||
if (isDisabled) {
|
||||
@ -662,7 +668,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
let longestLength = 0;
|
||||
|
||||
this.options.forEach((option, index) => {
|
||||
const len = option.text.length + (!!option.decoratorRight ? option.decoratorRight.length : 0);
|
||||
const detailLength = !!option.detail ? option.detail.length : 0;
|
||||
const rightDecoratorLength = !!option.decoratorRight ? option.decoratorRight.length : 0;
|
||||
|
||||
const len = option.text.length + detailLength + rightDecoratorLength;
|
||||
if (len > longestLength) {
|
||||
longest = index;
|
||||
longestLength = len;
|
||||
@ -697,6 +706,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
accessibilityProvider: {
|
||||
getAriaLabel: element => {
|
||||
let label = element.text;
|
||||
if (element.detail) {
|
||||
label += `. ${element.detail}`;
|
||||
}
|
||||
|
||||
if (element.decoratorRight) {
|
||||
label += `. ${element.decoratorRight}`;
|
||||
}
|
||||
|
@ -38,23 +38,12 @@
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header > .twisties {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform-origin: center;
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
.monaco-pane-view .pane > .pane-header > .codicon:first-of-type {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .twisties {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
|
||||
transform: rotate(90deg);
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .codicon:first-of-type {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the pane, but they aren't yet */
|
||||
|
@ -20,31 +20,36 @@
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container {
|
||||
.monaco-split-view2 > .monaco-scrollable-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view {
|
||||
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view {
|
||||
white-space: initial;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view:not(.visible) {
|
||||
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view:not(.visible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-split-view2.vertical > .split-view-container > .split-view-view {
|
||||
.monaco-split-view2.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-container > .split-view-view {
|
||||
.monaco-split-view2.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2.separator-border > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
.monaco-split-view2.separator-border > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -54,12 +59,12 @@
|
||||
background-color: var(--separator-border);
|
||||
}
|
||||
|
||||
.monaco-split-view2.separator-border.horizontal > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
.monaco-split-view2.separator-border.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
.monaco-split-view2.separator-border.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ import { range, pushToStart, pushToEnd } from 'vs/base/common/arrays';
|
||||
import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { $, append } from 'vs/base/browser/dom';
|
||||
import { $, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export interface ISplitViewStyles {
|
||||
@ -213,6 +215,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
readonly el: HTMLElement;
|
||||
private sashContainer: HTMLElement;
|
||||
private viewContainer: HTMLElement;
|
||||
private scrollable: Scrollable;
|
||||
private scrollableElement: SmoothScrollableElement;
|
||||
private size = 0;
|
||||
private layoutContext: TLayoutContext | undefined;
|
||||
private contentSize = 0;
|
||||
@ -301,7 +305,20 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
container.appendChild(this.el);
|
||||
|
||||
this.sashContainer = append(this.el, $('.sash-container'));
|
||||
this.viewContainer = append(this.el, $('.split-view-container'));
|
||||
this.viewContainer = $('.split-view-container');
|
||||
|
||||
this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame);
|
||||
this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, {
|
||||
vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
|
||||
horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
|
||||
}, this.scrollable));
|
||||
|
||||
this._register(this.scrollableElement.onScroll(e => {
|
||||
this.viewContainer.scrollTop = e.scrollTop;
|
||||
this.viewContainer.scrollLeft = e.scrollLeft;
|
||||
}));
|
||||
|
||||
append(this.el, this.scrollableElement.getDomNode());
|
||||
|
||||
this.style(options.styles || defaultStyles);
|
||||
|
||||
@ -897,6 +914,21 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
// Layout sashes
|
||||
this.sashItems.forEach(item => item.sash.layout());
|
||||
this.updateSashEnablement();
|
||||
this.updateScrollableElement();
|
||||
}
|
||||
|
||||
private updateScrollableElement(): void {
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
height: this.size,
|
||||
scrollHeight: this.contentSize
|
||||
});
|
||||
} else {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
width: this.size,
|
||||
scrollWidth: this.contentSize
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateSashEnablement(): void {
|
||||
|
@ -11,12 +11,12 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, CSSIcon, registerCodicon } from 'vs/base/common/codicons';
|
||||
import { EventMultiplexer } from 'vs/base/common/event';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||
|
||||
const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more);
|
||||
const toolBarMoreIcon = registerCodicon('toolbar-more', Codicon.more);
|
||||
|
||||
export interface IToolBarOptions {
|
||||
orientation?: ActionsOrientation;
|
||||
@ -27,6 +27,7 @@ export interface IToolBarOptions {
|
||||
toggleMenuTitle?: string;
|
||||
anchorAlignmentProvider?: () => AnchorAlignment;
|
||||
renderDropdownAsChildElement?: boolean;
|
||||
moreIcon?: CSSIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,7 +73,7 @@ export class ToolBar extends Disposable {
|
||||
actionViewItemProvider: this.options.actionViewItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
keybindingProvider: this.options.getKeyBinding,
|
||||
classNames: toolBarMoreIcon.classNamesArray,
|
||||
classNames: (options.moreIcon ?? toolBarMoreIcon).classNames,
|
||||
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
|
||||
menuAsChild: !!this.options.renderDropdownAsChildElement
|
||||
}
|
||||
|
@ -400,15 +400,23 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
}
|
||||
|
||||
private renderTwistie(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) {
|
||||
templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray);
|
||||
|
||||
let twistieRendered = false;
|
||||
|
||||
if (this.renderer.renderTwistie) {
|
||||
this.renderer.renderTwistie(node.element, templateData.twistie);
|
||||
twistieRendered = this.renderer.renderTwistie(node.element, templateData.twistie);
|
||||
}
|
||||
|
||||
if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
|
||||
templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray, 'collapsible');
|
||||
if (!twistieRendered) {
|
||||
templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray);
|
||||
}
|
||||
|
||||
templateData.twistie.classList.add('collapsible');
|
||||
templateData.twistie.classList.toggle('collapsed', node.collapsed);
|
||||
} else {
|
||||
templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray, 'collapsible', 'collapsed');
|
||||
templateData.twistie.classList.remove('collapsible', 'collapsed');
|
||||
}
|
||||
|
||||
if (node.collapsible) {
|
||||
@ -811,7 +819,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
const onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
const x = event.screenX - left;
|
||||
const x = event.clientX - left;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
@ -954,6 +962,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly expandOnlyOnDoubleClick?: boolean;
|
||||
readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T
|
||||
}
|
||||
|
||||
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
|
||||
@ -961,7 +970,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly dnd?: ITreeDragAndDrop<T>;
|
||||
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
|
||||
readonly additionalScrollHeight?: number;
|
||||
}
|
||||
|
||||
@ -1109,7 +1117,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
expandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick;
|
||||
}
|
||||
|
||||
if (expandOnlyOnTwistieClick && !onTwistie) {
|
||||
if (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) {
|
||||
return super.onViewPointer(e);
|
||||
}
|
||||
|
||||
|
@ -110,10 +110,11 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
if (element.slow) {
|
||||
twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray);
|
||||
return true;
|
||||
} else {
|
||||
twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
@ -1053,10 +1054,11 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
if (element.slow) {
|
||||
twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray);
|
||||
return true;
|
||||
} else {
|
||||
twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
|
@ -38,8 +38,8 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
|
||||
const elements = [element.element];
|
||||
const incompressible = element.incompressible || false;
|
||||
|
||||
let childrenIterator: Iterable<ITreeElement<T>>;
|
||||
let children: ITreeElement<T>[];
|
||||
let childrenIterator: Iterable<ICompressedTreeElement<T>>;
|
||||
let children: ICompressedTreeElement<T>[];
|
||||
|
||||
while (true) {
|
||||
[children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2);
|
||||
@ -48,12 +48,11 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
|
||||
break;
|
||||
}
|
||||
|
||||
element = children[0];
|
||||
|
||||
if (element.incompressible) {
|
||||
if (children[0].incompressible) {
|
||||
break;
|
||||
}
|
||||
|
||||
element = children[0];
|
||||
elements.push(element.element);
|
||||
}
|
||||
|
||||
|
@ -128,10 +128,11 @@ class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateDat
|
||||
this.renderer.disposeTemplate(templateData.data);
|
||||
}
|
||||
|
||||
renderTwistie?(element: T, twistieElement: HTMLElement): void {
|
||||
renderTwistie?(element: T, twistieElement: HTMLElement): boolean {
|
||||
if (this.renderer.renderTwistie) {
|
||||
this.renderer.renderTwistie(element, twistieElement);
|
||||
return this.renderer.renderTwistie(element, twistieElement);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
|
||||
}
|
||||
|
||||
export interface ITreeRenderer<T, TFilterData = void, TTemplateData = void> extends IListRenderer<ITreeNode<T, TFilterData>, TTemplateData> {
|
||||
renderTwistie?(element: T, twistieElement: HTMLElement): void;
|
||||
renderTwistie?(element: T, twistieElement: HTMLElement): boolean;
|
||||
onDidChangeTwistieState?: Event<T>;
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
|
||||
export const treeItemExpandedIcon = registerIcon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
|
||||
export const treeItemExpandedIcon = registerCodicon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
|
||||
|
||||
export const treeFilterOnTypeOnIcon = registerIcon('tree-filter-on-type-on', Codicon.listFilter);
|
||||
export const treeFilterOnTypeOffIcon = registerIcon('tree-filter-on-type-off', Codicon.listSelection);
|
||||
export const treeFilterClearIcon = registerIcon('tree-filter-clear', Codicon.close);
|
||||
export const treeFilterOnTypeOnIcon = registerCodicon('tree-filter-on-type-on', Codicon.listFilter);
|
||||
export const treeFilterOnTypeOffIcon = registerCodicon('tree-filter-on-type-off', Codicon.listSelection);
|
||||
export const treeFilterClearIcon = registerCodicon('tree-filter-clear', Codicon.close);
|
||||
|
||||
export const treeItemLoadingIcon = registerIcon('tree-item-loading', Codicon.loading);
|
||||
export const treeItemLoadingIcon = registerCodicon('tree-item-loading', Codicon.loading);
|
||||
|
@ -36,7 +36,7 @@ export interface IAction extends IDisposable {
|
||||
export interface IActionRunner extends IDisposable {
|
||||
run(action: IAction, context?: any): Promise<any>;
|
||||
readonly onDidRun: Event<IRunEvent>;
|
||||
readonly onDidBeforeRun: Event<IRunEvent>;
|
||||
readonly onBeforeRun: Event<IRunEvent>;
|
||||
}
|
||||
|
||||
export interface IActionViewItem extends IDisposable {
|
||||
@ -178,8 +178,8 @@ export interface IRunEvent {
|
||||
|
||||
export class ActionRunner extends Disposable implements IActionRunner {
|
||||
|
||||
private _onDidBeforeRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidBeforeRun: Event<IRunEvent> = this._onDidBeforeRun.event;
|
||||
private _onBeforeRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onBeforeRun: Event<IRunEvent> = this._onBeforeRun.event;
|
||||
|
||||
private _onDidRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidRun: Event<IRunEvent> = this._onDidRun.event;
|
||||
@ -189,7 +189,7 @@ export class ActionRunner extends Disposable implements IActionRunner {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
this._onDidBeforeRun.fire({ action: action });
|
||||
this._onBeforeRun.fire({ action: action });
|
||||
|
||||
try {
|
||||
const result = await this.runAction(action, context);
|
||||
@ -235,6 +235,17 @@ export class Separator extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class ActionWithMenuAction extends Action {
|
||||
|
||||
get actions(): IAction[] {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
constructor(id: string, private _actions: IAction[], label?: string, cssClass?: string, enabled?: boolean, actionCallback?: (event?: any) => Promise<any>) {
|
||||
super(id, label, cssClass, enabled, actionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmenuAction extends Action {
|
||||
|
||||
get actions(): IAction[] {
|
||||
@ -242,7 +253,7 @@ export class SubmenuAction extends Action {
|
||||
}
|
||||
|
||||
constructor(id: string, label: string, private _actions: IAction[], cssClass?: string) {
|
||||
super(id, label, cssClass, true);
|
||||
super(id, label, cssClass, !!_actions?.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
/**
|
||||
@ -18,3 +19,133 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st
|
||||
export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI {
|
||||
return URI.parse(requirefn.toUrl(relativePath));
|
||||
}
|
||||
|
||||
export abstract class LoaderStats {
|
||||
abstract get amdLoad(): [string, number][];
|
||||
abstract get amdInvoke(): [string, number][];
|
||||
abstract get nodeRequire(): [string, number][];
|
||||
abstract get nodeEval(): [string, number][];
|
||||
abstract get nodeRequireTotal(): number;
|
||||
|
||||
|
||||
static get(): LoaderStats {
|
||||
|
||||
|
||||
const amdLoadScript = new Map<string, number>();
|
||||
const amdInvokeFactory = new Map<string, number>();
|
||||
const nodeRequire = new Map<string, number>();
|
||||
const nodeEval = new Map<string, number>();
|
||||
|
||||
function mark(map: Map<string, number>, stat: LoaderEvent) {
|
||||
if (map.has(stat.detail)) {
|
||||
// console.warn('BAD events, DOUBLE start', stat);
|
||||
// map.delete(stat.detail);
|
||||
return;
|
||||
}
|
||||
map.set(stat.detail, -stat.timestamp);
|
||||
}
|
||||
|
||||
function diff(map: Map<string, number>, stat: LoaderEvent) {
|
||||
let duration = map.get(stat.detail);
|
||||
if (!duration) {
|
||||
// console.warn('BAD events, end WITHOUT start', stat);
|
||||
// map.delete(stat.detail);
|
||||
return;
|
||||
}
|
||||
if (duration >= 0) {
|
||||
// console.warn('BAD events, DOUBLE end', stat);
|
||||
// map.delete(stat.detail);
|
||||
return;
|
||||
}
|
||||
map.set(stat.detail, duration + stat.timestamp);
|
||||
}
|
||||
|
||||
const stats = mergeSort(require.getStats().slice(0), (a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
for (const stat of stats) {
|
||||
switch (stat.type) {
|
||||
case LoaderEventType.BeginLoadingScript:
|
||||
mark(amdLoadScript, stat);
|
||||
break;
|
||||
case LoaderEventType.EndLoadingScriptOK:
|
||||
case LoaderEventType.EndLoadingScriptError:
|
||||
diff(amdLoadScript, stat);
|
||||
break;
|
||||
|
||||
case LoaderEventType.BeginInvokeFactory:
|
||||
mark(amdInvokeFactory, stat);
|
||||
break;
|
||||
case LoaderEventType.EndInvokeFactory:
|
||||
diff(amdInvokeFactory, stat);
|
||||
break;
|
||||
|
||||
case LoaderEventType.NodeBeginNativeRequire:
|
||||
mark(nodeRequire, stat);
|
||||
break;
|
||||
case LoaderEventType.NodeEndNativeRequire:
|
||||
diff(nodeRequire, stat);
|
||||
break;
|
||||
|
||||
case LoaderEventType.NodeBeginEvaluatingScript:
|
||||
mark(nodeEval, stat);
|
||||
break;
|
||||
case LoaderEventType.NodeEndEvaluatingScript:
|
||||
diff(nodeEval, stat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let nodeRequireTotal = 0;
|
||||
nodeRequire.forEach(value => nodeRequireTotal += value);
|
||||
|
||||
function to2dArray(map: Map<string, number>): [string, number][] {
|
||||
let res: [string, number][] = [];
|
||||
map.forEach((value, index) => res.push([index, value]));
|
||||
return res;
|
||||
}
|
||||
|
||||
return {
|
||||
amdLoad: to2dArray(amdLoadScript),
|
||||
amdInvoke: to2dArray(amdInvokeFactory),
|
||||
nodeRequire: to2dArray(nodeRequire),
|
||||
nodeEval: to2dArray(nodeEval),
|
||||
nodeRequireTotal
|
||||
};
|
||||
}
|
||||
|
||||
static toMarkdownTable(header: string[], rows: Array<Array<{ toString(): string } | undefined>>): string {
|
||||
let result = '';
|
||||
|
||||
let lengths: number[] = [];
|
||||
header.forEach((cell, ci) => {
|
||||
lengths[ci] = cell.length;
|
||||
});
|
||||
rows.forEach(row => {
|
||||
row.forEach((cell, ci) => {
|
||||
if (typeof cell === 'undefined') {
|
||||
cell = row[ci] = '-';
|
||||
}
|
||||
const len = cell.toString().length;
|
||||
lengths[ci] = Math.max(len, lengths[ci]);
|
||||
});
|
||||
});
|
||||
|
||||
// header
|
||||
header.forEach((cell, ci) => { result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; });
|
||||
result += '|\n';
|
||||
header.forEach((_cell, ci) => { result += `| ${'-'.repeat(lengths[ci])} `; });
|
||||
result += '|\n';
|
||||
|
||||
// cells
|
||||
rows.forEach(row => {
|
||||
row.forEach((cell, ci) => {
|
||||
if (typeof cell !== 'undefined') {
|
||||
result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `;
|
||||
}
|
||||
});
|
||||
result += '|\n';
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -588,3 +588,17 @@ export function asArray<T>(x: T | T[]): T[] {
|
||||
export function getRandomElement<T>(arr: T[]): T | undefined {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first mapped value of the array which is not undefined.
|
||||
*/
|
||||
export function mapFind<T, R>(array: Iterable<T>, mapFn: (value: T) => R | undefined): R | undefined {
|
||||
for (const value of array) {
|
||||
const mapped = mapFn(value);
|
||||
if (mapped !== undefined) {
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
@ -445,6 +445,45 @@ export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t:
|
||||
return loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the first promise that matches the "shouldStop",
|
||||
* running all promises in parallel. Supports cancelable promises.
|
||||
*/
|
||||
export function firstParallel<T>(promiseList: Promise<T>[], shouldStop?: (t: T) => boolean, defaultValue?: T | null): Promise<T | null>;
|
||||
export function firstParallel<T, R extends T>(promiseList: Promise<T>[], shouldStop: (t: T) => t is R, defaultValue?: R | null): Promise<R | null>;
|
||||
export function firstParallel<T>(promiseList: Promise<T>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null) {
|
||||
if (promiseList.length === 0) {
|
||||
return Promise.resolve(defaultValue);
|
||||
}
|
||||
|
||||
let todo = promiseList.length;
|
||||
const finish = () => {
|
||||
todo = -1;
|
||||
for (const promise of promiseList) {
|
||||
(promise as Partial<CancelablePromise<T>>).cancel?.();
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<T | null>((resolve, reject) => {
|
||||
for (const promise of promiseList) {
|
||||
promise.then(result => {
|
||||
if (--todo >= 0 && shouldStop(result)) {
|
||||
finish();
|
||||
resolve(result);
|
||||
} else if (todo === 0) {
|
||||
resolve(defaultValue);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (--todo >= 0) {
|
||||
finish();
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface ILimitedTaskFactory<T> {
|
||||
factory: ITask<Promise<T>>;
|
||||
c: (value: T | Promise<T>) => void;
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
import { codiconStartMarker } from 'vs/base/common/codicon';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface IIconRegistry {
|
||||
readonly all: IterableIterator<Codicon>;
|
||||
@ -18,9 +19,12 @@ class Registry implements IIconRegistry {
|
||||
private readonly _onDidRegister = new Emitter<Codicon>();
|
||||
|
||||
public add(icon: Codicon) {
|
||||
if (!this._icons.has(icon.id)) {
|
||||
const existing = this._icons.get(icon.id);
|
||||
if (!existing) {
|
||||
this._icons.set(icon.id, icon);
|
||||
this._onDidRegister.fire(icon);
|
||||
} else if (icon.description) {
|
||||
existing.description = icon.description;
|
||||
} else {
|
||||
console.error(`Duplicate registration of codicon ${icon.id}`);
|
||||
}
|
||||
@ -43,11 +47,11 @@ const _registry = new Registry();
|
||||
|
||||
export const iconRegistry: IIconRegistry = _registry;
|
||||
|
||||
export function registerIcon(id: string, def: Codicon, description?: string) {
|
||||
return new Codicon(id, def);
|
||||
export function registerCodicon(id: string, def: Codicon, description?: string): Codicon {
|
||||
return new Codicon(id, def, description);
|
||||
}
|
||||
|
||||
export class Codicon {
|
||||
export class Codicon implements CSSIcon {
|
||||
constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) {
|
||||
_registry.add(this);
|
||||
}
|
||||
@ -57,6 +61,12 @@ export class Codicon {
|
||||
public get cssSelector() { return '.codicon.codicon-' + this.id; }
|
||||
}
|
||||
|
||||
export interface CSSIcon {
|
||||
readonly classNames: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface IconDefinition {
|
||||
character: string;
|
||||
}
|
||||
@ -484,8 +494,15 @@ export namespace Codicon {
|
||||
export const redo = new Codicon('redo', { character: '\\ebb0' });
|
||||
export const checkAll = new Codicon('check-all', { character: '\\ebb1' });
|
||||
export const pinnedDirty = new Codicon('pinned-dirty', { character: '\\ebb2' });
|
||||
export const passFilled = new Codicon('pass-filled', { character: '\\ebb3' });
|
||||
export const circleLargeFilled = new Codicon('circle-large-filled', { character: '\\ebb4' });
|
||||
export const circleLargeOutline = new Codicon('circle-large-outline', { character: '\\ebb5' });
|
||||
|
||||
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition, localize('dropDownButton', 'Icon for drop down buttons.'));
|
||||
}
|
||||
|
||||
// common icons
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -76,7 +76,37 @@ export function fromMap<T>(original: Map<string, T>): IStringDictionary<T> {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function diffSets<T>(before: Set<T>, after: Set<T>): { removed: T[], added: T[] } {
|
||||
const removed: T[] = [];
|
||||
const added: T[] = [];
|
||||
for (let element of before) {
|
||||
if (!after.has(element)) {
|
||||
removed.push(element);
|
||||
}
|
||||
}
|
||||
for (let element of after) {
|
||||
if (!before.has(element)) {
|
||||
added.push(element);
|
||||
}
|
||||
}
|
||||
return { removed, added };
|
||||
}
|
||||
|
||||
export function diffMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
|
||||
const removed: V[] = [];
|
||||
const added: V[] = [];
|
||||
for (let [index, value] of before) {
|
||||
if (!after.has(index)) {
|
||||
removed.push(value);
|
||||
}
|
||||
}
|
||||
for (let [index, value] of after) {
|
||||
if (!before.has(index)) {
|
||||
added.push(value);
|
||||
}
|
||||
}
|
||||
return { removed, added };
|
||||
}
|
||||
export class SetMap<K, V> {
|
||||
|
||||
private map = new Map<K, Set<V>>();
|
||||
|
@ -880,9 +880,81 @@ export class LcsDiff {
|
||||
change.modifiedStart -= bestDelta;
|
||||
}
|
||||
|
||||
// There could be multiple longest common substrings.
|
||||
// Give preference to the ones containing longer lines
|
||||
if (this._hasStrings) {
|
||||
for (let i = 1, len = changes.length; i < len; i++) {
|
||||
const aChange = changes[i - 1];
|
||||
const bChange = changes[i];
|
||||
const matchedLength = bChange.originalStart - aChange.originalStart - aChange.originalLength;
|
||||
const aOriginalStart = aChange.originalStart;
|
||||
const bOriginalEnd = bChange.originalStart + bChange.originalLength;
|
||||
const abOriginalLength = bOriginalEnd - aOriginalStart;
|
||||
const aModifiedStart = aChange.modifiedStart;
|
||||
const bModifiedEnd = bChange.modifiedStart + bChange.modifiedLength;
|
||||
const abModifiedLength = bModifiedEnd - aModifiedStart;
|
||||
// Avoid wasting a lot of time with these searches
|
||||
if (matchedLength < 5 && abOriginalLength < 20 && abModifiedLength < 20) {
|
||||
const t = this._findBetterContiguousSequence(
|
||||
aOriginalStart, abOriginalLength,
|
||||
aModifiedStart, abModifiedLength,
|
||||
matchedLength
|
||||
);
|
||||
if (t) {
|
||||
const [originalMatchStart, modifiedMatchStart] = t;
|
||||
if (originalMatchStart !== aChange.originalStart + aChange.originalLength || modifiedMatchStart !== aChange.modifiedStart + aChange.modifiedLength) {
|
||||
// switch to another sequence that has a better score
|
||||
aChange.originalLength = originalMatchStart - aChange.originalStart;
|
||||
aChange.modifiedLength = modifiedMatchStart - aChange.modifiedStart;
|
||||
bChange.originalStart = originalMatchStart + matchedLength;
|
||||
bChange.modifiedStart = modifiedMatchStart + matchedLength;
|
||||
bChange.originalLength = bOriginalEnd - bChange.originalStart;
|
||||
bChange.modifiedLength = bModifiedEnd - bChange.modifiedStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private _findBetterContiguousSequence(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number, desiredLength: number): [number, number] | null {
|
||||
if (originalLength < desiredLength || modifiedLength < desiredLength) {
|
||||
return null;
|
||||
}
|
||||
const originalMax = originalStart + originalLength - desiredLength + 1;
|
||||
const modifiedMax = modifiedStart + modifiedLength - desiredLength + 1;
|
||||
let bestScore = 0;
|
||||
let bestOriginalStart = 0;
|
||||
let bestModifiedStart = 0;
|
||||
for (let i = originalStart; i < originalMax; i++) {
|
||||
for (let j = modifiedStart; j < modifiedMax; j++) {
|
||||
const score = this._contiguousSequenceScore(i, j, desiredLength);
|
||||
if (score > 0 && score > bestScore) {
|
||||
bestScore = score;
|
||||
bestOriginalStart = i;
|
||||
bestModifiedStart = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestScore > 0) {
|
||||
return [bestOriginalStart, bestModifiedStart];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _contiguousSequenceScore(originalStart: number, modifiedStart: number, length: number): number {
|
||||
let score = 0;
|
||||
for (let l = 0; l < length; l++) {
|
||||
if (!this.ElementsAreEqual(originalStart + l, modifiedStart + l)) {
|
||||
return 0;
|
||||
}
|
||||
score += this._originalStringElements[originalStart + l].length;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
private _OriginalIsBoundary(index: number): boolean {
|
||||
if (index <= 0 || index >= this._originalElementsOrHash.length - 1) {
|
||||
return true;
|
||||
|
@ -629,7 +629,7 @@ export class PauseableEmitter<T> extends Emitter<T> {
|
||||
if (this._mergeFn) {
|
||||
// use the merge function to create a single composite
|
||||
// event. make a copy in case firing pauses this emitter
|
||||
const events = this._eventQueue.toArray();
|
||||
const events = Array.from(this._eventQueue);
|
||||
this._eventQueue.clear();
|
||||
super.fire(this._mergeFn(events));
|
||||
|
||||
|
@ -106,8 +106,14 @@ function leftPad(value: string, length: number, char: string = '0'): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
function toHexString(value: number, bitsize: number = 32): string {
|
||||
return leftPad((value >>> 0).toString(16), bitsize / 4);
|
||||
export function toHexString(buffer: ArrayBuffer): string;
|
||||
export function toHexString(value: number, bitsize?: number): string;
|
||||
export function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {
|
||||
if (bufferOrValue instanceof ArrayBuffer) {
|
||||
return Array.from(new Uint8Array(bufferOrValue)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
return leftPad((bufferOrValue >>> 0).toString(16), bitsize / 4);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,52 +21,52 @@ export const enum MarkdownStringTextNewlineStyle {
|
||||
}
|
||||
|
||||
export class MarkdownString implements IMarkdownString {
|
||||
private readonly _isTrusted: boolean;
|
||||
private readonly _supportThemeIcons: boolean;
|
||||
|
||||
public value: string;
|
||||
public isTrusted?: boolean;
|
||||
public supportThemeIcons?: boolean;
|
||||
|
||||
constructor(
|
||||
private _value: string = '',
|
||||
value: string = '',
|
||||
isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false,
|
||||
) {
|
||||
if (typeof this._value !== 'string') {
|
||||
this.value = value;
|
||||
if (typeof this.value !== 'string') {
|
||||
throw illegalArgument('value');
|
||||
}
|
||||
|
||||
if (typeof isTrustedOrOptions === 'boolean') {
|
||||
this._isTrusted = isTrustedOrOptions;
|
||||
this._supportThemeIcons = false;
|
||||
this.isTrusted = isTrustedOrOptions;
|
||||
this.supportThemeIcons = false;
|
||||
}
|
||||
else {
|
||||
this._isTrusted = isTrustedOrOptions.isTrusted ?? false;
|
||||
this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
|
||||
this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined;
|
||||
this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
get value() { return this._value; }
|
||||
get isTrusted() { return this._isTrusted; }
|
||||
get supportThemeIcons() { return this._supportThemeIcons; }
|
||||
|
||||
appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString {
|
||||
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
this._value += (this._supportThemeIcons ? escapeCodicons(value) : value)
|
||||
this.value += (this.supportThemeIcons ? escapeCodicons(value) : value)
|
||||
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
|
||||
.replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length))
|
||||
.replace(/^>/gm, '\\>')
|
||||
.replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
appendMarkdown(value: string): MarkdownString {
|
||||
this._value += value;
|
||||
|
||||
this.value += value;
|
||||
return this;
|
||||
}
|
||||
|
||||
appendCodeblock(langId: string, code: string): MarkdownString {
|
||||
this._value += '\n```';
|
||||
this._value += langId;
|
||||
this._value += '\n';
|
||||
this._value += code;
|
||||
this._value += '\n```\n';
|
||||
this.value += '\n```';
|
||||
this.value += langId;
|
||||
this.value += '\n';
|
||||
this.value += code;
|
||||
this.value += '\n```\n';
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ export interface Location {
|
||||
/**
|
||||
* Matches the locations path against a pattern consisting of strings (for properties) and numbers (for array indices).
|
||||
* '*' will match a single segment, of any property name or index.
|
||||
* '**' will match a sequece of segments or no segment, of any property name or index.
|
||||
* '**' will match a sequence of segments or no segment, of any property name or index.
|
||||
*/
|
||||
matches: (patterns: JSONPath) => boolean;
|
||||
/**
|
||||
|
@ -95,6 +95,10 @@ function hasDriveLetter(path: string): boolean {
|
||||
return !!(isWindows && path && path[1] === ':');
|
||||
}
|
||||
|
||||
export function extractDriveLetter(path: string): string | undefined {
|
||||
return hasDriveLetter(path) ? path[0] : undefined;
|
||||
}
|
||||
|
||||
export function normalizeDriveLetter(path: string): string {
|
||||
if (hasDriveLetter(path)) {
|
||||
return path.charAt(0).toUpperCase() + path.slice(1);
|
||||
|
@ -131,12 +131,4 @@ export class LinkedList<E> {
|
||||
node = node.next;
|
||||
}
|
||||
}
|
||||
|
||||
toArray(): E[] {
|
||||
const result: E[] = [];
|
||||
for (let node = this._first; node !== Node.Undefined; node = node.next) {
|
||||
result.push(node.element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { compareSubstringIgnoreCase, compare, compareSubstring, compareIgnoreCase } from 'vs/base/common/strings';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
let result = map.get(key);
|
||||
@ -77,6 +75,57 @@ export class StringIterator implements IKeyIterator<string> {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigKeysIterator implements IKeyIterator<string> {
|
||||
|
||||
private _value!: string;
|
||||
private _from!: number;
|
||||
private _to!: number;
|
||||
|
||||
constructor(
|
||||
private readonly _caseSensitive: boolean = true
|
||||
) { }
|
||||
|
||||
reset(key: string): this {
|
||||
this._value = key;
|
||||
this._from = 0;
|
||||
this._to = 0;
|
||||
return this.next();
|
||||
}
|
||||
|
||||
hasNext(): boolean {
|
||||
return this._to < this._value.length;
|
||||
}
|
||||
|
||||
next(): this {
|
||||
// this._data = key.split(/[\\/]/).filter(s => !!s);
|
||||
this._from = this._to;
|
||||
let justSeps = true;
|
||||
for (; this._to < this._value.length; this._to++) {
|
||||
const ch = this._value.charCodeAt(this._to);
|
||||
if (ch === CharCode.Period) {
|
||||
if (justSeps) {
|
||||
this._from++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
justSeps = false;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
cmp(a: string): number {
|
||||
return this._caseSensitive
|
||||
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
|
||||
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
|
||||
}
|
||||
|
||||
value(): string {
|
||||
return this._value.substring(this._from, this._to);
|
||||
}
|
||||
}
|
||||
|
||||
export class PathIterator implements IKeyIterator<string> {
|
||||
|
||||
private _value!: string;
|
||||
@ -140,7 +189,7 @@ export class UriIterator implements IKeyIterator<URI> {
|
||||
private _states: UriIteratorState[] = [];
|
||||
private _stateIdx: number = 0;
|
||||
|
||||
constructor(private readonly _ignorePathCasing: boolean | undefined) { }
|
||||
constructor(private readonly _ignorePathCasing: (uri: URI) => boolean) { }
|
||||
|
||||
reset(key: URI): this {
|
||||
this._value = key;
|
||||
@ -152,10 +201,7 @@ export class UriIterator implements IKeyIterator<URI> {
|
||||
this._states.push(UriIteratorState.Authority);
|
||||
}
|
||||
if (this._value.path) {
|
||||
this._pathIterator = new PathIterator(false, this._ignorePathCasing === undefined
|
||||
? key.scheme === Schemas.file && isLinux
|
||||
: !this._ignorePathCasing
|
||||
);
|
||||
this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key));
|
||||
this._pathIterator.reset(key.path);
|
||||
if (this._pathIterator.value()) {
|
||||
this._states.push(UriIteratorState.Path);
|
||||
@ -231,7 +277,7 @@ class TernarySearchTreeNode<K, V> {
|
||||
|
||||
export class TernarySearchTree<K, V> {
|
||||
|
||||
static forUris<E>(ignorePathCasing?: boolean): TernarySearchTree<URI, E> {
|
||||
static forUris<E>(ignorePathCasing: (key: URI) => boolean = () => false): TernarySearchTree<URI, E> {
|
||||
return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing));
|
||||
}
|
||||
|
||||
@ -243,6 +289,10 @@ export class TernarySearchTree<K, V> {
|
||||
return new TernarySearchTree<string, E>(new StringIterator());
|
||||
}
|
||||
|
||||
static forConfigKeys<E>(): TernarySearchTree<string, E> {
|
||||
return new TernarySearchTree<string, E>(new ConfigKeysIterator());
|
||||
}
|
||||
|
||||
private _iter: IKeyIterator<K>;
|
||||
private _root: TernarySearchTreeNode<K, V> | undefined;
|
||||
|
||||
@ -301,6 +351,10 @@ export class TernarySearchTree<K, V> {
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
return this._getNode(key)?.value;
|
||||
}
|
||||
|
||||
private _getNode(key: K) {
|
||||
const iter = this._iter.reset(key);
|
||||
let node = this._root;
|
||||
while (node) {
|
||||
@ -319,7 +373,12 @@ export class TernarySearchTree<K, V> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return node ? node.value : undefined;
|
||||
return node;
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
const node = this._getNode(key);
|
||||
return !(node?.value === undefined && node?.mid === undefined);
|
||||
}
|
||||
|
||||
delete(key: K): void {
|
||||
@ -352,11 +411,18 @@ export class TernarySearchTree<K, V> {
|
||||
stack.push([0, node]);
|
||||
node = node.mid;
|
||||
} else {
|
||||
// remove element
|
||||
node.value = undefined;
|
||||
if (superStr) {
|
||||
// remove children
|
||||
node.left = undefined;
|
||||
node.mid = undefined;
|
||||
node.right = undefined;
|
||||
} else {
|
||||
// remove element
|
||||
node.value = undefined;
|
||||
}
|
||||
|
||||
// clean up empty nodes
|
||||
while (stack.length > 0 && (node.isEmpty() || superStr)) {
|
||||
while (stack.length > 0 && node.isEmpty()) {
|
||||
let [dir, parent] = stack.pop()!;
|
||||
switch (dir) {
|
||||
case 1: parent.left = undefined; break;
|
||||
@ -394,7 +460,7 @@ export class TernarySearchTree<K, V> {
|
||||
return node && node.value || candidate;
|
||||
}
|
||||
|
||||
findSuperstr(key: K): Iterator<V> | undefined {
|
||||
findSuperstr(key: K): IterableIterator<[K, V]> | undefined {
|
||||
const iter = this._iter.reset(key);
|
||||
let node = this._root;
|
||||
while (node) {
|
||||
@ -414,7 +480,7 @@ export class TernarySearchTree<K, V> {
|
||||
if (!node.mid) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._values(node.mid);
|
||||
return this._entries(node.mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -431,12 +497,6 @@ export class TernarySearchTree<K, V> {
|
||||
yield* this._entries(this._root);
|
||||
}
|
||||
|
||||
private *_values(node: TernarySearchTreeNode<K, V>): IterableIterator<V> {
|
||||
for (const [, value] of this._entries(node)) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
|
||||
private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
|
||||
if (node) {
|
||||
// left
|
||||
|
@ -6,11 +6,11 @@
|
||||
"git": {
|
||||
"name": "marked",
|
||||
"repositoryUrl": "https://github.com/markedjs/marked",
|
||||
"commitHash": "529a8d4e185a8aa561e4d8d2891f8556b5717cd4"
|
||||
"commitHash": "8cfa29ccd2a2759e8e60fe0d8d6df8c022beda4e"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "0.6.2"
|
||||
"version": "1.1.0"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
|
@ -12,7 +12,7 @@
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, global.marked = factory());
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.marked = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
function _defineProperties(target, props) {
|
||||
@ -368,11 +368,28 @@
|
||||
|
||||
function checkSanitizeDeprecation(opt) {
|
||||
if (opt && opt.sanitize && !opt.silent) {
|
||||
// VS CODE CHANGE
|
||||
// Disable logging about sanitize options. We already use insane after running the sanitizer
|
||||
|
||||
// console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
|
||||
console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
|
||||
}
|
||||
} // copied from https://stackoverflow.com/a/5450113/806777
|
||||
|
||||
|
||||
function repeatString(pattern, count) {
|
||||
if (count < 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var result = '';
|
||||
|
||||
while (count > 1) {
|
||||
if (count & 1) {
|
||||
result += pattern;
|
||||
}
|
||||
|
||||
count >>= 1;
|
||||
pattern += pattern;
|
||||
}
|
||||
|
||||
return result + pattern;
|
||||
}
|
||||
|
||||
var helpers = {
|
||||
@ -386,7 +403,8 @@
|
||||
splitCells: splitCells,
|
||||
rtrim: rtrim,
|
||||
findClosingBracket: findClosingBracket,
|
||||
checkSanitizeDeprecation: checkSanitizeDeprecation
|
||||
checkSanitizeDeprecation: checkSanitizeDeprecation,
|
||||
repeatString: repeatString
|
||||
};
|
||||
|
||||
var defaults$1 = defaults.defaults;
|
||||
@ -620,7 +638,7 @@
|
||||
// so it is seen as the next token.
|
||||
|
||||
space = item.length;
|
||||
item = item.replace(/^ *([*+-]|\d+[.)]) */, ''); // Outdent whatever the
|
||||
item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the
|
||||
// list item contains. Hacky.
|
||||
|
||||
if (~item.indexOf('\n ')) {
|
||||
@ -1138,11 +1156,11 @@
|
||||
|
||||
block.gfm = merge$1({}, block.normal, {
|
||||
nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header
|
||||
+ ' *([-:]+ *\\|[-| :]*)' // Align
|
||||
+ ' {0,3}([-:]+ *\\|[-| :]*)' // Align
|
||||
+ '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)',
|
||||
// Cells
|
||||
table: '^ *\\|(.+)\\n' // Header
|
||||
+ ' *\\|?( *[-:]+[-| :]*)' // Align
|
||||
+ ' {0,3}\\|?( *[-:]+[-| :]*)' // Align
|
||||
+ '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells
|
||||
|
||||
});
|
||||
@ -1187,24 +1205,24 @@
|
||||
start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,
|
||||
// (1) returns if starts w/ punctuation
|
||||
middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,
|
||||
endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation\s]|$))/,
|
||||
endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/,
|
||||
// last char can't be punct, or final * must also be followed by punct (or endline)
|
||||
endUnd: /[^\s]__(?!_)(?:(?=[punctuation\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
|
||||
endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
|
||||
|
||||
},
|
||||
em: {
|
||||
start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,
|
||||
// (1) returns if starts w/ punctuation
|
||||
middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,
|
||||
endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation\s]|$))/,
|
||||
endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/,
|
||||
// last char can't be punct, or final * must also be followed by punct (or endline)
|
||||
endUnd: /[^\s]_(?!_)(?:(?=[punctuation\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
|
||||
endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
|
||||
|
||||
},
|
||||
code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
|
||||
br: /^( {2,}|\\)\n(?!\s*$)/,
|
||||
del: noopTest$1,
|
||||
text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\<!\[`*]|\b_|$)|[^ ](?= {2,}\n))|(?= {2,}\n))/,
|
||||
text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*]|\b_|$)|[^ ](?= {2,}\n)))/,
|
||||
punctuation: /^([\s*punctuation])/
|
||||
}; // list of punctuation marks from common mark spec
|
||||
// without * and _ to workaround cases with double emphasis
|
||||
@ -1220,7 +1238,7 @@
|
||||
inline.em.endAst = edit$1(inline.em.endAst, 'g').replace(/punctuation/g, inline._punctuation).getRegex();
|
||||
inline.em.endUnd = edit$1(inline.em.endUnd, 'g').replace(/punctuation/g, inline._punctuation).getRegex();
|
||||
inline.strong.start = edit$1(inline.strong.start).replace(/punctuation/g, inline._punctuation).getRegex();
|
||||
inline.strong.middle = edit$1(inline.strong.middle).replace(/punctuation/g, inline._punctuation).replace(/blockSkip/g, inline._blockSkip).getRegex();
|
||||
inline.strong.middle = edit$1(inline.strong.middle).replace(/punctuation/g, inline._punctuation).replace(/overlapSkip/g, inline._overlapSkip).getRegex();
|
||||
inline.strong.endAst = edit$1(inline.strong.endAst, 'g').replace(/punctuation/g, inline._punctuation).getRegex();
|
||||
inline.strong.endUnd = edit$1(inline.strong.endUnd, 'g').replace(/punctuation/g, inline._punctuation).getRegex();
|
||||
inline.blockSkip = edit$1(inline._blockSkip, 'g').getRegex();
|
||||
@ -1272,7 +1290,7 @@
|
||||
url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
|
||||
_backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
|
||||
del: /^~+(?=\S)([\s\S]*?\S)~+/,
|
||||
text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\<!\[`*~]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?= {2,}\n|[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/
|
||||
text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*~]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/
|
||||
});
|
||||
inline.gfm.url = edit$1(inline.gfm.url, 'i').replace('email', inline.gfm._extended_email).getRegex();
|
||||
/**
|
||||
@ -1291,6 +1309,7 @@
|
||||
var defaults$2 = defaults.defaults;
|
||||
var block$1 = rules.block,
|
||||
inline$1 = rules.inline;
|
||||
var repeatString$1 = helpers.repeatString;
|
||||
/**
|
||||
* smartypants text replacement
|
||||
*/
|
||||
@ -1373,6 +1392,15 @@
|
||||
var lexer = new Lexer(options);
|
||||
return lexer.lex(src);
|
||||
}
|
||||
/**
|
||||
* Static Lex Inline Method
|
||||
*/
|
||||
;
|
||||
|
||||
Lexer.lexInline = function lexInline(src, options) {
|
||||
var lexer = new Lexer(options);
|
||||
return lexer.inlineTokens(src);
|
||||
}
|
||||
/**
|
||||
* Preprocessing
|
||||
*/
|
||||
@ -1652,7 +1680,7 @@
|
||||
if (links.length > 0) {
|
||||
while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
|
||||
if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
|
||||
maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
|
||||
maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1660,7 +1688,7 @@
|
||||
|
||||
|
||||
while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
|
||||
maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
|
||||
maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
|
||||
}
|
||||
|
||||
while (src) {
|
||||
@ -2073,6 +2101,15 @@
|
||||
var parser = new Parser(options);
|
||||
return parser.parse(tokens);
|
||||
}
|
||||
/**
|
||||
* Static Parse Inline Method
|
||||
*/
|
||||
;
|
||||
|
||||
Parser.parseInline = function parseInline(tokens, options) {
|
||||
var parser = new Parser(options);
|
||||
return parser.parseInline(tokens);
|
||||
}
|
||||
/**
|
||||
* Parse Loop
|
||||
*/
|
||||
@ -2606,6 +2643,42 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Parse Inline
|
||||
*/
|
||||
|
||||
|
||||
marked.parseInline = function (src, opt) {
|
||||
// throw error in case of non string input
|
||||
if (typeof src === 'undefined' || src === null) {
|
||||
throw new Error('marked.parseInline(): input parameter is undefined or null');
|
||||
}
|
||||
|
||||
if (typeof src !== 'string') {
|
||||
throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
|
||||
}
|
||||
|
||||
opt = merge$2({}, marked.defaults, opt || {});
|
||||
checkSanitizeDeprecation$1(opt);
|
||||
|
||||
try {
|
||||
var tokens = Lexer_1.lexInline(src, opt);
|
||||
|
||||
if (opt.walkTokens) {
|
||||
marked.walkTokens(tokens, opt.walkTokens);
|
||||
}
|
||||
|
||||
return Parser_1.parseInline(tokens, opt);
|
||||
} catch (e) {
|
||||
e.message += '\nPlease report this to https://github.com/markedjs/marked.';
|
||||
|
||||
if (opt.silent) {
|
||||
return '<p>An error occurred:</p><pre>' + escape$2(e.message + '', true) + '</pre>';
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
@ -78,6 +78,12 @@ export namespace Schemas {
|
||||
* Scheme used for extension pages
|
||||
*/
|
||||
export const extension = 'extension';
|
||||
|
||||
/**
|
||||
* Scheme used as a replacement of `file` scheme to load
|
||||
* files with our custom protocol handler (desktop only).
|
||||
*/
|
||||
export const vscodeFileResource = 'vscode-file';
|
||||
}
|
||||
|
||||
class RemoteAuthoritiesImpl {
|
||||
@ -133,6 +139,8 @@ export const RemoteAuthorities = new RemoteAuthoritiesImpl();
|
||||
|
||||
class FileAccessImpl {
|
||||
|
||||
private readonly FALLBACK_AUTHORITY = 'vscode-app';
|
||||
|
||||
/**
|
||||
* Returns a URI to use in contexts where the browser is responsible
|
||||
* for loading (e.g. fetch()) or when used within the DOM.
|
||||
@ -144,13 +152,43 @@ class FileAccessImpl {
|
||||
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
// Handle remote URIs via `RemoteAuthorities`
|
||||
if (uri.scheme === Schemas.vscodeRemote) {
|
||||
return RemoteAuthorities.rewrite(uri);
|
||||
}
|
||||
|
||||
// Only convert the URI if we are in a native context and it has `file:` scheme
|
||||
if (platform.isElectronSandboxed && platform.isNative && uri.scheme === Schemas.file) {
|
||||
return this.toCodeFileUri(uri);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO@bpasero remove me eventually when vscode-file is adopted everywhere
|
||||
*/
|
||||
_asCodeFileUri(uri: URI): URI;
|
||||
_asCodeFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
_asCodeFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
return this.toCodeFileUri(uri);
|
||||
}
|
||||
|
||||
private toCodeFileUri(uri: URI): URI {
|
||||
return uri.with({
|
||||
scheme: Schemas.vscodeFileResource,
|
||||
// We need to provide an authority here so that it can serve
|
||||
// as origin for network and loading matters in chromium.
|
||||
// If the URI is not coming with an authority already, we
|
||||
// add our own
|
||||
authority: uri.authority || this.FALLBACK_AUTHORITY,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `file` URI to use in contexts where node.js
|
||||
* is responsible for loading.
|
||||
@ -160,6 +198,19 @@ class FileAccessImpl {
|
||||
asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
// Only convert the URI if it is `vscode-file:` scheme
|
||||
if (uri.scheme === Schemas.vscodeFileResource) {
|
||||
return uri.with({
|
||||
scheme: Schemas.file,
|
||||
// Only preserve the `authority` if it is different from
|
||||
// our fallback authority. This ensures we properly preserve
|
||||
// Windows UNC paths that come with their own authority.
|
||||
authority: uri.authority !== this.FALLBACK_AUTHORITY ? uri.authority : null,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ function _factory(sharedObj) {
|
||||
sharedObj.MonacoPerformanceMarks = sharedObj.MonacoPerformanceMarks || [];
|
||||
|
||||
const _dataLen = 2;
|
||||
const _timeStamp = typeof console.timeStamp === 'function' ? console.timeStamp.bind(console) : () => { };
|
||||
const _nativeMark = typeof performance === 'object' && typeof performance.mark === 'function' ? performance.mark.bind(performance) : () => { };
|
||||
|
||||
function importEntries(entries) {
|
||||
sharedObj.MonacoPerformanceMarks.splice(0, 0, ...entries);
|
||||
@ -55,7 +55,7 @@ function _factory(sharedObj) {
|
||||
|
||||
function mark(name) {
|
||||
sharedObj.MonacoPerformanceMarks.push(name, Date.now());
|
||||
_timeStamp(name);
|
||||
_nativeMark(name);
|
||||
}
|
||||
|
||||
const exports = {
|
||||
|
@ -33,8 +33,8 @@ export interface INodeProcess {
|
||||
versions?: {
|
||||
electron?: string;
|
||||
};
|
||||
sandboxed?: boolean; // Electron
|
||||
type?: string;
|
||||
getuid(): number;
|
||||
cwd(): string;
|
||||
}
|
||||
declare const process: INodeProcess;
|
||||
@ -55,11 +55,12 @@ if (typeof process !== 'undefined') {
|
||||
// Native environment (non-sandboxed)
|
||||
nodeProcess = process;
|
||||
} else if (typeof _globals.vscode !== 'undefined') {
|
||||
// Native envionment (sandboxed)
|
||||
// Native environment (sandboxed)
|
||||
nodeProcess = _globals.vscode.process;
|
||||
}
|
||||
|
||||
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
|
||||
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
|
||||
|
||||
// Web environment
|
||||
if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
@ -145,10 +146,6 @@ export const isIOS = _isIOS;
|
||||
export const platform = _platform;
|
||||
export const userAgent = _userAgent;
|
||||
|
||||
export function isRootUser(): boolean {
|
||||
return !!nodeProcess && !_isWindows && (nodeProcess.getuid() === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The language used for the user interface. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
|
@ -16,7 +16,16 @@ if (typeof process !== 'undefined') {
|
||||
|
||||
// Native sandbox environment
|
||||
else if (typeof globals.vscode !== 'undefined') {
|
||||
safeProcess = globals.vscode.process;
|
||||
safeProcess = {
|
||||
|
||||
// Supported
|
||||
get platform(): 'win32' | 'linux' | 'darwin' { return globals.vscode.process.platform; },
|
||||
get env() { return globals.vscode.process.env; },
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
|
||||
|
||||
// Unsupported
|
||||
cwd(): string { return globals.vscode.process.env['VSCODE_CWD'] || globals.vscode.process.execPath.substr(0, globals.vscode.process.execPath.lastIndexOf(globals.vscode.process.platform === 'win32' ? '\\' : '/')); }
|
||||
};
|
||||
}
|
||||
|
||||
// Web environment
|
||||
@ -29,8 +38,7 @@ else {
|
||||
|
||||
// Unsupported
|
||||
get env() { return Object.create(null); },
|
||||
cwd(): string { return '/'; },
|
||||
getuid(): number { return -1; }
|
||||
cwd(): string { return '/'; }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { relativePath, joinPath } from 'vs/base/common/resources';
|
||||
import { IExtUri, extUri as defaultExtUri } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { PathIterator } from 'vs/base/common/map';
|
||||
|
||||
@ -95,12 +95,12 @@ export class ResourceTree<T extends NonNullable<any>, C> {
|
||||
return obj instanceof Node;
|
||||
}
|
||||
|
||||
constructor(context: C, rootURI: URI = URI.file('/')) {
|
||||
constructor(context: C, rootURI: URI = URI.file('/'), private extUri: IExtUri = defaultExtUri) {
|
||||
this.root = new Node(rootURI, '', context);
|
||||
}
|
||||
|
||||
add(uri: URI, element: T): void {
|
||||
const key = relativePath(this.root.uri, uri) || uri.fsPath;
|
||||
const key = this.extUri.relativePath(this.root.uri, uri) || uri.path;
|
||||
const iterator = new PathIterator(false).reset(key);
|
||||
let node = this.root;
|
||||
let path = '';
|
||||
@ -113,7 +113,7 @@ export class ResourceTree<T extends NonNullable<any>, C> {
|
||||
|
||||
if (!child) {
|
||||
child = new Node(
|
||||
joinPath(this.root.uri, path),
|
||||
this.extUri.joinPath(this.root.uri, path),
|
||||
path,
|
||||
this.root.context,
|
||||
iterator.hasNext() ? undefined : element,
|
||||
@ -136,7 +136,7 @@ export class ResourceTree<T extends NonNullable<any>, C> {
|
||||
}
|
||||
|
||||
delete(uri: URI): T | undefined {
|
||||
const key = relativePath(this.root.uri, uri) || uri.fsPath;
|
||||
const key = this.extUri.relativePath(this.root.uri, uri) || uri.path;
|
||||
const iterator = new PathIterator(false).reset(key);
|
||||
return this._delete(this.root, iterator);
|
||||
}
|
||||
@ -168,7 +168,7 @@ export class ResourceTree<T extends NonNullable<any>, C> {
|
||||
}
|
||||
|
||||
getNode(uri: URI): IResourceNode<T, C> | undefined {
|
||||
const key = relativePath(this.root.uri, uri) || uri.fsPath;
|
||||
const key = this.extUri.relativePath(this.root.uri, uri) || uri.path;
|
||||
const iterator = new PathIterator(false).reset(key);
|
||||
let node = this.root;
|
||||
|
||||
|
@ -10,8 +10,6 @@ import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings'
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
|
||||
export function originalFSPath(uri: URI): string {
|
||||
return uriToFsPath(uri, true);
|
||||
@ -58,6 +56,11 @@ export interface IExtUri {
|
||||
*/
|
||||
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
|
||||
|
||||
/**
|
||||
* Whether the casing of the path-component of the uri should be ignored.
|
||||
*/
|
||||
ignorePathCasing(uri: URI): boolean;
|
||||
|
||||
// --- path math
|
||||
|
||||
basenameOrAuthority(resource: URI): string;
|
||||
@ -161,6 +164,10 @@ export class ExtUri implements IExtUri {
|
||||
}).toString();
|
||||
}
|
||||
|
||||
ignorePathCasing(uri: URI): boolean {
|
||||
return this._ignorePathCasing(uri);
|
||||
}
|
||||
|
||||
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
@ -427,33 +434,6 @@ export namespace DataUri {
|
||||
}
|
||||
}
|
||||
|
||||
export class ResourceGlobMatcher {
|
||||
|
||||
private readonly globalExpression: ParsedExpression;
|
||||
private readonly expressionsByRoot: TernarySearchTree<URI, { root: URI, expression: ParsedExpression }> = TernarySearchTree.forUris<{ root: URI, expression: ParsedExpression }>();
|
||||
|
||||
constructor(
|
||||
globalExpression: IExpression,
|
||||
rootExpressions: { root: URI, expression: IExpression }[]
|
||||
) {
|
||||
this.globalExpression = parse(globalExpression);
|
||||
for (const expression of rootExpressions) {
|
||||
this.expressionsByRoot.set(expression.root, { root: expression.root, expression: parse(expression.expression) });
|
||||
}
|
||||
}
|
||||
|
||||
matches(resource: URI): boolean {
|
||||
const rootExpression = this.expressionsByRoot.findSubstr(resource);
|
||||
if (rootExpression) {
|
||||
const path = relativePath(rootExpression.root, resource);
|
||||
if (path && !!rootExpression.expression(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return !!this.globalExpression(resource.path);
|
||||
}
|
||||
}
|
||||
|
||||
export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI {
|
||||
if (authority) {
|
||||
let path = resource.path;
|
||||
|
@ -69,6 +69,14 @@ export function count(value: string, character: string): number {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function truncate(value: string, maxLength: number, suffix = '…'): string {
|
||||
if (value.length <= maxLength) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return `${value.substr(0, maxLength)}${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of needle from the beginning and end of haystack.
|
||||
* @param haystack string to trim
|
||||
@ -208,6 +216,10 @@ export function regExpFlags(regexp: RegExp): string {
|
||||
+ ((regexp as any /* standalone editor compilation */).unicode ? 'u' : '');
|
||||
}
|
||||
|
||||
export function splitLines(str: string): string[] {
|
||||
return str.split(/\r\n|\r|\n/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first index of the string that is not whitespace.
|
||||
* If string is empty or contains only whitespaces, returns -1
|
||||
@ -879,9 +891,15 @@ export function getNLines(str: string, n = 1): string {
|
||||
n--;
|
||||
} while (n > 0 && idx >= 0);
|
||||
|
||||
return idx >= 0 ?
|
||||
str.substr(0, idx) :
|
||||
str;
|
||||
if (idx === -1) {
|
||||
return str;
|
||||
}
|
||||
|
||||
if (str[idx - 1] === '\r') {
|
||||
idx--;
|
||||
}
|
||||
|
||||
return str.substr(0, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,7 +345,7 @@ export class URI implements UriComponents {
|
||||
*/
|
||||
static joinPath(uri: URI, ...pathFragment: string[]): URI {
|
||||
if (!uri.path) {
|
||||
throw new Error(`[UriError]: cannot call joinPaths on URI without path`);
|
||||
throw new Error(`[UriError]: cannot call joinPath on URI without path`);
|
||||
}
|
||||
let newPath: string;
|
||||
if (isWindows && uri.scheme === 'file') {
|
||||
|
@ -17,8 +17,9 @@ for (let i = 0; i < 256; i++) {
|
||||
_hex.push(i.toString(16).padStart(2, '0'));
|
||||
}
|
||||
|
||||
// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback
|
||||
// todo@joh use browser-crypto
|
||||
// todo@jrieken
|
||||
// 1. node nodejs use`crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback
|
||||
// 2. use browser-crypto
|
||||
const _fillRandomValues = function (bucket: Uint8Array): Uint8Array {
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
bucket[i] = Math.floor(Math.random() * 256);
|
||||
|
@ -358,6 +358,10 @@ export class SimpleWorkerServer<H extends object> {
|
||||
delete loaderConfig.paths['vs'];
|
||||
}
|
||||
}
|
||||
if (typeof loaderConfig.trustedTypesPolicy !== undefined) {
|
||||
// don't use, it has been destroyed during serialize
|
||||
delete loaderConfig['trustedTypesPolicy'];
|
||||
}
|
||||
|
||||
// Since this is in a web worker, enable catching errors
|
||||
loaderConfig.catchError = true;
|
||||
|
@ -5,12 +5,7 @@
|
||||
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
|
||||
interface IPaths {
|
||||
getAppDataPath(platform: string): string;
|
||||
getDefaultUserDataPath(platform: string): string;
|
||||
}
|
||||
|
||||
const pathsPath = FileAccess.asFileUri('paths', require).fsPath;
|
||||
const paths = require.__$__nodeRequire<IPaths>(pathsPath);
|
||||
export const getAppDataPath = paths.getAppDataPath;
|
||||
const paths = require.__$__nodeRequire<{ getDefaultUserDataPath(): string }>(pathsPath);
|
||||
|
||||
export const getDefaultUserDataPath = paths.getDefaultUserDataPath;
|
||||
|
@ -126,7 +126,8 @@ const enum ProtocolMessageType {
|
||||
Control = 2,
|
||||
Ack = 3,
|
||||
KeepAlive = 4,
|
||||
Disconnect = 5
|
||||
Disconnect = 5,
|
||||
ReplayRequest = 6
|
||||
}
|
||||
|
||||
export const enum ProtocolConstants {
|
||||
@ -274,7 +275,11 @@ class ProtocolWriter {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.flush();
|
||||
try {
|
||||
this.flush();
|
||||
} catch (err) {
|
||||
// ignore error, since the socket could be already closed
|
||||
}
|
||||
this._isDisposed = true;
|
||||
}
|
||||
|
||||
@ -601,6 +606,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
private _outgoingKeepAliveTimeout: any | null;
|
||||
private _incomingKeepAliveTimeout: any | null;
|
||||
|
||||
private _lastReplayRequestTime: number;
|
||||
|
||||
private _socket: ISocket;
|
||||
private _socketWriter: ProtocolWriter;
|
||||
private _socketReader: ProtocolReader;
|
||||
@ -642,6 +649,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
this._outgoingKeepAliveTimeout = null;
|
||||
this._incomingKeepAliveTimeout = null;
|
||||
|
||||
this._lastReplayRequestTime = 0;
|
||||
|
||||
this._socketDisposables = [];
|
||||
this._socket = socket;
|
||||
this._socketWriter = new ProtocolWriter(this._socket);
|
||||
@ -747,6 +756,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
this._onSocketTimeout.flushBuffer();
|
||||
this._socket.dispose();
|
||||
|
||||
this._lastReplayRequestTime = 0;
|
||||
|
||||
this._socket = socket;
|
||||
this._socketWriter = new ProtocolWriter(this._socket);
|
||||
this._socketDisposables.push(this._socketWriter);
|
||||
@ -792,17 +803,31 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
if (msg.type === ProtocolMessageType.Regular) {
|
||||
if (msg.id > this._incomingMsgId) {
|
||||
if (msg.id !== this._incomingMsgId + 1) {
|
||||
console.error(`PROTOCOL CORRUPTION, LAST SAW MSG ${this._incomingMsgId} AND HAVE NOW RECEIVED MSG ${msg.id}`);
|
||||
// in case we missed some messages we ask the other party to resend them
|
||||
const now = Date.now();
|
||||
if (now - this._lastReplayRequestTime > 10000) {
|
||||
// send a replay request at most once every 10s
|
||||
this._lastReplayRequestTime = now;
|
||||
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer()));
|
||||
}
|
||||
} else {
|
||||
this._incomingMsgId = msg.id;
|
||||
this._incomingMsgLastTime = Date.now();
|
||||
this._sendAckCheck();
|
||||
this._onMessage.fire(msg.data);
|
||||
}
|
||||
this._incomingMsgId = msg.id;
|
||||
this._incomingMsgLastTime = Date.now();
|
||||
this._sendAckCheck();
|
||||
this._onMessage.fire(msg.data);
|
||||
}
|
||||
} else if (msg.type === ProtocolMessageType.Control) {
|
||||
this._onControlMessage.fire(msg.data);
|
||||
} else if (msg.type === ProtocolMessageType.Disconnect) {
|
||||
this._onClose.fire();
|
||||
} else if (msg.type === ProtocolMessageType.ReplayRequest) {
|
||||
// Send again all unacknowledged messages
|
||||
const toSend = this._outgoingUnackMsg.toArray();
|
||||
for (let i = 0, len = toSend.length; i < len; i++) {
|
||||
this._socketWriter.write(toSend[i]);
|
||||
}
|
||||
this._recvAckCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +130,11 @@
|
||||
padding: 5px 5px 2px 5px;
|
||||
}
|
||||
|
||||
.quick-input-message > .codicon {
|
||||
margin: 0 0.2em;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.quick-input-progress.monaco-progress-container {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/lis
|
||||
import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { registerIcon, Codicon } from 'vs/base/common/codicons';
|
||||
import { registerCodicon, Codicon } from 'vs/base/common/codicons';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
|
||||
export interface IQuickInputOptions {
|
||||
idPrefix: string;
|
||||
@ -70,7 +72,7 @@ const $ = dom.$;
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
|
||||
const backButtonIcon = registerIcon('quick-input-back', Codicon.arrowLeft);
|
||||
const backButtonIcon = registerCodicon('quick-input-back', Codicon.arrowLeft, localize('backButtonIcon', 'Icon for the back button in the quick input dialog.'));
|
||||
|
||||
const backButton = {
|
||||
iconClass: backButtonIcon.classNames,
|
||||
@ -413,6 +415,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _valueSelection: Readonly<[number, number]> | undefined;
|
||||
private valueSelectionUpdated = true;
|
||||
private _validationMessage: string | undefined;
|
||||
private _lastValidationMessage: string | undefined;
|
||||
private _ok: boolean | 'default' = 'default';
|
||||
private _customButton = false;
|
||||
private _customButtonLabel: string | undefined;
|
||||
@ -961,12 +964,11 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.selectedItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
if (this.validationMessage) {
|
||||
this.ui.message.textContent = this.validationMessage;
|
||||
this.showMessageDecoration(Severity.Error);
|
||||
} else {
|
||||
this.ui.message.textContent = null;
|
||||
this.showMessageDecoration(Severity.Ignore);
|
||||
const validationMessage = this.validationMessage || '';
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderCodicons(escape(validationMessage)));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
this.ui.customButton.label = this.customLabel || '';
|
||||
this.ui.customButton.element.title = this.customHover || '';
|
||||
@ -996,6 +998,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
private _prompt: string | undefined;
|
||||
private noValidationMessage = InputBox.noPromptMessage;
|
||||
private _validationMessage: string | undefined;
|
||||
private _lastValidationMessage: string | undefined;
|
||||
private readonly onDidValueChangeEmitter = this._register(new Emitter<string>());
|
||||
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
|
||||
@ -1097,13 +1100,11 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
if (this.ui.inputBox.password !== this.password) {
|
||||
this.ui.inputBox.password = this.password;
|
||||
}
|
||||
if (!this.validationMessage && this.ui.message.textContent !== this.noValidationMessage) {
|
||||
this.ui.message.textContent = this.noValidationMessage;
|
||||
this.showMessageDecoration(Severity.Ignore);
|
||||
}
|
||||
if (this.validationMessage && this.ui.message.textContent !== this.validationMessage) {
|
||||
this.ui.message.textContent = this.validationMessage;
|
||||
this.showMessageDecoration(Severity.Error);
|
||||
const validationMessage = this.validationMessage || this.noValidationMessage;
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderCodicons(validationMessage));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1528,7 +1529,7 @@ export class QuickInputController extends Disposable {
|
||||
ui.inputBox.showDecoration(Severity.Ignore);
|
||||
ui.visibleCount.setCount(0);
|
||||
ui.count.setCount(0);
|
||||
ui.message.textContent = '';
|
||||
dom.reset(ui.message);
|
||||
ui.progressBar.stop();
|
||||
ui.list.setElements([]);
|
||||
ui.list.matchOnDescription = false;
|
||||
@ -1696,7 +1697,7 @@ export class QuickInputController extends Disposable {
|
||||
this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : '';
|
||||
this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : '';
|
||||
this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : '';
|
||||
this.ui.container.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : '';
|
||||
this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
|
||||
this.ui.inputBox.style(this.styles.inputBox);
|
||||
this.ui.count.style(this.styles.countBadge);
|
||||
this.ui.ok.style(this.styles.button);
|
||||
|
@ -35,6 +35,17 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {any[]} args
|
||||
* @returns {Promise<any> | undefined}
|
||||
*/
|
||||
invoke(channel, ...args) {
|
||||
if (validateIPC(channel)) {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
@ -97,80 +108,48 @@
|
||||
|
||||
/**
|
||||
* Support for a subset of access to node.js global `process`.
|
||||
*
|
||||
* Note: when `sandbox` is enabled, the only properties available
|
||||
* are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox
|
||||
*/
|
||||
process: {
|
||||
get platform() { return process.platform; },
|
||||
get env() { return process.env; },
|
||||
get versions() { return process.versions; },
|
||||
get type() { return 'renderer'; },
|
||||
get execPath() { return process.execPath; },
|
||||
|
||||
_whenEnvResolved: undefined,
|
||||
whenEnvResolved:
|
||||
/**
|
||||
* @returns when the shell environment has been resolved.
|
||||
*/
|
||||
function () {
|
||||
if (!this._whenEnvResolved) {
|
||||
this._whenEnvResolved = resolveEnv();
|
||||
}
|
||||
/**
|
||||
* @param {{[key: string]: string}} userEnv
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
resolveEnv(userEnv) {
|
||||
return resolveEnv(userEnv);
|
||||
},
|
||||
|
||||
return this._whenEnvResolved;
|
||||
},
|
||||
/**
|
||||
* @returns {Promise<import('electron').ProcessMemoryInfo>}
|
||||
*/
|
||||
getProcessMemoryInfo() {
|
||||
return process.getProcessMemoryInfo();
|
||||
},
|
||||
|
||||
nextTick:
|
||||
/**
|
||||
* Adds callback to the "next tick queue". This queue is fully drained
|
||||
* after the current operation on the JavaScript stack runs to completion
|
||||
* and before the event loop is allowed to continue.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @param {any[]} args
|
||||
*/
|
||||
function nextTick(callback, ...args) {
|
||||
return process.nextTick(callback, ...args);
|
||||
},
|
||||
|
||||
cwd:
|
||||
/**
|
||||
* @returns the current working directory.
|
||||
*/
|
||||
function () {
|
||||
return process.cwd();
|
||||
},
|
||||
|
||||
getuid:
|
||||
/**
|
||||
* @returns the numeric user identity of the process
|
||||
*/
|
||||
function () {
|
||||
return process.getuid();
|
||||
},
|
||||
|
||||
getProcessMemoryInfo:
|
||||
/**
|
||||
* @returns {Promise<import('electron').ProcessMemoryInfo>}
|
||||
*/
|
||||
function () {
|
||||
return process.getProcessMemoryInfo();
|
||||
},
|
||||
|
||||
on:
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
function (type, callback) {
|
||||
if (validateProcessEventType(type)) {
|
||||
process.on(type, callback);
|
||||
}
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
on(type, callback) {
|
||||
if (validateProcessEventType(type)) {
|
||||
process.on(type, callback);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Some information about the context we are running in.
|
||||
*/
|
||||
context: {
|
||||
get sandbox() { return process.argv.includes('--enable-sandbox'); }
|
||||
get sandbox() { return process.sandboxed; }
|
||||
}
|
||||
};
|
||||
|
||||
@ -197,6 +176,7 @@
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @returns {true | never}
|
||||
*/
|
||||
function validateIPC(channel) {
|
||||
if (!channel || !channel.startsWith('vscode:')) {
|
||||
@ -218,32 +198,40 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {Promise<void> | undefined} */
|
||||
let resolvedEnv = undefined;
|
||||
|
||||
/**
|
||||
* If VSCode is not run from a terminal, we should resolve additional
|
||||
* shell specific environment from the OS shell to ensure we are seeing
|
||||
* all development related environment variables. We do this from the
|
||||
* main process because it may involve spawning a shell.
|
||||
*
|
||||
* @param {{[key: string]: string}} userEnv
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function resolveEnv() {
|
||||
return new Promise(function (resolve) {
|
||||
const handle = setTimeout(function () {
|
||||
console.warn('Preload: Unable to resolve shell environment in a reasonable time');
|
||||
function resolveEnv(userEnv) {
|
||||
if (!resolvedEnv) {
|
||||
|
||||
// It took too long to fetch the shell environment, return
|
||||
resolve();
|
||||
}, 3000);
|
||||
// Apply `userEnv` directly
|
||||
Object.assign(process.env, userEnv);
|
||||
|
||||
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
|
||||
clearTimeout(handle);
|
||||
// Resolve `shellEnv` from the main side
|
||||
resolvedEnv = new Promise(function (resolve) {
|
||||
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
|
||||
|
||||
// Assign all keys of the shell environment to our process environment
|
||||
Object.assign(process.env, shellEnv);
|
||||
// Assign all keys of the shell environment to our process environment
|
||||
// But make sure that the user environment wins in the end
|
||||
Object.assign(process.env, shellEnv, userEnv);
|
||||
|
||||
resolve();
|
||||
resolve();
|
||||
});
|
||||
|
||||
ipcRenderer.send('vscode:fetchShellEnv');
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.send('vscode:fetchShellEnv');
|
||||
});
|
||||
return resolvedEnv;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
@ -12,45 +12,45 @@ export interface ISandboxNodeProcess extends INodeProcess {
|
||||
* The process.platform property returns a string identifying the operating system platform
|
||||
* on which the Node.js process is running.
|
||||
*/
|
||||
platform: 'win32' | 'linux' | 'darwin';
|
||||
readonly platform: 'win32' | 'linux' | 'darwin';
|
||||
|
||||
/**
|
||||
* The type will always be Electron renderer.
|
||||
*/
|
||||
type: 'renderer';
|
||||
readonly type: 'renderer';
|
||||
|
||||
/**
|
||||
* A list of versions for the current node.js/electron configuration.
|
||||
*/
|
||||
versions: { [key: string]: string | undefined };
|
||||
readonly versions: { [key: string]: string | undefined };
|
||||
|
||||
/**
|
||||
* The process.env property returns an object containing the user environment.
|
||||
*/
|
||||
env: IProcessEnvironment;
|
||||
readonly env: IProcessEnvironment;
|
||||
|
||||
/**
|
||||
* The current working directory.
|
||||
* The `execPath` will be the location of the executable of this application.
|
||||
*/
|
||||
cwd(): string;
|
||||
readonly execPath: string;
|
||||
|
||||
/**
|
||||
* Returns the numeric user identity of the process.
|
||||
* Resolve the true process environment to use and apply it to `process.env`.
|
||||
*
|
||||
* There are different layers of environment that will apply:
|
||||
* - `process.env`: this is the actual environment of the process before this method
|
||||
* - `shellEnv` : if the program was not started from a terminal, we resolve all shell
|
||||
* variables to get the same experience as if the program was started from
|
||||
* a terminal (Linux, macOS)
|
||||
* - `userEnv` : this is instance specific environment, e.g. if the user started the program
|
||||
* from a terminal and changed certain variables
|
||||
*
|
||||
* The order of overwrites is `process.env` < `shellEnv` < `userEnv`.
|
||||
*
|
||||
* It is critical that every process awaits this method early on startup to get the right
|
||||
* set of environment in `process.env`.
|
||||
*/
|
||||
getuid(): number;
|
||||
|
||||
/**
|
||||
* Allows to await resolving the full process environment by checking for the shell environment
|
||||
* of the OS in certain cases (e.g. when the app is started from the Dock on macOS).
|
||||
*/
|
||||
whenEnvResolved(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Adds callback to the "next tick queue". This queue is fully drained
|
||||
* after the current operation on the JavaScript stack runs to completion
|
||||
* and before the event loop is allowed to continue.
|
||||
*/
|
||||
nextTick(callback: (...args: any[]) => void, ...args: any[]): void;
|
||||
resolveEnv(userEnv: IProcessEnvironment): Promise<void>;
|
||||
|
||||
/**
|
||||
* A listener on the process. Only a small subset of listener types are allowed.
|
||||
|
@ -43,9 +43,10 @@ export interface IStorageDatabase {
|
||||
|
||||
export interface IStorage extends IDisposable {
|
||||
|
||||
readonly onDidChangeStorage: Event<string>;
|
||||
|
||||
readonly items: Map<string, string>;
|
||||
readonly size: number;
|
||||
readonly onDidChangeStorage: Event<string>;
|
||||
|
||||
init(): Promise<void>;
|
||||
|
||||
@ -61,6 +62,8 @@ export interface IStorage extends IDisposable {
|
||||
set(key: string, value: string | boolean | number | undefined | null): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
|
||||
whenFlushed(): Promise<void>;
|
||||
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
@ -86,6 +89,8 @@ export class Storage extends Disposable implements IStorage {
|
||||
private pendingDeletes = new Set<string>();
|
||||
private pendingInserts = new Map<string, string>();
|
||||
|
||||
private readonly whenFlushedCallbacks: Function[] = [];
|
||||
|
||||
constructor(
|
||||
protected readonly database: IStorageDatabase,
|
||||
private readonly options: IStorageOptions = Object.create(null)
|
||||
@ -273,8 +278,12 @@ export class Storage extends Disposable implements IStorage {
|
||||
await this.database.close(() => this.cache);
|
||||
}
|
||||
|
||||
private get hasPending() {
|
||||
return this.pendingInserts.size > 0 || this.pendingDeletes.size > 0;
|
||||
}
|
||||
|
||||
private flushPending(): Promise<void> {
|
||||
if (this.pendingInserts.size === 0 && this.pendingDeletes.size === 0) {
|
||||
if (!this.hasPending) {
|
||||
return Promise.resolve(); // return early if nothing to do
|
||||
}
|
||||
|
||||
@ -285,8 +294,23 @@ export class Storage extends Disposable implements IStorage {
|
||||
this.pendingDeletes = new Set<string>();
|
||||
this.pendingInserts = new Map<string, string>();
|
||||
|
||||
// Update in storage
|
||||
return this.database.updateItems(updateRequest);
|
||||
// Update in storage and release any
|
||||
// waiters we have once done
|
||||
return this.database.updateItems(updateRequest).finally(() => {
|
||||
if (!this.hasPending) {
|
||||
while (this.whenFlushedCallbacks.length) {
|
||||
this.whenFlushedCallbacks.pop()?.();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
whenFlushed(): Promise<void> {
|
||||
if (!this.hasPending) {
|
||||
return Promise.resolve(); // return early if nothing to do
|
||||
}
|
||||
|
||||
return new Promise(resolve => this.whenFlushedCallbacks.push(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,11 +45,16 @@ suite('Storage Library', function () {
|
||||
changes.add(key);
|
||||
});
|
||||
|
||||
await storage.whenFlushed(); // returns immediately when no pending updates
|
||||
|
||||
// Simple updates
|
||||
const set1Promise = storage.set('bar', 'foo');
|
||||
const set2Promise = storage.set('barNumber', 55);
|
||||
const set3Promise = storage.set('barBoolean', true);
|
||||
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('bar'), 'foo');
|
||||
equal(storage.getNumber('barNumber'), 55);
|
||||
equal(storage.getBoolean('barBoolean'), true);
|
||||
@ -62,6 +67,7 @@ suite('Storage Library', function () {
|
||||
let setPromiseResolved = false;
|
||||
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
|
||||
equal(setPromiseResolved, true);
|
||||
equal(flushPromiseResolved, true);
|
||||
|
||||
changes = new Set<string>();
|
||||
|
||||
@ -166,6 +172,9 @@ suite('Storage Library', function () {
|
||||
const set1Promise = storage.set('foo', 'bar');
|
||||
const set2Promise = storage.set('bar', 'foo');
|
||||
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('foo'), 'bar');
|
||||
equal(storage.get('bar'), 'foo');
|
||||
|
||||
@ -175,6 +184,7 @@ suite('Storage Library', function () {
|
||||
await storage.close();
|
||||
|
||||
equal(setPromiseResolved, true);
|
||||
equal(flushPromiseResolved, true);
|
||||
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
await storage.init();
|
||||
@ -226,6 +236,9 @@ suite('Storage Library', function () {
|
||||
const set2Promise = storage.set('foo', 'bar2');
|
||||
const set3Promise = storage.set('foo', 'bar3');
|
||||
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('foo'), 'bar3');
|
||||
equal(changes.size, 1);
|
||||
ok(changes.has('foo'));
|
||||
@ -233,6 +246,7 @@ suite('Storage Library', function () {
|
||||
let setPromiseResolved = false;
|
||||
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
|
||||
ok(setPromiseResolved);
|
||||
ok(flushPromiseResolved);
|
||||
|
||||
changes = new Set<string>();
|
||||
|
||||
|
@ -197,7 +197,7 @@ suite('Comparers', () => {
|
||||
// name-only comparisons
|
||||
assert(compareFileNamesDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
|
||||
assert(compareFileNamesDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
// assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesDefault), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// numeric comparisons
|
||||
@ -259,7 +259,7 @@ suite('Comparers', () => {
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensionsDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
|
||||
assert(compareFileExtensionsDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
// assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsDefault), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
|
@ -2,8 +2,10 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { hash, StringSHA1 } from 'vs/base/common/hash';
|
||||
import { sha1Hex } from 'vs/base/browser/hash';
|
||||
|
||||
suite('Hash', () => {
|
||||
test('string', () => {
|
||||
@ -71,28 +73,32 @@ suite('Hash', () => {
|
||||
});
|
||||
|
||||
|
||||
function checkSHA1(strings: string[], expected: string) {
|
||||
async function checkSHA1(str: string, expected: string) {
|
||||
|
||||
// Test with StringSHA1
|
||||
const hash = new StringSHA1();
|
||||
for (const str of strings) {
|
||||
hash.update(str);
|
||||
}
|
||||
const actual = hash.digest();
|
||||
hash.update(str);
|
||||
let actual = hash.digest();
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// Test with crypto.subtle
|
||||
actual = await sha1Hex(str);
|
||||
assert.equal(actual, expected);
|
||||
}
|
||||
|
||||
test('sha1-1', () => {
|
||||
checkSHA1(['\udd56'], '9bdb77276c1852e1fb067820472812fcf6084024');
|
||||
return checkSHA1('\udd56', '9bdb77276c1852e1fb067820472812fcf6084024');
|
||||
});
|
||||
|
||||
test('sha1-2', () => {
|
||||
checkSHA1(['\udb52'], '9bdb77276c1852e1fb067820472812fcf6084024');
|
||||
return checkSHA1('\udb52', '9bdb77276c1852e1fb067820472812fcf6084024');
|
||||
});
|
||||
|
||||
test('sha1-3', () => {
|
||||
checkSHA1(['\uda02ꑍ'], '9b483a471f22fe7e09d83f221871a987244bbd3f');
|
||||
return checkSHA1('\uda02ꑍ', '9b483a471f22fe7e09d83f221871a987244bbd3f');
|
||||
});
|
||||
|
||||
test('sha1-4', () => {
|
||||
checkSHA1(['hello'], 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d');
|
||||
return checkSHA1('hello', 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d');
|
||||
});
|
||||
});
|
@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
||||
import { renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
|
||||
import { MarkdownString, IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
@ -57,7 +57,7 @@ suite('MarkdownRenderer', () => {
|
||||
mds.appendText('$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
});
|
||||
|
||||
test('render appendMarkdown', () => {
|
||||
@ -85,7 +85,7 @@ suite('MarkdownRenderer', () => {
|
||||
mds.appendText('$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
});
|
||||
|
||||
test('render appendMarkdown with escaped icon', () => {
|
||||
@ -115,4 +115,20 @@ suite('MarkdownRenderer', () => {
|
||||
assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));
|
||||
});
|
||||
|
||||
suite('PlaintextMarkdownRender', () => {
|
||||
|
||||
test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => {
|
||||
const markdown = { value: '`code`\n>quote\n# heading\n- list\n\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' };
|
||||
const expected = 'code\nquote\nheading\nlist\ntable table2 one two \nbold\nitalic\ndel\nsome text\n';
|
||||
const result: string = renderMarkdownAsPlaintext(markdown);
|
||||
assert.strictEqual(result, expected);
|
||||
});
|
||||
|
||||
test('test html, hr, image, link are rendered plaintext', () => {
|
||||
const markdown = { value: '<div>html</div>\n\n---\n\n[text](textLink)' };
|
||||
const expected = '\ntext\n';
|
||||
const result: string = renderMarkdownAsPlaintext(markdown);
|
||||
assert.strictEqual(result, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,8 +18,11 @@ suite('ScrollbarState', () => {
|
||||
assert.equal(actual.getSliderSize(), 20);
|
||||
assert.equal(actual.getSliderPosition(), 249);
|
||||
|
||||
|
||||
assert.equal(actual.getDesiredScrollPositionFromOffset(259), 32849);
|
||||
|
||||
// 259 is greater than 230 so page down, 32787 + 339 = 33126
|
||||
assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(259), 33126);
|
||||
|
||||
actual.setScrollPosition(32849);
|
||||
assert.equal(actual.getArrowSize(), 0);
|
||||
assert.equal(actual.getScrollPosition(), 32849);
|
||||
@ -41,8 +44,11 @@ suite('ScrollbarState', () => {
|
||||
assert.equal(actual.getSliderSize(), 20);
|
||||
assert.equal(actual.getSliderPosition(), 230);
|
||||
|
||||
|
||||
assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 32811);
|
||||
|
||||
// 240 + 12 = 252; greater than 230 so page down, 32787 + 339 = 33126
|
||||
assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(240 + 12), 33126);
|
||||
|
||||
actual.setScrollPosition(32811);
|
||||
assert.equal(actual.getArrowSize(), 12);
|
||||
assert.equal(actual.getScrollPosition(), 32811);
|
||||
|
@ -91,7 +91,7 @@ suite('Splitview', () => {
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
|
||||
|
||||
let sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
|
||||
@ -99,7 +99,7 @@ suite('Splitview', () => {
|
||||
|
||||
splitview.removeView(2);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
|
||||
@ -107,7 +107,7 @@ suite('Splitview', () => {
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
|
||||
@ -115,7 +115,7 @@ suite('Splitview', () => {
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 0, 'split view should have no views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
|
||||
|
@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import * as async from 'vs/base/common/async';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
suite('Async', () => {
|
||||
|
||||
@ -651,41 +651,39 @@ suite('Async', () => {
|
||||
test('raceCancellation', async () => {
|
||||
const cts = new CancellationTokenSource();
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
const p = async.raceCancellation(async.timeout(100), cts.token);
|
||||
let triggered = false;
|
||||
const p = async.raceCancellation(async.timeout(100).then(() => triggered = true), cts.token);
|
||||
cts.cancel();
|
||||
|
||||
await p;
|
||||
|
||||
assert.ok(Date.now() - now < 100);
|
||||
assert.ok(!triggered);
|
||||
});
|
||||
|
||||
test('raceTimeout', async () => {
|
||||
const cts = new CancellationTokenSource();
|
||||
|
||||
// timeout wins
|
||||
let now = Date.now();
|
||||
let timedout = false;
|
||||
let triggered = false;
|
||||
|
||||
const p1 = async.raceTimeout(async.timeout(100), 1, () => timedout = true);
|
||||
const p1 = async.raceTimeout(async.timeout(100).then(() => triggered = true), 1, () => timedout = true);
|
||||
cts.cancel();
|
||||
|
||||
await p1;
|
||||
|
||||
assert.ok(Date.now() - now < 100);
|
||||
assert.ok(!triggered);
|
||||
assert.equal(timedout, true);
|
||||
|
||||
// promise wins
|
||||
now = Date.now();
|
||||
timedout = false;
|
||||
|
||||
const p2 = async.raceTimeout(async.timeout(1), 100, () => timedout = true);
|
||||
const p2 = async.raceTimeout(async.timeout(1).then(() => triggered = true), 100, () => timedout = true);
|
||||
cts.cancel();
|
||||
|
||||
await p2;
|
||||
|
||||
assert.ok(Date.now() - now < 100);
|
||||
assert.ok(triggered);
|
||||
assert.equal(timedout, false);
|
||||
});
|
||||
|
||||
@ -719,4 +717,63 @@ suite('Async', () => {
|
||||
assert.equal(counter.increment(), 2);
|
||||
assert.equal(counter.increment(), 3);
|
||||
});
|
||||
|
||||
test('firstParallel - simple', async () => {
|
||||
const a = await async.firstParallel([
|
||||
Promise.resolve(1),
|
||||
Promise.resolve(2),
|
||||
Promise.resolve(3),
|
||||
], v => v === 2);
|
||||
assert.equal(a, 2);
|
||||
});
|
||||
|
||||
test('firstParallel - uses null default', async () => {
|
||||
assert.equal(await async.firstParallel([Promise.resolve(1)], v => v === 2), null);
|
||||
});
|
||||
|
||||
test('firstParallel - uses value default', async () => {
|
||||
assert.equal(await async.firstParallel([Promise.resolve(1)], v => v === 2, 4), 4);
|
||||
});
|
||||
|
||||
test('firstParallel - empty', async () => {
|
||||
assert.equal(await async.firstParallel([], v => v === 2, 4), 4);
|
||||
});
|
||||
|
||||
test('firstParallel - cancels', async () => {
|
||||
let ct1: CancellationToken;
|
||||
const p1 = async.createCancelablePromise(async (ct) => {
|
||||
ct1 = ct;
|
||||
await async.timeout(200, ct);
|
||||
return 1;
|
||||
});
|
||||
let ct2: CancellationToken;
|
||||
const p2 = async.createCancelablePromise(async (ct) => {
|
||||
ct2 = ct;
|
||||
await async.timeout(2, ct);
|
||||
return 2;
|
||||
});
|
||||
|
||||
assert.equal(await async.firstParallel([p1, p2], v => v === 2, 4), 2);
|
||||
assert.equal(ct1!.isCancellationRequested, true, 'should cancel a');
|
||||
assert.equal(ct2!.isCancellationRequested, true, 'should cancel b');
|
||||
});
|
||||
|
||||
test('firstParallel - rejection handling', async () => {
|
||||
let ct1: CancellationToken;
|
||||
const p1 = async.createCancelablePromise(async (ct) => {
|
||||
ct1 = ct;
|
||||
await async.timeout(200, ct);
|
||||
return 1;
|
||||
});
|
||||
let ct2: CancellationToken;
|
||||
const p2 = async.createCancelablePromise(async (ct) => {
|
||||
ct2 = ct;
|
||||
await async.timeout(2, ct);
|
||||
throw new Error('oh no');
|
||||
});
|
||||
|
||||
assert.equal(await async.firstParallel([p1, p2], v => v === 2, 4).catch(() => 'ok'), 'ok');
|
||||
assert.equal(ct1!.isCancellationRequested, true, 'should cancel a');
|
||||
assert.equal(ct2!.isCancellationRequested, true, 'should cancel b');
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ suite('LinkedList', function () {
|
||||
assert.equal(list.size, elements.length);
|
||||
|
||||
// assert toArray
|
||||
assert.deepEqual(list.toArray(), elements);
|
||||
assert.deepEqual(Array.from(list), elements);
|
||||
|
||||
// assert Symbol.iterator (1)
|
||||
assert.deepEqual([...list], elements);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user