Archived
1
0

chore(vscode): update to 1.55.2

This commit is contained in:
Akash Satheesan
2021-04-09 11:32:27 +05:30
1102 changed files with 39988 additions and 23544 deletions

View File

@ -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 = [];

View File

@ -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
*

View File

@ -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

View File

@ -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') {

View File

@ -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;
}

View File

@ -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"
],

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -14,6 +14,10 @@
align-items: center;
}
.monaco-text-button:focus {
outline-offset: 2px !important;
}
.monaco-text-button:hover {
text-decoration: none !important;
}

View File

@ -20,4 +20,5 @@ export interface IHoverDelegateOptions {
export interface IHoverDelegate {
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
delay: number;
}

View File

@ -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);

View File

@ -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 {

View File

@ -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[] {

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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`;
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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');

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -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);
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 => ({

View File

@ -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) {

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}

View File

@ -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.

View File

@ -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 ---

View File

@ -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;

View File

@ -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);
}
})();

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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.
*/

View File

@ -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

View File

@ -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

View File

@ -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');

View File

@ -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';

View File

@ -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 {

View File

@ -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

View File

@ -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'), {

View File

@ -7,7 +7,7 @@
position: absolute;
width: 600px;
z-index: 2000;
padding-bottom: 6px;
padding: 0 1px 6px 1px;
left: 50%;
margin-left: -300px;
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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');

View File

@ -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;

View File

@ -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);

View File

@ -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());

View File

@ -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;
}

View File

@ -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;

View File

@ -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 () => {

View File

@ -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 = {

View File

@ -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 () {

View File

@ -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 () {

View File

@ -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', () => {

View File

@ -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;
}

View File

@ -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);
});
});
};

View 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>

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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({

View File

@ -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

View File

@ -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)) {

View File

@ -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 */ });
}
}

View File

@ -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),
))
);

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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> {

View File

@ -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();
}

View File

@ -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');

View File

@ -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) {

View File

@ -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

View File

@ -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};',

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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'],

View File

@ -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.")

View File

@ -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;
}

View File

@ -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--;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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