chore(vscode): update to 1.55.2
This commit is contained in:
6
lib/vscode/src/bootstrap-fork.js
vendored
6
lib/vscode/src/bootstrap-fork.js
vendored
@ -49,7 +49,11 @@ require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']);
|
||||
function pipeLoggingToParent() {
|
||||
const MAX_LENGTH = 100000;
|
||||
|
||||
// Prevent circular stringify and convert arguments to real array
|
||||
/**
|
||||
* Prevent circular stringify and convert arguments to real array
|
||||
*
|
||||
* @param {IArguments} args
|
||||
*/
|
||||
function safeToArray(args) {
|
||||
const seen = [];
|
||||
const argsArray = [];
|
||||
|
36
lib/vscode/src/bootstrap-node.js
vendored
36
lib/vscode/src/bootstrap-node.js
vendored
@ -6,6 +6,42 @@
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
// Setup current working directory in all our node & electron processes
|
||||
// - Windows: call `process.chdir()` to always set application folder as cwd
|
||||
// - Posix: allow to change the current working dir via `VSCODE_CWD` if defined
|
||||
// - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups
|
||||
// TODO@bpasero revisit if chdir() on Windows is needed in the future still
|
||||
function setupCurrentWorkingDirectory() {
|
||||
const path = require('path');
|
||||
|
||||
try {
|
||||
let cwd = process.env['VSCODE_CWD'];
|
||||
|
||||
// remember current working directory in environment
|
||||
// unless it was given to us already from outside
|
||||
if (typeof cwd !== 'string') {
|
||||
cwd = process.cwd();
|
||||
process.env['VSCODE_CWD'] = cwd;
|
||||
}
|
||||
|
||||
// Windows: always set application folder as current working dir
|
||||
if (process.platform === 'win32') {
|
||||
process.chdir(path.dirname(process.execPath));
|
||||
}
|
||||
|
||||
// Linux/macOS: allow to change current working dir based on env
|
||||
else {
|
||||
if (cwd !== process.cwd()) {
|
||||
process.chdir(cwd);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
setupCurrentWorkingDirectory();
|
||||
|
||||
/**
|
||||
* Add support for redirecting the loading of node modules
|
||||
*
|
||||
|
11
lib/vscode/src/bootstrap-window.js
vendored
11
lib/vscode/src/bootstrap-window.js
vendored
@ -23,11 +23,10 @@
|
||||
}(this, function () {
|
||||
const bootstrapLib = bootstrap();
|
||||
const preloadGlobals = globals();
|
||||
const sandbox = preloadGlobals.context.sandbox;
|
||||
const webFrame = preloadGlobals.webFrame;
|
||||
const safeProcess = preloadGlobals.process;
|
||||
const configuration = parseWindowConfiguration();
|
||||
const useCustomProtocol = sandbox || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string';
|
||||
const useCustomProtocol = safeProcess.sandboxed || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string';
|
||||
|
||||
// Start to resolve process.env before anything gets load
|
||||
// so that we can run loading and resolving in parallel
|
||||
@ -83,7 +82,7 @@
|
||||
}
|
||||
|
||||
// replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only)
|
||||
if (!sandbox) {
|
||||
if (!safeProcess.sandboxed) {
|
||||
require.define('fs', [], function () { return require.__$__nodeRequire('original-fs'); });
|
||||
}
|
||||
|
||||
@ -115,7 +114,7 @@
|
||||
// - sandbox: we list paths of webpacked modules to help the loader
|
||||
// - non-sandbox: we signal that any module that does not begin with
|
||||
// `vs/` should be loaded using node.js require()
|
||||
if (sandbox) {
|
||||
if (safeProcess.sandboxed) {
|
||||
loaderConfig.paths = {
|
||||
'vscode-textmate': `../node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `../node_modules/vscode-oniguruma/release/main`,
|
||||
@ -159,7 +158,9 @@
|
||||
|
||||
// Wait for process environment being fully resolved
|
||||
performance.mark('code/willWaitForShellEnv');
|
||||
await whenEnvResolved;
|
||||
if (!safeProcess.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) {
|
||||
await whenEnvResolved;
|
||||
}
|
||||
performance.mark('code/didWaitForShellEnv');
|
||||
|
||||
// Callback only after process environment is resolved
|
||||
|
2
lib/vscode/src/bootstrap.js
vendored
2
lib/vscode/src/bootstrap.js
vendored
@ -180,7 +180,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').IPartialNodeProcess | NodeJS.Process}
|
||||
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process}
|
||||
*/
|
||||
function safeProcess() {
|
||||
if (typeof process !== 'undefined') {
|
||||
|
@ -15,7 +15,7 @@ const os = require('os');
|
||||
const { getNLSConfiguration } = require('./vs/base/node/languagePacks');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const bootstrapNode = require('./bootstrap-node');
|
||||
const { getDefaultUserDataPath } = require('./vs/base/node/userDataPath');
|
||||
const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath');
|
||||
/** @type {Partial<import('./vs/platform/product/common/productService').IProductConfiguration>} */
|
||||
const product = require('../product.json');
|
||||
const { app, protocol, crashReporter } = require('electron');
|
||||
@ -49,9 +49,6 @@ if (portable && portable.isPortable) {
|
||||
app.setAppLogsPath(path.join(userDataPath, 'logs'));
|
||||
}
|
||||
|
||||
// Update cwd based on environment and platform
|
||||
setCurrentWorkingDirectory();
|
||||
|
||||
// Register custom schemes with privileges
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
@ -161,9 +158,12 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
// Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775
|
||||
'enable-proposed-api',
|
||||
|
||||
// TODO@bpasero remove me once testing is done on `vscode-file` protocol
|
||||
// TODO@sandbox remove me once testing is done on `vscode-file` protocol
|
||||
// (all traces of `enable-browser-code-loading` and `ENABLE_VSCODE_BROWSER_CODE_LOADING`)
|
||||
'enable-browser-code-loading'
|
||||
'enable-browser-code-loading',
|
||||
|
||||
// Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.
|
||||
'log-level',
|
||||
];
|
||||
|
||||
// Read argv config
|
||||
@ -208,6 +208,12 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
process.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] = argvValue;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'log-level':
|
||||
if (typeof argvValue === 'string') {
|
||||
process.argv.push('--log', argvValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -424,19 +430,6 @@ function getJSFlags(cliArgs) {
|
||||
return jsFlags.length > 0 ? jsFlags.join(' ') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getUserDataPath(cliArgs) {
|
||||
if (portable.isPortable) {
|
||||
return path.join(portable.portableDataPath, 'user-data');
|
||||
}
|
||||
|
||||
return path.resolve(cliArgs['user-data-dir'] || getDefaultUserDataPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import('./vs/platform/environment/common/argv').NativeParsedArgs}
|
||||
*/
|
||||
@ -454,19 +447,6 @@ function parseCLIArgs() {
|
||||
});
|
||||
}
|
||||
|
||||
function setCurrentWorkingDirectory() {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
process.env['VSCODE_CWD'] = process.cwd(); // remember as environment variable
|
||||
process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd
|
||||
} else if (process.env['VSCODE_CWD']) {
|
||||
process.chdir(process.env['VSCODE_CWD']);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function registerListeners() {
|
||||
|
||||
/**
|
||||
@ -516,7 +496,7 @@ function getNodeCachedDir() {
|
||||
return new class {
|
||||
|
||||
constructor() {
|
||||
this.value = this._compute();
|
||||
this.value = this.compute();
|
||||
}
|
||||
|
||||
async ensureExists() {
|
||||
@ -531,7 +511,7 @@ function getNodeCachedDir() {
|
||||
}
|
||||
}
|
||||
|
||||
_compute() {
|
||||
compute() {
|
||||
if (process.argv.indexOf('--no-cached-data') > 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
{
|
||||
"ban-eval-calls": [
|
||||
"vs/workbench/api/worker/extHostExtensionService.ts"
|
||||
"vs/workbench/api/worker/extHostExtensionService.ts",
|
||||
"vs/base/worker/workerMain",
|
||||
"vs/workbench/services/extensions/worker/extensionHostWorkerMain"
|
||||
],
|
||||
"ban-function-calls": [
|
||||
"vs/workbench/api/worker/extHostExtensionService.ts",
|
||||
"vs/base/worker/workerMain",
|
||||
"vs/workbench/services/extensions/worker/extensionHostWorkerMain",
|
||||
"vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts",
|
||||
"vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts"
|
||||
],
|
||||
|
@ -383,6 +383,7 @@ export class SelectActionViewItem extends BaseActionViewItem {
|
||||
super(ctx, action);
|
||||
|
||||
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
|
||||
this.selectBox.setFocusable(false);
|
||||
|
||||
this._register(this.selectBox);
|
||||
this.registerListeners();
|
||||
@ -406,6 +407,10 @@ export class SelectActionViewItem extends BaseActionViewItem {
|
||||
return option;
|
||||
}
|
||||
|
||||
setFocusable(focusable: boolean): void {
|
||||
this.selectBox.setFocusable(focusable);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.selectBox) {
|
||||
this.selectBox.focus();
|
||||
|
@ -20,10 +20,6 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.monaco-action-bar.reverse .actions-container {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
@ -28,9 +28,7 @@ export interface IActionViewItemProvider {
|
||||
|
||||
export const enum ActionsOrientation {
|
||||
HORIZONTAL,
|
||||
HORIZONTAL_REVERSE,
|
||||
VERTICAL,
|
||||
VERTICAL_REVERSE,
|
||||
}
|
||||
|
||||
export interface ActionTrigger {
|
||||
@ -135,21 +133,11 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
previousKeys = [KeyCode.LeftArrow];
|
||||
nextKeys = [KeyCode.RightArrow];
|
||||
break;
|
||||
case ActionsOrientation.HORIZONTAL_REVERSE:
|
||||
previousKeys = [KeyCode.RightArrow];
|
||||
nextKeys = [KeyCode.LeftArrow];
|
||||
this.domNode.className += ' reverse';
|
||||
break;
|
||||
case ActionsOrientation.VERTICAL:
|
||||
previousKeys = [KeyCode.UpArrow];
|
||||
nextKeys = [KeyCode.DownArrow];
|
||||
this.domNode.className += ' vertical';
|
||||
break;
|
||||
case ActionsOrientation.VERTICAL_REVERSE:
|
||||
previousKeys = [KeyCode.DownArrow];
|
||||
nextKeys = [KeyCode.UpArrow];
|
||||
this.domNode.className += ' vertical reverse';
|
||||
break;
|
||||
}
|
||||
|
||||
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
|
||||
@ -163,8 +151,12 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
eventHandled = this.focusNext();
|
||||
} else if (event.equals(KeyCode.Escape) && this.cancelHasListener) {
|
||||
this._onDidCancel.fire();
|
||||
} else if (event.equals(KeyCode.Home)) {
|
||||
eventHandled = this.focusFirst();
|
||||
} else if (event.equals(KeyCode.End)) {
|
||||
eventHandled = this.focusLast();
|
||||
} else if (event.equals(KeyCode.Tab) && focusedItem instanceof BaseActionViewItem && focusedItem.trapsArrowNavigation) {
|
||||
this.focusNext();
|
||||
eventHandled = this.focusNext();
|
||||
} else if (this.isTriggerKeyEvent(event)) {
|
||||
// Staying out of the else branch even if not triggered
|
||||
if (this._triggerKeys.keyDown) {
|
||||
@ -425,9 +417,21 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private focusFirst(): boolean {
|
||||
this.focusedItem = this.length() > 1 ? 1 : 0;
|
||||
return this.focusPrevious();
|
||||
}
|
||||
|
||||
private focusLast(): boolean {
|
||||
this.focusedItem = this.length() < 2 ? 0 : this.length() - 2;
|
||||
return this.focusNext();
|
||||
}
|
||||
|
||||
protected focusNext(): boolean {
|
||||
if (typeof this.focusedItem === 'undefined') {
|
||||
this.focusedItem = this.viewItems.length - 1;
|
||||
} else if (this.viewItems.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const startIndex = this.focusedItem;
|
||||
@ -450,6 +454,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
protected focusPrevious(): boolean {
|
||||
if (typeof this.focusedItem === 'undefined') {
|
||||
this.focusedItem = 0;
|
||||
} else if (this.viewItems.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const startIndex = this.focusedItem;
|
||||
|
@ -14,6 +14,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monaco-text-button:focus {
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
.monaco-text-button:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
Binary file not shown.
@ -20,4 +20,5 @@ export interface IHoverDelegateOptions {
|
||||
|
||||
export interface IHoverDelegate {
|
||||
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
|
||||
delay: number;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import { IMatch } from 'vs/base/common/filters';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/base/common/range';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
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';
|
||||
@ -218,9 +217,6 @@ export class IconLabel extends Disposable {
|
||||
htmlElement.removeAttribute('title');
|
||||
let tooltip = this.getTooltipForCustom(markdownTooltip);
|
||||
|
||||
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
|
||||
// On Mac, the delay is 1500.
|
||||
const hoverDelay = isMacintosh ? 1500 : 500;
|
||||
let hoverOptions: IHoverDelegateOptions | undefined;
|
||||
let mouseX: number | undefined;
|
||||
let isHovering = false;
|
||||
@ -232,9 +228,12 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
tokenSource = new CancellationTokenSource();
|
||||
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
|
||||
if ((e.type === dom.EventType.MOUSE_DOWN) || (<any>e).fromElement === htmlElement) {
|
||||
const isMouseDown = e.type === dom.EventType.MOUSE_DOWN;
|
||||
if (isMouseDown) {
|
||||
hoverDisposable?.dispose();
|
||||
hoverDisposable = undefined;
|
||||
}
|
||||
if (isMouseDown || (<any>e).fromElement === htmlElement) {
|
||||
isHovering = false;
|
||||
hoverOptions = undefined;
|
||||
tokenSource.dispose(true);
|
||||
@ -282,7 +281,7 @@ export class IconLabel extends Disposable {
|
||||
|
||||
}
|
||||
mouseMoveDisposable.dispose();
|
||||
}, hoverDelay);
|
||||
}, hoverDelegate.delay);
|
||||
}
|
||||
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
|
||||
this.customHovers.set(htmlElement, mouseOverDisposable);
|
||||
|
@ -27,6 +27,7 @@ const $ = dom.$;
|
||||
|
||||
export interface IInputOptions extends IInputBoxStyles {
|
||||
readonly placeholder?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly ariaLabel?: string;
|
||||
readonly type?: string;
|
||||
readonly validationOptions?: IInputValidationOptions;
|
||||
@ -95,6 +96,7 @@ export class InputBox extends Widget {
|
||||
private options: IInputOptions;
|
||||
private message: IMessage | null;
|
||||
private placeholder: string;
|
||||
private tooltip: string;
|
||||
private ariaLabel: string;
|
||||
private validation?: IInputValidator;
|
||||
private state: 'idle' | 'open' | 'closed' = 'idle';
|
||||
@ -133,6 +135,7 @@ export class InputBox extends Widget {
|
||||
mixin(this.options, defaultOpts, false);
|
||||
this.message = null;
|
||||
this.placeholder = this.options.placeholder || '';
|
||||
this.tooltip = this.options.tooltip ?? (this.placeholder || '');
|
||||
this.ariaLabel = this.options.ariaLabel || '';
|
||||
|
||||
this.inputBackground = this.options.inputBackground;
|
||||
@ -207,6 +210,10 @@ export class InputBox extends Widget {
|
||||
this.setPlaceHolder(this.placeholder);
|
||||
}
|
||||
|
||||
if (this.tooltip) {
|
||||
this.setTooltip(this.tooltip);
|
||||
}
|
||||
|
||||
this.oninput(this.input, () => this.onValueChange());
|
||||
this.onblur(this.input, () => this.onBlur());
|
||||
this.onfocus(this.input, () => this.onFocus());
|
||||
@ -235,7 +242,11 @@ export class InputBox extends Widget {
|
||||
public setPlaceHolder(placeHolder: string): void {
|
||||
this.placeholder = placeHolder;
|
||||
this.input.setAttribute('placeholder', placeHolder);
|
||||
this.input.title = placeHolder;
|
||||
}
|
||||
|
||||
public setTooltip(tooltip: string): void {
|
||||
this.tooltip = tooltip;
|
||||
this.input.title = tooltip;
|
||||
}
|
||||
|
||||
public setAriaLabel(label: string): void {
|
||||
|
@ -226,6 +226,14 @@ export class PagedList<T> implements IThemable, IDisposable {
|
||||
this.list.scrollLeft = scrollLeft;
|
||||
}
|
||||
|
||||
setAnchor(index: number | undefined): void {
|
||||
this.list.setAnchor(index);
|
||||
}
|
||||
|
||||
getAnchor(): number | undefined {
|
||||
return this.list.getAnchor();
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
this.list.setFocus(indexes);
|
||||
}
|
||||
@ -238,12 +246,20 @@ export class PagedList<T> implements IThemable, IDisposable {
|
||||
this.list.focusPrevious(n, loop);
|
||||
}
|
||||
|
||||
focusNextPage(): void {
|
||||
this.list.focusNextPage();
|
||||
focusNextPage(): Promise<void> {
|
||||
return this.list.focusNextPage();
|
||||
}
|
||||
|
||||
focusPreviousPage(): void {
|
||||
this.list.focusPreviousPage();
|
||||
focusPreviousPage(): Promise<void> {
|
||||
return this.list.focusPreviousPage();
|
||||
}
|
||||
|
||||
focusLast(): void {
|
||||
this.list.focusLast();
|
||||
}
|
||||
|
||||
focusFirst(): void {
|
||||
this.list.focusFirst();
|
||||
}
|
||||
|
||||
getFocus(): number[] {
|
||||
|
@ -6,7 +6,7 @@
|
||||
import 'vs/css!./list';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { range, binarySearch } from 'vs/base/common/arrays';
|
||||
import { range, binarySearch, firstOrDefault } from 'vs/base/common/arrays';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
@ -27,6 +27,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
import { createStyleSheet } from 'vs/base/browser/dom';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
interface ITraitChangeEvent {
|
||||
indexes: number[];
|
||||
@ -617,27 +618,25 @@ export class MouseController<T> implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
let reference = this.list.getFocus()[0];
|
||||
const selection = this.list.getSelection();
|
||||
reference = reference === undefined ? selection[0] : reference;
|
||||
|
||||
const focus = e.index;
|
||||
|
||||
if (typeof focus === 'undefined') {
|
||||
this.list.setFocus([], e.browserEvent);
|
||||
this.list.setSelection([], e.browserEvent);
|
||||
this.list.setAnchor(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.multipleSelectionSupport && this.isSelectionRangeChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
return this.changeSelection(e);
|
||||
}
|
||||
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
return this.changeSelection(e);
|
||||
}
|
||||
|
||||
this.list.setFocus([focus], e.browserEvent);
|
||||
this.list.setAnchor(focus);
|
||||
|
||||
if (!isMouseRightClick(e.browserEvent)) {
|
||||
this.list.setSelection([focus], e.browserEvent);
|
||||
@ -659,15 +658,16 @@ export class MouseController<T> implements IDisposable {
|
||||
this.list.setSelection(focus, e.browserEvent);
|
||||
}
|
||||
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
const focus = e.index!;
|
||||
const anchor = this.list.getAnchor();
|
||||
|
||||
if (this.isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
const min = Math.min(reference, focus);
|
||||
const max = Math.max(reference, focus);
|
||||
if (this.isSelectionRangeChangeEvent(e) && typeof anchor === 'number') {
|
||||
const min = Math.min(anchor, focus);
|
||||
const max = Math.max(anchor, focus);
|
||||
const rangeSelection = range(min, max + 1);
|
||||
const selection = this.list.getSelection();
|
||||
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [reference]), reference);
|
||||
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [anchor]), anchor);
|
||||
|
||||
if (contiguousRange.length === 0) {
|
||||
return;
|
||||
@ -675,12 +675,14 @@ export class MouseController<T> implements IDisposable {
|
||||
|
||||
const newSelection = disjunction(rangeSelection, relativeComplement(selection, contiguousRange));
|
||||
this.list.setSelection(newSelection, e.browserEvent);
|
||||
this.list.setFocus([focus], e.browserEvent);
|
||||
|
||||
} else if (this.isSelectionSingleChangeEvent(e)) {
|
||||
const selection = this.list.getSelection();
|
||||
const newSelection = selection.filter(i => i !== focus);
|
||||
|
||||
this.list.setFocus([focus]);
|
||||
this.list.setAnchor(focus);
|
||||
|
||||
if (selection.length === newSelection.length) {
|
||||
this.list.setSelection([...newSelection, focus], e.browserEvent);
|
||||
@ -1130,8 +1132,9 @@ export interface IListOptionsUpdate extends IListViewOptionsUpdate {
|
||||
|
||||
export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
|
||||
private focus: Trait<T>;
|
||||
private focus = new Trait<T>('focused');
|
||||
private selection: Trait<T>;
|
||||
private anchor = new Trait<T>('anchor');
|
||||
private eventBufferer = new EventBufferer();
|
||||
protected view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
@ -1223,7 +1226,6 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
) {
|
||||
const role = this._options.accessibilityProvider && this._options.accessibilityProvider.getWidgetRole ? this._options.accessibilityProvider?.getWidgetRole() : 'list';
|
||||
this.selection = new SelectionTrait(role !== 'listbox');
|
||||
this.focus = new Trait('focused');
|
||||
|
||||
mixin(_options, defaultStyles, false);
|
||||
|
||||
@ -1259,11 +1261,13 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
this.spliceable = new CombinedSpliceable([
|
||||
new TraitSpliceable(this.focus, this.view, _options.identityProvider),
|
||||
new TraitSpliceable(this.selection, this.view, _options.identityProvider),
|
||||
new TraitSpliceable(this.anchor, this.view, _options.identityProvider),
|
||||
this.view
|
||||
]);
|
||||
|
||||
this.disposables.add(this.focus);
|
||||
this.disposables.add(this.selection);
|
||||
this.disposables.add(this.anchor);
|
||||
this.disposables.add(this.view);
|
||||
this.disposables.add(this._onDidDispose);
|
||||
|
||||
@ -1436,6 +1440,23 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
return this.getSelection().map(i => this.view.element(i));
|
||||
}
|
||||
|
||||
setAnchor(index: number | undefined): void {
|
||||
if (typeof index === 'undefined') {
|
||||
this.anchor.set([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
|
||||
this.anchor.set([index]);
|
||||
}
|
||||
|
||||
getAnchor(): number | undefined {
|
||||
return firstOrDefault(this.anchor.get(), undefined);
|
||||
}
|
||||
|
||||
setFocus(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
@ -1468,7 +1489,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
async focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> {
|
||||
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
|
||||
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
|
||||
const lastPageElement = this.view.element(lastPageIndex);
|
||||
@ -1490,12 +1511,13 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
this.setFocus([]);
|
||||
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
|
||||
await timeout(0);
|
||||
await this.focusNextPage(browserEvent, filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
async focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> {
|
||||
let firstPageIndex: number;
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
|
||||
@ -1524,7 +1546,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
this.setFocus([]);
|
||||
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
|
||||
await timeout(0);
|
||||
await this.focusPreviousPage(browserEvent, filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,3 +85,11 @@
|
||||
.menubar.compact .toolbar-toggle-more::before {
|
||||
content: "\eb94" !important;
|
||||
}
|
||||
|
||||
/* Match behavior of outline for activity bar icons */
|
||||
.menubar.compact > .menubar-menu-button.open,
|
||||
.menubar.compact > .menubar-menu-button:focus,
|
||||
.menubar.compact > .menubar-menu-button:hover {
|
||||
outline-width: 1px !important;
|
||||
outline-offset: -8px !important;
|
||||
}
|
||||
|
@ -976,7 +976,7 @@ export class MenuBar extends Disposable {
|
||||
menuHolder.style.right = `${this.container.clientWidth}px`;
|
||||
menuHolder.style.left = 'auto';
|
||||
} else {
|
||||
menuHolder.style.top = `${this.container.clientHeight}px`;
|
||||
menuHolder.style.top = `${buttonBoundingRect.bottom}px`;
|
||||
menuHolder.style.left = `${buttonBoundingRect.left}px`;
|
||||
}
|
||||
|
||||
|
@ -85,27 +85,42 @@
|
||||
}
|
||||
|
||||
.monaco-sash.vertical > .orthogonal-drag-handle.start {
|
||||
left: calc(var(--sash-size) / -2);
|
||||
left: calc(var(--sash-size) * -0.5);
|
||||
top: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.vertical > .orthogonal-drag-handle.end {
|
||||
left: calc(var(--sash-size) / -2);
|
||||
left: calc(var(--sash-size) * -0.5);
|
||||
bottom: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.horizontal > .orthogonal-drag-handle.start {
|
||||
top: calc(var(--sash-size) / -2);
|
||||
top: calc(var(--sash-size) * -0.5);
|
||||
left: calc(var(--sash-size) * -1);
|
||||
}
|
||||
.monaco-sash.horizontal > .orthogonal-drag-handle.end {
|
||||
top: calc(var(--sash-size) / -2);
|
||||
top: calc(var(--sash-size) * -0.5);
|
||||
right: calc(var(--sash-size) * -1);
|
||||
}
|
||||
|
||||
.monaco-sash {
|
||||
.monaco-sash:before {
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: background-color 0.1s ease-out;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.monaco-sash.vertical:before {
|
||||
width: var(--sash-hover-size);
|
||||
left: calc(50% - (var(--sash-hover-size) / 2));
|
||||
}
|
||||
|
||||
.monaco-sash.horizontal:before {
|
||||
height: var(--sash-hover-size);
|
||||
top: calc(50% - (var(--sash-hover-size) / 2));
|
||||
}
|
||||
|
||||
/** Debug **/
|
||||
|
||||
.monaco-sash.debug {
|
||||
|
@ -81,6 +81,13 @@ export function setGlobalSashSize(size: number): void {
|
||||
onDidChangeGlobalSize.fire(size);
|
||||
}
|
||||
|
||||
let globalHoverDelay = 300;
|
||||
const onDidChangeHoverDelay = new Emitter<number>();
|
||||
export function setGlobalHoverDelay(size: number): void {
|
||||
globalHoverDelay = size;
|
||||
onDidChangeHoverDelay.fire(size);
|
||||
}
|
||||
|
||||
export class Sash extends Disposable {
|
||||
|
||||
private el: HTMLElement;
|
||||
@ -88,7 +95,8 @@ export class Sash extends Disposable {
|
||||
private hidden: boolean;
|
||||
private orientation!: Orientation;
|
||||
private size: number;
|
||||
private hoverDelayer = this._register(new Delayer(300));
|
||||
private hoverDelay = globalHoverDelay;
|
||||
private hoverDelayer = this._register(new Delayer(this.hoverDelay));
|
||||
|
||||
private _state: SashState = SashState.Enabled;
|
||||
get state(): SashState { return this._state; }
|
||||
@ -221,6 +229,8 @@ export class Sash extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay));
|
||||
|
||||
this.hidden = false;
|
||||
this.layoutProvider = layoutProvider;
|
||||
|
||||
@ -403,7 +413,7 @@ export class Sash extends Disposable {
|
||||
sash.hoverDelayer.cancel();
|
||||
sash.el.classList.add('hover');
|
||||
} else {
|
||||
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'));
|
||||
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'), sash.hoverDelay).then(undefined, () => { });
|
||||
}
|
||||
|
||||
if (!fromLinkedSash && sash.linkedSash) {
|
||||
|
@ -29,6 +29,7 @@ export interface ISelectBoxDelegate extends IDisposable {
|
||||
setAriaLabel(label: string): void;
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
setFocusable(focus: boolean): void;
|
||||
|
||||
// Delegated Widget interface
|
||||
render(container: HTMLElement): void;
|
||||
@ -93,41 +94,43 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
|
||||
// Public SelectBox Methods - routed through delegate interface
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
get onDidSelect(): Event<ISelectData> {
|
||||
return this.selectBoxDelegate.onDidSelect;
|
||||
}
|
||||
|
||||
public setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
this.selectBoxDelegate.setOptions(options, selected);
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
select(index: number): void {
|
||||
this.selectBoxDelegate.select(index);
|
||||
}
|
||||
|
||||
public setAriaLabel(label: string): void {
|
||||
setAriaLabel(label: string): void {
|
||||
this.selectBoxDelegate.setAriaLabel(label);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
this.selectBoxDelegate.focus();
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
blur(): void {
|
||||
this.selectBoxDelegate.blur();
|
||||
}
|
||||
|
||||
// Public Widget Methods - routed through delegate interface
|
||||
setFocusable(focusable: boolean): void {
|
||||
this.selectBoxDelegate.setFocusable(focusable);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
render(container: HTMLElement): void {
|
||||
this.selectBoxDelegate.render(container);
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
style(styles: ISelectBoxStyles): void {
|
||||
this.selectBoxDelegate.style(styles);
|
||||
}
|
||||
|
||||
public applyStyles(): void {
|
||||
applyStyles(): void {
|
||||
this.selectBoxDelegate.applyStyles();
|
||||
}
|
||||
}
|
||||
|
@ -293,16 +293,22 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.tabIndex = 0;
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.tabIndex = -1;
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public setFocusable(focusable: boolean): void {
|
||||
this.selectElement.tabIndex = focusable ? 0 : -1;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
container.classList.add('select-container');
|
||||
|
@ -132,16 +132,22 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.tabIndex = 0;
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.tabIndex = -1;
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public setFocusable(focusable: boolean): void {
|
||||
this.selectElement.tabIndex = focusable ? 0 : -1;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.classList.add('select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
|
@ -73,6 +73,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container {
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
.monaco-table-tr {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-table-th {
|
||||
@ -35,18 +36,12 @@
|
||||
.monaco-table-th,
|
||||
.monaco-table-td {
|
||||
box-sizing: border-box;
|
||||
padding-left: 10px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-table-th[data-col-index="0"],
|
||||
.monaco-table-td[data-col-index="0"] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
@ -250,7 +250,10 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
|
||||
|
||||
this.cachedHeight = height;
|
||||
this.splitview.layout(width);
|
||||
this.list.layout(height - this.virtualDelegate.headerRowHeight, width);
|
||||
|
||||
const listHeight = height - this.virtualDelegate.headerRowHeight;
|
||||
this.list.getHTMLElement().style.height = `${listHeight}px`;
|
||||
this.list.layout(listHeight, width);
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
@ -273,6 +276,14 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
|
||||
this.list.domFocus();
|
||||
}
|
||||
|
||||
setAnchor(index: number | undefined): void {
|
||||
this.list.setAnchor(index);
|
||||
}
|
||||
|
||||
getAnchor(): number | undefined {
|
||||
return this.list.getAnchor();
|
||||
}
|
||||
|
||||
getSelectedElements(): TRow[] {
|
||||
return this.list.getSelectedElements();
|
||||
}
|
||||
@ -297,12 +308,12 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
|
||||
this.list.focusPrevious(n, loop, browserEvent);
|
||||
}
|
||||
|
||||
focusNextPage(browserEvent?: UIEvent): void {
|
||||
this.list.focusNextPage(browserEvent);
|
||||
focusNextPage(browserEvent?: UIEvent): Promise<void> {
|
||||
return this.list.focusNextPage(browserEvent);
|
||||
}
|
||||
|
||||
focusPreviousPage(browserEvent?: UIEvent): void {
|
||||
this.list.focusPreviousPage(browserEvent);
|
||||
focusPreviousPage(browserEvent?: UIEvent): Promise<void> {
|
||||
return this.list.focusPreviousPage(browserEvent);
|
||||
}
|
||||
|
||||
focusFirst(browserEvent?: UIEvent): void {
|
||||
@ -317,6 +328,10 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
|
||||
return this.list.getFocus();
|
||||
}
|
||||
|
||||
getFocusedElements(): TRow[] {
|
||||
return this.list.getFocusedElements();
|
||||
}
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
this.list.reveal(index, relativeTop);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { range, equals, distinctES6 } from 'vs/base/common/arrays';
|
||||
import { range, equals, distinctES6, firstOrDefault } from 'vs/base/common/arrays';
|
||||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
|
||||
@ -683,6 +683,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
if (typeof options.filterOnType !== 'undefined') {
|
||||
this._filterOnType = !!options.filterOnType;
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.updateFilterOnTypeTitleAndIcon();
|
||||
}
|
||||
|
||||
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
|
||||
@ -1168,6 +1169,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
private focusTrait: Trait<T>,
|
||||
private selectionTrait: Trait<T>,
|
||||
private anchorTrait: Trait<T>,
|
||||
options: ITreeNodeListOptions<T, TFilterData, TRef>
|
||||
) {
|
||||
super(user, container, virtualDelegate, renderers, options);
|
||||
@ -1186,6 +1188,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
|
||||
const additionalFocus: number[] = [];
|
||||
const additionalSelection: number[] = [];
|
||||
let anchor: number | undefined;
|
||||
|
||||
elements.forEach((node, index) => {
|
||||
if (this.focusTrait.has(node)) {
|
||||
@ -1195,6 +1198,10 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
if (this.selectionTrait.has(node)) {
|
||||
additionalSelection.push(start + index);
|
||||
}
|
||||
|
||||
if (this.anchorTrait.has(node)) {
|
||||
anchor = start + index;
|
||||
}
|
||||
});
|
||||
|
||||
if (additionalFocus.length > 0) {
|
||||
@ -1204,6 +1211,10 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
if (additionalSelection.length > 0) {
|
||||
super.setSelection(distinctES6([...super.getSelection(), ...additionalSelection]));
|
||||
}
|
||||
|
||||
if (typeof anchor === 'number') {
|
||||
super.setAnchor(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
setFocus(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void {
|
||||
@ -1221,6 +1232,18 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
this.selectionTrait.set(indexes.map(i => this.element(i)), browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
setAnchor(index: number | undefined, fromAPI = false): void {
|
||||
super.setAnchor(index);
|
||||
|
||||
if (!fromAPI) {
|
||||
if (typeof index === 'undefined') {
|
||||
this.anchorTrait.set([]);
|
||||
} else {
|
||||
this.anchorTrait.set([this.element(index)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
|
||||
@ -1230,6 +1253,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
protected model: ITreeModel<T, TFilterData, TRef>;
|
||||
private focus: Trait<T>;
|
||||
private selection: Trait<T>;
|
||||
private anchor: Trait<T>;
|
||||
private eventBufferer = new EventBufferer();
|
||||
private typeFilterController?: TypeFilterController<T, TFilterData>;
|
||||
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
|
||||
@ -1298,7 +1322,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
|
||||
this.focus = new Trait(_options.identityProvider);
|
||||
this.selection = new Trait(_options.identityProvider);
|
||||
this.view = new TreeNodeList(user, container, treeDelegate, this.renderers, this.focus, this.selection, { ...asListOptions(() => this.model, _options), tree: this });
|
||||
this.anchor = new Trait(_options.identityProvider);
|
||||
this.view = new TreeNodeList(user, container, treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this });
|
||||
|
||||
this.model = this.createModel(user, this.view, _options);
|
||||
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
|
||||
@ -1552,6 +1577,25 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
this.model.refilter();
|
||||
}
|
||||
|
||||
setAnchor(element: TRef | undefined): void {
|
||||
if (typeof element === 'undefined') {
|
||||
return this.view.setAnchor(undefined);
|
||||
}
|
||||
|
||||
const node = this.model.getNode(element);
|
||||
this.anchor.set([node]);
|
||||
|
||||
const index = this.model.getListIndex(element);
|
||||
|
||||
if (index > -1) {
|
||||
this.view.setAnchor(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
getAnchor(): T | undefined {
|
||||
return firstOrDefault(this.anchor.get(), undefined);
|
||||
}
|
||||
|
||||
setSelection(elements: TRef[], browserEvent?: UIEvent): void {
|
||||
const nodes = elements.map(e => this.model.getNode(e));
|
||||
this.selection.set(nodes, browserEvent);
|
||||
@ -1580,12 +1624,12 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
this.view.focusPrevious(n, loop, browserEvent, filter);
|
||||
}
|
||||
|
||||
focusNextPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {
|
||||
this.view.focusNextPage(browserEvent, filter);
|
||||
focusNextPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): Promise<void> {
|
||||
return this.view.focusNextPage(browserEvent, filter);
|
||||
}
|
||||
|
||||
focusPreviousPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {
|
||||
this.view.focusPreviousPage(browserEvent, filter);
|
||||
focusPreviousPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): Promise<void> {
|
||||
return this.view.focusPreviousPage(browserEvent, filter);
|
||||
}
|
||||
|
||||
focusLast(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {
|
||||
|
@ -616,7 +616,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return this.tree.isCollapsible(this.getDataNode(element));
|
||||
}
|
||||
|
||||
isCollapsed(element: T): boolean {
|
||||
isCollapsed(element: TInput | T): boolean {
|
||||
return this.tree.isCollapsed(this.getDataNode(element));
|
||||
}
|
||||
|
||||
@ -628,6 +628,15 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.tree.refilter();
|
||||
}
|
||||
|
||||
setAnchor(element: T | undefined): void {
|
||||
this.tree.setAnchor(typeof element === 'undefined' ? undefined : this.getDataNode(element));
|
||||
}
|
||||
|
||||
getAnchor(): T | undefined {
|
||||
const node = this.tree.getAnchor();
|
||||
return node?.element as T;
|
||||
}
|
||||
|
||||
setSelection(elements: T[], browserEvent?: UIEvent): void {
|
||||
const nodes = elements.map(e => this.getDataNode(e));
|
||||
this.tree.setSelection(nodes, browserEvent);
|
||||
@ -651,12 +660,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.tree.focusPrevious(n, loop, browserEvent);
|
||||
}
|
||||
|
||||
focusNextPage(browserEvent?: UIEvent): void {
|
||||
this.tree.focusNextPage(browserEvent);
|
||||
focusNextPage(browserEvent?: UIEvent): Promise<void> {
|
||||
return this.tree.focusNextPage(browserEvent);
|
||||
}
|
||||
|
||||
focusPreviousPage(browserEvent?: UIEvent): void {
|
||||
this.tree.focusPreviousPage(browserEvent);
|
||||
focusPreviousPage(browserEvent?: UIEvent): Promise<void> {
|
||||
return this.tree.focusPreviousPage(browserEvent);
|
||||
}
|
||||
|
||||
focusLast(browserEvent?: UIEvent): void {
|
||||
|
@ -8,7 +8,6 @@ import { IndexTreeModel, IIndexTreeModelOptions, IList, IIndexTreeModelSpliceOpt
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
|
||||
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
@ -130,7 +129,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
private preserveCollapseState(elements: Iterable<ITreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> {
|
||||
if (this.sorter) {
|
||||
elements = mergeSort([...elements], this.sorter.compare.bind(this.sorter));
|
||||
elements = [...elements].sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return Iterable.map(elements, treeElement => {
|
||||
@ -185,7 +184,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
let childrenNodes = [...node.children] as ITreeNode<T, TFilterData>[];
|
||||
|
||||
if (recursive || first) {
|
||||
childrenNodes = mergeSort(childrenNodes, this.sorter!.compare.bind(this.sorter));
|
||||
childrenNodes = childrenNodes.sort(this.sorter!.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return Iterable.map<ITreeNode<T | null, TFilterData>, ITreeElement<T>>(childrenNodes, node => ({
|
||||
|
@ -3,23 +3,6 @@
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* @deprecated use `FileAccess.asFileUri(relativePath, requireFn).fsPath`
|
||||
*/
|
||||
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
|
||||
return getUriFromAmdModule(requirefn, relativePath).fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts.
|
||||
*/
|
||||
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][];
|
||||
@ -27,10 +10,7 @@ export abstract class LoaderStats {
|
||||
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>();
|
||||
@ -60,7 +40,7 @@ export abstract class LoaderStats {
|
||||
map.set(stat.detail, duration + stat.timestamp);
|
||||
}
|
||||
|
||||
const stats = mergeSort(require.getStats().slice(0), (a, b) => a.timestamp - b.timestamp);
|
||||
const stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
for (const stat of stats) {
|
||||
switch (stat.type) {
|
||||
|
@ -121,58 +121,10 @@ export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
|
||||
* so only use this when actually needing stable sort.
|
||||
*/
|
||||
export function mergeSort<T>(data: T[], compare: Compare<T>): T[] {
|
||||
_sort(data, compare, 0, data.length - 1, []);
|
||||
return data;
|
||||
}
|
||||
|
||||
function _merge<T>(a: T[], compare: Compare<T>, lo: number, mid: number, hi: number, aux: T[]): void {
|
||||
let leftIdx = lo, rightIdx = mid + 1;
|
||||
for (let i = lo; i <= hi; i++) {
|
||||
aux[i] = a[i];
|
||||
}
|
||||
for (let i = lo; i <= hi; i++) {
|
||||
if (leftIdx > mid) {
|
||||
// left side consumed
|
||||
a[i] = aux[rightIdx++];
|
||||
} else if (rightIdx > hi) {
|
||||
// right side consumed
|
||||
a[i] = aux[leftIdx++];
|
||||
} else if (compare(aux[rightIdx], aux[leftIdx]) < 0) {
|
||||
// right element is less -> comes first
|
||||
a[i] = aux[rightIdx++];
|
||||
} else {
|
||||
// left element comes first (less or equal)
|
||||
a[i] = aux[leftIdx++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _sort<T>(a: T[], compare: Compare<T>, lo: number, hi: number, aux: T[]) {
|
||||
if (hi <= lo) {
|
||||
return;
|
||||
}
|
||||
const mid = lo + ((hi - lo) / 2) | 0;
|
||||
_sort(a, compare, lo, mid, aux);
|
||||
_sort(a, compare, mid + 1, hi, aux);
|
||||
if (compare(a[mid], a[mid + 1]) <= 0) {
|
||||
// left and right are sorted and if the last-left element is less
|
||||
// or equals than the first-right element there is nothing else
|
||||
// to do
|
||||
return;
|
||||
}
|
||||
_merge(a, compare, lo, mid, hi, aux);
|
||||
}
|
||||
|
||||
|
||||
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
|
||||
const result: T[][] = [];
|
||||
let currentGroup: T[] | undefined = undefined;
|
||||
for (const element of mergeSort(data.slice(0), compare)) {
|
||||
for (const element of data.slice(0).sort(compare)) {
|
||||
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
|
||||
currentGroup = [element];
|
||||
result.push(currentGroup);
|
||||
|
@ -8,6 +8,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event, Listener } from 'vs/base/common/event';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export function isThenable<T>(obj: unknown): obj is Promise<T> {
|
||||
@ -592,19 +593,21 @@ export class ResourceQueue implements IDisposable {
|
||||
|
||||
private readonly queues = new Map<string, Queue<void>>();
|
||||
|
||||
queueFor(resource: URI): Queue<void> {
|
||||
const key = resource.toString();
|
||||
if (!this.queues.has(key)) {
|
||||
const queue = new Queue<void>();
|
||||
queue.onFinished(() => {
|
||||
queue.dispose();
|
||||
queueFor(resource: URI, extUri: IExtUri = defaultExtUri): Queue<void> {
|
||||
const key = extUri.getComparisonKey(resource);
|
||||
|
||||
let queue = this.queues.get(key);
|
||||
if (!queue) {
|
||||
queue = new Queue<void>();
|
||||
Event.once(queue.onFinished)(() => {
|
||||
queue?.dispose();
|
||||
this.queues.delete(key);
|
||||
});
|
||||
|
||||
this.queues.set(key, queue);
|
||||
}
|
||||
|
||||
return this.queues.get(key)!;
|
||||
return queue;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
@ -548,6 +548,9 @@ export namespace Codicon {
|
||||
export const runBelow = new Codicon('run-below', { fontCharacter: '\\ebbe' });
|
||||
export const notebookTemplate = new Codicon('notebook-template', { fontCharacter: '\\ebbf' });
|
||||
export const debugRerun = new Codicon('debug-rerun', { fontCharacter: '\\ebc0' });
|
||||
export const workspaceTrusted = new Codicon('workspace-trusted', { fontCharacter: '\\ebc1' });
|
||||
export const workspaceUntrusted = new Codicon('workspace-untrusted', { fontCharacter: '\\ebc2' });
|
||||
export const workspaceUnknown = new Codicon('workspace-unknown', { fontCharacter: '\\ebc3' });
|
||||
|
||||
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition);
|
||||
}
|
||||
|
@ -838,12 +838,8 @@ export class LcsDiff {
|
||||
let modifiedStop = 0;
|
||||
if (i > 0) {
|
||||
const prevChange = changes[i - 1];
|
||||
if (prevChange.originalLength > 0) {
|
||||
originalStop = prevChange.originalStart + prevChange.originalLength;
|
||||
}
|
||||
if (prevChange.modifiedLength > 0) {
|
||||
modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
|
||||
}
|
||||
originalStop = prevChange.originalStart + prevChange.originalLength;
|
||||
modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
|
||||
}
|
||||
|
||||
const checkOriginal = change.originalLength > 0;
|
||||
@ -868,7 +864,11 @@ export class LcsDiff {
|
||||
break;
|
||||
}
|
||||
|
||||
const score = this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength);
|
||||
const touchingPreviousChange = (originalStart === originalStop && modifiedStart === modifiedStop);
|
||||
const score = (
|
||||
(touchingPreviousChange ? 5 : 0)
|
||||
+ this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength)
|
||||
);
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
@ -878,6 +878,14 @@ export class LcsDiff {
|
||||
|
||||
change.originalStart -= bestDelta;
|
||||
change.modifiedStart -= bestDelta;
|
||||
|
||||
const mergedChangeArr: Array<DiffChange | null> = [null];
|
||||
if (i > 0 && this.ChangesOverlap(changes[i - 1], changes[i], mergedChangeArr)) {
|
||||
changes[i - 1] = mergedChangeArr[0]!;
|
||||
changes.splice(i, 1);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// There could be multiple longest common substrings.
|
||||
|
@ -365,28 +365,15 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
|
||||
return score ? createMatches(score) : null;
|
||||
}
|
||||
|
||||
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
|
||||
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
let matches: number[] = [];
|
||||
let score = 0;
|
||||
let idx = _wordPos;
|
||||
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
|
||||
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
|
||||
if (wordPos >= 0) {
|
||||
score += 1;
|
||||
matches.unshift(wordPos);
|
||||
idx = wordPos + 1;
|
||||
} else if (matches.length > 0) {
|
||||
// once we have started matching things
|
||||
// we need to match the remaining pattern
|
||||
// characters
|
||||
break;
|
||||
export function anyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number): FuzzyScore {
|
||||
const max = Math.min(13, pattern.length);
|
||||
for (; patternPos < max; patternPos++) {
|
||||
const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, false);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return [score, _wordPos, ...matches];
|
||||
return [0, wordPos];
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
import { ParseError, Node, JSONPath, Segment, parseTree, findNodeAtLocation } from './json';
|
||||
import { Edit, format, isEOL, FormattingOptions } from './jsonFormatter';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
|
||||
|
||||
export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] {
|
||||
@ -156,7 +155,7 @@ export function applyEdit(text: string, edit: Edit): string {
|
||||
}
|
||||
|
||||
export function applyEdits(text: string, edits: Edit[]): string {
|
||||
let sortedEdits = mergeSort(edits, (a, b) => {
|
||||
let sortedEdits = edits.slice(0).sort((a, b) => {
|
||||
const diff = a.offset - b.offset;
|
||||
if (diff === 0) {
|
||||
return a.length - b.length;
|
||||
|
@ -7,113 +7,117 @@
|
||||
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _definePolyfillMarks(timeOrigin) {
|
||||
(function () {
|
||||
|
||||
const _data = [];
|
||||
if (typeof timeOrigin === 'number') {
|
||||
_data.push('code/timeOrigin', timeOrigin);
|
||||
}
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _definePolyfillMarks(timeOrigin) {
|
||||
|
||||
function mark(name) {
|
||||
_data.push(name, Date.now());
|
||||
}
|
||||
function getMarks() {
|
||||
const result = [];
|
||||
for (let i = 0; i < _data.length; i += 2) {
|
||||
result.push({
|
||||
name: _data[i],
|
||||
startTime: _data[i + 1],
|
||||
});
|
||||
const _data = [];
|
||||
if (typeof timeOrigin === 'number') {
|
||||
_data.push('code/timeOrigin', timeOrigin);
|
||||
}
|
||||
return result;
|
||||
|
||||
function mark(name) {
|
||||
_data.push(name, Date.now());
|
||||
}
|
||||
function getMarks() {
|
||||
const result = [];
|
||||
for (let i = 0; i < _data.length; i += 2) {
|
||||
result.push({
|
||||
name: _data[i],
|
||||
startTime: _data[i + 1],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return { mark, getMarks };
|
||||
}
|
||||
return { mark, getMarks };
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _define() {
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _define() {
|
||||
|
||||
if (typeof performance === 'object' && typeof performance.mark === 'function') {
|
||||
// in a browser context, reuse performance-util
|
||||
if (typeof performance === 'object' && typeof performance.mark === 'function') {
|
||||
// in a browser context, reuse performance-util
|
||||
|
||||
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
|
||||
// safari & webworker: because there is no timeOrigin and no workaround
|
||||
// we use the `Date.now`-based polyfill.
|
||||
return _definePolyfillMarks();
|
||||
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
|
||||
// safari & webworker: because there is no timeOrigin and no workaround
|
||||
// we use the `Date.now`-based polyfill.
|
||||
return _definePolyfillMarks();
|
||||
|
||||
} else {
|
||||
// use "native" performance for mark and getMarks
|
||||
return {
|
||||
mark(name) {
|
||||
performance.mark(name);
|
||||
},
|
||||
getMarks() {
|
||||
let timeOrigin = performance.timeOrigin;
|
||||
if (typeof timeOrigin !== 'number') {
|
||||
// safari: there is no timerOrigin but in renderers there is the timing-property
|
||||
// see https://bugs.webkit.org/show_bug.cgi?id=174862
|
||||
timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart;
|
||||
}
|
||||
const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }];
|
||||
for (const entry of performance.getEntriesByType('mark')) {
|
||||
result.push({
|
||||
name: entry.name,
|
||||
startTime: Math.round(timeOrigin + entry.startTime)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} else if (typeof process === 'object') {
|
||||
// node.js: use the normal polyfill but add the timeOrigin
|
||||
// from the node perf_hooks API as very first mark
|
||||
const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin);
|
||||
return _definePolyfillMarks(timeOrigin);
|
||||
|
||||
} else {
|
||||
// use "native" performance for mark and getMarks
|
||||
return {
|
||||
mark(name) {
|
||||
performance.mark(name);
|
||||
},
|
||||
getMarks() {
|
||||
let timeOrigin = performance.timeOrigin;
|
||||
if (typeof timeOrigin !== 'number') {
|
||||
// safari: there is no timerOrigin but in renderers there is the timing-property
|
||||
// see https://bugs.webkit.org/show_bug.cgi?id=174862
|
||||
timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart;
|
||||
}
|
||||
const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }];
|
||||
for (const entry of performance.getEntriesByType('mark')) {
|
||||
result.push({
|
||||
name: entry.name,
|
||||
startTime: Math.round(timeOrigin + entry.startTime)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
// unknown environment
|
||||
console.trace('perf-util loaded in UNKNOWN environment');
|
||||
return _definePolyfillMarks();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (typeof process === 'object') {
|
||||
// node.js: use the normal polyfill but add the timeOrigin
|
||||
// from the node perf_hooks API as very first mark
|
||||
const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin);
|
||||
return _definePolyfillMarks(timeOrigin);
|
||||
function _factory(sharedObj) {
|
||||
if (!sharedObj.MonacoPerformanceMarks) {
|
||||
sharedObj.MonacoPerformanceMarks = _define();
|
||||
}
|
||||
return sharedObj.MonacoPerformanceMarks;
|
||||
}
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
// Because we want both instances to use the same perf-data
|
||||
// we store them globally
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var sharedObj;
|
||||
if (typeof global === 'object') {
|
||||
// nodejs
|
||||
sharedObj = global;
|
||||
} else if (typeof self === 'object') {
|
||||
// browser
|
||||
sharedObj = self;
|
||||
} else {
|
||||
// unknown environment
|
||||
console.trace('perf-util loaded in UNKNOWN environment');
|
||||
return _definePolyfillMarks();
|
||||
sharedObj = {};
|
||||
}
|
||||
}
|
||||
|
||||
function _factory(sharedObj) {
|
||||
if (!sharedObj.MonacoPerformanceMarks) {
|
||||
sharedObj.MonacoPerformanceMarks = _define();
|
||||
if (typeof define === 'function') {
|
||||
// amd
|
||||
define([], function () { return _factory(sharedObj); });
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
// commonjs
|
||||
module.exports = _factory(sharedObj);
|
||||
} else {
|
||||
console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)');
|
||||
sharedObj.perf = _factory(sharedObj);
|
||||
}
|
||||
return sharedObj.MonacoPerformanceMarks;
|
||||
}
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
// Because we want both instances to use the same perf-data
|
||||
// we store them globally
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var sharedObj;
|
||||
if (typeof global === 'object') {
|
||||
// nodejs
|
||||
sharedObj = global;
|
||||
} else if (typeof self === 'object') {
|
||||
// browser
|
||||
sharedObj = self;
|
||||
} else {
|
||||
sharedObj = {};
|
||||
}
|
||||
|
||||
if (typeof define === 'function') {
|
||||
// amd
|
||||
define([], function () { return _factory(sharedObj); });
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
// commonjs
|
||||
module.exports = _factory(sharedObj);
|
||||
} else {
|
||||
console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)');
|
||||
sharedObj.perf = _factory(sharedObj);
|
||||
}
|
||||
})();
|
||||
|
@ -27,37 +27,38 @@ export interface IProcessEnvironment {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface is intentionally not identical to node.js
|
||||
* process because it also works in sandboxed environments
|
||||
* where the process object is implemented differently. We
|
||||
* define the properties here that we need for `platform`
|
||||
* to work and nothing else.
|
||||
*/
|
||||
export interface INodeProcess {
|
||||
platform: 'win32' | 'linux' | 'darwin';
|
||||
platform: string;
|
||||
env: IProcessEnvironment;
|
||||
nextTick: Function;
|
||||
nextTick?: (callback: (...args: any[]) => void) => void;
|
||||
versions?: {
|
||||
electron?: string;
|
||||
};
|
||||
sandboxed?: boolean; // Electron
|
||||
sandboxed?: boolean;
|
||||
type?: string;
|
||||
cwd(): string;
|
||||
cwd: () => string;
|
||||
}
|
||||
|
||||
declare const process: INodeProcess;
|
||||
declare const global: any;
|
||||
declare const global: unknown;
|
||||
declare const self: unknown;
|
||||
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
maxTouchPoints?: number;
|
||||
}
|
||||
declare const navigator: INavigator;
|
||||
declare const self: any;
|
||||
|
||||
const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
|
||||
export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {});
|
||||
|
||||
let nodeProcess: INodeProcess | undefined = undefined;
|
||||
if (typeof process !== 'undefined') {
|
||||
// Native environment (non-sandboxed)
|
||||
nodeProcess = process;
|
||||
} else if (typeof _globals.vscode !== 'undefined') {
|
||||
} else if (typeof globals.vscode !== 'undefined') {
|
||||
// Native environment (sandboxed)
|
||||
nodeProcess = _globals.vscode.process;
|
||||
nodeProcess = globals.vscode.process;
|
||||
}
|
||||
|
||||
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
|
||||
@ -83,6 +84,13 @@ export const browserCodeLoadingCacheStrategy: 'none' | 'code' | 'bypassHeatCheck
|
||||
})();
|
||||
export const isPreferringBrowserCodeLoad = typeof browserCodeLoadingCacheStrategy === 'string';
|
||||
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
maxTouchPoints?: number;
|
||||
}
|
||||
declare const navigator: INavigator;
|
||||
|
||||
// Web environment
|
||||
if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_userAgent = navigator.userAgent;
|
||||
@ -209,10 +217,8 @@ export const locale = _locale;
|
||||
*/
|
||||
export const translationsConfigFile = _translationsConfigFile;
|
||||
|
||||
export const globals: any = _globals;
|
||||
|
||||
interface ISetImmediate {
|
||||
(callback: (...args: any[]) => void): void;
|
||||
(callback: (...args: unknown[]) => void): void;
|
||||
}
|
||||
|
||||
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
@ -247,11 +253,11 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
|
||||
};
|
||||
}
|
||||
if (nodeProcess && typeof nodeProcess.nextTick === 'function') {
|
||||
if (typeof nodeProcess?.nextTick === 'function') {
|
||||
return nodeProcess.nextTick.bind(nodeProcess);
|
||||
}
|
||||
const _promise = Promise.resolve();
|
||||
return (callback: (...args: any[]) => void) => _promise.then(callback);
|
||||
return (callback: (...args: unknown[]) => void) => _promise.then(callback);
|
||||
})();
|
||||
|
||||
export const enum OperatingSystem {
|
||||
|
@ -5,26 +5,27 @@
|
||||
|
||||
import { isWindows, isMacintosh, setImmediate, globals, INodeProcess } from 'vs/base/common/platform';
|
||||
|
||||
declare const process: INodeProcess;
|
||||
|
||||
let safeProcess: INodeProcess;
|
||||
let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) => void; };
|
||||
|
||||
// Native node.js environment
|
||||
declare const process: INodeProcess;
|
||||
if (typeof process !== 'undefined') {
|
||||
safeProcess = process;
|
||||
safeProcess = {
|
||||
get platform() { return process.platform; },
|
||||
get env() { return process.env; },
|
||||
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); },
|
||||
nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); }
|
||||
};
|
||||
}
|
||||
|
||||
// Native sandbox environment
|
||||
else if (typeof globals.vscode !== 'undefined') {
|
||||
const sandboxProcess: INodeProcess = 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' ? '\\' : '/')); }
|
||||
get platform() { return sandboxProcess.platform; },
|
||||
get env() { return sandboxProcess.env; },
|
||||
cwd() { return sandboxProcess.cwd(); },
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
|
||||
};
|
||||
}
|
||||
|
||||
@ -33,16 +34,39 @@ else {
|
||||
safeProcess = {
|
||||
|
||||
// Supported
|
||||
get platform(): 'win32' | 'linux' | 'darwin' { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
|
||||
get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
|
||||
|
||||
// Unsupported
|
||||
get env() { return Object.create(null); },
|
||||
cwd(): string { return '/'; }
|
||||
cwd() { return '/'; }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides safe access to the `cwd` property in node.js, sandboxed or web
|
||||
* environments.
|
||||
*
|
||||
* Note: in web, this property is hardcoded to be `/`.
|
||||
*/
|
||||
export const cwd = safeProcess.cwd;
|
||||
|
||||
/**
|
||||
* Provides safe access to the `env` property in node.js, sandboxed or web
|
||||
* environments.
|
||||
*
|
||||
* Note: in web, this property is hardcoded to be `{}`.
|
||||
*/
|
||||
export const env = safeProcess.env;
|
||||
|
||||
/**
|
||||
* Provides safe access to the `platform` property in node.js, sandboxed or web
|
||||
* environments.
|
||||
*/
|
||||
export const platform = safeProcess.platform;
|
||||
|
||||
/**
|
||||
* Provides safe access to the `nextTick` method in node.js, sandboxed or web
|
||||
* environments.
|
||||
*/
|
||||
export const nextTick = safeProcess.nextTick;
|
||||
|
@ -20,6 +20,8 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
|
||||
return pattern.toLowerCase();
|
||||
} else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) {
|
||||
return pattern[0].toUpperCase() + pattern.substr(1);
|
||||
} else if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) {
|
||||
return pattern[0].toLowerCase() + pattern.substr(1);
|
||||
} else {
|
||||
// we don't understand its pattern yet.
|
||||
return pattern;
|
||||
|
@ -101,19 +101,19 @@ export interface WriteableStream<T> extends ReadableStream<T> {
|
||||
/**
|
||||
* Signals an error to the consumer of the stream via the
|
||||
* on('error') handler if the stream is flowing.
|
||||
*
|
||||
* NOTE: call `end` to signal that the stream has ended,
|
||||
* this DOES NOT happen automatically from `error`.
|
||||
*/
|
||||
error(error: Error): void;
|
||||
|
||||
/**
|
||||
* Signals the end of the stream to the consumer. If the
|
||||
* result is not an error, will trigger the on('data') event
|
||||
* result is provided, will trigger the on('data') event
|
||||
* listener if the stream is flowing and buffer the data
|
||||
* otherwise until the stream is flowing.
|
||||
*
|
||||
* In case of an error, the on('error') event will be used
|
||||
* if the stream is flowing.
|
||||
*/
|
||||
end(result?: T | Error): void;
|
||||
end(result?: T): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,15 +267,13 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: T | Error): void {
|
||||
end(result?: T): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (typeof result !== 'undefined') {
|
||||
// end with data if provided
|
||||
if (typeof result !== 'undefined') {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,13 @@ export function isNumber(obj: unknown): obj is number {
|
||||
return (typeof obj === 'number' && !isNaN(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is an Iterable, casting to the given generic
|
||||
*/
|
||||
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
|
||||
return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Boolean or not.
|
||||
*/
|
||||
|
@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { rtrim } from 'vs/base/common/strings';
|
||||
import { sep, join, normalize, dirname, basename } from 'vs/base/common/path';
|
||||
import { readdirSync } from 'vs/base/node/pfs';
|
||||
@ -52,7 +53,11 @@ export function realcaseSync(path: string): string | null {
|
||||
|
||||
export async function realpath(path: string): Promise<string> {
|
||||
try {
|
||||
return await fs.promises.realpath(path);
|
||||
// DO NOT USE `fs.promises.realpath` here as it internally
|
||||
// calls `fs.native.realpath` which will result in subst
|
||||
// drives to be resolved to their target on Windows
|
||||
// https://github.com/microsoft/vscode/issues/118562
|
||||
return await promisify(fs.realpath)(path);
|
||||
} catch (error) {
|
||||
|
||||
// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization
|
||||
|
@ -6,27 +6,13 @@
|
||||
import * as fs from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { ResourceQueue } from 'vs/base/common/async';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isEqualOrParent, isRootOrDriveLetter } from 'vs/base/common/extpath';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
|
||||
//#region Constants
|
||||
|
||||
// See https://github.com/microsoft/vscode/issues/30180
|
||||
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
|
||||
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
|
||||
|
||||
// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149
|
||||
const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB
|
||||
const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB
|
||||
|
||||
export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
|
||||
export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE;
|
||||
|
||||
//#endregion
|
||||
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
//#region rimraf
|
||||
|
||||
@ -361,6 +347,11 @@ export namespace SymlinkSupport {
|
||||
|
||||
//#region Write File
|
||||
|
||||
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
|
||||
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
|
||||
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
|
||||
const writeQueues = new ResourceQueue();
|
||||
|
||||
/**
|
||||
* Same as `fs.writeFile` but with an additional call to
|
||||
* `fs.fdatasync` after writing to ensure changes are
|
||||
@ -373,47 +364,13 @@ export function writeFile(path: string, data: Buffer, options?: IWriteFileOption
|
||||
export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> {
|
||||
const queueKey = toQueueKey(path);
|
||||
|
||||
return ensureWriteFileQueue(queueKey).queue(() => {
|
||||
return writeQueues.queueFor(URI.file(path), extUriBiasedIgnorePathCase).queue(() => {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
|
||||
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
|
||||
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
|
||||
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
|
||||
const writeFilePathQueues: Map<string, Queue<void>> = new Map();
|
||||
|
||||
function toQueueKey(path: string): string {
|
||||
let queueKey = path;
|
||||
if (isWindows || isMacintosh) {
|
||||
queueKey = queueKey.toLowerCase(); // accommodate for case insensitive file systems
|
||||
}
|
||||
|
||||
return queueKey;
|
||||
}
|
||||
|
||||
function ensureWriteFileQueue(queueKey: string): Queue<void> {
|
||||
const existingWriteFileQueue = writeFilePathQueues.get(queueKey);
|
||||
if (existingWriteFileQueue) {
|
||||
return existingWriteFileQueue;
|
||||
}
|
||||
|
||||
const writeFileQueue = new Queue<void>();
|
||||
writeFilePathQueues.set(queueKey, writeFileQueue);
|
||||
|
||||
const onFinish = Event.once(writeFileQueue.onFinished);
|
||||
onFinish(() => {
|
||||
writeFilePathQueues.delete(queueKey);
|
||||
writeFileQueue.dispose();
|
||||
});
|
||||
|
||||
return writeFileQueue;
|
||||
}
|
||||
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
@ -607,9 +564,6 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr
|
||||
|
||||
// Symlink
|
||||
if (symbolicLink) {
|
||||
if (symbolicLink.dangling) {
|
||||
return; // do not copy dangling symbolic links (https://github.com/microsoft/vscode/issues/111621)
|
||||
}
|
||||
|
||||
// Try to re-create the symlink unless `preserveSymlinks: false`
|
||||
if (payload.options.preserveSymlinks) {
|
||||
@ -620,6 +574,10 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr
|
||||
console.warn('[node.js fs] copy of symlink failed: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (symbolicLink.dangling) {
|
||||
return; // skip dangling symbolic links from here on (https://github.com/microsoft/vscode/issues/111621)
|
||||
}
|
||||
}
|
||||
|
||||
// Folder
|
||||
|
@ -6,7 +6,6 @@
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { env } from 'vs/base/common/process';
|
||||
|
||||
// This is required, since parseInt("7-preview") will return 7.
|
||||
const IntRegex: RegExp = /^\d+$/;
|
||||
@ -103,17 +102,17 @@ function getProgramFilesPath(
|
||||
|
||||
if (!useAlternateBitness) {
|
||||
// Just use the native system bitness
|
||||
return env.ProgramFiles || null;
|
||||
return process.env.ProgramFiles || null;
|
||||
}
|
||||
|
||||
// We might be a 64-bit process looking for 32-bit program files
|
||||
if (processArch === Arch.x64) {
|
||||
return env['ProgramFiles(x86)'] || null;
|
||||
return process.env['ProgramFiles(x86)'] || null;
|
||||
}
|
||||
|
||||
// We might be a 32-bit process looking for 64-bit program files
|
||||
if (osArch === Arch.x64) {
|
||||
return env.ProgramW6432 || null;
|
||||
return process.env.ProgramW6432 || null;
|
||||
}
|
||||
|
||||
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
|
||||
@ -194,12 +193,12 @@ async function findPSCoreWindowsInstallation(
|
||||
|
||||
async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
|
||||
// We can't proceed if there's no LOCALAPPDATA path
|
||||
if (!env.LOCALAPPDATA) {
|
||||
if (!process.env.LOCALAPPDATA) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the base directory for MSIX application exe shortcuts
|
||||
const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
|
||||
const msixAppDir = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
|
||||
|
||||
if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) {
|
||||
return null;
|
||||
@ -211,15 +210,11 @@ async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}):
|
||||
: { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' };
|
||||
|
||||
// We should find only one such application, so return on the first one
|
||||
try {
|
||||
for (const subdir of await pfs.readdir(msixAppDir)) {
|
||||
if (pwshMsixDirRegex.test(subdir)) {
|
||||
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
|
||||
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
|
||||
}
|
||||
for (const subdir of await pfs.readdir(msixAppDir)) {
|
||||
if (pwshMsixDirRegex.test(subdir)) {
|
||||
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
|
||||
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Unable to read MSIX directory (${msixAppDir}) because of the following error: ${err}`);
|
||||
}
|
||||
|
||||
// If we find nothing, return null
|
||||
@ -234,7 +229,7 @@ function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
|
||||
|
||||
function findWinPS(): IPossiblePowerShellExe | null {
|
||||
const winPSPath = path.join(
|
||||
env.windir!,
|
||||
process.env.windir!,
|
||||
processArch === Arch.x86 && osArch !== Arch.x86 ? 'SysNative' : 'System32',
|
||||
'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
||||
|
||||
|
@ -8,6 +8,7 @@ import * as fs from 'fs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as cp from 'child_process';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as process from 'vs/base/common/process';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
|
@ -13,7 +13,7 @@ import * as processes from 'vs/base/node/processes';
|
||||
* shell that the terminal uses by default.
|
||||
* @param p The platform to detect the shell of.
|
||||
*/
|
||||
export async function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): Promise<string> {
|
||||
export async function getSystemShell(p: platform.Platform, env: platform.IProcessEnvironment): Promise<string> {
|
||||
if (p === platform.Platform.Windows) {
|
||||
if (platform.isWindows) {
|
||||
return getSystemShellWindows();
|
||||
@ -25,7 +25,7 @@ export async function getSystemShell(p: platform.Platform, env = process.env as
|
||||
return getSystemShellUnixLike(p, env);
|
||||
}
|
||||
|
||||
export function getSystemShellSync(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string {
|
||||
export function getSystemShellSync(p: platform.Platform, env: platform.IProcessEnvironment): string {
|
||||
if (p === platform.Platform.Windows) {
|
||||
if (platform.isWindows) {
|
||||
return getSystemShellWindowsSync(env);
|
||||
@ -45,7 +45,7 @@ function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvi
|
||||
}
|
||||
|
||||
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
|
||||
let unixLikeTerminal: string;
|
||||
let unixLikeTerminal: string | undefined;
|
||||
if (platform.isWindows) {
|
||||
unixLikeTerminal = '/bin/bash'; // for WSL
|
||||
} else {
|
||||
|
@ -18,13 +18,31 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Platform, platform } from 'vs/base/common/platform';
|
||||
|
||||
export class NodeSocket implements ISocket {
|
||||
|
||||
public readonly socket: Socket;
|
||||
private readonly _errorListener: (err: any) => void;
|
||||
|
||||
constructor(socket: Socket) {
|
||||
this.socket = socket;
|
||||
this._errorListener = (err: any) => {
|
||||
if (err) {
|
||||
if (err.code === 'EPIPE') {
|
||||
// An EPIPE exception at the wrong time can lead to a renderer process crash
|
||||
// so ignore the error since the socket will fire the close event soon anyways:
|
||||
// > https://nodejs.org/api/errors.html#errors_common_system_errors
|
||||
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
|
||||
// > process to read the data. Commonly encountered at the net and http layers,
|
||||
// > indicative that the remote side of the stream being written to has been closed.
|
||||
return;
|
||||
}
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
};
|
||||
this.socket.on('error', this._errorListener);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.off('error', this._errorListener);
|
||||
this.socket.destroy();
|
||||
}
|
||||
|
||||
@ -62,7 +80,20 @@ export class NodeSocket implements ISocket {
|
||||
// > However, the false return value is only advisory and the writable stream will unconditionally
|
||||
// > accept and buffer chunk even if it has not been allowed to drain.
|
||||
try {
|
||||
this.socket.write(<Buffer>buffer.buffer);
|
||||
this.socket.write(<Buffer>buffer.buffer, (err: any) => {
|
||||
if (err) {
|
||||
if (err.code === 'EPIPE') {
|
||||
// An EPIPE exception at the wrong time can lead to a renderer process crash
|
||||
// so ignore the error since the socket will fire the close event soon anyways:
|
||||
// > https://nodejs.org/api/errors.html#errors_common_system_errors
|
||||
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
|
||||
// > process to read the data. Commonly encountered at the net and http layers,
|
||||
// > indicative that the remote side of the stream being written to has been closed.
|
||||
return;
|
||||
}
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === 'EPIPE') {
|
||||
// An EPIPE exception at the wrong time can lead to a renderer process crash
|
||||
|
@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { TestServiceClient } from './testService';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
|
||||
function createClient(): Client {
|
||||
return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), {
|
||||
|
@ -7,7 +7,7 @@
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
z-index: 2000;
|
||||
padding-bottom: 6px;
|
||||
padding: 0 1px 6px 1px;
|
||||
left: 50%;
|
||||
margin-left: -300px;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation, QuickInputHideReason, IQuickInputHideEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickInputList, QuickInputListFocus } from './quickInputList';
|
||||
@ -31,6 +31,7 @@ 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 { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
|
||||
export interface IQuickInputOptions {
|
||||
idPrefix: string;
|
||||
@ -133,6 +134,7 @@ type Visibilities = {
|
||||
};
|
||||
|
||||
class QuickInput extends Disposable implements IQuickInput {
|
||||
protected static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
|
||||
|
||||
private _title: string | undefined;
|
||||
private _description: string | undefined;
|
||||
@ -144,9 +146,14 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
private _busy = false;
|
||||
private _ignoreFocusOut = false;
|
||||
private _buttons: IQuickInputButton[] = [];
|
||||
protected noValidationMessage = QuickInput.noPromptMessage;
|
||||
private _validationMessage: string | undefined;
|
||||
private _lastValidationMessage: string | undefined;
|
||||
private _severity: Severity = Severity.Ignore;
|
||||
private _lastSeverity: Severity | undefined;
|
||||
private buttonsUpdated = false;
|
||||
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
||||
private readonly onDidHideEmitter = this._register(new Emitter<void>());
|
||||
private readonly onDidHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());
|
||||
private readonly onDisposeEmitter = this._register(new Emitter<void>());
|
||||
|
||||
protected readonly visibleDisposables = this._register(new DisposableStore());
|
||||
@ -241,6 +248,24 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
this.update();
|
||||
}
|
||||
|
||||
get validationMessage() {
|
||||
return this._validationMessage;
|
||||
}
|
||||
|
||||
set validationMessage(validationMessage: string | undefined) {
|
||||
this._validationMessage = validationMessage;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get severity() {
|
||||
return this._severity;
|
||||
}
|
||||
|
||||
set severity(severity: Severity) {
|
||||
this._severity = severity;
|
||||
this.update();
|
||||
}
|
||||
|
||||
readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event;
|
||||
|
||||
show(): void {
|
||||
@ -266,10 +291,10 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
this.ui.hide();
|
||||
}
|
||||
|
||||
didHide(): void {
|
||||
didHide(reason = QuickInputHideReason.Other): void {
|
||||
this.visible = false;
|
||||
this.visibleDisposables.clear();
|
||||
this.onDidHideEmitter.fire();
|
||||
this.onDidHideEmitter.fire({ reason });
|
||||
}
|
||||
|
||||
readonly onDidHide = this.onDidHideEmitter.event;
|
||||
@ -328,6 +353,16 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
this.ui.ignoreFocusOut = this.ignoreFocusOut;
|
||||
this.ui.setEnabled(this.enabled);
|
||||
this.ui.setContextKey(this.contextKey);
|
||||
|
||||
const validationMessage = this.validationMessage || this.noValidationMessage;
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderLabelWithIcons(escape(validationMessage)));
|
||||
}
|
||||
if (this._lastSeverity !== this.severity) {
|
||||
this._lastSeverity = this.severity;
|
||||
this.showMessageDecoration(this.severity);
|
||||
}
|
||||
}
|
||||
|
||||
private getTitle() {
|
||||
@ -359,7 +394,7 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
|
||||
protected showMessageDecoration(severity: Severity) {
|
||||
this.ui.inputBox.showDecoration(severity);
|
||||
if (severity === Severity.Error) {
|
||||
if (severity !== Severity.Ignore) {
|
||||
const styles = this.ui.inputBox.stylesForType(severity);
|
||||
this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';
|
||||
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
|
||||
@ -414,8 +449,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>());
|
||||
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;
|
||||
@ -587,15 +620,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
get validationMessage() {
|
||||
return this._validationMessage;
|
||||
}
|
||||
|
||||
set validationMessage(validationMessage: string | undefined) {
|
||||
this._validationMessage = validationMessage;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get customButton() {
|
||||
return this._customButton;
|
||||
}
|
||||
@ -964,12 +988,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.selectedItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
const validationMessage = this.validationMessage || '';
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderLabelWithIcons(escape(validationMessage)));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
this.ui.customButton.label = this.customLabel || '';
|
||||
this.ui.customButton.element.title = this.customHover || '';
|
||||
this.ui.setComboboxAccessibility(true);
|
||||
@ -987,18 +1005,12 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}
|
||||
|
||||
class InputBox extends QuickInput implements IInputBox {
|
||||
|
||||
private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
|
||||
|
||||
private _value = '';
|
||||
private _valueSelection: Readonly<[number, number]> | undefined;
|
||||
private valueSelectionUpdated = true;
|
||||
private _placeholder: string | undefined;
|
||||
private _password = false;
|
||||
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>());
|
||||
|
||||
@ -1043,16 +1055,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
this._prompt = prompt;
|
||||
this.noValidationMessage = prompt
|
||||
? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt)
|
||||
: InputBox.noPromptMessage;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get validationMessage() {
|
||||
return this._validationMessage;
|
||||
}
|
||||
|
||||
set validationMessage(validationMessage: string | undefined) {
|
||||
this._validationMessage = validationMessage;
|
||||
: QuickInput.noPromptMessage;
|
||||
this.update();
|
||||
}
|
||||
|
||||
@ -1100,12 +1103,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
if (this.ui.inputBox.password !== this.password) {
|
||||
this.ui.inputBox.password = this.password;
|
||||
}
|
||||
const validationMessage = this.validationMessage || this.noValidationMessage;
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderLabelWithIcons(validationMessage));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1222,9 +1220,6 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`));
|
||||
|
||||
const progressBar = new ProgressBar(container);
|
||||
progressBar.getContainer().classList.add('quick-input-progress');
|
||||
|
||||
const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options));
|
||||
this._register(list.onChangedAllVisibleChecked(checked => {
|
||||
checkAll.checked = checked;
|
||||
@ -1250,6 +1245,9 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
const progressBar = new ProgressBar(container);
|
||||
progressBar.getContainer().classList.add('quick-input-progress');
|
||||
|
||||
const focusTracker = dom.trackFocus(container);
|
||||
this._register(focusTracker);
|
||||
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => {
|
||||
@ -1257,7 +1255,7 @@ export class QuickInputController extends Disposable {
|
||||
}, true));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
|
||||
this.hide();
|
||||
this.hide(QuickInputHideReason.Blur);
|
||||
}
|
||||
this.previousFocusElement = undefined;
|
||||
}));
|
||||
@ -1273,7 +1271,7 @@ export class QuickInputController extends Disposable {
|
||||
break;
|
||||
case KeyCode.Escape:
|
||||
dom.EventHelper.stop(e, true);
|
||||
this.hide();
|
||||
this.hide(QuickInputHideReason.Gesture);
|
||||
break;
|
||||
case KeyCode.Tab:
|
||||
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
|
||||
@ -1320,8 +1318,8 @@ export class QuickInputController extends Disposable {
|
||||
message,
|
||||
customButtonContainer,
|
||||
customButton,
|
||||
progressBar,
|
||||
list,
|
||||
progressBar,
|
||||
onDidAccept: this.onDidAcceptEmitter.event,
|
||||
onDidCustom: this.onDidCustomEmitter.event,
|
||||
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
|
||||
@ -1408,6 +1406,7 @@ export class QuickInputController extends Disposable {
|
||||
resolve(undefined);
|
||||
}),
|
||||
];
|
||||
input.title = options.title;
|
||||
input.canSelectMany = !!options.canPickMany;
|
||||
input.placeholder = options.placeHolder;
|
||||
input.ignoreFocusOut = !!options.ignoreFocusLost;
|
||||
@ -1438,6 +1437,22 @@ export class QuickInputController extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private setValidationOnInput(input: IInputBox, validationResult: string | {
|
||||
content: string;
|
||||
severity: Severity;
|
||||
} | null | undefined) {
|
||||
if (validationResult && isString(validationResult)) {
|
||||
input.severity = Severity.Error;
|
||||
input.validationMessage = validationResult;
|
||||
} else if (validationResult && !isString(validationResult)) {
|
||||
input.severity = validationResult.severity;
|
||||
input.validationMessage = validationResult.content;
|
||||
} else {
|
||||
input.severity = Severity.Ignore;
|
||||
input.validationMessage = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise<string | undefined> {
|
||||
return new Promise<string | undefined>((resolve) => {
|
||||
if (token.isCancellationRequested) {
|
||||
@ -1458,7 +1473,7 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
validation.then(result => {
|
||||
if (value === validationValue) {
|
||||
input.validationMessage = result || undefined;
|
||||
this.setValidationOnInput(input, result);
|
||||
}
|
||||
});
|
||||
}),
|
||||
@ -1469,11 +1484,11 @@ export class QuickInputController extends Disposable {
|
||||
validationValue = value;
|
||||
}
|
||||
validation.then(result => {
|
||||
if (!result) {
|
||||
if (!result || (!isString(result) && result.severity !== Severity.Error)) {
|
||||
resolve(value);
|
||||
input.hide();
|
||||
} else if (value === validationValue) {
|
||||
input.validationMessage = result;
|
||||
this.setValidationOnInput(input, result);
|
||||
}
|
||||
});
|
||||
}),
|
||||
@ -1485,6 +1500,8 @@ export class QuickInputController extends Disposable {
|
||||
resolve(undefined);
|
||||
}),
|
||||
];
|
||||
|
||||
input.title = options.title;
|
||||
input.value = options.value || '';
|
||||
input.valueSelection = options.valueSelection;
|
||||
input.prompt = options.prompt;
|
||||
@ -1600,7 +1617,7 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
hide(reason?: QuickInputHideReason) {
|
||||
const controller = this.controller;
|
||||
if (controller) {
|
||||
const focusChanged = !this.ui?.container.contains(document.activeElement);
|
||||
@ -1615,7 +1632,7 @@ export class QuickInputController extends Disposable {
|
||||
this.options.returnFocus();
|
||||
}
|
||||
}
|
||||
controller.didHide();
|
||||
controller.didHide(reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { IItemAccessor } from 'vs/base/common/fuzzyScorer';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
export interface IQuickPickItemHighlights {
|
||||
label?: IMatch[];
|
||||
@ -58,6 +59,11 @@ export interface IQuickNavigateConfiguration {
|
||||
|
||||
export interface IPickOptions<T extends IQuickPickItem> {
|
||||
|
||||
/**
|
||||
* an optional string to show as the title of the quick input
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* an optional string to show as placeholder in the input box to guide the user what she picks on
|
||||
*/
|
||||
@ -115,6 +121,11 @@ export interface IPickOptions<T extends IQuickPickItem> {
|
||||
|
||||
export interface IInputOptions {
|
||||
|
||||
/**
|
||||
* an optional string to show as the title of the quick input
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* the value to prefill in the input box
|
||||
*/
|
||||
@ -145,12 +156,34 @@ export interface IInputOptions {
|
||||
/**
|
||||
* an optional function that is used to validate user input.
|
||||
*/
|
||||
validateInput?: (input: string) => Promise<string | null | undefined>;
|
||||
validateInput?: (input: string) => Promise<string | null | undefined | { content: string, severity: Severity }>;
|
||||
}
|
||||
|
||||
export enum QuickInputHideReason {
|
||||
|
||||
/**
|
||||
* Focus moved away from the quick input.
|
||||
*/
|
||||
Blur = 1,
|
||||
|
||||
/**
|
||||
* An explicit user gesture, e.g. pressing Escape key.
|
||||
*/
|
||||
Gesture,
|
||||
|
||||
/**
|
||||
* Anything else.
|
||||
*/
|
||||
Other
|
||||
}
|
||||
|
||||
export interface IQuickInputHideEvent {
|
||||
reason: QuickInputHideReason;
|
||||
}
|
||||
|
||||
export interface IQuickInput extends IDisposable {
|
||||
|
||||
readonly onDidHide: Event<void>;
|
||||
readonly onDidHide: Event<IQuickInputHideEvent>;
|
||||
readonly onDispose: Event<void>;
|
||||
|
||||
title: string | undefined;
|
||||
@ -301,6 +334,8 @@ export interface IInputBox extends IQuickInput {
|
||||
prompt: string | undefined;
|
||||
|
||||
validationMessage: string | undefined;
|
||||
|
||||
severity: Severity;
|
||||
}
|
||||
|
||||
export interface IQuickInputButton {
|
||||
|
@ -22,6 +22,8 @@
|
||||
/**
|
||||
* A minimal set of methods exposed from Electron's `ipcRenderer`
|
||||
* to support communication to main process.
|
||||
*
|
||||
* @type {import('../electron-sandbox/electronTypes').IpcRenderer}
|
||||
*/
|
||||
ipcRenderer: {
|
||||
|
||||
@ -49,34 +51,46 @@
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
|
||||
*/
|
||||
on(channel, listener) {
|
||||
if (validateIPC(channel)) {
|
||||
ipcRenderer.on(channel, listener);
|
||||
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
|
||||
*/
|
||||
once(channel, listener) {
|
||||
if (validateIPC(channel)) {
|
||||
ipcRenderer.once(channel, listener);
|
||||
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
|
||||
*/
|
||||
removeListener(channel, listener) {
|
||||
if (validateIPC(channel)) {
|
||||
ipcRenderer.removeListener(channel, listener);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../electron-sandbox/globals').IpcMessagePort}
|
||||
*/
|
||||
ipcMessagePort: {
|
||||
|
||||
/**
|
||||
@ -106,6 +120,8 @@
|
||||
|
||||
/**
|
||||
* Support for subset of methods of Electron's `webFrame` type.
|
||||
*
|
||||
* @type {import('../electron-sandbox/electronTypes').WebFrame}
|
||||
*/
|
||||
webFrame: {
|
||||
|
||||
@ -121,6 +137,8 @@
|
||||
|
||||
/**
|
||||
* Support for subset of methods of Electron's `crashReporter` type.
|
||||
*
|
||||
* @type {import('../electron-sandbox/electronTypes').CrashReporter}
|
||||
*/
|
||||
crashReporter: {
|
||||
|
||||
@ -138,6 +156,8 @@
|
||||
*
|
||||
* Note: when `sandbox` is enabled, the only properties available
|
||||
* are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox
|
||||
*
|
||||
* @type {import('../electron-sandbox/globals').ISandboxNodeProcess}
|
||||
*/
|
||||
process: {
|
||||
get platform() { return process.platform; },
|
||||
@ -146,6 +166,21 @@
|
||||
get versions() { return process.versions; },
|
||||
get type() { return 'renderer'; },
|
||||
get execPath() { return process.execPath; },
|
||||
get sandboxed() { return process.sandboxed; },
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
cwd() {
|
||||
return process.env['VSCODE_CWD'] || process.execPath.substr(0, process.execPath.lastIndexOf(process.platform === 'win32' ? '\\' : '/'));
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise<typeof process.env>}
|
||||
*/
|
||||
getShellEnv() {
|
||||
return shellEnv;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{[key: string]: string}} userEnv
|
||||
@ -164,20 +199,17 @@
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {() => void} callback
|
||||
* @param {Function} callback
|
||||
* @returns {import('../electron-sandbox/globals').ISandboxNodeProcess}
|
||||
*/
|
||||
on(type, callback) {
|
||||
if (validateProcessEventType(type)) {
|
||||
// @ts-ignore
|
||||
process.on(type, callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Some information about the context we are running in.
|
||||
*/
|
||||
context: {
|
||||
get sandbox() { return process.sandboxed; }
|
||||
}
|
||||
};
|
||||
|
||||
@ -226,8 +258,8 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {Promise<void> | undefined} */
|
||||
let resolvedEnv = undefined;
|
||||
/** @type {Promise<typeof process.env> | undefined} */
|
||||
let shellEnv = undefined;
|
||||
|
||||
/**
|
||||
* If VSCode is not run from a terminal, we should resolve additional
|
||||
@ -238,28 +270,29 @@
|
||||
* @param {{[key: string]: string}} userEnv
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function resolveEnv(userEnv) {
|
||||
if (!resolvedEnv) {
|
||||
async function resolveEnv(userEnv) {
|
||||
if (!shellEnv) {
|
||||
|
||||
// Apply `userEnv` directly
|
||||
Object.assign(process.env, userEnv);
|
||||
|
||||
// Resolve `shellEnv` from the main side
|
||||
resolvedEnv = new Promise(function (resolve) {
|
||||
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
|
||||
shellEnv = new Promise(function (resolve) {
|
||||
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnvResult) {
|
||||
if (!process.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) {
|
||||
// Assign all keys of the shell environment to our process environment
|
||||
// But make sure that the user environment wins in the end over shell environment
|
||||
Object.assign(process.env, shellEnvResult, userEnv);
|
||||
}
|
||||
|
||||
// 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({ ...process.env, ...shellEnvResult, ...userEnv });
|
||||
});
|
||||
|
||||
ipcRenderer.send('vscode:fetchShellEnv');
|
||||
});
|
||||
}
|
||||
|
||||
return resolvedEnv;
|
||||
await shellEnv;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
@ -3,19 +3,19 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { globals, IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ProcessMemoryInfo, CrashReporter, IpcRenderer, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
|
||||
|
||||
/**
|
||||
* In sandboxed renderers we cannot expose all of the `process` global of node.js
|
||||
*/
|
||||
export interface IPartialNodeProcess {
|
||||
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.
|
||||
*/
|
||||
readonly platform: 'win32' | 'linux' | 'darwin';
|
||||
readonly platform: string;
|
||||
|
||||
/**
|
||||
* The process.arch property returns a string identifying the CPU architecture
|
||||
@ -24,9 +24,14 @@ export interface IPartialNodeProcess {
|
||||
readonly arch: string;
|
||||
|
||||
/**
|
||||
* The type will always be Electron renderer.
|
||||
* The type will always be `renderer`.
|
||||
*/
|
||||
readonly type: 'renderer';
|
||||
readonly type: string;
|
||||
|
||||
/**
|
||||
* Whether the process is sandboxed or not.
|
||||
*/
|
||||
readonly sandboxed: boolean;
|
||||
|
||||
/**
|
||||
* A list of versions for the current node.js/electron configuration.
|
||||
@ -48,6 +53,11 @@ export interface IPartialNodeProcess {
|
||||
*/
|
||||
on: (type: string, callback: Function) => void;
|
||||
|
||||
/**
|
||||
* The current working directory of the process.
|
||||
*/
|
||||
cwd: () => string;
|
||||
|
||||
/**
|
||||
* Resolves with a ProcessMemoryInfo
|
||||
*
|
||||
@ -62,9 +72,6 @@ export interface IPartialNodeProcess {
|
||||
* process on macOS.
|
||||
*/
|
||||
getProcessMemoryInfo: () => Promise<ProcessMemoryInfo>;
|
||||
}
|
||||
|
||||
export interface ISandboxNodeProcess extends IPartialNodeProcess {
|
||||
|
||||
/**
|
||||
* A custom method we add to `process`: Resolve the true process environment to use and
|
||||
@ -84,14 +91,12 @@ export interface ISandboxNodeProcess extends IPartialNodeProcess {
|
||||
* set of environment in `process.env`.
|
||||
*/
|
||||
resolveEnv(userEnv: IProcessEnvironment): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISandboxContext {
|
||||
|
||||
/**
|
||||
* Whether the renderer runs with `sandbox` enabled or not.
|
||||
* Returns a process environment that includes any shell environment even if the application
|
||||
* was not started from a shell / terminal / console.
|
||||
*/
|
||||
sandbox: boolean;
|
||||
getShellEnv(): Promise<IProcessEnvironment>;
|
||||
}
|
||||
|
||||
export interface IpcMessagePort {
|
||||
@ -114,4 +119,3 @@ export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort;
|
||||
export const webFrame: WebFrame = globals.vscode.webFrame;
|
||||
export const crashReporter: CrashReporter = globals.vscode.crashReporter;
|
||||
export const process: ISandboxNodeProcess = globals.vscode.process;
|
||||
export const context: ISandboxContext = globals.vscode.context;
|
||||
|
@ -8,7 +8,7 @@ import { ipcRenderer, crashReporter, webFrame, process } from 'vs/base/parts/san
|
||||
|
||||
suite('Sandbox', () => {
|
||||
test('globals', () => {
|
||||
assert.ok(typeof ipcRenderer.invoke === 'function');
|
||||
assert.ok(typeof ipcRenderer.send === 'function');
|
||||
assert.ok(typeof crashReporter.addExtraParameter === 'function');
|
||||
assert.ok(typeof webFrame.setZoomLevel === 'function');
|
||||
assert.ok(typeof process.platform === 'string');
|
||||
|
@ -49,80 +49,6 @@ suite('Arrays', () => {
|
||||
assertMedian(13, [13, 4, 8], 2);
|
||||
});
|
||||
|
||||
test('stableSort', () => {
|
||||
function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
|
||||
for (let i = 0; i < num; i++) {
|
||||
arr[i] = valueFn();
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
let data = fill(10000, () => ({ n: 1, m: counter++ }));
|
||||
|
||||
arrays.mergeSort(data, (a, b) => a.n - b.n);
|
||||
|
||||
let lastM = -1;
|
||||
for (const element of data) {
|
||||
assert.ok(lastM < element.m);
|
||||
lastM = element.m;
|
||||
}
|
||||
});
|
||||
|
||||
test('mergeSort', () => {
|
||||
let data = arrays.mergeSort([6, 5, 3, 1, 8, 7, 2, 4], (a, b) => a - b);
|
||||
assert.deepStrictEqual(data, [1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
});
|
||||
|
||||
test('mergeSort, sorted array', function () {
|
||||
let data = arrays.mergeSort([1, 2, 3, 4, 5, 6], (a, b) => a - b);
|
||||
assert.deepStrictEqual(data, [1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
|
||||
test('mergeSort, is stable', function () {
|
||||
|
||||
let numbers = arrays.mergeSort([33, 22, 11, 4, 99, 1], (a, b) => 0);
|
||||
assert.deepStrictEqual(numbers, [33, 22, 11, 4, 99, 1]);
|
||||
});
|
||||
|
||||
test('mergeSort, many random numbers', function () {
|
||||
|
||||
function compare(a: number, b: number) {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function assertSorted(array: number[]) {
|
||||
let last = array[0];
|
||||
for (let i = 1; i < array.length; i++) {
|
||||
let n = array[i];
|
||||
if (last > n) {
|
||||
assert.fail(JSON.stringify(array.slice(i - 10, i + 10)));
|
||||
}
|
||||
}
|
||||
}
|
||||
const MAX = 101;
|
||||
const data: number[][] = [];
|
||||
for (let i = 1; i < MAX; i++) {
|
||||
let array: number[] = [];
|
||||
for (let j = 0; j < 10 + i; j++) {
|
||||
array.push(Math.random() * 10e8 | 0);
|
||||
}
|
||||
data.push(array);
|
||||
}
|
||||
|
||||
for (const array of data) {
|
||||
arrays.mergeSort(array, compare);
|
||||
assertSorted(array);
|
||||
}
|
||||
});
|
||||
|
||||
test('sortedDiff', () => {
|
||||
function compare(a: number, b: number): number {
|
||||
return a - b;
|
||||
|
@ -88,7 +88,8 @@ suite('Buffer', () => {
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(new Error());
|
||||
stream.error(new Error());
|
||||
stream.end();
|
||||
|
||||
assert.strictEqual(chunks.length, 1);
|
||||
assert.strictEqual(chunks[0].toString(), 'Hello');
|
||||
@ -329,7 +330,8 @@ suite('Buffer', () => {
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(new Error());
|
||||
stream.error(new Error());
|
||||
stream.end();
|
||||
|
||||
assert.strictEqual(chunks.length, 0);
|
||||
assert.strictEqual(ended, false);
|
||||
|
@ -66,6 +66,10 @@ suite('Stream', () => {
|
||||
stream.error(new Error());
|
||||
assert.strictEqual(error, true);
|
||||
|
||||
error = false;
|
||||
stream.error(new Error());
|
||||
assert.strictEqual(error, true);
|
||||
|
||||
stream.end('Final Bit');
|
||||
assert.strictEqual(chunks.length, 4);
|
||||
assert.strictEqual(chunks[3], 'Final Bit');
|
||||
@ -86,6 +90,15 @@ suite('Stream', () => {
|
||||
assert.strictEqual(result, '');
|
||||
});
|
||||
|
||||
test('WriteableStream - end with error works', async () => {
|
||||
const reducer = (errors: Error[]) => errors.length > 0 ? errors[0] : null as unknown as Error;
|
||||
const stream = newWriteableStream<Error>(reducer);
|
||||
stream.end(new Error('error'));
|
||||
|
||||
const result = await consumeStream(stream, reducer);
|
||||
assert.ok(result instanceof Error);
|
||||
});
|
||||
|
||||
test('WriteableStream - removeListener', () => {
|
||||
const stream = newWriteableStream<string>(strings => strings.join());
|
||||
|
||||
|
@ -28,3 +28,14 @@ export function testRepeat(n: number, description: string, callback: (this: any)
|
||||
test(`${description} (iteration ${i})`, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertThrowsAsync(block: () => any, message: string | Error = 'Missing expected exception'): Promise<void> {
|
||||
try {
|
||||
await block();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const err = message instanceof Error ? message : new Error(message);
|
||||
throw err;
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { promises } from 'fs';
|
||||
import { rimraf, writeFile } from 'vs/base/node/pfs';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
|
||||
suite('Crypto', () => {
|
||||
flakySuite('Crypto', () => {
|
||||
|
||||
let testDir: string;
|
||||
|
||||
|
@ -10,10 +10,9 @@ import { join, sep } from 'vs/base/common/path';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { copy, exists, move, readdir, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFile, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { canNormalize } from 'vs/base/common/normalization';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
flakySuite('PFS', function () {
|
||||
@ -232,14 +231,19 @@ flakySuite('PFS', function () {
|
||||
assert.ok(!symbolicLink2);
|
||||
}
|
||||
|
||||
// Copy ignores dangling symlinks
|
||||
// Copy does not fail over dangling symlinks
|
||||
|
||||
await rimraf(copyTarget);
|
||||
await rimraf(symbolicLinkTarget);
|
||||
|
||||
await copy(symLink, copyTarget, { preserveSymlinks: true }); // this should not throw
|
||||
|
||||
assert.ok(!fs.existsSync(copyTarget));
|
||||
if (!isWindows) {
|
||||
const { symbolicLink } = await SymlinkSupport.stat(copyTarget);
|
||||
assert.ok(symbolicLink?.dangling);
|
||||
} else {
|
||||
assert.ok(!fs.existsSync(copyTarget));
|
||||
}
|
||||
});
|
||||
|
||||
test('copy handles symbolic links when the reference is inside source', async () => {
|
||||
|
@ -8,7 +8,7 @@ import * as cp from 'child_process';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as processes from 'vs/base/node/processes';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
|
||||
function fork(id: string): cp.ChildProcess {
|
||||
const opts: any = {
|
||||
|
@ -5,12 +5,17 @@
|
||||
|
||||
import type { Suite } from 'mocha';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export function getRandomTestPath(tmpdir: string, ...segments: string[]): string {
|
||||
return join(tmpdir, ...segments, generateUuid());
|
||||
}
|
||||
|
||||
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
|
||||
return URI.parse(requirefn.toUrl(relativePath)).fsPath;
|
||||
}
|
||||
|
||||
export function flakySuite(title: string, fn: (this: Suite) => void): Suite {
|
||||
return suite(title, function () {
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { readFileSync } from 'fs';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
|
||||
suite('URI - perf', function () {
|
||||
|
||||
|
@ -9,9 +9,8 @@ import { tmpdir } from 'os';
|
||||
import { promises } from 'fs';
|
||||
import { extract } from 'vs/base/node/zip';
|
||||
import { rimraf, exists } from 'vs/base/node/pfs';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
|
||||
suite('Zip', () => {
|
||||
|
||||
|
@ -31,23 +31,15 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
|
||||
}
|
||||
|
||||
// ESM-comment-begin
|
||||
export function getWorkerBootstrapUrl(scriptPath: string, label: string, forceDataUri: boolean = false): string {
|
||||
if (forceDataUri || /^((http:)|(https:)|(file:))/.test(scriptPath)) {
|
||||
const currentUrl = String(window.location);
|
||||
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
|
||||
if (forceDataUri || scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
|
||||
// this is the cross-origin case
|
||||
// i.e. the webpage is running at a different origin than where the scripts are loaded from
|
||||
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
|
||||
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
|
||||
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
|
||||
if (forceDataUri) {
|
||||
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
|
||||
return url;
|
||||
}
|
||||
const blob = new Blob([js], { type: 'application/javascript' });
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
|
||||
if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, self.origin.length) !== self.origin) {
|
||||
// this is the cross-origin case
|
||||
// i.e. the webpage is running at a different origin than where the scripts are loaded from
|
||||
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
|
||||
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
|
||||
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
|
||||
const blob = new Blob([js], { type: 'application/javascript' });
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
return scriptPath + '#' + label;
|
||||
}
|
||||
|
@ -10,36 +10,77 @@
|
||||
|
||||
const trustedTypesPolicy = (
|
||||
typeof self.trustedTypes?.createPolicy === 'function'
|
||||
? self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value })
|
||||
? self.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL: value => value,
|
||||
createScript: (_, ...args: string[]) => {
|
||||
// workaround a chrome issue not allowing to create new functions
|
||||
// see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
|
||||
const fnArgs = args.slice(0, -1).join(',');
|
||||
const fnBody = args.pop()!.toString();
|
||||
const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`;
|
||||
return body;
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
);
|
||||
|
||||
if (typeof (<any>self).define !== 'function' || !(<any>self).define.amd) {
|
||||
let loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js';
|
||||
if (trustedTypesPolicy) {
|
||||
loaderSrc = trustedTypesPolicy.createScriptURL(loaderSrc);
|
||||
}
|
||||
importScripts(loaderSrc as string);
|
||||
function loadAMDLoader() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (typeof (<any>self).define === 'function' && (<any>self).define.amd) {
|
||||
return resolve();
|
||||
}
|
||||
const loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js';
|
||||
|
||||
const isCrossOrigin = (/^((http:)|(https:)|(file:))/.test(loaderSrc) && loaderSrc.substring(0, self.origin.length) !== self.origin);
|
||||
if (!isCrossOrigin) {
|
||||
// use `fetch` if possible because `importScripts`
|
||||
// is synchronous and can lead to deadlocks on Safari
|
||||
fetch(loaderSrc).then((response) => {
|
||||
if (response.status !== 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
}).then((text) => {
|
||||
text = `${text}\n//# sourceURL=${loaderSrc}`;
|
||||
const func = (
|
||||
trustedTypesPolicy
|
||||
? self.eval(trustedTypesPolicy.createScript('', text) as unknown as string)
|
||||
: new Function(text)
|
||||
);
|
||||
func.call(self);
|
||||
resolve();
|
||||
}).then(undefined, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trustedTypesPolicy) {
|
||||
importScripts(trustedTypesPolicy.createScriptURL(loaderSrc) as unknown as string);
|
||||
} else {
|
||||
importScripts(loaderSrc as string);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
require.config({
|
||||
baseUrl: monacoBaseUrl,
|
||||
catchError: true,
|
||||
trustedTypesPolicy,
|
||||
});
|
||||
const loadCode = function (moduleId: string) {
|
||||
loadAMDLoader().then(() => {
|
||||
require.config({
|
||||
baseUrl: monacoBaseUrl,
|
||||
catchError: true,
|
||||
trustedTypesPolicy,
|
||||
});
|
||||
require([moduleId], function (ws) {
|
||||
setTimeout(function () {
|
||||
let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => {
|
||||
(<any>self).postMessage(msg, transfer);
|
||||
}, null);
|
||||
|
||||
let loadCode = function (moduleId: string) {
|
||||
require([moduleId], function (ws) {
|
||||
setTimeout(function () {
|
||||
let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => {
|
||||
(<any>self).postMessage(msg, transfer);
|
||||
}, null);
|
||||
|
||||
self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data);
|
||||
while (beforeReadyMessages.length > 0) {
|
||||
self.onmessage(beforeReadyMessages.shift()!);
|
||||
}
|
||||
}, 0);
|
||||
self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data);
|
||||
while (beforeReadyMessages.length > 0) {
|
||||
self.onmessage(beforeReadyMessages.shift()!);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
80
lib/vscode/src/vs/code/browser/workbench/workbench-web.html
Normal file
80
lib/vscode/src/vs/code/browser/workbench/workbench-web.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
performance.mark('code/didStartRenderer')
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<!-- Workbench Configuration -->
|
||||
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
|
||||
|
||||
<!-- Workbench Auth Session -->
|
||||
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
|
||||
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="{{WORKBENCH_WEB_BASE_URL}}/favicon.ico" type="image/x-icon" />
|
||||
<link rel="manifest" href="{{WORKBENCH_WEB_BASE_URL}}/manifest.json">
|
||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<script>
|
||||
var baseUrl = '{{WORKBENCH_WEB_BASE_URL}}';
|
||||
self.require = {
|
||||
baseUrl: `${baseUrl}/out`,
|
||||
recordStats: true,
|
||||
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL(value) {
|
||||
if(value.startsWith(baseUrl)) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`Invalid script url: ${value}`)
|
||||
}
|
||||
}),
|
||||
paths: {
|
||||
'vscode-textmate': `${baseUrl}/node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `${baseUrl}/node_modules/vscode-oniguruma/release/main`,
|
||||
'xterm': `${baseUrl}/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${baseUrl}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${baseUrl}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-webgl': `${baseUrl}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'tas-client-umd': `${baseUrl}/node_modules/tas-client-umd/lib/tas-client-umd.js`,
|
||||
'iconv-lite-umd': `${baseUrl}/node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
||||
'jschardet': `${baseUrl}/node_modules/jschardet/dist/jschardet.min.js`,
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
|
||||
<script>
|
||||
performance.mark('code/willLoadWorkbenchMain');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
if ("{{WORKBENCH_DEV}}" === "true") {
|
||||
const workbench = document.createElement('script');
|
||||
workbench.innerText = "require(['vs/code/browser/workbench/workbench'], function() {});";
|
||||
document.body.appendChild(workbench);
|
||||
} else {
|
||||
const nls = document.createElement('script');
|
||||
nls.setAttribute('src', '{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.nls.js');
|
||||
document.body.appendChild(nls);
|
||||
|
||||
const api = document.createElement('script');
|
||||
api.setAttribute('src', '{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.js');
|
||||
document.body.appendChild(api);
|
||||
|
||||
const workbench = document.createElement('script');
|
||||
workbench.setAttribute('src', '{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.js');
|
||||
document.body.appendChild(workbench);
|
||||
}
|
||||
</script>
|
||||
</html>
|
@ -7,7 +7,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@ -32,9 +32,14 @@ interface LanguagePackFile {
|
||||
|
||||
export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
|
||||
private readonly _DataMaxAge = this._productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService private readonly _environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
) {
|
||||
super();
|
||||
// We have no Language pack support for dev version (run from source)
|
||||
@ -48,9 +53,6 @@ export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
let handle: any = setTimeout(async () => {
|
||||
handle = undefined;
|
||||
this._logService.info('Starting to clean up unused language packs.');
|
||||
const maxAge = product.nameLong.indexOf('Insiders') >= 0
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
try {
|
||||
const installed: IStringDictionary<boolean> = Object.create(null);
|
||||
const metaData: LanguagePackFile = JSON.parse(await fs.promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
@ -84,7 +86,7 @@ export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
const stat = await fs.promises.stat(candidate);
|
||||
if (stat.isDirectory()) {
|
||||
const diff = now - stat.mtime.getTime();
|
||||
if (diff > maxAge) {
|
||||
if (diff > this._DataMaxAge) {
|
||||
this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry));
|
||||
await pfs.rimraf(candidate);
|
||||
}
|
||||
|
@ -8,18 +8,19 @@ import { basename, dirname, join } from 'vs/base/common/path';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class NodeCachedDataCleaner {
|
||||
|
||||
private static readonly _DataMaxAge = product.nameLong.indexOf('Insiders') >= 0
|
||||
private readonly _DataMaxAge = this.productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private readonly nodeCachedDataDir: string | undefined
|
||||
private readonly nodeCachedDataDir: string | undefined,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
this._manageCachedDataSoon();
|
||||
}
|
||||
@ -61,7 +62,7 @@ export class NodeCachedDataCleaner {
|
||||
// * only when old enough
|
||||
if (stats.isDirectory()) {
|
||||
const diff = now - stats.mtime.getTime();
|
||||
if (diff > NodeCachedDataCleaner._DataMaxAge) {
|
||||
if (diff > this._DataMaxAge) {
|
||||
return rimraf(path);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { RequestService } from 'vs/platform/request/browser/requestService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
@ -58,7 +58,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService';
|
||||
import { NativeStorageService2 } from 'vs/platform/storage/electron-sandbox/storageService2';
|
||||
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||
@ -81,9 +81,12 @@ import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/err
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal';
|
||||
import { LocalPtyService } from 'vs/platform/terminal/electron-browser/localPtyService';
|
||||
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService';
|
||||
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
|
||||
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc';
|
||||
import { IChecksumService } from 'vs/platform/checksum/common/checksumService';
|
||||
import { ChecksumService } from 'vs/platform/checksum/node/checksumService';
|
||||
import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService';
|
||||
|
||||
class SharedProcessMain extends Disposable {
|
||||
|
||||
@ -129,7 +132,7 @@ class SharedProcessMain extends Disposable {
|
||||
|
||||
// Instantiate Contributions
|
||||
this._register(combinedDisposable(
|
||||
new NodeCachedDataCleaner(this.configuration.nodeCachedDataDir),
|
||||
instantiationService.createInstance(NodeCachedDataCleaner, this.configuration.nodeCachedDataDir),
|
||||
instantiationService.createInstance(LanguagePackCachedDataCleaner),
|
||||
instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath),
|
||||
instantiationService.createInstance(LogsDataCleaner),
|
||||
@ -141,9 +144,12 @@ class SharedProcessMain extends Disposable {
|
||||
private async initServices(): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Product
|
||||
const productService = { _serviceBrand: undefined, ...product };
|
||||
services.set(IProductService, productService);
|
||||
|
||||
// Environment
|
||||
const environmentService = new NativeEnvironmentService(this.configuration.args);
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
const environmentService = new NativeEnvironmentService(this.configuration.args, productService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
// Log
|
||||
@ -175,18 +181,18 @@ class SharedProcessMain extends Disposable {
|
||||
await configurationService.initialize();
|
||||
|
||||
// Storage (global access only)
|
||||
const storageService = new NativeStorageService2(undefined, mainProcessService, environmentService);
|
||||
const storageService = new NativeStorageService(undefined, mainProcessService, environmentService);
|
||||
services.set(IStorageService, storageService);
|
||||
|
||||
await storageService.initialize();
|
||||
this._register(toDisposable(() => storageService.flush()));
|
||||
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
// Request
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
|
||||
// Checksum
|
||||
services.set(IChecksumService, new SyncDescriptor(ChecksumService));
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = ProxyChannel.toService<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
|
||||
services.set(INativeHostService, nativeHostService);
|
||||
@ -208,19 +214,19 @@ class SharedProcessMain extends Disposable {
|
||||
|
||||
let telemetryService: ITelemetryService;
|
||||
let telemetryAppender: ITelemetryAppender;
|
||||
if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
|
||||
if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) {
|
||||
telemetryAppender = new TelemetryLogAppender(loggerService, environmentService);
|
||||
|
||||
// Application Insights
|
||||
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
|
||||
const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey);
|
||||
if (productService.aiConfig && productService.aiConfig.asimovKey && isBuilt) {
|
||||
const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey);
|
||||
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
telemetryAppender = combinedAppender(appInsightsAppender, telemetryAppender);
|
||||
}
|
||||
|
||||
telemetryService = new TelemetryService({
|
||||
appender: telemetryAppender,
|
||||
commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, this.configuration.machineId, product.msftInternalDomains, installSourcePath),
|
||||
commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath),
|
||||
sendErrorTelemetry: true,
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
}, configurationService);
|
||||
@ -232,6 +238,10 @@ class SharedProcessMain extends Disposable {
|
||||
this.server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender));
|
||||
services.set(ITelemetryService, telemetryService);
|
||||
|
||||
// Custom Endpoint Telemetry
|
||||
const customEndpointTelemetryService = new CustomEndpointTelemetryService(configurationService, telemetryService);
|
||||
services.set(ICustomEndpointTelemetryService, customEndpointTelemetryService);
|
||||
|
||||
// Extension Management
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
|
||||
@ -263,8 +273,7 @@ class SharedProcessMain extends Disposable {
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
|
||||
// Terminal
|
||||
const localPtyService = this._register(new LocalPtyService(logService));
|
||||
services.set(ILocalPtyService, localPtyService);
|
||||
services.set(ILocalPtyService, this._register(new PtyHostService(logService)));
|
||||
|
||||
return new InstantiationService(services);
|
||||
}
|
||||
@ -287,10 +296,18 @@ class SharedProcessMain extends Disposable {
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(accessor.get(IExtensionTipsService));
|
||||
this.server.registerChannel('extensionTipsService', extensionTipsChannel);
|
||||
|
||||
// Checksum
|
||||
const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService));
|
||||
this.server.registerChannel('checksum', checksumChannel);
|
||||
|
||||
// Settings Sync
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService));
|
||||
this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
// Custom Endpoint Telemetry
|
||||
const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService));
|
||||
this.server.registerChannel('customEndpointTelemetry', customEndpointTelemetryChannel);
|
||||
|
||||
const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService));
|
||||
this.server.registerChannel('userDataSyncAccount', userDataSyncAccountChannel);
|
||||
|
||||
|
@ -33,7 +33,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@ -80,13 +80,14 @@ import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encry
|
||||
import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker';
|
||||
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
@ -105,7 +106,8 @@ export class CodeApplication extends Disposable {
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -418,7 +420,7 @@ export class CodeApplication extends Disposable {
|
||||
// This will help Windows to associate the running program with
|
||||
// any shortcut that is pinned to the taskbar and prevent showing
|
||||
// two icons in the taskbar for the same app.
|
||||
const win32AppUserModelId = product.win32AppUserModelId;
|
||||
const win32AppUserModelId = this.productService.win32AppUserModelId;
|
||||
if (isWindows && win32AppUserModelId) {
|
||||
app.setAppUserModelId(win32AppUserModelId);
|
||||
}
|
||||
@ -559,9 +561,6 @@ export class CodeApplication extends Disposable {
|
||||
// Keyboard Layout
|
||||
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
|
||||
|
||||
// Display
|
||||
services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService));
|
||||
|
||||
// Native Host
|
||||
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess]));
|
||||
|
||||
@ -590,10 +589,10 @@ export class CodeApplication extends Disposable {
|
||||
services.set(IURLService, new SyncDescriptor(NativeURLService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!this.productService.enableTelemetry) {
|
||||
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentMainService.installSourcePath);
|
||||
const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath);
|
||||
const piiPaths = [this.environmentMainService.appRoot, this.environmentMainService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
@ -629,14 +628,14 @@ export class CodeApplication extends Disposable {
|
||||
const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService));
|
||||
mainProcessElectronServer.registerChannel('encryption', encryptionChannel);
|
||||
|
||||
// Signing
|
||||
const signChannel = ProxyChannel.fromService(accessor.get(ISignService));
|
||||
mainProcessElectronServer.registerChannel('sign', signChannel);
|
||||
|
||||
// Keyboard Layout
|
||||
const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService));
|
||||
mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
|
||||
|
||||
// Display
|
||||
const displayChannel = ProxyChannel.fromService(accessor.get(IDisplayMainService));
|
||||
mainProcessElectronServer.registerChannel('display', displayChannel);
|
||||
|
||||
// Native host (main & shared process)
|
||||
this.nativeHostMainService = accessor.get(INativeHostMainService);
|
||||
const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService);
|
||||
@ -751,6 +750,7 @@ export class CodeApplication extends Disposable {
|
||||
cli: { ...environmentService.args },
|
||||
urisToOpen: [windowOpenableFromProtocolLink],
|
||||
gotoLineMode: true
|
||||
/* remoteAuthority will be determined based on windowOpenableFromProtocolLink */
|
||||
});
|
||||
|
||||
window.focus(); // this should help ensuring that the right window gets focus when multiple are opened
|
||||
@ -765,7 +765,8 @@ export class CodeApplication extends Disposable {
|
||||
context: OpenContext.API,
|
||||
cli: { ...environmentService.args },
|
||||
forceEmpty: true,
|
||||
gotoLineMode: true
|
||||
gotoLineMode: true,
|
||||
remoteAuthority: getRemoteAuthority(uri)
|
||||
});
|
||||
|
||||
await window.ready();
|
||||
@ -789,7 +790,7 @@ export class CodeApplication extends Disposable {
|
||||
urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
|
||||
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService));
|
||||
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService, this.productService));
|
||||
|
||||
// Open our first window
|
||||
const args = this.environmentMainService.args;
|
||||
@ -800,6 +801,7 @@ export class CodeApplication extends Disposable {
|
||||
const hasFileURIs = !!args['file-uri'];
|
||||
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
|
||||
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
|
||||
const remoteAuthority = args.remote || undefined;
|
||||
|
||||
// check for a pending window to open from URI
|
||||
// e.g. when running code with --open-uri from
|
||||
@ -811,6 +813,7 @@ export class CodeApplication extends Disposable {
|
||||
urisToOpen: pendingWindowOpenablesFromProtocolLinks,
|
||||
gotoLineMode: true,
|
||||
initialStartup: true
|
||||
/* remoteAuthority will be determined based on pendingWindowOpenablesFromProtocolLinks */
|
||||
});
|
||||
}
|
||||
|
||||
@ -823,7 +826,8 @@ export class CodeApplication extends Disposable {
|
||||
forceEmpty: true,
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
initialStartup: true
|
||||
initialStartup: true,
|
||||
remoteAuthority
|
||||
});
|
||||
}
|
||||
|
||||
@ -835,7 +839,8 @@ export class CodeApplication extends Disposable {
|
||||
urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
initialStartup: true
|
||||
initialStartup: true,
|
||||
/* remoteAuthority will be determined based on macOpenFiles */
|
||||
});
|
||||
}
|
||||
|
||||
@ -848,21 +853,22 @@ export class CodeApplication extends Disposable {
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
gotoLineMode: args.goto,
|
||||
initialStartup: true
|
||||
initialStartup: true,
|
||||
remoteAuthority
|
||||
});
|
||||
}
|
||||
|
||||
private shouldBlockURI(uri: URI): boolean {
|
||||
if (uri.authority === Schemas.file && isWindows) {
|
||||
const res = dialog.showMessageBoxSync({
|
||||
title: product.nameLong,
|
||||
title: this.productService.nameLong,
|
||||
type: 'question',
|
||||
buttons: [
|
||||
mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")),
|
||||
mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")),
|
||||
],
|
||||
cancelId: 1,
|
||||
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), product.nameShort),
|
||||
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), this.productService.nameShort),
|
||||
detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"),
|
||||
noLink: true
|
||||
});
|
||||
@ -948,16 +954,22 @@ export class CodeApplication extends Disposable {
|
||||
type SharedProcessErrorClassification = {
|
||||
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
visible: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
};
|
||||
type SharedProcessErrorEvent = {
|
||||
type: WindowError;
|
||||
reason: string | undefined;
|
||||
visible: boolean;
|
||||
};
|
||||
telemetryService.publicLog2<SharedProcessErrorEvent, SharedProcessErrorClassification>('sharedprocesserror', { type, reason: typeof details !== 'string' ? details.reason : undefined });
|
||||
telemetryService.publicLog2<SharedProcessErrorEvent, SharedProcessErrorClassification>('sharedprocesserror', {
|
||||
type,
|
||||
reason: typeof details !== 'string' ? details?.reason : undefined,
|
||||
visible: sharedProcess.isVisible()
|
||||
});
|
||||
}));
|
||||
|
||||
// Windows: install mutex
|
||||
const win32MutexName = product.win32MutexName;
|
||||
const win32MutexName = this.productService.win32MutexName;
|
||||
if (isWindows && win32MutexName) {
|
||||
try {
|
||||
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
|
||||
@ -1027,7 +1039,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
recordingStopped = true; // only once
|
||||
|
||||
const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
|
||||
const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${this.productService.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
|
||||
|
||||
if (!timeout) {
|
||||
dialogMainService.showMessageBox({
|
||||
|
@ -12,7 +12,7 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
|
||||
@ -56,7 +56,7 @@ enum ProxyAuthState {
|
||||
|
||||
export class ProxyAuthHandler extends Disposable {
|
||||
|
||||
private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`;
|
||||
private readonly PROXY_CREDENTIALS_SERVICE_KEY = `${this.productService.urlProtocol}.proxy-credentials`;
|
||||
|
||||
private pendingProxyResolve: Promise<Credentials | undefined> | undefined = undefined;
|
||||
|
||||
@ -68,7 +68,8 @@ export class ProxyAuthHandler extends Disposable {
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
|
||||
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService
|
||||
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -153,7 +154,7 @@ export class ProxyAuthHandler extends Disposable {
|
||||
let storedUsername: string | undefined = undefined;
|
||||
let storedPassword: string | undefined = undefined;
|
||||
try {
|
||||
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
if (encryptedSerializedProxyCredentials) {
|
||||
const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials));
|
||||
|
||||
@ -211,9 +212,9 @@ export class ProxyAuthHandler extends Disposable {
|
||||
try {
|
||||
if (reply.remember) {
|
||||
const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials));
|
||||
await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
|
||||
await this.nativeHostMainService.setPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
|
||||
} else {
|
||||
await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
await this.nativeHostMainService.deletePassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error); // handle gracefully
|
||||
|
@ -23,7 +23,6 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService, ConsoleMainLogger, MultiplexLogService, getLogLevel, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
@ -54,6 +53,7 @@ import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/env
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { cwd } from 'vs/base/common/process';
|
||||
|
||||
/**
|
||||
* The main VS Code entry point.
|
||||
@ -84,7 +84,7 @@ class CodeMain {
|
||||
const args = this.resolveArgs();
|
||||
|
||||
// Create services
|
||||
const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService] = this.createServices(args);
|
||||
const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService, productService] = this.createServices(args);
|
||||
|
||||
try {
|
||||
|
||||
@ -94,7 +94,7 @@ class CodeMain {
|
||||
} catch (error) {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
this.handleStartupDataDirError(environmentService, productService.nameLong, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
@ -108,7 +108,7 @@ class CodeMain {
|
||||
// Create the main IPC server by trying to be the server
|
||||
// If this throws an error it means we are not the first
|
||||
// instance of VS Code running and so we would quit.
|
||||
const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true);
|
||||
const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, productService, true);
|
||||
|
||||
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
|
||||
bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel());
|
||||
@ -126,20 +126,23 @@ class CodeMain {
|
||||
}
|
||||
}
|
||||
|
||||
private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService] {
|
||||
private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService, IProductService] {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Product
|
||||
const productService = { _serviceBrand: undefined, ...product };
|
||||
services.set(IProductService, productService);
|
||||
|
||||
// Environment
|
||||
const environmentService = new EnvironmentMainService(args);
|
||||
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(IEnvironmentMainService, environmentService);
|
||||
const environmentMainService = new EnvironmentMainService(args, productService);
|
||||
const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment
|
||||
services.set(IEnvironmentMainService, environmentMainService);
|
||||
|
||||
// Log: We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentService)), bufferLogService]);
|
||||
const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
services.set(ILogService, logService);
|
||||
|
||||
@ -153,14 +156,14 @@ class CodeMain {
|
||||
services.set(ILoggerService, new LoggerService(logService, fileService));
|
||||
|
||||
// Configuration
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService);
|
||||
services.set(IConfigurationService, configurationService);
|
||||
|
||||
// Lifecycle
|
||||
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
|
||||
|
||||
// State
|
||||
const stateService = new StateService(environmentService, logService);
|
||||
const stateService = new StateService(environmentMainService, logService);
|
||||
services.set(IStateService, stateService);
|
||||
|
||||
// Request
|
||||
@ -172,13 +175,10 @@ class CodeMain {
|
||||
// Signing
|
||||
services.set(ISignService, new SyncDescriptor(SignService));
|
||||
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
// Tunnel
|
||||
services.set(ITunnelService, new SyncDescriptor(TunnelService));
|
||||
|
||||
return [new InstantiationService(services, true), instanceEnvironment, environmentService, configurationService, stateService, bufferLogService];
|
||||
return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService, productService];
|
||||
}
|
||||
|
||||
private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment {
|
||||
@ -219,7 +219,7 @@ class CodeMain {
|
||||
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<NodeIPCServer> {
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise<NodeIPCServer> {
|
||||
|
||||
// Try to setup a server for running. If that succeeds it means
|
||||
// we are the first instance to startup. Otherwise it is likely
|
||||
@ -235,7 +235,7 @@ class CodeMain {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentMainService, error);
|
||||
this.handleStartupDataDirError(environmentMainService, productService.nameLong, error);
|
||||
|
||||
// Any other runtime error is just printed to the console
|
||||
throw error;
|
||||
@ -251,8 +251,9 @@ class CodeMain {
|
||||
if (!retry || isWindows || error.code !== 'ECONNREFUSED') {
|
||||
if (error.code === 'EPERM') {
|
||||
this.showStartupWarningDialog(
|
||||
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
|
||||
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
|
||||
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", productService.nameShort),
|
||||
localize('secondInstanceAdminDetail', "Please close the other instance and try again."),
|
||||
productService.nameLong
|
||||
);
|
||||
}
|
||||
|
||||
@ -270,7 +271,7 @@ class CodeMain {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, false);
|
||||
return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, productService, false);
|
||||
}
|
||||
|
||||
// Tests from CLI require to be the only instance currently
|
||||
@ -289,8 +290,9 @@ class CodeMain {
|
||||
if (!args.wait && !args.status) {
|
||||
startupWarningDialogHandle = setTimeout(() => {
|
||||
this.showStartupWarningDialog(
|
||||
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
|
||||
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
|
||||
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort),
|
||||
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again."),
|
||||
productService.nameLong
|
||||
);
|
||||
}, 10000);
|
||||
}
|
||||
@ -300,7 +302,7 @@ class CodeMain {
|
||||
// Process Info
|
||||
if (args.status) {
|
||||
return instantiationService.invokeFunction(async () => {
|
||||
const diagnosticsService = new DiagnosticsService(NullTelemetryService);
|
||||
const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService);
|
||||
const mainProcessInfo = await launchService.getMainProcessInfo();
|
||||
const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
|
||||
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
|
||||
@ -344,23 +346,24 @@ class CodeMain {
|
||||
return mainProcessNodeIpcServer;
|
||||
}
|
||||
|
||||
private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
|
||||
private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, title: string, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentMainService));
|
||||
|
||||
this.showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n'))
|
||||
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')),
|
||||
title
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private showStartupWarningDialog(message: string, detail: string): void {
|
||||
private showStartupWarningDialog(message: string, detail: string, title: string): void {
|
||||
// use sync variant here because we likely exit after this method
|
||||
// due to startup issues and otherwise the dialog seems to disappear
|
||||
// https://github.com/microsoft/vscode/issues/104493
|
||||
dialog.showMessageBoxSync({
|
||||
title: product.nameLong,
|
||||
title,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message,
|
||||
@ -451,7 +454,7 @@ class CodeMain {
|
||||
}
|
||||
|
||||
private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
|
||||
const cwd = process.env['VSCODE_CWD'] || process.cwd();
|
||||
const currentWorkingDir = cwd();
|
||||
const result = args.map(arg => {
|
||||
let pathCandidate = String(arg);
|
||||
|
||||
@ -462,10 +465,10 @@ class CodeMain {
|
||||
}
|
||||
|
||||
if (pathCandidate) {
|
||||
pathCandidate = this.preparePath(cwd, pathCandidate);
|
||||
pathCandidate = this.preparePath(currentWorkingDir, pathCandidate);
|
||||
}
|
||||
|
||||
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
|
||||
const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir);
|
||||
|
||||
const filePathBasename = basename(sanitizedFilePath);
|
||||
if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) {
|
||||
|
@ -13,12 +13,14 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { isLinux, isPreferringBrowserCodeLoad } from 'vs/base/common/platform';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { extname } from 'vs/base/common/resources';
|
||||
|
||||
type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void };
|
||||
|
||||
export class FileProtocolHandler extends Disposable {
|
||||
|
||||
private readonly validRoots = TernarySearchTree.forUris<boolean>(() => !isLinux);
|
||||
private readonly validExtensions = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp']); // https://github.com/microsoft/vscode/issues/119384
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@ -85,14 +87,23 @@ export class FileProtocolHandler extends Disposable {
|
||||
const fileUri = URI.parse(request.url);
|
||||
|
||||
// isPreferringBrowserCodeLoad: false
|
||||
// => ensure the file path is in our expected roots
|
||||
if (!isPreferringBrowserCodeLoad) {
|
||||
|
||||
// first check by validRoots
|
||||
if (this.validRoots.findSubstr(fileUri)) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
// then check by validExtensions
|
||||
if (this.validExtensions.has(extname(fileUri))) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
// finally block to load the resource
|
||||
this.logService.error(`${Schemas.file}: Refused to load resource ${fileUri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`);
|
||||
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
@ -114,14 +125,24 @@ export class FileProtocolHandler extends Disposable {
|
||||
// ensure the root is valid and properly tell Chrome where the
|
||||
// resource is at.
|
||||
const fileUri = FileAccess.asFileUri(uri);
|
||||
|
||||
// first check by validRoots
|
||||
if (this.validRoots.findSubstr(fileUri)) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
} else {
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath} from ${Schemas.vscodeFileResource}: protocol (original URL: ${request.url})`);
|
||||
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
|
||||
// then check by validExtensions
|
||||
if (this.validExtensions.has(extname(fileUri))) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
// finally block to load the resource
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath} from ${Schemas.vscodeFileResource}: protocol (original URL: ${request.url})`);
|
||||
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,12 @@ interface SearchResult {
|
||||
state?: string;
|
||||
}
|
||||
|
||||
enum IssueSource {
|
||||
VSCode = 'vscode',
|
||||
Extension = 'extension',
|
||||
Marketplace = 'marketplace'
|
||||
}
|
||||
|
||||
export interface IssueReporterConfiguration extends IWindowConfiguration {
|
||||
windowId: number;
|
||||
disableExtensions: boolean;
|
||||
@ -53,6 +59,7 @@ export interface IssueReporterConfiguration extends IWindowConfiguration {
|
||||
commit: string | undefined;
|
||||
date: string | undefined;
|
||||
reportIssueUrl: string | undefined;
|
||||
reportMarketplaceIssueUrl: string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,17 +333,18 @@ export class IssueReporter extends Disposable {
|
||||
hide(problemSourceHelpText);
|
||||
}
|
||||
|
||||
const fileOnExtension = JSON.parse(value);
|
||||
this.issueReporterModel.update({ fileOnExtension: fileOnExtension });
|
||||
let fileOnExtension, fileOnMarketplace = false;
|
||||
if (value === IssueSource.Extension) {
|
||||
fileOnExtension = true;
|
||||
} else if (value === IssueSource.Marketplace) {
|
||||
fileOnMarketplace = true;
|
||||
}
|
||||
|
||||
this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace });
|
||||
this.render();
|
||||
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
|
||||
});
|
||||
|
||||
this.addEventListener('description', 'input', (e: Event) => {
|
||||
@ -353,23 +361,19 @@ export class IssueReporter extends Disposable {
|
||||
this.addEventListener('issue-title', 'input', (e: Event) => {
|
||||
const title = (<HTMLInputElement>e.target).value;
|
||||
const lengthValidationMessage = this.getElementById('issue-title-length-validation-error');
|
||||
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
|
||||
const issueUrl = this.getIssueUrl();
|
||||
if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) {
|
||||
show(lengthValidationMessage);
|
||||
} else {
|
||||
hide(lengthValidationMessage);
|
||||
}
|
||||
|
||||
const fileOnExtension = this.issueReporterModel.fileOnExtension();
|
||||
if (fileOnExtension === undefined) {
|
||||
const issueSource = this.getElementById<HTMLSelectElement>('issue-source');
|
||||
if (!issueSource || issueSource.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
|
||||
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
|
||||
});
|
||||
|
||||
this.previewButton.onDidClick(() => this.createIssue());
|
||||
@ -489,6 +493,19 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void {
|
||||
if (fileOnExtension) {
|
||||
return this.searchExtensionIssues(title);
|
||||
}
|
||||
|
||||
if (fileOnMarketplace) {
|
||||
return this.searchMarketplaceIssues(title);
|
||||
}
|
||||
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
|
||||
private searchExtensionIssues(title: string): void {
|
||||
const url = this.getExtensionGitHubUrl();
|
||||
if (title) {
|
||||
@ -509,6 +526,15 @@ export class IssueReporter extends Disposable {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
|
||||
private searchMarketplaceIssues(title: string): void {
|
||||
if (title) {
|
||||
const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!);
|
||||
if (gitHubInfo) {
|
||||
return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
similarIssues.innerText = '';
|
||||
@ -636,7 +662,7 @@ export class IssueReporter extends Disposable {
|
||||
reset(typeSelect,
|
||||
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue"))
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
|
||||
);
|
||||
|
||||
typeSelect.value = issueType.toString();
|
||||
@ -666,19 +692,15 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
sourceSelect.innerText = '';
|
||||
if (issueType === IssueType.FeatureRequest) {
|
||||
sourceSelect.append(...[
|
||||
this.makeOption('', localize('selectSource', "Select source"), true),
|
||||
this.makeOption('false', localize('vscode', "Visual Studio Code"), false),
|
||||
this.makeOption('true', localize('extension', "An extension"), false)
|
||||
]);
|
||||
} else {
|
||||
sourceSelect.append(...[
|
||||
this.makeOption('', localize('selectSource', "Select source"), true),
|
||||
this.makeOption('false', localize('vscode', "Visual Studio Code"), false),
|
||||
this.makeOption('true', localize('extension', "An extension"), false),
|
||||
this.makeOption('', localize('unknown', "Don't Know"), false)
|
||||
]);
|
||||
sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true));
|
||||
sourceSelect.append(this.makeOption('vscode', localize('vscode', "Visual Studio Code"), false));
|
||||
sourceSelect.append(this.makeOption('extension', localize('extension', "An extension"), false));
|
||||
if (this.configuration.product.reportMarketplaceIssueUrl) {
|
||||
sourceSelect.append(this.makeOption('marketplace', localize('marketplace', "Extensions marketplace"), false));
|
||||
}
|
||||
|
||||
if (issueType !== IssueType.FeatureRequest) {
|
||||
sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false));
|
||||
}
|
||||
|
||||
if (selected !== -1 && selected < sourceSelect.options.length) {
|
||||
@ -691,7 +713,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
|
||||
const { issueType, fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
|
||||
const blockContainer = this.getElementById('block-container');
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
@ -715,29 +737,35 @@ export class IssueReporter extends Disposable {
|
||||
hide(extensionSelector);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (!fileOnMarketplace) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(experimentsBlock);
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
} else if (!fileOnMarketplace) {
|
||||
show(extensionsBlock);
|
||||
}
|
||||
reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*'));
|
||||
reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."));
|
||||
} else if (issueType === IssueType.PerformanceIssue) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (!fileOnMarketplace) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(experimentsBlock);
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
} else if (!fileOnMarketplace) {
|
||||
show(extensionsBlock);
|
||||
}
|
||||
|
||||
@ -845,13 +873,13 @@ export class IssueReporter extends Disposable {
|
||||
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
const issueBody = this.issueReporterModel.serialize();
|
||||
|
||||
const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!;
|
||||
const issueUrl = this.getIssueUrl();
|
||||
const gitHubDetails = this.parseGitHubUrl(issueUrl);
|
||||
if (this.configuration.data.githubAccessToken && gitHubDetails) {
|
||||
return this.submitToGitHub(issueTitle, issueBody, gitHubDetails);
|
||||
}
|
||||
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value, issueUrl);
|
||||
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
|
||||
|
||||
if (url.length > MAX_URL_LENGTH) {
|
||||
@ -881,6 +909,14 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private getIssueUrl(): string {
|
||||
return this.issueReporterModel.fileOnExtension()
|
||||
? this.getExtensionGitHubUrl()
|
||||
: this.issueReporterModel.getData().fileOnMarketplace
|
||||
? this.configuration.product.reportMarketplaceIssueUrl!
|
||||
: this.configuration.product.reportIssueUrl!;
|
||||
}
|
||||
|
||||
private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } {
|
||||
// Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner.
|
||||
// Repository name and owner cannot contain '/'
|
||||
@ -909,16 +945,12 @@ export class IssueReporter extends Disposable {
|
||||
return repositoryUrl;
|
||||
}
|
||||
|
||||
private getIssueUrlWithTitle(issueTitle: string): string {
|
||||
let repositoryUrl = this.configuration.product.reportIssueUrl;
|
||||
private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string {
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
const extensionGitHubUrl = this.getExtensionGitHubUrl();
|
||||
if (extensionGitHubUrl) {
|
||||
repositoryUrl = extensionGitHubUrl + '/issues/new';
|
||||
}
|
||||
repositoryUrl = repositoryUrl + '/issues/new';
|
||||
}
|
||||
|
||||
const queryStringPrefix = this.configuration.product.reportIssueUrl && this.configuration.product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
}
|
||||
|
||||
@ -1156,7 +1188,7 @@ export class IssueReporter extends Disposable {
|
||||
),
|
||||
...extensions.map(extension => $('tr', undefined,
|
||||
$('td', undefined, extension.name),
|
||||
$('td', undefined, extension.publisher.substr(0, 3)),
|
||||
$('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'),
|
||||
$('td', undefined, extension.version),
|
||||
))
|
||||
);
|
||||
|
@ -26,6 +26,7 @@ export interface IssueReporterData {
|
||||
enabledNonThemeExtesions?: IssueReporterExtensionData[];
|
||||
extensionsDisabled?: boolean;
|
||||
fileOnExtension?: boolean;
|
||||
fileOnMarketplace?: boolean;
|
||||
selectedExtension?: IssueReporterExtensionData;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
@ -110,30 +111,30 @@ ${this.getInfos()}
|
||||
let info = '';
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeSystemInfo && this._data.systemInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeSystemInfo && this._data.systemInfo) {
|
||||
info += this.generateSystemInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.PerformanceIssue) {
|
||||
|
||||
if (this._data.includeProcessInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeProcessInfo) {
|
||||
info += this.generateProcessInfoMd();
|
||||
}
|
||||
|
||||
if (this._data.includeWorkspaceInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeWorkspaceInfo) {
|
||||
info += this.generateWorkspaceInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (!this._data.fileOnExtension && this._data.includeExtensions) {
|
||||
if (!this._data.fileOnMarketplace && !this._data.fileOnExtension && this._data.includeExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeExperiments && this._data.experimentInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeExperiments && this._data.experimentInfo) {
|
||||
info += this.generateExperimentsInfoMd();
|
||||
}
|
||||
}
|
||||
@ -238,7 +239,7 @@ ${this._data.experimentInfo}
|
||||
const tableHeader = `Extension|Author (truncated)|Version
|
||||
---|---|---`;
|
||||
const table = this._data.enabledNonThemeExtesions.map(e => {
|
||||
return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`;
|
||||
return `${e.name}|${e.publisher?.substr(0, 3) ?? 'N/A'}|${e.version}`;
|
||||
}).join('\n');
|
||||
|
||||
return `<details><summary>Extensions (${this._data.enabledNonThemeExtesions.length})</summary>
|
||||
|
@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { homedir } from 'os';
|
||||
import { constants, existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
|
||||
import { existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
|
||||
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
@ -23,7 +23,6 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean {
|
||||
return !!argv['install-source']
|
||||
|| !!argv['list-extensions']
|
||||
|| !!argv['install-extension']
|
||||
|| !!argv['install-builtin-extension']
|
||||
|| !!argv['uninstall-extension']
|
||||
|| !!argv['locate-extension']
|
||||
|| !!argv['telemetry'];
|
||||
@ -84,8 +83,8 @@ export async function main(argv: string[]): Promise<any> {
|
||||
let restoreMode = false;
|
||||
if (!!args['file-chmod']) {
|
||||
targetMode = statSync(target).mode;
|
||||
if (!(targetMode & constants.S_IWUSR)) {
|
||||
chmodSync(target, targetMode | constants.S_IWUSR);
|
||||
if (!(targetMode & 0o200 /* File mode indicating writable by owner */)) {
|
||||
chmodSync(target, targetMode | 0o200);
|
||||
restoreMode = true;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@ -46,6 +46,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { cwd } from 'vs/base/common/process';
|
||||
|
||||
class CliMain extends Disposable {
|
||||
|
||||
@ -94,9 +95,12 @@ class CliMain extends Disposable {
|
||||
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Product
|
||||
const productService = { _serviceBrand: undefined, ...product };
|
||||
services.set(IProductService, productService);
|
||||
|
||||
// Environment
|
||||
const environmentService = new NativeEnvironmentService(this.argv);
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
const environmentService = new NativeEnvironmentService(this.argv, productService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
// Init folders
|
||||
@ -131,9 +135,6 @@ class CliMain extends Disposable {
|
||||
const stateService = new StateService(environmentService, logService);
|
||||
services.set(IStateService, stateService);
|
||||
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
|
||||
|
||||
// Request
|
||||
@ -149,15 +150,15 @@ class CliMain extends Disposable {
|
||||
|
||||
// Telemetry
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
|
||||
if (product.aiConfig && product.aiConfig.asimovKey) {
|
||||
appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey));
|
||||
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) {
|
||||
if (productService.aiConfig && productService.aiConfig.asimovKey) {
|
||||
appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey));
|
||||
}
|
||||
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(...appenders),
|
||||
sendErrorTelemetry: false,
|
||||
commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath),
|
||||
commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, stateService.getItem('telemetry.machineId'), productService.msftInternalDomains, installSourcePath),
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
};
|
||||
|
||||
@ -217,7 +218,7 @@ class CliMain extends Disposable {
|
||||
}
|
||||
|
||||
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
|
||||
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
|
||||
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input);
|
||||
}
|
||||
|
||||
private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise<void> {
|
||||
|
@ -7,6 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { status } from 'vs/base/browser/ui/aria/aria';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Command, EditorCommand, ICommandOptions, registerEditorCommand, MultiCommand, UndoCommand, RedoCommand, SelectAllCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
@ -280,7 +281,7 @@ abstract class EditorOrNativeTextInputCommand {
|
||||
|
||||
constructor(target: MultiCommand) {
|
||||
// 1. handle case when focus is in editor.
|
||||
target.addImplementation(10000, (accessor: ServicesAccessor, args: any) => {
|
||||
target.addImplementation(10000, 'code-editor', (accessor: ServicesAccessor, args: any) => {
|
||||
// Only if editor text focus (i.e. not if editor has widget focus).
|
||||
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
|
||||
if (focusedEditor && focusedEditor.hasTextFocus()) {
|
||||
@ -290,7 +291,7 @@ abstract class EditorOrNativeTextInputCommand {
|
||||
});
|
||||
|
||||
// 2. handle case when focus is in some other `input` / `textarea`.
|
||||
target.addImplementation(1000, (accessor: ServicesAccessor, args: any) => {
|
||||
target.addImplementation(1000, 'generic-dom-input-textarea', (accessor: ServicesAccessor, args: any) => {
|
||||
// Only if focused on an element that allows for entering text
|
||||
const activeElement = <HTMLElement>document.activeElement;
|
||||
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
|
||||
@ -301,7 +302,7 @@ abstract class EditorOrNativeTextInputCommand {
|
||||
});
|
||||
|
||||
// 3. (default) handle case when focus is somewhere else.
|
||||
target.addImplementation(0, (accessor: ServicesAccessor, args: any) => {
|
||||
target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => {
|
||||
// Redirecting to active editor
|
||||
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
|
||||
if (activeEditor) {
|
||||
@ -1593,6 +1594,7 @@ export namespace CoreNavigationCommands {
|
||||
]
|
||||
);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
status(nls.localize('removedCursor', "Removed secondary cursors"));
|
||||
}
|
||||
});
|
||||
|
||||
@ -1820,7 +1822,7 @@ export namespace CoreEditingCommands {
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteLeft(viewModel.getPrevEditOperationType(), viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection));
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteLeft(viewModel.getPrevEditOperationType(), viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection), viewModel.getCursorAutoClosedCharacters());
|
||||
if (shouldPushStackElementBefore) {
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
this.textArea.setAttribute('aria-haspopup', 'false');
|
||||
this.textArea.setAttribute('aria-autocomplete', 'both');
|
||||
|
||||
if (platform.isWeb && options.get(EditorOption.readOnly)) {
|
||||
if (options.get(EditorOption.domReadOnly)) {
|
||||
this.textArea.setAttribute('readonly', 'true');
|
||||
}
|
||||
|
||||
@ -416,9 +416,8 @@ export class TextAreaHandler extends ViewPart {
|
||||
this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
|
||||
const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize);
|
||||
if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) {
|
||||
// If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 100 for a better experience
|
||||
// If we put more than 100 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717
|
||||
this._accessibilityPageSize = 100;
|
||||
// If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 1000 for a better experience
|
||||
this._accessibilityPageSize = 1000;
|
||||
} else {
|
||||
this._accessibilityPageSize = accessibilityPageSize;
|
||||
}
|
||||
@ -441,8 +440,8 @@ export class TextAreaHandler extends ViewPart {
|
||||
this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
|
||||
this.textArea.setAttribute('tabindex', String(options.get(EditorOption.tabIndex)));
|
||||
|
||||
if (platform.isWeb && e.hasChanged(EditorOption.readOnly)) {
|
||||
if (options.get(EditorOption.readOnly)) {
|
||||
if (e.hasChanged(EditorOption.domReadOnly)) {
|
||||
if (options.get(EditorOption.domReadOnly)) {
|
||||
this.textArea.setAttribute('readonly', 'true');
|
||||
} else {
|
||||
this.textArea.removeAttribute('readonly');
|
||||
|
@ -209,26 +209,30 @@ export class TextAreaInput extends Disposable {
|
||||
|
||||
if (
|
||||
platform.isMacintosh
|
||||
&& lastKeyDown
|
||||
&& lastKeyDown.equals(KeyCode.KEY_IN_COMPOSITION)
|
||||
&& this._textAreaState.selectionStart === this._textAreaState.selectionEnd
|
||||
&& this._textAreaState.selectionStart > 0
|
||||
&& this._textAreaState.value.substr(this._textAreaState.selectionStart - 1, 1) === e.data
|
||||
&& (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft')
|
||||
) {
|
||||
// Handling long press case on macOS + arrow key => pretend the character was selected
|
||||
if (_debugComposition) {
|
||||
console.log(`[compositionstart] Handling long press case on macOS + arrow key`, e);
|
||||
}
|
||||
this._textAreaState = new TextAreaState(
|
||||
this._textAreaState.value,
|
||||
this._textAreaState.selectionStart - 1,
|
||||
this._textAreaState.selectionEnd,
|
||||
this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null,
|
||||
this._textAreaState.selectionEndPosition
|
||||
const isArrowKey = (
|
||||
lastKeyDown && lastKeyDown.equals(KeyCode.KEY_IN_COMPOSITION)
|
||||
&& (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft')
|
||||
);
|
||||
this._onCompositionStart.fire({ revealDeltaColumns: -1 });
|
||||
return;
|
||||
if (isArrowKey || browser.isFirefox) {
|
||||
// Handling long press case on Chromium/Safari macOS + arrow key => pretend the character was selected
|
||||
// or long press case on Firefox on macOS
|
||||
if (_debugComposition) {
|
||||
console.log(`[compositionstart] Handling long press case on macOS + arrow key or Firefox`, e);
|
||||
}
|
||||
this._textAreaState = new TextAreaState(
|
||||
this._textAreaState.value,
|
||||
this._textAreaState.selectionStart - 1,
|
||||
this._textAreaState.selectionEnd,
|
||||
this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null,
|
||||
this._textAreaState.selectionEndPosition
|
||||
);
|
||||
this._onCompositionStart.fire({ revealDeltaColumns: -1 });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (browser.isAndroid) {
|
||||
|
@ -23,6 +23,7 @@ import { withNullAsUndefined, assertType } from 'vs/base/common/types';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
|
||||
export type ServicesAccessor = InstantiationServicesAccessor;
|
||||
@ -149,20 +150,26 @@ export abstract class Command {
|
||||
*/
|
||||
export type CommandImplementation = (accessor: ServicesAccessor, args: unknown) => boolean | Promise<void>;
|
||||
|
||||
interface ICommandImplementationRegistration {
|
||||
priority: number;
|
||||
name: string;
|
||||
implementation: CommandImplementation;
|
||||
}
|
||||
|
||||
export class MultiCommand extends Command {
|
||||
|
||||
private readonly _implementations: [number, CommandImplementation][] = [];
|
||||
private readonly _implementations: ICommandImplementationRegistration[] = [];
|
||||
|
||||
/**
|
||||
* A higher priority gets to be looked at first
|
||||
*/
|
||||
public addImplementation(priority: number, implementation: CommandImplementation): IDisposable {
|
||||
this._implementations.push([priority, implementation]);
|
||||
this._implementations.sort((a, b) => b[0] - a[0]);
|
||||
public addImplementation(priority: number, name: string, implementation: CommandImplementation): IDisposable {
|
||||
this._implementations.push({ priority, name, implementation });
|
||||
this._implementations.sort((a, b) => b.priority - a.priority);
|
||||
return {
|
||||
dispose: () => {
|
||||
for (let i = 0; i < this._implementations.length; i++) {
|
||||
if (this._implementations[i][1] === implementation) {
|
||||
if (this._implementations[i].implementation === implementation) {
|
||||
this._implementations.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
@ -172,9 +179,11 @@ export class MultiCommand extends Command {
|
||||
}
|
||||
|
||||
public runCommand(accessor: ServicesAccessor, args: any): void | Promise<void> {
|
||||
const logService = accessor.get(ILogService);
|
||||
for (const impl of this._implementations) {
|
||||
const result = impl[1](accessor, args);
|
||||
const result = impl.implementation(accessor, args);
|
||||
if (result) {
|
||||
logService.trace(`Command '${this.id}' was handled by '${impl.name}'.`);
|
||||
if (typeof result === 'boolean') {
|
||||
return;
|
||||
}
|
||||
@ -339,13 +348,13 @@ export abstract class EditorAction extends EditorCommand {
|
||||
public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class MultiEditorAction extends EditorAction {
|
||||
export class MultiEditorAction extends EditorAction {
|
||||
|
||||
private readonly _implementations: [number, CommandImplementation][] = [];
|
||||
|
||||
constructor(opts: IActionOptions) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* A higher priority gets to be looked at first
|
||||
*/
|
||||
public addImplementation(priority: number, implementation: CommandImplementation): IDisposable {
|
||||
this._implementations.push([priority, implementation]);
|
||||
this._implementations.sort((a, b) => b[0] - a[0]);
|
||||
@ -361,20 +370,18 @@ export abstract class MultiEditorAction extends EditorAction {
|
||||
};
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void> {
|
||||
this.reportTelemetry(accessor, editor);
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void> {
|
||||
for (const impl of this._implementations) {
|
||||
if (impl[1](accessor, args)) {
|
||||
return;
|
||||
const result = impl[1](accessor, args);
|
||||
if (result) {
|
||||
if (typeof result === 'boolean') {
|
||||
return;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return this.run(accessor, editor, args || {});
|
||||
}
|
||||
|
||||
public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void>;
|
||||
|
||||
}
|
||||
|
||||
//#endregion EditorAction
|
||||
|
@ -88,7 +88,10 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService {
|
||||
private readonly _editorStyleSheets = new Map<string, RefCountedStyleSheet>();
|
||||
private readonly _themeService: IThemeService;
|
||||
|
||||
constructor(@IThemeService themeService: IThemeService, styleSheet: GlobalStyleSheet | null = null) {
|
||||
constructor(
|
||||
styleSheet: GlobalStyleSheet | null,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super();
|
||||
this._globalStyleSheet = styleSheet ? styleSheet : null;
|
||||
this._themeService = themeService;
|
||||
@ -328,7 +331,7 @@ export class DecorationTypeOptionsProvider implements IModelDecorationOptionsPro
|
||||
}
|
||||
|
||||
|
||||
const _CSS_MAP: { [prop: string]: string; } = {
|
||||
export const _CSS_MAP: { [prop: string]: string; } = {
|
||||
color: 'color:{0} !important;',
|
||||
opacity: 'opacity:{0};',
|
||||
backgroundColor: 'background-color:{0};',
|
||||
|
@ -166,7 +166,7 @@ export class OpenerService implements IOpenerService {
|
||||
// check with contributed validators
|
||||
const targetURI = typeof target === 'string' ? URI.parse(target) : target;
|
||||
// validate against the original URI that this URI resolves to, if one exists
|
||||
const validationTarget = this._resolvedUriTargets.get(targetURI) ?? targetURI;
|
||||
const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target;
|
||||
for (const validator of this._validators) {
|
||||
if (!(await validator.shouldOpen(validationTarget))) {
|
||||
return false;
|
||||
|
@ -106,7 +106,7 @@ export class View extends ViewEventHandler {
|
||||
|
||||
// The view context is passed on to most classes (basically to reduce param. counts in ctors)
|
||||
this._context = new ViewContext(configuration, themeService.getColorTheme(), model);
|
||||
this._configPixelRatio = this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio);
|
||||
this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio);
|
||||
|
||||
// Ensure the view is the first event handler in order to update the layout
|
||||
this._context.addEventHandler(this);
|
||||
|
@ -121,9 +121,9 @@ export class RangeUtil {
|
||||
startChildIndex = Math.min(max, Math.max(min, startChildIndex));
|
||||
endChildIndex = Math.min(max, Math.max(min, endChildIndex));
|
||||
|
||||
if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0) {
|
||||
if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0 && !domNode.children[startChildIndex].firstChild) {
|
||||
// We must find the position at the beginning of a <span>
|
||||
// To cover cases of empty <span>s, aboid using a range and use the <span>'s bounding box
|
||||
// To cover cases of empty <span>s, avoid using a range and use the <span>'s bounding box
|
||||
const clientRects = domNode.children[startChildIndex].getClientRects();
|
||||
return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft);
|
||||
}
|
||||
|
@ -509,7 +509,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
nls.localize('wordBasedSuggestionsMode.matchingDocuments', 'Suggest words from all open documents of the same language.'),
|
||||
nls.localize('wordBasedSuggestionsMode.allDocuments', 'Suggest words from all open documents.')
|
||||
],
|
||||
description: nls.localize('wordBasedSuggestionsMode', "Controls from what documents word based completions are computed.")
|
||||
description: nls.localize('wordBasedSuggestionsMode', "Controls from which documents word based completions are computed.")
|
||||
},
|
||||
'editor.semanticHighlighting.enabled': {
|
||||
enum: [true, false, 'configuredByTheme'],
|
||||
|
@ -28,7 +28,7 @@ export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'bracket
|
||||
/**
|
||||
* Configuration options for typing over closing quotes or brackets
|
||||
*/
|
||||
export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never';
|
||||
export type EditorAutoClosingEditStrategy = 'always' | 'auto' | 'never';
|
||||
|
||||
/**
|
||||
* Configuration options for auto indentation in the editor
|
||||
@ -139,10 +139,15 @@ export interface IEditorOptions {
|
||||
*/
|
||||
extraEditorClassName?: string;
|
||||
/**
|
||||
* Should the editor be read only.
|
||||
* Should the editor be read only. See also `domReadOnly`.
|
||||
* Defaults to false.
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* Should the textarea used for input use the DOM `readonly` attribute.
|
||||
* Defaults to false.
|
||||
*/
|
||||
domReadOnly?: boolean;
|
||||
/**
|
||||
* Enable linked editing.
|
||||
* Defaults to false.
|
||||
@ -413,10 +418,14 @@ export interface IEditorOptions {
|
||||
* Defaults to language defined behavior.
|
||||
*/
|
||||
autoClosingQuotes?: EditorAutoClosingStrategy;
|
||||
/**
|
||||
* Options for pressing backspace near quotes or bracket pairs.
|
||||
*/
|
||||
autoClosingDelete?: EditorAutoClosingEditStrategy;
|
||||
/**
|
||||
* Options for typing over closing quotes or brackets.
|
||||
*/
|
||||
autoClosingOvertype?: EditorAutoClosingOvertypeStrategy;
|
||||
autoClosingOvertype?: EditorAutoClosingEditStrategy;
|
||||
/**
|
||||
* Options for auto surrounding.
|
||||
* Defaults to always allowing auto surrounding.
|
||||
@ -1393,8 +1402,8 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
enum: ['never', 'always', 'multiline'],
|
||||
default: defaults.autoFindInSelection,
|
||||
enumDescriptions: [
|
||||
nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in selection automatically (default)'),
|
||||
nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in selection automatically'),
|
||||
nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in selection automatically (default).'),
|
||||
nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in selection automatically.'),
|
||||
nls.localize('editor.find.autoFindInSelection.multiline', 'Turn on Find in selection automatically when multiple lines of content are selected.')
|
||||
],
|
||||
description: nls.localize('find.autoFindInSelection', "Controls the condition for turning on find in selection automatically.")
|
||||
@ -3140,7 +3149,7 @@ export interface ISuggestOptions {
|
||||
*/
|
||||
snippetsPreventQuickSuggestions?: boolean;
|
||||
/**
|
||||
* Favours words that appear close to the cursor.
|
||||
* Favors words that appear close to the cursor.
|
||||
*/
|
||||
localityBonus?: boolean;
|
||||
/**
|
||||
@ -3332,7 +3341,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
'editor.suggest.localityBonus': {
|
||||
type: 'boolean',
|
||||
default: defaults.localityBonus,
|
||||
description: nls.localize('suggest.localityBonus', "Controls whether sorting favours words that appear close to the cursor.")
|
||||
description: nls.localize('suggest.localityBonus', "Controls whether sorting favors words that appear close to the cursor.")
|
||||
},
|
||||
'editor.suggest.shareSuggestSelections': {
|
||||
type: 'boolean',
|
||||
@ -3725,6 +3734,7 @@ export const enum EditorOption {
|
||||
accessibilityPageSize,
|
||||
ariaLabel,
|
||||
autoClosingBrackets,
|
||||
autoClosingDelete,
|
||||
autoClosingOvertype,
|
||||
autoClosingQuotes,
|
||||
autoIndent,
|
||||
@ -3746,6 +3756,7 @@ export const enum EditorOption {
|
||||
cursorWidth,
|
||||
disableLayerHinting,
|
||||
disableMonospaceOptimizations,
|
||||
domReadOnly,
|
||||
dragAndDrop,
|
||||
emptySelectionClipboard,
|
||||
extraEditorClassName,
|
||||
@ -3883,7 +3894,10 @@ export const EditorOptions = {
|
||||
)),
|
||||
accessibilitySupport: register(new EditorAccessibilitySupport()),
|
||||
accessibilityPageSize: register(new EditorIntOption(EditorOption.accessibilityPageSize, 'accessibilityPageSize', 10, 1, Constants.MAX_SAFE_SMALL_INTEGER,
|
||||
{ description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader. Warning: this has a performance implication for numbers larger than the default.") })),
|
||||
{
|
||||
description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader at once. When we detect a screen reader we automatically set the default to be 2000. Warning: this has a performance implication for numbers larger than the default."),
|
||||
deprecationMessage: nls.localize('accessibilityPageSize.deprecated', "This setting is deprecated, editor will automatically choose the accessibility page size when we detect a screen reader. 2000 lines will be the new default.")
|
||||
})),
|
||||
ariaLabel: register(new EditorStringOption(
|
||||
EditorOption.ariaLabel, 'ariaLabel', nls.localize('editorViewAccessibleLabel', "Editor content")
|
||||
)),
|
||||
@ -3901,6 +3915,19 @@ export const EditorOptions = {
|
||||
description: nls.localize('autoClosingBrackets', "Controls whether the editor should automatically close brackets after the user adds an opening bracket.")
|
||||
}
|
||||
)),
|
||||
autoClosingDelete: register(new EditorStringEnumOption(
|
||||
EditorOption.autoClosingDelete, 'autoClosingDelete',
|
||||
'auto' as 'always' | 'auto' | 'never',
|
||||
['always', 'auto', 'never'] as const,
|
||||
{
|
||||
enumDescriptions: [
|
||||
'',
|
||||
nls.localize('editor.autoClosingDelete.auto', "Remove adjacent closing quotes or brackets only if they were automatically inserted."),
|
||||
'',
|
||||
],
|
||||
description: nls.localize('autoClosingDelete', "Controls whether the editor should remove adjacent closing quotes or brackets when deleting.")
|
||||
}
|
||||
)),
|
||||
autoClosingOvertype: register(new EditorStringEnumOption(
|
||||
EditorOption.autoClosingOvertype, 'autoClosingOvertype',
|
||||
'auto' as 'always' | 'auto' | 'never',
|
||||
@ -3963,7 +3990,7 @@ export const EditorOptions = {
|
||||
)),
|
||||
stickyTabStops: register(new EditorBooleanOption(
|
||||
EditorOption.stickyTabStops, 'stickyTabStops', false,
|
||||
{ description: nls.localize('stickyTabStops', "Emulate selection behaviour of tab characters when using spaces for indentation. Selection will stick to tab stops.") }
|
||||
{ description: nls.localize('stickyTabStops', "Emulate selection behavior of tab characters when using spaces for indentation. Selection will stick to tab stops.") }
|
||||
)),
|
||||
codeLens: register(new EditorBooleanOption(
|
||||
EditorOption.codeLens, 'codeLens', true,
|
||||
@ -4042,6 +4069,9 @@ export const EditorOptions = {
|
||||
disableMonospaceOptimizations: register(new EditorBooleanOption(
|
||||
EditorOption.disableMonospaceOptimizations, 'disableMonospaceOptimizations', false
|
||||
)),
|
||||
domReadOnly: register(new EditorBooleanOption(
|
||||
EditorOption.domReadOnly, 'domReadOnly', false,
|
||||
)),
|
||||
dragAndDrop: register(new EditorBooleanOption(
|
||||
EditorOption.dragAndDrop, 'dragAndDrop', true,
|
||||
{ description: nls.localize('dragAndDrop', "Controls whether the editor should allow moving selections via drag and drop.") }
|
||||
@ -4264,7 +4294,7 @@ export const EditorOptions = {
|
||||
)),
|
||||
renderLineHighlightOnlyWhenFocus: register(new EditorBooleanOption(
|
||||
EditorOption.renderLineHighlightOnlyWhenFocus, 'renderLineHighlightOnlyWhenFocus', false,
|
||||
{ description: nls.localize('renderLineHighlightOnlyWhenFocus', "Controls if the editor should render the current line highlight only when the editor is focused") }
|
||||
{ description: nls.localize('renderLineHighlightOnlyWhenFocus', "Controls if the editor should render the current line highlight only when the editor is focused.") }
|
||||
)),
|
||||
renderValidationDecorations: register(new EditorStringEnumOption(
|
||||
EditorOption.renderValidationDecorations, 'renderValidationDecorations',
|
||||
@ -4280,7 +4310,7 @@ export const EditorOptions = {
|
||||
'',
|
||||
nls.localize('renderWhitespace.boundary', "Render whitespace characters except for single spaces between words."),
|
||||
nls.localize('renderWhitespace.selection', "Render whitespace characters only on selected text."),
|
||||
nls.localize('renderWhitespace.trailing', "Render only trailing whitespace characters"),
|
||||
nls.localize('renderWhitespace.trailing', "Render only trailing whitespace characters."),
|
||||
''
|
||||
],
|
||||
description: nls.localize('renderWhitespace', "Controls how the editor should render whitespace characters.")
|
||||
|
@ -620,6 +620,10 @@ export class Cursor extends Disposable {
|
||||
this._isDoingComposition = isDoingComposition;
|
||||
}
|
||||
|
||||
public getAutoClosedCharacters(): Range[] {
|
||||
return AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
|
||||
}
|
||||
|
||||
public startComposition(eventsCollector: ViewModelEventsCollector): void {
|
||||
this._selectionsWhenCompositionStarted = this.getSelections().slice(0);
|
||||
}
|
||||
@ -628,8 +632,7 @@ export class Cursor extends Disposable {
|
||||
this._executeEdit(() => {
|
||||
if (source === 'keyboard') {
|
||||
// composition finishes, let's check if we need to auto complete if necessary.
|
||||
const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
|
||||
this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.cursorConfig, this._model, this._selectionsWhenCompositionStarted, this.getSelections(), autoClosedCharacters));
|
||||
this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.cursorConfig, this._model, this._selectionsWhenCompositionStarted, this.getSelections(), this.getAutoClosedCharacters()));
|
||||
this._selectionsWhenCompositionStarted = null;
|
||||
}
|
||||
}, eventsCollector, source);
|
||||
@ -647,8 +650,7 @@ export class Cursor extends Disposable {
|
||||
const chr = text.substr(offset, charLength);
|
||||
|
||||
// Here we must interpret each typed character individually
|
||||
const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
|
||||
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._isDoingComposition, this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), autoClosedCharacters, chr));
|
||||
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._isDoingComposition, this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), this.getAutoClosedCharacters(), chr));
|
||||
|
||||
offset += charLength;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ export class ColumnSelection {
|
||||
|
||||
public static columnSelectLeft(config: CursorConfiguration, model: ICursorSimpleModel, prevColumnSelectData: IColumnSelectData): IColumnSelectResult {
|
||||
let toViewVisualColumn = prevColumnSelectData.toViewVisualColumn;
|
||||
if (toViewVisualColumn > 1) {
|
||||
if (toViewVisualColumn > 0) {
|
||||
toViewVisualColumn--;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingEditStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
@ -73,7 +73,8 @@ export class CursorConfiguration {
|
||||
public readonly multiCursorPaste: 'spread' | 'full';
|
||||
public readonly autoClosingBrackets: EditorAutoClosingStrategy;
|
||||
public readonly autoClosingQuotes: EditorAutoClosingStrategy;
|
||||
public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy;
|
||||
public readonly autoClosingDelete: EditorAutoClosingEditStrategy;
|
||||
public readonly autoClosingOvertype: EditorAutoClosingEditStrategy;
|
||||
public readonly autoSurround: EditorAutoSurroundStrategy;
|
||||
public readonly autoIndent: EditorAutoIndentStrategy;
|
||||
public readonly autoClosingPairs: AutoClosingPairs;
|
||||
@ -92,6 +93,7 @@ export class CursorConfiguration {
|
||||
|| e.hasChanged(EditorOption.multiCursorPaste)
|
||||
|| e.hasChanged(EditorOption.autoClosingBrackets)
|
||||
|| e.hasChanged(EditorOption.autoClosingQuotes)
|
||||
|| e.hasChanged(EditorOption.autoClosingDelete)
|
||||
|| e.hasChanged(EditorOption.autoClosingOvertype)
|
||||
|| e.hasChanged(EditorOption.autoSurround)
|
||||
|| e.hasChanged(EditorOption.useTabStops)
|
||||
@ -125,6 +127,7 @@ export class CursorConfiguration {
|
||||
this.multiCursorPaste = options.get(EditorOption.multiCursorPaste);
|
||||
this.autoClosingBrackets = options.get(EditorOption.autoClosingBrackets);
|
||||
this.autoClosingQuotes = options.get(EditorOption.autoClosingQuotes);
|
||||
this.autoClosingDelete = options.get(EditorOption.autoClosingDelete);
|
||||
this.autoClosingOvertype = options.get(EditorOption.autoClosingOvertype);
|
||||
this.autoSurround = options.get(EditorOption.autoSurround);
|
||||
this.autoIndent = options.get(EditorOption.autoIndent);
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand';
|
||||
import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorAutoClosingEditStrategy, EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { MoveOperations } from 'vs/editor/common/controller/cursorMoveOperations';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@ -50,15 +50,20 @@ export class DeleteOperations {
|
||||
}
|
||||
|
||||
public static isAutoClosingPairDelete(
|
||||
autoClosingDelete: EditorAutoClosingEditStrategy,
|
||||
autoClosingBrackets: EditorAutoClosingStrategy,
|
||||
autoClosingQuotes: EditorAutoClosingStrategy,
|
||||
autoClosingPairsOpen: Map<string, StandardAutoClosingPairConditional[]>,
|
||||
model: ICursorSimpleModel,
|
||||
selections: Selection[]
|
||||
selections: Selection[],
|
||||
autoClosedCharacters: Range[]
|
||||
): boolean {
|
||||
if (autoClosingBrackets === 'never' && autoClosingQuotes === 'never') {
|
||||
return false;
|
||||
}
|
||||
if (autoClosingDelete === 'never') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
@ -100,6 +105,21 @@ export class DeleteOperations {
|
||||
if (!foundAutoClosingPair) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must delete the pair only if it was automatically inserted by the editor
|
||||
if (autoClosingDelete === 'auto') {
|
||||
let found = false;
|
||||
for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) {
|
||||
const autoClosedCharacter = autoClosedCharacters[j];
|
||||
if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -120,9 +140,9 @@ export class DeleteOperations {
|
||||
return [true, commands];
|
||||
}
|
||||
|
||||
public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, Array<ICommand | null>] {
|
||||
public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], autoClosedCharacters: Range[]): [boolean, Array<ICommand | null>] {
|
||||
|
||||
if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairs.autoClosingPairsOpenByEnd, model, selections)) {
|
||||
if (this.isAutoClosingPairDelete(config.autoClosingDelete, config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairs.autoClosingPairsOpenByEnd, model, selections, autoClosedCharacters)) {
|
||||
return this._runAutoClosingPairDelete(config, model, selections);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorAutoClosingEditStrategy, EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { CursorConfiguration, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
|
||||
import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier';
|
||||
@ -52,9 +52,11 @@ export interface DeleteWordContext {
|
||||
model: ITextModel;
|
||||
selection: Selection;
|
||||
whitespaceHeuristics: boolean;
|
||||
autoClosingDelete: EditorAutoClosingEditStrategy;
|
||||
autoClosingBrackets: EditorAutoClosingStrategy;
|
||||
autoClosingQuotes: EditorAutoClosingStrategy;
|
||||
autoClosingPairs: AutoClosingPairs;
|
||||
autoClosedCharacters: Range[];
|
||||
}
|
||||
|
||||
export class WordOperations {
|
||||
@ -384,7 +386,7 @@ export class WordOperations {
|
||||
return selection;
|
||||
}
|
||||
|
||||
if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpenByEnd, ctx.model, [ctx.selection])) {
|
||||
if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingDelete, ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpenByEnd, ctx.model, [ctx.selection], ctx.autoClosedCharacters)) {
|
||||
const position = ctx.selection.getPosition();
|
||||
return new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1);
|
||||
}
|
||||
|
@ -23,10 +23,26 @@ export interface IStringBuilder {
|
||||
appendASCIIString(str: string): void;
|
||||
}
|
||||
|
||||
let _utf16LE_TextDecoder: TextDecoder | null;
|
||||
function getUTF16LE_TextDecoder(): TextDecoder {
|
||||
if (!_utf16LE_TextDecoder) {
|
||||
_utf16LE_TextDecoder = new TextDecoder('UTF-16LE');
|
||||
}
|
||||
return _utf16LE_TextDecoder;
|
||||
}
|
||||
|
||||
let _utf16BE_TextDecoder: TextDecoder | null;
|
||||
function getUTF16BE_TextDecoder(): TextDecoder {
|
||||
if (!_utf16BE_TextDecoder) {
|
||||
_utf16BE_TextDecoder = new TextDecoder('UTF-16BE');
|
||||
}
|
||||
return _utf16BE_TextDecoder;
|
||||
}
|
||||
|
||||
let _platformTextDecoder: TextDecoder | null;
|
||||
export function getPlatformTextDecoder(): TextDecoder {
|
||||
if (!_platformTextDecoder) {
|
||||
_platformTextDecoder = new TextDecoder(platform.isLittleEndian() ? 'UTF-16LE' : 'UTF-16BE');
|
||||
_platformTextDecoder = platform.isLittleEndian() ? getUTF16LE_TextDecoder() : getUTF16BE_TextDecoder();
|
||||
}
|
||||
return _platformTextDecoder;
|
||||
}
|
||||
@ -45,7 +61,14 @@ if (hasTextDecoder) {
|
||||
|
||||
function standardDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string {
|
||||
const view = new Uint16Array(source.buffer, offset, len);
|
||||
return getPlatformTextDecoder().decode(view);
|
||||
if (len > 0 && (view[0] === 0xFEFF || view[0] === 0xFFFE)) {
|
||||
// UTF16 sometimes starts with a BOM https://de.wikipedia.org/wiki/Byte_Order_Mark
|
||||
// It looks like TextDecoder.decode will eat up a leading BOM (0xFEFF or 0xFFFE)
|
||||
// We don't want that behavior because we know the string is UTF16LE and the BOM should be maintained
|
||||
// So we use the manual decoder
|
||||
return compatDecodeUTF16LE(source, offset, len);
|
||||
}
|
||||
return getUTF16LE_TextDecoder().decode(view);
|
||||
}
|
||||
|
||||
function compatDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string {
|
||||
|
@ -19,7 +19,7 @@ function uriGetComparisonKey(resource: URI): string {
|
||||
return resource.toString();
|
||||
}
|
||||
|
||||
class SingleModelEditStackData {
|
||||
export class SingleModelEditStackData {
|
||||
|
||||
public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData {
|
||||
const alternativeVersionId = model.getAlternativeVersionId();
|
||||
|
@ -881,55 +881,49 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
* Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc.
|
||||
* Will try to not allocate if possible.
|
||||
*/
|
||||
private _validateRangeRelaxedNoAllocations(range: IRange): Range {
|
||||
public _validateRangeRelaxedNoAllocations(range: IRange): Range {
|
||||
const linesCount = this._buffer.getLineCount();
|
||||
|
||||
const initialStartLineNumber = range.startLineNumber;
|
||||
const initialStartColumn = range.startColumn;
|
||||
let startLineNumber: number;
|
||||
let startColumn: number;
|
||||
let startLineNumber = Math.floor((typeof initialStartLineNumber === 'number' && !isNaN(initialStartLineNumber)) ? initialStartLineNumber : 1);
|
||||
let startColumn = Math.floor((typeof initialStartColumn === 'number' && !isNaN(initialStartColumn)) ? initialStartColumn : 1);
|
||||
|
||||
if (initialStartLineNumber < 1) {
|
||||
if (startLineNumber < 1) {
|
||||
startLineNumber = 1;
|
||||
startColumn = 1;
|
||||
} else if (initialStartLineNumber > linesCount) {
|
||||
} else if (startLineNumber > linesCount) {
|
||||
startLineNumber = linesCount;
|
||||
startColumn = this.getLineMaxColumn(startLineNumber);
|
||||
} else {
|
||||
startLineNumber = initialStartLineNumber | 0;
|
||||
if (initialStartColumn <= 1) {
|
||||
if (startColumn <= 1) {
|
||||
startColumn = 1;
|
||||
} else {
|
||||
const maxColumn = this.getLineMaxColumn(startLineNumber);
|
||||
if (initialStartColumn >= maxColumn) {
|
||||
if (startColumn >= maxColumn) {
|
||||
startColumn = maxColumn;
|
||||
} else {
|
||||
startColumn = initialStartColumn | 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialEndLineNumber = range.endLineNumber;
|
||||
const initialEndColumn = range.endColumn;
|
||||
let endLineNumber: number;
|
||||
let endColumn: number;
|
||||
let endLineNumber = Math.floor((typeof initialEndLineNumber === 'number' && !isNaN(initialEndLineNumber)) ? initialEndLineNumber : 1);
|
||||
let endColumn = Math.floor((typeof initialEndColumn === 'number' && !isNaN(initialEndColumn)) ? initialEndColumn : 1);
|
||||
|
||||
if (initialEndLineNumber < 1) {
|
||||
if (endLineNumber < 1) {
|
||||
endLineNumber = 1;
|
||||
endColumn = 1;
|
||||
} else if (initialEndLineNumber > linesCount) {
|
||||
} else if (endLineNumber > linesCount) {
|
||||
endLineNumber = linesCount;
|
||||
endColumn = this.getLineMaxColumn(endLineNumber);
|
||||
} else {
|
||||
endLineNumber = initialEndLineNumber | 0;
|
||||
if (initialEndColumn <= 1) {
|
||||
if (endColumn <= 1) {
|
||||
endColumn = 1;
|
||||
} else {
|
||||
const maxColumn = this.getLineMaxColumn(endLineNumber);
|
||||
if (initialEndColumn >= maxColumn) {
|
||||
if (endColumn >= maxColumn) {
|
||||
endColumn = maxColumn;
|
||||
} else {
|
||||
endColumn = initialEndColumn | 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -297,11 +297,11 @@ export interface EvaluatableExpressionProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* An open ended information bag passed to the inline value provider.
|
||||
* A minimal context containes just the document location where the debugger has stopped.
|
||||
* A value-object that contains contextual information when requesting inline values from a InlineValuesProvider.
|
||||
* @internal
|
||||
*/
|
||||
export interface InlineValueContext {
|
||||
frameId: number;
|
||||
stoppedLocation: Range;
|
||||
}
|
||||
|
||||
@ -699,8 +699,8 @@ export interface CodeAction {
|
||||
* @internal
|
||||
*/
|
||||
export const enum CodeActionTriggerType {
|
||||
Auto = 1,
|
||||
Manual = 2,
|
||||
Invoke = 1,
|
||||
Auto = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1683,33 +1683,6 @@ export interface CommentThreadChangedEvent {
|
||||
readonly changed: CommentThread[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IWebviewPortMapping {
|
||||
webviewPort: number;
|
||||
extensionHostPort: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IWebviewOptions {
|
||||
readonly enableScripts?: boolean;
|
||||
readonly enableCommandUris?: boolean;
|
||||
readonly localResourceRoots?: ReadonlyArray<UriComponents>;
|
||||
readonly portMapping?: ReadonlyArray<IWebviewPortMapping>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IWebviewPanelOptions {
|
||||
readonly enableFindWidget?: boolean;
|
||||
readonly retainContextWhenHidden?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface CodeLens {
|
||||
range: IRange;
|
||||
id?: string;
|
||||
|
@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { stringDiff } from 'vs/base/common/diff/diff';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { globals } from 'vs/base/common/platform';
|
||||
@ -455,7 +454,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
||||
const result: TextEdit[] = [];
|
||||
let lastEol: EndOfLineSequence | undefined = undefined;
|
||||
|
||||
edits = mergeSort(edits, (a, b) => {
|
||||
edits = edits.slice(0).sort((a, b) => {
|
||||
if (a.range && b.range) {
|
||||
return Range.compareRangesUsingStarts(a.range, b.range);
|
||||
}
|
||||
|
@ -741,6 +741,19 @@ export class ModelSemanticColoring extends Disposable {
|
||||
this._fetchDocumentSemanticTokens.schedule();
|
||||
}
|
||||
}));
|
||||
this._register(this._model.onDidChangeLanguage(() => {
|
||||
// clear any outstanding state
|
||||
if (this._currentDocumentResponse) {
|
||||
this._currentDocumentResponse.dispose();
|
||||
this._currentDocumentResponse = null;
|
||||
}
|
||||
if (this._currentDocumentRequestCancellationTokenSource) {
|
||||
this._currentDocumentRequestCancellationTokenSource.cancel();
|
||||
this._currentDocumentRequestCancellationTokenSource = null;
|
||||
}
|
||||
this._setDocumentSemanticTokens(null, null, null, []);
|
||||
this._fetchDocumentSemanticTokens.schedule(0);
|
||||
}));
|
||||
const bindDocumentChangeListeners = () => {
|
||||
dispose(this._documentProvidersChangeListeners);
|
||||
this._documentProvidersChangeListeners = [];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user