Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae

This commit is contained in:
ADS Merger
2020-06-19 10:59:23 -07:00
committed by AzureDataStudio
parent a971aee5bd
commit 5e7071e466
1002 changed files with 24201 additions and 13193 deletions
+6
View File
@@ -109,6 +109,9 @@ function pipeLoggingToParent() {
return res;
}
/**
* @param {{ type: string; severity: string; arguments: string; }} arg
*/
function safeSend(arg) {
try {
process.send(arg);
@@ -117,6 +120,9 @@ function pipeLoggingToParent() {
}
}
/**
* @param {unknown} obj
*/
function isObject(obj) {
return typeof obj === 'object'
&& obj !== null
+5 -2
View File
@@ -18,7 +18,6 @@ exports.assign = function assign(destination, source) {
};
/**
*
* @param {string[]} modulePaths
* @param {(result, configuration: object) => any} resultCallback
* @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options
@@ -218,6 +217,10 @@ function registerDeveloperKeybindings(disallowReloadKeybinding) {
};
}
/**
* @param {string | Error} error
* @param {boolean} enableDeveloperTools
*/
function onUnexpectedError(error, enableDeveloperTools) {
const ipc = require('electron').ipcRenderer;
@@ -228,7 +231,7 @@ function onUnexpectedError(error, enableDeveloperTools) {
console.error('[uncaught exception]: ' + error);
if (error && error.stack) {
if (error && typeof error !== 'string' && error.stack) {
console.error(error.stack);
}
}
+18 -11
View File
@@ -13,7 +13,6 @@ Error.stackTraceLimit = 100;
// Workaround for Electron not installing a handler to ignore SIGPIPE
// (https://github.com/electron/electron/issues/13254)
// @ts-ignore
process.on('SIGPIPE', () => {
console.error(new Error('Unexpected SIGPIPE'));
});
@@ -27,7 +26,6 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
throw new Error('Missing injectPath');
}
// @ts-ignore
const Module = require('module');
const path = require('path');
@@ -51,12 +49,12 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
return paths;
};
};
//#endregion
//#region Remove global paths from the node lookup paths
exports.removeGlobalNodeModuleLookupPaths = function() {
// @ts-ignore
exports.removeGlobalNodeModuleLookupPaths = function () {
const Module = require('module');
// @ts-ignore
const globalPaths = Module.globalPaths;
@@ -74,15 +72,15 @@ exports.removeGlobalNodeModuleLookupPaths = function() {
return paths.slice(0, paths.length - commonSuffixLength);
};
};
//#endregion
//#region Add support for using node_modules.asar
/**
* @param {string=} nodeModulesPath
*/
exports.enableASARSupport = function (nodeModulesPath) {
// @ts-ignore
const Module = require('module');
const path = require('path');
@@ -111,9 +109,11 @@ exports.enableASARSupport = function (nodeModulesPath) {
return paths;
};
};
//#endregion
//#region URI helpers
/**
* @param {string} _path
* @returns {string}
@@ -136,9 +136,11 @@ exports.uriFromPath = function (_path) {
return uri.replace(/#/g, '%23');
};
//#endregion
//#region FS helpers
/**
* @param {string} file
* @returns {Promise<string>}
@@ -185,9 +187,11 @@ exports.mkdirp = function mkdirp(dir) {
return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
};
//#endregion
//#region NLS helpers
/**
* @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean }}
*/
@@ -235,14 +239,15 @@ exports.setupNLS = function () {
return nlsConfig;
};
//#endregion
//#region Portable helpers
/**
* @returns {{ portableDataPath: string, isPortable: boolean }}
*/
exports.configurePortable = function () {
// @ts-ignore
const product = require('../product.json');
const path = require('path');
const fs = require('fs');
@@ -270,6 +275,7 @@ exports.configurePortable = function () {
return path.join(getApplicationPath(), 'data');
}
// @ts-ignore
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
return path.join(path.dirname(getApplicationPath()), portableDataName);
}
@@ -299,16 +305,17 @@ exports.configurePortable = function () {
isPortable
};
};
//#endregion
//#region ApplicationInsights
/**
* Prevents appinsights from monkey patching modules.
* This should be called before importing the applicationinsights module
*/
// Prevents appinsights from monkey patching modules.
// This should be called before importing the applicationinsights module
exports.avoidMonkeyPatchFromAppInsights = function () {
// @ts-ignore
process.env['APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL'] = true; // Skip monkey patching of 3rd party modules by appinsights
global['diagnosticsSource'] = {}; // Prevents diagnostic channel (which patches "require") from initializing entirely
};
//#endregion
+49 -20
View File
@@ -16,9 +16,8 @@ const fs = require('fs');
const os = require('os');
const bootstrap = require('./bootstrap');
const paths = require('./paths');
// @ts-ignore
/** @type {any} */
const product = require('../product.json');
// @ts-ignore
const { app, protocol } = require('electron');
// Enable portable support
@@ -49,6 +48,11 @@ let crashReporterDirectory = args['crash-reporter-directory'];
if (crashReporterDirectory) {
crashReporterDirectory = path.normalize(crashReporterDirectory);
if (!path.isAbsolute(crashReporterDirectory)) {
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
app.exit(1);
}
if (!fs.existsSync(crashReporterDirectory)) {
try {
fs.mkdirSync(crashReporterDirectory);
@@ -65,9 +69,11 @@ if (crashReporterDirectory) {
// Start crash reporter
const { crashReporter } = require('electron');
const productName = (product.crashReporter && product.crashReporter.productName) || product.nameShort;
const companyName = (product.crashReporter && product.crashReporter.companyName) || 'Microsoft';
crashReporter.start({
companyName: 'Microsoft',
productName: product.nameShort,
companyName: companyName,
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
submitURL: '',
uploadToServer: false
});
@@ -87,6 +93,13 @@ setCurrentWorkingDirectory();
// Register custom schemes with privileges
protocol.registerSchemesAsPrivileged([
{
scheme: 'vscode-resource',
privileges: {
secure: true,
supportFetchAPI: true,
corsEnabled: true,
}
}, {
scheme: 'vscode-webview-resource',
privileges: {
secure: true,
@@ -129,7 +142,6 @@ if (locale) {
// Load our code once ready
app.once('ready', function () {
if (args['trace']) {
// @ts-ignore
const contentTracing = require('electron').contentTracing;
const traceOptions = {
@@ -196,30 +208,45 @@ function configureCommandlineSwitchesSync(cliArgs) {
SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
}
const SUPPORTED_MAIN_PROCESS_SWITCHES = [
// Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775
'enable-proposed-api'
];
// Read argv config
const argvConfig = readArgvConfigSync();
// Append each flag to Electron
Object.keys(argvConfig).forEach(argvKey => {
if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) === -1) {
return; // unsupported argv key
}
const argvValue = argvConfig[argvKey];
// Color profile
if (argvKey === 'force-color-profile') {
if (argvValue) {
app.commandLine.appendSwitch(argvKey, argvValue);
// Append Electron flags to Electron
if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) {
// Color profile
if (argvKey === 'force-color-profile') {
if (argvValue) {
app.commandLine.appendSwitch(argvKey, argvValue);
}
}
// Others
else if (argvValue === true || argvValue === 'true') {
if (argvKey === 'disable-hardware-acceleration') {
app.disableHardwareAcceleration(); // needs to be called explicitly
} else {
app.commandLine.appendSwitch(argvKey);
}
}
}
// Others
else if (argvValue === true || argvValue === 'true') {
if (argvKey === 'disable-hardware-acceleration') {
app.disableHardwareAcceleration(); // needs to be called explicitly
} else {
app.commandLine.appendSwitch(argvKey);
// Append main process flags to process.argv
else if (SUPPORTED_MAIN_PROCESS_SWITCHES.indexOf(argvKey) !== -1) {
if (argvKey === 'enable-proposed-api') {
if (Array.isArray(argvValue)) {
argvValue.forEach(id => id && typeof id === 'string' && process.argv.push('--enable-proposed-api', id));
} else {
console.error(`Unexpected value for \`enable-proposed-api\` in argv.json. Expected array of extension ids.`);
}
}
}
});
@@ -476,6 +503,7 @@ function getNodeCachedDir() {
}
//#region NLS Support
/**
* Resolve the NLS configuration
*
@@ -571,4 +599,5 @@ function getLegacyUserDefinedLocaleSync(localeConfigPath) {
// ignore
}
}
//#endregion
-1
View File
@@ -6,7 +6,6 @@
//@ts-check
'use strict';
// @ts-expect-error
// const pkg = require('../package.json');
const path = require('path');
const os = require('os');
@@ -70,11 +70,6 @@ export class BrowserClipboardService implements IClipboardService {
return this._vsClipboardService.hasResources();
}
readTextSync(): string | undefined {
// eslint-disable-next-line no-sync
return this._vsClipboardService.readTextSync();
}
readFindTextSync(): string {
// eslint-disable-next-line no-sync
return this._vsClipboardService.readFindTextSync();
@@ -89,8 +89,8 @@ export default class DiffEditorComponent extends ComponentBase implements ICompo
}
});
let editorinput1 = this._instantiationService.createInstance(ResourceEditorInput, 'source', undefined, uri1, undefined);
let editorinput2 = this._instantiationService.createInstance(ResourceEditorInput, 'target', undefined, uri2, undefined);
let editorinput1 = this._instantiationService.createInstance(ResourceEditorInput, uri1, 'source', undefined, undefined);
let editorinput2 = this._instantiationService.createInstance(ResourceEditorInput, uri2, 'target', undefined, undefined);
this._editorInput = new DiffEditorInput('DiffEditor', undefined, editorinput1, editorinput2, true);
this._editor.setInput(this._editorInput, undefined, cancellationTokenSource.token);
@@ -26,7 +26,7 @@ import { URI } from 'vs/base/common/uri';
import { dirname, basename } from 'vs/base/common/resources';
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService';
import { FileKind } from 'vs/platform/files/common/files';
import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { localize } from 'vs/nls';
import { timeout } from 'vs/base/common/async';
@@ -487,9 +487,7 @@ export class TreeView extends Disposable implements ITreeView {
}));
this.tree.setInput(this.root).then(() => this.updateContentAreas());
const treeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false });
this._register(treeNavigator);
this._register(treeNavigator.onDidOpenResource(e => {
this._register(this.tree.onDidOpen(e => {
if (!e.browserEvent) {
return;
}
@@ -393,7 +393,7 @@ suite('commandLineService tests', () => {
querymodelService.setup(c => c.onRunQueryComplete).returns(() => Event.None);
let uri = URI.file(args._[0]);
const workbenchinstantiationService = workbenchInstantiationService();
const editorInput = workbenchinstantiationService.createInstance(FileEditorInput, uri, undefined, undefined);
const editorInput = workbenchinstantiationService.createInstance(FileEditorInput, uri, undefined, undefined, undefined);
const queryInput = new FileQueryEditorInput(undefined, editorInput, undefined, connectionManagementService.object, querymodelService.object, configurationService.object);
queryInput.state.connected = true;
const editorService: TypeMoq.Mock<IEditorService> = TypeMoq.Mock.ofType<IEditorService>(TestEditorService, TypeMoq.MockBehavior.Strict);
@@ -85,7 +85,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo
public sendMessage(message: string): void {
if (this._webview) {
this._webview.sendMessage(message);
this._webview.postMessage(message);
}
}
@@ -86,7 +86,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget,
public sendMessage(message: string): void {
if (this._webview) {
this._webview.sendMessage(message);
this._webview.postMessage(message);
}
}
@@ -71,7 +71,7 @@ suite('Editor Replacer Contribution', () => {
const editorService = new MockEditorService(instantiationService);
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -86,7 +86,7 @@ suite('Editor Replacer Contribution', () => {
const editorService = new MockEditorService(instantiationService);
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.other'), undefined, 'sql');
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.other'), undefined, 'sql', undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -101,7 +101,7 @@ suite('Editor Replacer Contribution', () => {
const editorService = new MockEditorService(instantiationService);
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.notebook'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.notebook'), undefined, undefined, undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -116,7 +116,7 @@ suite('Editor Replacer Contribution', () => {
const editorService = new MockEditorService(instantiationService);
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.iynb'), undefined, 'notebook');
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.iynb'), undefined, 'notebook', undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -191,10 +191,10 @@ class MockEditorService extends TestEditorService {
});
}
fireOpenEditor(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext, id?: string) {
fireOpenEditor(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext) {
for (const handler of this.overridenOpens) {
let response: IOpenEditorOverride | undefined;
if (response = handler.open(editor, options, group, context, id)) {
if (response = handler.open(editor, options, group, context)) {
return response;
}
}
@@ -114,7 +114,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen
private sendMessage(): void {
if (this._webview && this.message) {
this._webview.sendMessage(this.message);
this._webview.postMessage(this.message);
}
}
@@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { TestEnvironmentService, TestLifecycleService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestEnvironmentService, TestLifecycleService, TestTextFileService, workbenchInstantiationService, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices';
import { Range } from 'vs/editor/common/core/range';
import { nb } from 'azdata';
import { Emitter } from 'vs/base/common/event';
@@ -884,7 +884,7 @@ suite('Notebook Editor Model', function (): void {
async function createTextEditorModel(self: Mocha.ITestCallbackContext): Promise<NotebookEditorModel> {
let textFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(self, defaultUri.toString()), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.files).add(textFileEditorModel.resource, textFileEditorModel);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textFileEditorModel.resource, textFileEditorModel);
await textFileEditorModel.load();
return new NotebookEditorModel(defaultUri, textFileEditorModel, mockNotebookService.object, testResourcePropertiesService);
}
@@ -15,9 +15,7 @@ import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'
import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { URI } from 'vs/base/common/uri';
type PublicPart<T> = { [K in keyof T]: T[K] };
export class FileQueryEditorInput extends QueryEditorInput implements PublicPart<FileEditorInput> {
export class FileQueryEditorInput extends QueryEditorInput {
public static readonly ID = 'workbench.editorInput.fileQueryInput';
@@ -84,7 +82,7 @@ export class FileQueryEditorInput extends QueryEditorInput implements PublicPart
return this.text.isResolved();
}
public move(group: GroupIdentifier, target: URI): IMoveResult {
return this.text.move(group, target);
public rename(group: GroupIdentifier, target: URI): IMoveResult {
return this.text.rename(group, target);
}
}
@@ -36,7 +36,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
queryEditorLanguageAssociation.convertInput(input);
assert(connectionManagementService.numberConnects === 1, 'Convert input should have called connect when active OE connection exists');
});
@@ -49,7 +49,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
const response = queryEditorLanguageAssociation.convertInput(input);
assert(isThenable(response));
await response;
@@ -64,7 +64,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
queryEditorLanguageAssociation.convertInput(input);
assert(connectionManagementService.numberConnects === 1, 'Convert input should have called connect when active editor connection exists');
});
@@ -77,7 +77,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
const response = queryEditorLanguageAssociation.convertInput(input);
assert(isThenable(response));
await response;
@@ -115,7 +115,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
queryEditorLanguageAssociation.syncConvertinput(input);
assert(connectionManagementService.numberConnects === 0, 'Convert input should not have been called connect when no global connections exist');
});
@@ -127,7 +127,7 @@ suite('Query Input Factory', () => {
instantiationService.stub(IConnectionManagementService, connectionManagementService);
instantiationService.stub(IEditorService, editorService);
const queryEditorLanguageAssociation = instantiationService.createInstance(QueryEditorLanguageAssociation);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined, undefined);
const response = queryEditorLanguageAssociation.convertInput(input);
assert(isThenable(response));
await response;
@@ -145,7 +145,7 @@ export class WebViewDialog extends Modal {
}
public sendMessage(message: any): void {
this._webview.sendMessage(message);
this._webview.postMessage(message);
}
public open() {
@@ -61,7 +61,7 @@ suite('set mode', () => {
const replaceEditorStub = sinon.stub(editorService, 'replaceEditors', () => Promise.resolve());
const stub = sinon.stub();
const modeSupport = { setMode: stub };
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext');
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext', undefined);
await instantiationService.invokeFunction(setMode, modeSupport, activeEditor, 'json');
assert(stub.calledOnce);
assert(stub.calledWithExactly('json'));
@@ -75,7 +75,7 @@ suite('set mode', () => {
const stub = sinon.stub();
const modeSupport = { setMode: stub };
const uri = URI.file('/test/file.sql');
const textInput = instantiationService.createInstance(FileEditorInput, uri, undefined, 'sql');
const textInput = instantiationService.createInstance(FileEditorInput, uri, undefined, 'sql', undefined);
const activeEditor = instantiationService.createInstance(FileQueryEditorInput, '', textInput, instantiationService.createInstance(QueryResultsInput, uri.toString()));
await instantiationService.invokeFunction(setMode, modeSupport, activeEditor, 'notebooks');
assert(stub.calledOnce);
@@ -89,7 +89,7 @@ suite('set mode', () => {
const stub = sinon.stub();
const modeSupport = { setMode: stub };
const uri = URI.file('/test/file.sql');
const textInput = instantiationService.createInstance(FileEditorInput, uri, undefined, 'sql');
const textInput = instantiationService.createInstance(FileEditorInput, uri, undefined, 'sql', undefined);
const activeEditor = instantiationService.createInstance(FileQueryEditorInput, '', textInput, instantiationService.createInstance(QueryResultsInput, uri.toString()));
await instantiationService.invokeFunction(setMode, modeSupport, activeEditor, 'plaintext');
assert(stub.calledOnce);
@@ -102,7 +102,7 @@ suite('set mode', () => {
instantiationService.stub(IEditorService, editorService);
const stub = sinon.stub();
const modeSupport = { setMode: stub };
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext');
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext', undefined);
await instantiationService.invokeFunction(setMode, modeSupport, activeEditor, 'sql');
assert(stub.calledOnce);
assert(stub.calledWithExactly('sql'));
@@ -117,7 +117,7 @@ suite('set mode', () => {
(instantiationService as TestInstantiationService).stub(INotificationService, 'error', errorStub);
const stub = sinon.stub();
const modeSupport = { setMode: stub };
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext');
const activeEditor = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.txt'), undefined, 'plaintext', undefined);
sinon.stub(activeEditor, 'isDirty', () => true);
await instantiationService.invokeFunction(setMode, modeSupport, activeEditor, 'sql');
assert(stub.notCalled);
@@ -136,16 +136,18 @@ class MockEditorService extends TestEditorService {
group: {}
};
activeTextEditorControl: ICodeEditor = <any>{
getModel: () => {
return <any>{
getLanguageIdentifier: () => {
return { language: this.mode };
}
};
},
getEditorType: () => EditorType.ICodeEditor
};
get activeTextEditorControl(): ICodeEditor {
return {
getModel: () => {
return <any>{
getLanguageIdentifier: () => {
return { language: this.mode };
}
};
},
getEditorType: () => EditorType.ICodeEditor
} as any;
}
openEditor(_editor: any, _options?: any, _group?: any): Promise<any> {
return Promise.resolve(_editor);
+14 -6
View File
@@ -88,9 +88,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
if (options.baseUrl) {
href = resolvePath(options.baseUrl, href).toString();
}
try {
const hrefAsUri = URI.parse(href);
if (options.baseUrl && hrefAsUri.scheme === Schemas.file) { // absolute or relative local path, or file: uri
href = resolvePath(options.baseUrl, href).toString();
}
} catch (err) { }
attributes.push(`src="${href}"`);
}
if (text) {
@@ -225,9 +229,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
);
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
if (token.tag === 'span' && markdown.isTrusted) {
if (token.attrs['style'] && Object.keys(token.attrs).length === 1) {
if (token.tag === 'span' && markdown.isTrusted && (Object.keys(token.attrs).length === 1)) {
if (token.attrs['style']) {
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
} else if (token.attrs['class']) {
// The class should match codicon rendering in src\vs\base\common\codicons.ts
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
}
return false;
}
@@ -239,7 +246,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
// allowedTags should included everything that markdown renders to.
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'tr', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
// HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
allowedAttributes: {
'a': ['href', 'name', 'target', 'data-href'],
'img': ['src', 'title', 'alt', 'width', 'height'],
+14 -13
View File
@@ -479,27 +479,27 @@ export class ActionBar extends Disposable implements IActionRunner {
DOM.addClass(this.domNode, 'animated');
}
let previousKeys: KeyCode[];
let nextKeys: KeyCode[];
let previousKey: KeyCode;
let nextKey: KeyCode;
switch (this.options.orientation) {
case ActionsOrientation.HORIZONTAL:
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
previousKey = KeyCode.LeftArrow;
nextKey = KeyCode.RightArrow;
break;
case ActionsOrientation.HORIZONTAL_REVERSE:
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
previousKey = KeyCode.RightArrow;
nextKey = KeyCode.LeftArrow;
this.domNode.className += ' reverse';
break;
case ActionsOrientation.VERTICAL:
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
previousKey = KeyCode.UpArrow;
nextKey = KeyCode.DownArrow;
this.domNode.className += ' vertical';
break;
case ActionsOrientation.VERTICAL_REVERSE:
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
previousKey = KeyCode.DownArrow;
nextKey = KeyCode.UpArrow;
this.domNode.className += ' vertical reverse';
break;
}
@@ -508,9 +508,9 @@ export class ActionBar extends Disposable implements IActionRunner {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
if (event.equals(previousKey)) {
this.focusPrevious();
} else if (nextKeys && (event.equals(nextKeys[0]) || event.equals(nextKeys[1]))) {
} else if (event.equals(nextKey)) {
this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
this._onDidCancel.fire();
@@ -696,7 +696,8 @@ export class ActionBar extends Disposable implements IActionRunner {
}
clear(): void {
this.viewItems = dispose(this.viewItems);
dispose(this.viewItems);
this.viewItems = [];
DOM.clearNode(this.actionsList);
}
+49 -21
View File
@@ -11,50 +11,78 @@ import * as dom from 'vs/base/browser/dom';
const MAX_MESSAGE_LENGTH = 20000;
let ariaContainer: HTMLElement;
let alertContainer: HTMLElement;
let alertContainer2: HTMLElement;
let statusContainer: HTMLElement;
let statusContainer2: HTMLElement;
export function setARIAContainer(parent: HTMLElement) {
ariaContainer = document.createElement('div');
ariaContainer.className = 'monaco-aria-container';
alertContainer = document.createElement('div');
alertContainer.className = 'monaco-alert';
alertContainer.setAttribute('role', 'alert');
alertContainer.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(alertContainer);
const createAlertContainer = () => {
const element = document.createElement('div');
element.className = 'monaco-alert';
element.setAttribute('role', 'alert');
element.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(element);
return element;
};
alertContainer = createAlertContainer();
alertContainer2 = createAlertContainer();
statusContainer = document.createElement('div');
statusContainer.className = 'monaco-status';
statusContainer.setAttribute('role', 'complementary');
statusContainer.setAttribute('aria-live', 'polite');
statusContainer.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(statusContainer);
const createStatusContainer = () => {
const element = document.createElement('div');
element.className = 'monaco-status';
element.setAttribute('role', 'complementary');
element.setAttribute('aria-live', 'polite');
element.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(element);
return element;
};
statusContainer = createStatusContainer();
statusContainer2 = createStatusContainer();
parent.appendChild(ariaContainer);
}
/**
* Given the provided message, will make sure that it is read as alert to screen readers.
*/
export function alert(msg: string): void {
insertMessage(alertContainer, msg);
if (!ariaContainer) {
return;
}
// Use alternate containers such that duplicated messages get read out by screen readers #99466
if (alertContainer.textContent !== msg) {
dom.clearNode(alertContainer2);
insertMessage(alertContainer, msg);
} else {
dom.clearNode(alertContainer);
insertMessage(alertContainer2, msg);
}
}
/**
* Given the provided message, will make sure that it is read as status to screen readers.
*/
export function status(msg: string): void {
if (isMacintosh) {
alert(msg); // VoiceOver does not seem to support status role
} else {
insertMessage(statusContainer, msg);
}
}
function insertMessage(target: HTMLElement, msg: string): void {
if (!ariaContainer) {
return;
}
if (isMacintosh) {
alert(msg); // VoiceOver does not seem to support status role
} else {
if (statusContainer.textContent !== msg) {
dom.clearNode(statusContainer2);
insertMessage(statusContainer, msg);
} else {
dom.clearNode(statusContainer);
insertMessage(statusContainer2, msg);
}
}
}
function insertMessage(target: HTMLElement, msg: string): void {
dom.clearNode(target);
if (msg.length > MAX_MESSAGE_LENGTH) {
msg = msg.substr(0, MAX_MESSAGE_LENGTH);
+31 -4
View File
@@ -18,12 +18,16 @@ import { escape } from 'vs/base/common/strings';
export interface IButtonOptions extends IButtonStyles {
readonly title?: boolean | string;
readonly supportCodicons?: boolean;
readonly secondary?: boolean;
}
export interface IButtonStyles {
buttonBackground?: Color;
buttonHoverBackground?: Color;
buttonForeground?: Color;
buttonSecondaryBackground?: Color;
buttonSecondaryHoverBackground?: Color;
buttonSecondaryForeground?: Color;
buttonBorder?: Color;
}
@@ -41,6 +45,9 @@ export class Button extends Disposable {
private buttonBackground: Color | undefined;
private buttonHoverBackground: Color | undefined;
private buttonForeground: Color | undefined;
private buttonSecondaryBackground: Color | undefined;
private buttonSecondaryHoverBackground: Color | undefined;
private buttonSecondaryForeground: Color | undefined;
private buttonBorder: Color | undefined;
private _onDidClick = this._register(new Emitter<Event>());
@@ -54,9 +61,14 @@ export class Button extends Disposable {
this.options = options || Object.create(null);
mixin(this.options, defaultOptions, false);
this.buttonForeground = this.options.buttonForeground;
this.buttonBackground = this.options.buttonBackground;
this.buttonHoverBackground = this.options.buttonHoverBackground;
this.buttonForeground = this.options.buttonForeground;
this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
this.buttonBorder = this.options.buttonBorder;
this._element = document.createElement('a');
@@ -114,7 +126,12 @@ export class Button extends Disposable {
}
private setHoverBackground(): void {
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
let hoverBackground;
if (this.options.secondary) {
hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
} else {
hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
}
if (hoverBackground) {
this._element.style.backgroundColor = hoverBackground;
}
@@ -124,6 +141,9 @@ export class Button extends Disposable {
this.buttonForeground = styles.buttonForeground;
this.buttonBackground = styles.buttonBackground;
this.buttonHoverBackground = styles.buttonHoverBackground;
this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
this.buttonBorder = styles.buttonBorder;
this.applyStyles();
@@ -132,8 +152,15 @@ export class Button extends Disposable {
// {{SQL CARBON EDIT}} -- removed 'private' access modifier @todo anthonydresser 4/12/19 things needs investigation whether we need this
applyStyles(): void {
if (this._element) {
const background = this.buttonBackground ? this.buttonBackground.toString() : '';
const foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
let background, foreground;
if (this.options.secondary) {
foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
} else {
foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
background = this.buttonBackground ? this.buttonBackground.toString() : '';
}
const border = this.buttonBorder ? this.buttonBorder.toString() : '';
this._element.style.color = foreground;
@@ -23,6 +23,7 @@ export interface ICheckboxOpts extends ICheckboxStyles {
export interface ICheckboxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -34,6 +35,7 @@ export interface ISimpleCheckboxStyles {
const defaultOpts = {
inputActiveOptionBorder: Color.fromHex('#007ACC00'),
inputActiveOptionForeground: Color.fromHex('#FFFFFF'),
inputActiveOptionBackground: Color.fromHex('#0E639C50')
};
@@ -170,6 +172,9 @@ export class Checkbox extends Widget {
if (styles.inputActiveOptionBorder) {
this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder;
}
if (styles.inputActiveOptionForeground) {
this._opts.inputActiveOptionForeground = styles.inputActiveOptionForeground;
}
if (styles.inputActiveOptionBackground) {
this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground;
}
@@ -179,6 +184,7 @@ export class Checkbox extends Widget {
protected applyStyles(): void {
if (this.domNode) {
this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : 'transparent';
this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit';
this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : 'transparent';
}
}
Binary file not shown.
+24 -1
View File
@@ -73,6 +73,7 @@ export class Dialog extends Disposable {
this.modal = this.container.appendChild($(`.monaco-dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
this.element = this.shadowElement.appendChild($('.monaco-dialog-box'));
this.element.setAttribute('role', 'dialog');
hide(this.element);
// If no button is provided, default to OK
@@ -109,6 +110,28 @@ export class Dialog extends Disposable {
this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar'));
}
private getAriaLabel(): string {
let typeLabel = nls.localize('dialogInfoMessage', 'Info');
switch (this.options.type) {
case 'error':
nls.localize('dialogErrorMessage', 'Error');
break;
case 'warning':
nls.localize('dialogWarningMessage', 'Warning');
break;
case 'pending':
nls.localize('dialogPendingMessage', 'In Progress');
break;
case 'none':
case 'info':
case 'question':
default:
break;
}
return `${typeLabel}: ${this.message} ${this.options.detail || ''}`;
}
updateMessage(message: string): void {
if (this.messageDetailElement) {
this.messageDetailElement.innerText = message;
@@ -242,7 +265,7 @@ export class Dialog extends Disposable {
this.applyStyles();
this.element.setAttribute('aria-label', this.message);
this.element.setAttribute('aria-label', this.getAriaLabel());
show(this.element);
// Focus first element
+11 -5
View File
@@ -214,6 +214,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions {
actions?: ReadonlyArray<IAction>;
actionProvider?: IActionProvider;
menuClassName?: string;
menuAsChild?: boolean; // scope down for #99448
}
export class DropdownMenu extends BaseDropdown {
@@ -222,6 +223,7 @@ export class DropdownMenu extends BaseDropdown {
private _actions: ReadonlyArray<IAction> = [];
private actionProvider?: IActionProvider;
private menuClassName: string;
private menuAsChild?: boolean;
constructor(container: HTMLElement, options: IDropdownMenuOptions) {
super(container, options);
@@ -230,6 +232,7 @@ export class DropdownMenu extends BaseDropdown {
this.actions = options.actions || [];
this.actionProvider = options.actionProvider;
this.menuClassName = options.menuClassName || '';
this.menuAsChild = !!options.menuAsChild;
}
set menuOptions(options: IMenuOptions | undefined) {
@@ -267,7 +270,7 @@ export class DropdownMenu extends BaseDropdown {
onHide: () => this.onHide(),
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT,
anchorAsContainer: true
anchorAsContainer: this.menuAsChild
});
}
@@ -289,10 +292,11 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
private keybindings?: (action: IAction) => ResolvedKeybinding | undefined;
private clazz: string | undefined;
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
private menuAsChild?: boolean;
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) {
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) {
super(null, action);
this.menuActionsOrProvider = menuActionsOrProvider;
@@ -302,6 +306,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.keybindings = keybindings;
this.clazz = clazz;
this.anchorAlignmentProvider = anchorAlignmentProvider;
this.menuAsChild = menuAsChild;
}
render(container: HTMLElement): void {
@@ -322,7 +327,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
const options: IDropdownMenuOptions = {
contextMenuProvider: this.contextMenuProvider,
labelRenderer: labelRenderer
labelRenderer: labelRenderer,
menuAsChild: this.menuAsChild
};
// Render the DropdownMenu around a simple action to toggle it
@@ -35,6 +35,7 @@ export interface IFindInputOptions extends IFindInputStyles {
export interface IFindInputStyles extends IInputBoxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -51,6 +52,7 @@ export class FindInput extends Widget {
private fixFocusOnOptionClickEnabled = true;
private inputActiveOptionBorder?: Color;
private inputActiveOptionForeground?: Color;
private inputActiveOptionBackground?: Color;
private inputBackground?: Color;
private inputForeground?: Color;
@@ -101,6 +103,7 @@ export class FindInput extends Widget {
this.label = options.label || NLS_DEFAULT_LABEL;
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
this.inputBackground = options.inputBackground;
this.inputForeground = options.inputForeground;
@@ -155,6 +158,7 @@ export class FindInput extends Widget {
appendTitle: appendRegexLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.regex.onChange(viaKeyboard => {
@@ -172,6 +176,7 @@ export class FindInput extends Widget {
appendTitle: appendWholeWordsLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.wholeWords.onChange(viaKeyboard => {
@@ -186,6 +191,7 @@ export class FindInput extends Widget {
appendTitle: appendCaseSensitiveLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.caseSensitive.onChange(viaKeyboard => {
@@ -301,6 +307,7 @@ export class FindInput extends Widget {
public style(styles: IFindInputStyles): void {
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
this.inputBackground = styles.inputBackground;
this.inputForeground = styles.inputForeground;
@@ -323,6 +330,7 @@ export class FindInput extends Widget {
if (this.domNode) {
const checkBoxStyles: ICheckboxStyles = {
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
};
this.regex.style(checkBoxStyles);
@@ -12,6 +12,7 @@ export interface IFindInputCheckboxOpts {
readonly appendTitle: string;
readonly isChecked: boolean;
readonly inputActiveOptionBorder?: Color;
readonly inputActiveOptionForeground?: Color;
readonly inputActiveOptionBackground?: Color;
}
@@ -26,6 +27,7 @@ export class CaseSensitiveCheckbox extends Checkbox {
title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -38,6 +40,7 @@ export class WholeWordsCheckbox extends Checkbox {
title: NLS_WHOLE_WORD_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -50,6 +53,7 @@ export class RegexCheckbox extends Checkbox {
title: NLS_REGEX_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -33,6 +33,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles {
export interface IReplaceInputStyles extends IInputBoxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -47,6 +48,7 @@ export class PreserveCaseCheckbox extends Checkbox {
title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -63,6 +65,7 @@ export class ReplaceInput extends Widget {
private fixFocusOnOptionClickEnabled = true;
private inputActiveOptionBorder?: Color;
private inputActiveOptionForeground?: Color;
private inputActiveOptionBackground?: Color;
private inputBackground?: Color;
private inputForeground?: Color;
@@ -109,6 +112,7 @@ export class ReplaceInput extends Widget {
this.label = options.label || NLS_DEFAULT_LABEL;
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
this.inputBackground = options.inputBackground;
this.inputForeground = options.inputForeground;
@@ -160,6 +164,7 @@ export class ReplaceInput extends Widget {
appendTitle: '',
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
}));
this._register(this.preserveCase.onChange(viaKeyboard => {
@@ -271,6 +276,7 @@ export class ReplaceInput extends Widget {
public style(styles: IReplaceInputStyles): void {
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
this.inputBackground = styles.inputBackground;
this.inputForeground = styles.inputForeground;
@@ -293,6 +299,7 @@ export class ReplaceInput extends Widget {
if (this.domNode) {
const checkBoxStyles: ICheckboxStyles = {
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
};
this.preserveCase.style(checkBoxStyles);
+4 -1
View File
@@ -35,6 +35,7 @@
}
.monaco-hover p,
.monaco-hover .code,
.monaco-hover ul {
margin: 8px 0;
}
@@ -45,18 +46,20 @@
.monaco-hover hr {
margin-top: 4px;
margin-bottom: -6px;
margin-bottom: -4px;
margin-left: -10px;
margin-right: -10px;
height: 1px;
}
.monaco-hover p:first-child,
.monaco-hover .code:first-child,
.monaco-hover ul:first-child {
margin-top: 0;
}
.monaco-hover p:last-child,
.monaco-hover .code:last-child,
.monaco-hover ul:last-child {
margin-bottom: 0;
}
@@ -55,6 +55,10 @@
white-space: pre; /* enable to show labels that include multiple whitespaces */
}
.vs .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
opacity: .95;
}
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
font-style: italic;
+24 -15
View File
@@ -6,12 +6,13 @@
import 'vs/css!./list';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { range } from 'vs/base/common/arrays';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list';
import { List, IListStyles, IListOptions, IListAccessibilityProvider } from './listWidget';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent } from './list';
import { List, IListStyles, IListOptions, IListAccessibilityProvider, IListOptionsUpdate } from './listWidget';
import { IPagedModel } from 'vs/base/common/paging';
import { Event } from 'vs/base/common/event';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemable } from 'vs/base/common/styler';
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
@@ -119,7 +120,7 @@ function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: I
};
}
export class PagedList<T> implements IDisposable {
export class PagedList<T> implements IThemable, IDisposable {
private list: List<number>;
private _model!: IPagedModel<T>;
@@ -136,6 +137,10 @@ export class PagedList<T> implements IDisposable {
this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));
}
updateOptions(options: IListOptionsUpdate) {
this.list.updateOptions(options);
}
getHTMLElement(): HTMLElement {
return this.list.getHTMLElement();
}
@@ -164,22 +169,30 @@ export class PagedList<T> implements IDisposable {
return this.list.onDidDispose;
}
get onMouseClick(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onMouseDblClick(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onTap(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onPointer(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onDidChangeFocus(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onDidOpen(): Event<IListEvent<T>> {
return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onDidChangeSelection(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onPin(): Event<IListEvent<T>> {
return Event.map(this.list.onDidPin, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onContextMenu(): Event<IListContextMenuEvent<T>> {
return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
}
@@ -213,10 +226,6 @@ export class PagedList<T> implements IDisposable {
this.list.scrollLeft = scrollLeft;
}
open(indexes: number[], browserEvent?: UIEvent): void {
this.list.open(indexes, browserEvent);
}
setFocus(indexes: number[]): void {
this.list.setFocus(indexes);
}
+48 -12
View File
@@ -51,6 +51,7 @@ export interface IListViewAccessibilityProvider<T> {
export interface IListViewOptionsUpdate {
readonly additionalScrollHeight?: number;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
}
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
@@ -61,7 +62,6 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate {
readonly setRowHeight?: boolean;
readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean;
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
readonly transformOptimization?: boolean;
}
@@ -86,7 +86,14 @@ const DefaultOptions = {
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
readonly elements: T[];
context: TContext | undefined;
private _context: TContext | undefined;
public get context(): TContext | undefined {
return this._context;
}
public set context(value: TContext | undefined) {
this._context = value;
}
constructor(elements: T[]) {
this.elements = elements;
@@ -220,7 +227,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private setRowLineHeight: boolean;
private setRowHeight: boolean;
private supportDynamicHeights: boolean;
private horizontalScrolling: boolean;
private additionalScrollHeight: number;
private accessibilityProvider: ListViewAccessibilityProvider<T>;
private scrollWidth: number | undefined;
@@ -242,6 +248,35 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
get containerDomNode(): HTMLElement { return this.rowsContainer; }
private _horizontalScrolling: boolean = false;
private get horizontalScrolling(): boolean { return this._horizontalScrolling; }
private set horizontalScrolling(value: boolean) {
if (value === this._horizontalScrolling) {
return;
}
if (value && this.supportDynamicHeights) {
throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
}
this._horizontalScrolling = value;
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling);
if (this._horizontalScrolling) {
for (const item of this.items) {
this.measureItemWidth(item);
}
this.updateScrollWidth();
this.scrollableElement.setScrollDimensions({ width: DOM.getContentWidth(this.domNode) });
this.rowsContainer.style.width = `${Math.max(this.scrollWidth || 0, this.renderWidth)}px`;
} else {
this.scrollableElementWidthDelayer.cancel();
this.scrollableElement.setScrollDimensions({ width: this.renderWidth, scrollWidth: this.renderWidth });
this.rowsContainer.style.width = '';
}
}
constructor(
container: HTMLElement,
private virtualDelegate: IListVirtualDelegate<T>,
@@ -273,8 +308,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling);
this._horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling);
this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
@@ -293,7 +328,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => DOM.scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: true,
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
}, this.scrollable));
@@ -322,7 +357,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.layout();
}
updateOptions(options: IListViewOptions<T>) {
updateOptions(options: IListViewOptionsUpdate) {
if (options.additionalScrollHeight !== undefined) {
this.additionalScrollHeight = options.additionalScrollHeight;
}
@@ -330,6 +365,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (options.smoothScrolling !== undefined) {
this.scrollable.setSmoothScrollDuration(options.smoothScrolling ? 125 : 0);
}
if (options.horizontalScrolling !== undefined) {
this.horizontalScrolling = options.horizontalScrolling;
}
}
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
@@ -478,6 +517,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private eventuallyUpdateScrollWidth(): void {
if (!this.horizontalScrolling) {
this.scrollableElementWidthDelayer.cancel();
return;
}
@@ -489,10 +529,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return;
}
if (this.items.length === 0) {
this.scrollableElement.setScrollDimensions({ scrollWidth: 0 });
}
let scrollWidth = 0;
for (const item of this.items) {
@@ -502,7 +538,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
this.scrollWidth = scrollWidth;
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth + 10 });
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth === 0 ? 0 : (scrollWidth + 10) });
}
updateWidth(index: number): void {
+13 -63
View File
@@ -26,6 +26,7 @@ import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IThemable } from 'vs/base/common/styler';
interface ITraitChangeEvent {
indexes: number[];
@@ -231,7 +232,6 @@ function isInputElement(e: HTMLElement): boolean {
class KeyboardController<T> implements IDisposable {
private readonly disposables = new DisposableStore();
private openController: IOpenController;
constructor(
private list: List<T>,
@@ -240,8 +240,6 @@ class KeyboardController<T> implements IDisposable {
) {
const multipleSelectionSupport = options.multipleSelectionSupport !== false;
this.openController = options.openController || DefaultOpenController;
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -262,10 +260,6 @@ class KeyboardController<T> implements IDisposable {
e.preventDefault();
e.stopPropagation();
this.list.setSelection(this.list.getFocus(), e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(this.list.getFocus(), e.browserEvent);
}
}
private onUpArrow(e: StandardKeyboardEvent): void {
@@ -527,24 +521,16 @@ const DefaultMultipleSelectionController = {
isSelectionRangeChangeEvent
};
const DefaultOpenController: IOpenController = {
shouldOpen: (event: UIEvent) => {
if (event instanceof MouseEvent) {
return !isMouseRightClick(event);
}
return true;
}
};
export class MouseController<T> implements IDisposable {
private multipleSelectionSupport: boolean;
readonly multipleSelectionController: IMultipleSelectionController<T> | undefined;
private openController: IOpenController;
private mouseSupport: boolean;
private readonly disposables = new DisposableStore();
private _onPointer = new Emitter<IListMouseEvent<T>>();
readonly onPointer: Event<IListMouseEvent<T>> = this._onPointer.event;
constructor(protected list: List<T>) {
this.multipleSelectionSupport = !(list.options.multipleSelectionSupport === false);
@@ -552,7 +538,6 @@ export class MouseController<T> implements IDisposable {
this.multipleSelectionController = list.options.multipleSelectionController || DefaultMultipleSelectionController;
}
this.openController = list.options.openController || DefaultOpenController;
this.mouseSupport = typeof list.options.mouseSupport === 'undefined' || !!list.options.mouseSupport;
if (this.mouseSupport) {
@@ -563,9 +548,7 @@ export class MouseController<T> implements IDisposable {
this.disposables.add(Gesture.addTarget(list.getHTMLElement()));
}
list.onMouseClick(this.onPointer, this, this.disposables);
list.onMouseMiddleClick(this.onPointer, this, this.disposables);
list.onTap(this.onPointer, this, this.disposables);
Event.any(list.onMouseClick, list.onMouseMiddleClick, list.onTap)(this.onViewPointer, this, this.disposables);
}
protected isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
@@ -599,7 +582,7 @@ export class MouseController<T> implements IDisposable {
this.list.setFocus(focus, e.browserEvent);
}
protected onPointer(e: IListMouseEvent<T>): void {
protected onViewPointer(e: IListMouseEvent<T>): void {
if (!this.mouseSupport) {
return;
}
@@ -632,11 +615,9 @@ export class MouseController<T> implements IDisposable {
if (!isMouseRightClick(e.browserEvent)) {
this.list.setSelection([focus], e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open([focus], e.browserEvent);
}
}
this._onPointer.fire(e);
}
protected onDoubleClick(e: IListMouseEvent<T>): void {
@@ -650,7 +631,6 @@ export class MouseController<T> implements IDisposable {
const focus = this.list.getFocus();
this.list.setSelection(focus, e.browserEvent);
this.list.pin(focus);
}
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
@@ -694,10 +674,6 @@ export interface IMultipleSelectionController<T> {
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
}
export interface IOpenController {
shouldOpen(event: UIEvent): boolean;
}
export interface IStyleController {
style(styles: IListStyles): void;
}
@@ -841,7 +817,6 @@ export interface IListOptions<T> {
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly openController?: IOpenController;
readonly styleController?: (suffix: string) => IStyleController;
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
@@ -1112,7 +1087,7 @@ export interface IListOptionsUpdate extends IListViewOptionsUpdate {
readonly automaticKeyboardNavigation?: boolean;
}
export class List<T> implements ISpliceable<T>, IDisposable {
export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
private focus: Trait<T>;
private selection: Trait<T>;
@@ -1122,6 +1097,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private styleController: IStyleController;
private typeLabelController?: TypeLabelController<T>;
private accessibilityProvider?: IListAccessibilityProvider<T>;
private mouseController: MouseController<T>;
private _ariaLabel: string = '';
protected readonly disposables = new DisposableStore();
@@ -1134,17 +1110,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
}
private readonly _onDidOpen = new Emitter<IListEvent<T>>();
readonly onDidOpen: Event<IListEvent<T>> = this._onDidOpen.event;
private readonly _onDidPin = new Emitter<IListEvent<T>>();
readonly onDidPin: Event<IListEvent<T>> = this._onDidPin.event;
get domId(): string { return this.view.domId; }
get onDidScroll(): Event<ScrollEvent> { return this.view.onDidScroll; }
get onMouseClick(): Event<IListMouseEvent<T>> { return this.view.onMouseClick; }
get onMouseDblClick(): Event<IListMouseEvent<T>> { return this.view.onMouseDblClick; }
get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return this.view.onMouseMiddleClick; }
get onPointer(): Event<IListMouseEvent<T>> { return this.mouseController.onPointer; }
get onMouseUp(): Event<IListMouseEvent<T>> { return this.view.onMouseUp; }
get onMouseDown(): Event<IListMouseEvent<T>> { return this.view.onMouseDown; }
get onMouseOver(): Event<IListMouseEvent<T>> { return this.view.onMouseOver; }
@@ -1263,7 +1234,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.disposables.add(this.typeLabelController);
}
this.disposables.add(this.createMouseController(_options));
this.mouseController = this.createMouseController(_options);
this.disposables.add(this.mouseController);
this.onDidChangeFocus(this._onFocusChange, this, this.disposables);
this.onDidChangeSelection(this._onSelectionChange, this, this.disposables);
@@ -1625,26 +1597,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.domNode;
}
open(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
}
this._onDidOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
pin(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
}
this._onDidPin.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
style(styles: IListStyles): void {
this.styleController.style(styles);
}
@@ -1687,8 +1639,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this._onDidDispose.fire();
this.disposables.dispose();
this._onDidOpen.dispose();
this._onDidPin.dispose();
this._onDidDispose.dispose();
}
}
+10
View File
@@ -446,6 +446,16 @@ export class MenuBar extends Disposable {
return this.container.clientHeight;
}
toggleFocus(): void {
if (!this.isFocused && this.options.visibility !== 'hidden') {
this.mnemonicsInUse = true;
this.focusedMenu = { index: this.numMenusShown > 0 ? 0 : MenuBar.OVERFLOW_INDEX };
this.focusState = MenubarState.FOCUSED;
} else if (!this.isOpen) {
this.setUnfocusedState();
}
}
private updateOverflowAction(): void {
if (!this.menuCache || !this.menuCache.length) {
return;
@@ -343,7 +343,13 @@ export abstract class AbstractScrollableElement extends Widget {
const classifier = MouseWheelClassifier.INSTANCE;
if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) {
classifier.accept(Date.now(), e.deltaX, e.deltaY);
if (platform.isWindows) {
// On Windows, the incoming delta events are multiplied with the device pixel ratio,
// so to get a better classification, simply undo that.
classifier.accept(Date.now(), e.deltaX / window.devicePixelRatio, e.deltaY / window.devicePixelRatio);
} else {
classifier.accept(Date.now(), e.deltaX, e.deltaY);
}
}
// console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`);
@@ -30,7 +30,7 @@
.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown code {
line-height: 15px; /** For some reason, this is needed, otherwise <code> will take up 20px height */
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-family: var(--monaco-monospace-font);
}
@@ -475,6 +475,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
// Track initial selection the case user escape, blur
this._currentSelection = this.selected;
this._isVisible = true;
this.selectElement.setAttribute('aria-expanded', 'true');
}
private hideSelectDropDown(focusSelect: boolean) {
@@ -483,6 +484,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
this._isVisible = false;
this.selectElement.setAttribute('aria-expanded', 'false');
if (focusSelect) {
this.selectElement.focus();
@@ -29,6 +29,7 @@
display: flex;
cursor: pointer;
align-items: center;
box-sizing: border-box;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {
+2 -1
View File
@@ -69,7 +69,8 @@ export class ToolBar extends Disposable {
this.actionRunner,
this.options.getKeyBinding,
toolBarMoreIcon.classNames,
this.options.anchorAlignmentProvider
this.options.anchorAlignmentProvider,
true
);
this.toggleMenuActionViewItem.value.setActionContext(this.actionBar.context);
+13 -31
View File
@@ -6,7 +6,7 @@
import 'vs/css!./media/tree';
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -22,7 +22,6 @@ import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTr
import { localize } from 'vs/nls';
import { disposableTimeout } from 'vs/base/common/async';
import { isMacintosh } from 'vs/base/common/platform';
import { values } from 'vs/base/common/map';
import { clamp } from 'vs/base/common/numbers';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { SetMap } from 'vs/base/common/collections';
@@ -922,13 +921,6 @@ function isInputElement(e: HTMLElement): boolean {
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
}
function asTreeEvent<T>(event: IListEvent<ITreeNode<T, any>>): ITreeEvent<T> {
return {
elements: event.elements.map(node => node.element),
browserEvent: event.browserEvent
};
}
function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {
let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;
@@ -961,8 +953,8 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly automaticKeyboardNavigation?: boolean;
readonly simpleKeyboardNavigation?: boolean;
readonly filterOnType?: boolean;
readonly openOnSingleClick?: boolean;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
@@ -1042,7 +1034,7 @@ class Trait<T> {
const set = this.createNodeSet();
const visit = (node: ITreeNode<T, any>) => set.delete(node);
deletedNodes.forEach(node => dfs(node, visit));
this.set(values(set));
this.set([...set.values()]);
return;
}
@@ -1091,7 +1083,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
super(list);
}
protected onPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
protected onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
if (isInputElement(e.browserEvent.target as HTMLElement)) {
return;
}
@@ -1099,19 +1091,14 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
const node = e.element;
if (!node) {
return super.onPointer(e);
return super.onViewPointer(e);
}
if (this.isSelectionRangeChangeEvent(e) || this.isSelectionSingleChangeEvent(e)) {
return super.onPointer(e);
return super.onViewPointer(e);
}
const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie');
if (!this.tree.openOnSingleClick && e.browserEvent.detail !== 2 && !onTwistie) {
return super.onPointer(e);
}
let expandOnlyOnTwistieClick = false;
if (typeof this.tree.expandOnlyOnTwistieClick === 'function') {
@@ -1121,7 +1108,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
if (expandOnlyOnTwistieClick && !onTwistie) {
return super.onPointer(e);
return super.onViewPointer(e);
}
if (node.collapsible) {
@@ -1135,7 +1122,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
}
super.onPointer(e);
super.onViewPointer(e);
}
protected onDoubleClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
@@ -1238,12 +1225,12 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeFocus(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidOpen, asTreeEvent); }
get onDidPin(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidPin, asTreeEvent); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onPointer, asTreeMouseEvent); }
get onKeyDown(): Event<KeyboardEvent> { return this.view.onKeyDown; }
get onKeyUp(): Event<KeyboardEvent> { return this.view.onKeyUp; }
@@ -1261,7 +1248,6 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get filterOnType(): boolean { return !!this._options.filterOnType; }
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
private readonly _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
@@ -1328,7 +1314,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
set.add(node);
}
return values(set);
return [...set.values()];
}).event;
if (_options.keyboardSupport !== false) {
@@ -1362,7 +1348,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.view.updateOptions({
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation,
smoothScrolling: this._options.smoothScrolling
smoothScrolling: this._options.smoothScrolling,
horizontalScrolling: this._options.horizontalScrolling
});
if (this.typeFilterController) {
@@ -1601,11 +1588,6 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.focus.get();
}
open(elements: TRef[], browserEvent?: UIEvent): void {
const indexes = elements.map(e => this.model.getListIndex(e)).filter(i => i >= 0);
this.view.open(indexes, browserEvent);
}
reveal(location: TRef, relativeTop?: number): void {
this.model.expandTo(location);
+7 -9
View File
@@ -16,7 +16,6 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { removeClasses, addClasses } from 'vs/base/browser/dom';
import { values } from 'vs/base/common/map';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { IThemable } from 'vs/base/common/styler';
@@ -331,12 +330,13 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidChangeFocus(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidOpen, asTreeEvent); }
get onKeyDown(): Event<KeyboardEvent> { return this.tree.onKeyDown; }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onPointer, asTreeMouseEvent); }
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
@@ -345,7 +345,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
get filterOnType(): boolean { return this.tree.filterOnType; }
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
return this.tree.expandOnlyOnTwistieClick;
@@ -408,6 +407,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.updateOptions(options);
}
get options(): IAsyncDataTreeOptions<T, TFilterData> {
return this.tree.options as unknown as IAsyncDataTreeOptions<T, TFilterData>; // {{SQL CARBON EDIT}} strict-null-check
}
// Widget
getHTMLElement(): HTMLElement {
@@ -668,11 +671,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return nodes.map(n => n!.element as T);
}
open(elements: T[], browserEvent?: UIEvent): void {
const nodes = elements.map(e => this.getDataNode(e));
this.tree.open(nodes, browserEvent);
}
reveal(element: T, relativeTop?: number): void {
this.tree.reveal(this.getDataNode(element), relativeTop);
}
@@ -904,7 +902,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return childAsyncDataTreeNode;
});
for (const node of values(nodesToForget)) {
for (const node of nodesToForget.values()) {
dfs(node, node => this.nodes.delete(node.element as T));
}
+15 -6
View File
@@ -489,12 +489,21 @@ export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string,
export function insert<T>(array: T[], element: T): () => void {
array.push(element);
return () => {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
}
};
return () => remove(array, element);
}
/**
* Removes an element from an array if it can be found.
*/
export function remove<T>(array: T[], element: T): T | undefined {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
return element;
}
return undefined;
}
/**
+19 -2
View File
@@ -194,6 +194,8 @@ export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer>
export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
export interface VSBufferReadableBufferedStream extends streams.ReadableBufferedStream<VSBuffer> { }
export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
}
@@ -206,6 +208,21 @@ export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promis
return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
}
export async function bufferedStreamToBuffer(bufferedStream: streams.ReadableBufferedStream<VSBuffer>): Promise<VSBuffer> {
if (bufferedStream.ended) {
return VSBuffer.concat(bufferedStream.buffer);
}
return VSBuffer.concat([
// Include already read chunks...
...bufferedStream.buffer,
// ...and all additional chunks
await streamToBuffer(bufferedStream.stream)
]);
}
export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
}
@@ -214,6 +231,6 @@ export function streamToBufferReadableStream(stream: streams.ReadableStreamEvent
return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
}
export function newWriteableBufferStream(): streams.WriteableStream<VSBuffer> {
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks));
export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream<VSBuffer> {
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks), options);
}
+1
View File
@@ -495,6 +495,7 @@ export function markdownUnescapeCodicons(text: string): string {
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
export function renderCodicons(text: string): string {
return text.replace(renderCodiconsRegex, (_, escaped, codicon, name, animation) => {
// If the class for codicons is changed, it should also be updated in src\vs\base\browser\markdownRenderer.ts
return escaped
? `$(${codicon})`
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;
-19
View File
@@ -32,25 +32,6 @@ export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[
return result;
}
export function size<T>(from: IStringDictionary<T> | INumberDictionary<T>): number {
let count = 0;
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
count += 1;
}
}
return count;
}
export function first<T>(from: IStringDictionary<T> | INumberDictionary<T>): T | undefined {
for (const key in from) {
if (hasOwnProperty.call(from, key)) {
return (from as any)[key];
}
}
return undefined;
}
/**
* Iterates over each entry in the provided dictionary. The iterator allows
* to remove elements and will stop when the callback returns {{false}}.
+3 -3
View File
@@ -440,14 +440,14 @@ class LeakageMonitor {
this._warnCountdown = threshold * 0.5;
// find most frequent listener and print warning
let topStack: string;
let topStack: string | undefined;
let topCount: number = 0;
this._stacks.forEach((count, stack) => {
for (const [stack, count] of this._stacks) {
if (!topStack || topCount < count) {
topStack = stack;
topCount = count;
}
});
}
console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
console.warn(topStack!);
+1 -1
View File
@@ -285,7 +285,7 @@ export function isRootOrDriveLetter(path: string): boolean {
return pathNormalized === posix.sep;
}
export function indexOfPath(path: string, candidate: string, ignoreCase: boolean): number {
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
if (candidate.length > path.length) {
return -1;
}
+3 -1
View File
@@ -182,7 +182,9 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe
}
// the actual key
result.push(key);
if (key !== '') {
result.push(key);
}
return result.join(labels.separator);
}
+4 -4
View File
@@ -66,7 +66,7 @@ export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | un
d.dispose();
}
}
return arg;
return Array.isArray(arg) ? [] : arg;
} else if (arg) {
markTracked(arg);
arg.dispose();
@@ -219,11 +219,11 @@ export abstract class ReferenceCollection<T> {
private readonly references: Map<string, { readonly object: T; counter: number; }> = new Map();
acquire(key: string): IReference<T> {
acquire(key: string, ...args: any[]): IReference<T> {
let reference = this.references.get(key);
if (!reference) {
reference = { counter: 0, object: this.createReferencedObject(key) };
reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
this.references.set(key, reference);
}
@@ -240,7 +240,7 @@ export abstract class ReferenceCollection<T> {
return { object, dispose };
}
protected abstract createReferencedObject(key: string): T;
protected abstract createReferencedObject(key: string, ...args: any[]): T;
protected abstract destroyReferencedObject(key: string, object: T): void;
}
+29 -7
View File
@@ -481,14 +481,40 @@ export class TernarySearchTree<K, V> {
}
}
interface ResourceMapKeyFn {
(resource: URI): string;
}
export class ResourceMap<T> implements Map<URI, T> {
private static readonly defaultToKey = (resource: URI) => resource.toString();
readonly [Symbol.toStringTag] = 'ResourceMap';
protected readonly map: Map<string, T>;
private readonly map: Map<string, T>;
private readonly toKey: ResourceMapKeyFn;
constructor(other?: ResourceMap<T>) {
this.map = other ? new Map(other.map) : new Map();
/**
*
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(toKey?: ResourceMapKeyFn);
/**
*
* @param other Another resource which this maps is created from
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
constructor(mapOrKeyFn?: ResourceMap<T> | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
if (mapOrKeyFn instanceof ResourceMap) {
this.map = new Map(mapOrKeyFn.map);
this.toKey = toKey ?? ResourceMap.defaultToKey;
} else {
this.map = new Map();
this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey;
}
}
set(resource: URI, value: T): this {
@@ -546,10 +572,6 @@ export class ResourceMap<T> implements Map<URI, T> {
yield [URI.parse(item[0]), item[1]];
}
}
private toKey(resource: URI): string {
return resource.toString();
}
}
interface Item<K, V> {
+7
View File
@@ -56,11 +56,18 @@ export namespace Schemas {
export const vscodeCustomEditor = 'vscode-custom-editor';
export const vscodeNotebook = 'vscode-notebook';
export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel';
export const vscodeWebviewResource = 'vscode-webview-resource';
/**
* Scheme used for extension pages
*/
export const extension = 'extension';
}
class RemoteAuthoritiesImpl {
+1 -3
View File
@@ -5,7 +5,7 @@
export interface PerformanceEntry {
readonly name: string;
readonly timestamp: number;
readonly startTime: number;
}
export function mark(name: string): void;
@@ -15,8 +15,6 @@ export function mark(name: string): void;
*/
export function getEntries(): PerformanceEntry[];
export function getEntry(name: string): PerformanceEntry;
export function getDuration(from: string, to: string): number;
type ExportData = any[];
+1 -14
View File
@@ -28,24 +28,12 @@ function _factory(sharedObj) {
for (let i = 0; i < entries.length; i += _dataLen) {
result.push({
name: entries[i],
timestamp: entries[i + 1],
startTime: entries[i + 1],
});
}
return result;
}
function getEntry(name) {
const entries = sharedObj._performanceEntries;
for (let i = 0; i < entries.length; i += _dataLen) {
if (entries[i] === name) {
return {
name: entries[i],
timestamp: entries[i + 1],
};
}
}
}
function getDuration(from, to) {
const entries = sharedObj._performanceEntries;
let target = to;
@@ -73,7 +61,6 @@ function _factory(sharedObj) {
const exports = {
mark: mark,
getEntries: getEntries,
getEntry: getEntry,
getDuration: getDuration,
importEntries: importEntries,
exportEntries: exportEntries
+2 -2
View File
@@ -7,7 +7,7 @@ import { memoize } from 'vs/base/common/decorators';
import * as paths from 'vs/base/common/path';
import { relativePath, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { PathIterator, values } from 'vs/base/common/map';
import { PathIterator } from 'vs/base/common/map';
export interface IResourceNode<T, C = void> {
readonly uri: URI;
@@ -30,7 +30,7 @@ class Node<T, C> implements IResourceNode<T, C> {
}
get children(): Iterable<Node<T, C>> {
return [...values(this._children)];
return this._children.values();
}
@memoize
+54 -56
View File
@@ -6,9 +6,9 @@
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI, uriToFsPath } from 'vs/base/common/uri';
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -138,32 +138,10 @@ export class ExtUri implements IExtUri {
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
// scheme
let ret = strCompare(uri1.scheme, uri2.scheme);
if (ret === 0) {
// authority
ret = compareIgnoreCase(uri1.authority, uri2.authority);
if (ret === 0) {
// path
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
// query
if (ret === 0) {
ret = strCompare(uri1.query, uri2.query);
// fragment
if (ret === 0 && !ignoreFragment) {
ret = strCompare(uri1.fragment, uri2.fragment);
}
}
}
if (uri1 === uri2) {
return 0;
}
return ret;
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
}
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
@@ -173,11 +151,14 @@ export class ExtUri implements IExtUri {
if (!uri1 || !uri2) {
return false;
}
if (uri1.scheme !== uri2.scheme || !isEqualAuthority(uri1.authority, uri2.authority)) {
return false;
}
const p1 = uri1.path, p2 = uri2.path;
return (p1 === p2 || this._ignorePathCasing(uri1) && equalsIgnoreCase(p1, p2)) && uri1.query === uri2.query && (ignoreFragment || uri1.fragment === uri2.fragment);
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
@@ -332,6 +313,7 @@ export class ExtUri implements IExtUri {
}
}
/**
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
* uri#toString() usages. The following is true
@@ -342,36 +324,52 @@ export class ExtUri implements IExtUri {
export const extUri = new ExtUri(() => false);
/**
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
* BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
* understand what you are doing.
*
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
* knows when path casing matters and when not.
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri.scheme === Schemas.file ? !isLinux : true;
});
/**
* BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
* understand what you are doing.
*
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriIgnorePathCase = new ExtUri(_ => true);
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri && uri.scheme === Schemas.file ? !isLinux : true;
});
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
export const isEqual = extUri.isEqual.bind(extUri);
export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
export const basename = extUri.basename.bind(extUri);
export const extname = extUri.extname.bind(extUri);
export const dirname = extUri.dirname.bind(extUri);
export const joinPath = extUri.joinPath.bind(extUri);
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const normalizePath = extUri.normalizePath.bind(extUri);
export const relativePath = extUri.relativePath.bind(extUri);
export const resolvePath = extUri.resolvePath.bind(extUri);
export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
//#endregion
-6
View File
@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
enum Severity {
@@ -20,11 +19,6 @@ namespace Severity {
const _warn = 'warn';
const _info = 'info';
const _displayStrings: { [value: number]: string; } = Object.create(null);
_displayStrings[Severity.Error] = nls.localize('sev.error', "Error");
_displayStrings[Severity.Warning] = nls.localize('sev.warning', "Warning");
_displayStrings[Severity.Info] = nls.localize('sev.info', "Info");
/**
* Parses 'error', 'warning', 'warn', 'info' in call casings
* and falls back to ignore.
+135 -46
View File
@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
/**
* The payload that flows in readable stream events.
*/
@@ -49,6 +51,11 @@ export interface ReadableStream<T> extends ReadableStreamEvents<T> {
* Destroys the stream and stops emitting any event.
*/
destroy(): void;
/**
* Allows to remove a listener that was previously added.
*/
removeListener(event: string, callback: Function): void;
}
/**
@@ -74,8 +81,14 @@ export interface WriteableStream<T> extends ReadableStream<T> {
* Writing data to the stream will trigger the on('data')
* event listener if the stream is flowing and buffer the
* data otherwise until the stream is flowing.
*
* If a `highWaterMark` is configured and writing to the
* stream reaches this mark, a promise will be returned
* that should be awaited on before writing more data.
* Otherwise there is a risk of buffering a large number
* of data chunks without consumer.
*/
write(data: T): void;
write(data: T): void | Promise<void>;
/**
* Signals an error to the consumer of the stream via the
@@ -95,12 +108,43 @@ export interface WriteableStream<T> extends ReadableStream<T> {
end(result?: T | Error): void;
}
/**
* A stream that has a buffer already read. Returns the original stream
* that was read as well as the chunks that got read.
*
* The `ended` flag indicates if the stream has been fully consumed.
*/
export interface ReadableBufferedStream<T> {
/**
* The original stream that is being read.
*/
stream: ReadableStream<T>;
/**
* An array of chunks already read from this stream.
*/
buffer: T[];
/**
* Signals if the stream has ended or not. If not, consumers
* should continue to read from the stream until consumed.
*/
ended: boolean;
}
export function isReadableStream<T>(obj: unknown): obj is ReadableStream<T> {
const candidate = obj as ReadableStream<T>;
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
}
export function isReadableBufferedStream<T>(obj: unknown): obj is ReadableBufferedStream<T> {
const candidate = obj as ReadableBufferedStream<T>;
return candidate && isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean';
}
export interface IReducer<T> {
(data: T[]): T;
}
@@ -118,8 +162,18 @@ export interface ITransformer<Original, Transformed> {
error?: IErrorTransformer;
}
export function newWriteableStream<T>(reducer: IReducer<T>): WriteableStream<T> {
return new WriteableStreamImpl<T>(reducer);
export function newWriteableStream<T>(reducer: IReducer<T>, options?: WriteableStreamOptions): WriteableStream<T> {
return new WriteableStreamImpl<T>(reducer, options);
}
export interface WriteableStreamOptions {
/**
* The number of objects to buffer before WriteableStream#write()
* signals back that the buffer is full. Can be used to reduce
* the memory pressure when the stream is not flowing.
*/
highWaterMark?: number;
}
class WriteableStreamImpl<T> implements WriteableStream<T> {
@@ -141,7 +195,9 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
end: [] as { (): void }[]
};
constructor(private reducer: IReducer<T>) { }
private readonly pendingWritePromises: Function[] = [];
constructor(private reducer: IReducer<T>, private options?: WriteableStreamOptions) { }
pause(): void {
if (this.state.destroyed) {
@@ -166,7 +222,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
}
}
write(data: T): void {
write(data: T): void | Promise<void> {
if (this.state.destroyed) {
return;
}
@@ -179,6 +235,11 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
// not yet flowing: buffer data until flowing
else {
this.buffer.data.push(data);
// highWaterMark: if configured, signal back when buffer reached limits
if (typeof this.options?.highWaterMark === 'number' && this.buffer.data.length > this.options.highWaterMark) {
return new Promise(resolve => this.pendingWritePromises.push(resolve));
}
}
}
@@ -267,6 +328,35 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
}
}
removeListener(event: string, callback: Function): void {
if (this.state.destroyed) {
return;
}
let listeners: unknown[] | undefined = undefined;
switch (event) {
case 'data':
listeners = this.listeners.data;
break;
case 'end':
listeners = this.listeners.end;
break;
case 'error':
listeners = this.listeners.error;
break;
}
if (listeners) {
const index = listeners.indexOf(callback);
if (index >= 0) {
listeners.splice(index, 1);
}
}
}
private flowData(): void {
if (this.buffer.data.length > 0) {
const fullDataBuffer = this.reducer(this.buffer.data);
@@ -274,6 +364,11 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
this.listeners.data.forEach(listener => listener(fullDataBuffer));
this.buffer.data.length = 0;
// When the buffer is empty, resolve all pending writers
const pendingWritePromises = [...this.pendingWritePromises];
this.pendingWritePromises.length = 0;
pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise());
}
}
@@ -308,6 +403,8 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
this.listeners.data.length = 0;
this.listeners.error.length = 0;
this.listeners.end.length = 0;
this.pendingWritePromises.length = 0;
}
}
}
@@ -331,7 +428,7 @@ export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>):
* reached, will return a readable instead to ensure all data can still
* be read.
*/
export function consumeReadableWithLimit<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
export function peekReadable<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
const chunks: T[] = [];
let chunk: T | null | undefined = undefined;
@@ -388,58 +485,50 @@ export function consumeStream<T>(stream: ReadableStream<T>, reducer: IReducer<T>
}
/**
* Helper to read a T stream up to a maximum of chunks. If the limit is
* reached, will return a stream instead to ensure all data can still
* be read.
* Helper to peek up to `maxChunks` into a stream. The return type signals if
* the stream has ended or not. If not, caller needs to add a `data` listener
* to continue reading.
*/
export function consumeStreamWithLimit<T>(stream: ReadableStream<T>, reducer: IReducer<T>, maxChunks: number): Promise<T | ReadableStream<T>> {
export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Promise<ReadableBufferedStream<T>> {
return new Promise((resolve, reject) => {
const chunks: T[] = [];
const streamListeners = new DisposableStore();
let wrapperStream: WriteableStream<T> | undefined = undefined;
// Data Listener
const buffer: T[] = [];
const dataListener = (chunk: T) => {
stream.on('data', data => {
// Add to buffer
buffer.push(chunk);
// If we reach maxChunks, we start to return a stream
// and make sure that any data we have already read
// is in it as well
if (!wrapperStream && chunks.length === maxChunks) {
wrapperStream = newWriteableStream(reducer);
while (chunks.length) {
wrapperStream.write(chunks.shift()!);
}
// We reached maxChunks and thus need to return
if (buffer.length > maxChunks) {
wrapperStream.write(data);
// Dispose any listeners and ensure to pause the
// stream so that it can be consumed again by caller
streamListeners.dispose();
stream.pause();
return resolve(wrapperStream);
return resolve({ stream, buffer, ended: false });
}
};
if (wrapperStream) {
wrapperStream.write(data);
} else {
chunks.push(data);
}
});
streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener)));
stream.on('data', dataListener);
stream.on('error', error => {
if (wrapperStream) {
wrapperStream.error(error);
} else {
return reject(error);
}
});
// Error Listener
const errorListener = (error: Error) => {
return reject(error);
};
stream.on('end', () => {
if (wrapperStream) {
while (chunks.length) {
wrapperStream.write(chunks.shift()!);
}
streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener)));
stream.on('error', errorListener);
wrapperStream.end();
} else {
return resolve(reducer(chunks));
}
});
const endListener = () => {
return resolve({ stream, buffer, ended: true });
};
streamListeners.add(toDisposable(() => stream.removeListener('end', endListener)));
stream.on('end', endListener);
});
}
+2 -2
View File
@@ -728,9 +728,9 @@ export function isBasicASCII(str: string): boolean {
return IS_BASIC_ASCII.test(str);
}
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029]/; // LINE SEPARATOR (LS) or PARAGRAPH SEPARATOR (PS)
/**
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
* Returns true if `str` contains unusual line terminators, like LS or PS
*/
export function containsUnusualLineTerminators(str: string): boolean {
return UNUSUAL_LINE_TERMINATORS.test(str);
+7
View File
@@ -64,6 +64,13 @@ export function isUndefined(obj: any): obj is undefined {
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is defined.
*/
export function isDefined<T>(arg: T | null | undefined): arg is T {
return !isUndefinedOrNull(arg);
}
/**
* @returns whether the provided parameter is undefined or null.
*/
+1 -1
View File
@@ -413,7 +413,7 @@ interface UriState extends UriComponents {
const _pathSepMarker = isWindows ? 1 : undefined;
// eslint-disable-next-line @typescript-eslint/class-name-casing
// eslint-disable-next-line @typescript-eslint/naming-convention
class _URI extends URI {
_formatted: string | null = null;
+130 -99
View File
@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as iconv from 'iconv-lite';
import { Readable, Writable } from 'stream';
import { VSBuffer } from 'vs/base/common/buffer';
import { DecoderStream } from 'iconv-lite';
import { Readable, ReadableStream, newWriteableStream } from 'vs/base/common/stream';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';
@@ -31,125 +31,156 @@ export interface IDecodeStreamOptions {
guessEncoding: boolean;
minBytesRequiredForDetection?: number;
overwriteEncoding(detectedEncoding: string | null): string;
overwriteEncoding(detectedEncoding: string | null): Promise<string>;
}
export interface IDecodeStreamResult {
stream: NodeJS.ReadableStream;
stream: ReadableStream<string>;
detected: IDetectedEncodingResult;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
if (!options.minBytesRequiredForDetection) {
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
}
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
return new Promise<IDecodeStreamResult>((resolve, reject) => {
const writer = new class extends Writable {
private decodeStream: NodeJS.ReadWriteStream | undefined;
private decodeStreamPromise: Promise<void> | undefined;
const target = newWriteableStream<string>(strings => strings.join(''));
private bufferedChunks: Buffer[] = [];
private bytesBuffered = 0;
const bufferedChunks: VSBuffer[] = [];
let bytesBuffered = 0;
_write(chunk: Buffer, encoding: string, callback: (error: Error | null | undefined) => void): void {
if (!Buffer.isBuffer(chunk)) {
return callback(new Error('toDecodeStream(): data must be a buffer'));
}
let decoder: DecoderStream | undefined = undefined;
// if the decode stream is ready, we just write directly
if (this.decodeStream) {
this.decodeStream.write(chunk, callback);
return;
}
// otherwise we need to buffer the data until the stream is ready
this.bufferedChunks.push(chunk);
this.bytesBuffered += chunk.byteLength;
// waiting for the decoder to be ready
if (this.decodeStreamPromise) {
this.decodeStreamPromise.then(() => callback(null), error => callback(error));
}
// buffered enough data for encoding detection, create stream and forward data
else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) {
this._startDecodeStream(callback);
}
// only buffering until enough data for encoding detection is there
else {
callback(null);
}
}
_startDecodeStream(callback: (error: Error | null | undefined) => void): void {
const createDecoder = async () => {
try {
// detect encoding from buffer
this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this.bufferedChunks),
bytesRead: this.bytesBuffered
}, options.guessEncoding)).then(detected => {
const detected = await detectEncodingFromBuffer({
buffer: VSBuffer.concat(bufferedChunks),
bytesRead: bytesBuffered
}, options.guessEncoding);
// ensure to respect overwrite of encoding
detected.encoding = options.overwriteEncoding(detected.encoding);
// ensure to respect overwrite of encoding
detected.encoding = await options.overwriteEncoding(detected.encoding);
// decode and write buffer
this.decodeStream = decodeStream(detected.encoding);
this.decodeStream.write(Buffer.concat(this.bufferedChunks), callback);
this.bufferedChunks.length = 0;
// decode and write buffered content
const iconv = await import('iconv-lite');
decoder = iconv.getDecoder(toNodeEncoding(detected.encoding));
const decoded = decoder.write(Buffer.from(VSBuffer.concat(bufferedChunks).buffer));
target.write(decoded);
// signal to the outside our detected encoding
// and final decoder stream
resolve({ detected, stream: this.decodeStream });
}, error => {
this.emit('error', error);
bufferedChunks.length = 0;
bytesBuffered = 0;
callback(error);
// signal to the outside our detected encoding and final decoder stream
resolve({
stream: target,
detected
});
}
_final(callback: () => void) {
// normal finish
if (this.decodeStream) {
this.decodeStream.end(callback);
}
// we were still waiting for data to do the encoding
// detection. thus, wrap up starting the stream even
// without all the data to get things going
else {
this._startDecodeStream(() => {
if (this.decodeStream) {
this.decodeStream.end(callback);
}
});
}
} catch (error) {
reject(error);
}
};
// errors
readable.on('error', reject);
// Stream error: forward to target
source.on('error', error => target.error(error));
// pipe through
readable.pipe(writer);
// Stream data
source.on('data', async chunk => {
// if the decoder is ready, we just write directly
if (decoder) {
target.write(decoder.write(Buffer.from(chunk.buffer)));
}
// otherwise we need to buffer the data until the stream is ready
else {
bufferedChunks.push(chunk);
bytesBuffered += chunk.byteLength;
// buffered enough data for encoding detection, create stream
if (bytesBuffered >= minBytesRequiredForDetection) {
// pause stream here until the decoder is ready
source.pause();
await createDecoder();
// resume stream now that decoder is ready but
// outside of this stack to reduce recursion
setTimeout(() => source.resume());
}
}
});
// Stream end
source.on('end', async () => {
// we were still waiting for data to do the encoding
// detection. thus, wrap up starting the stream even
// without all the data to get things going
if (!decoder) {
await createDecoder();
}
// end the target with the remainders of the decoder
target.end(decoder?.end());
});
});
}
export function encodingExists(encoding: string): boolean {
export async function toEncodeReadable(readable: Readable<string>, encoding: string, options?: { addBOM?: boolean }): Promise<VSBufferReadable> {
const iconv = await import('iconv-lite');
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
let bytesRead = 0;
let done = false;
return {
read() {
if (done) {
return null;
}
const chunk = readable.read();
if (typeof chunk !== 'string') {
done = true;
// If we are instructed to add a BOM but we detect that no
// bytes have been read, we must ensure to return the BOM
// ourselves so that we comply with the contract.
if (bytesRead === 0 && options?.addBOM) {
switch (encoding) {
case UTF8:
case UTF8_with_bom:
return VSBuffer.wrap(Uint8Array.from(UTF8_BOM));
case UTF16be:
return VSBuffer.wrap(Uint8Array.from(UTF16be_BOM));
case UTF16le:
return VSBuffer.wrap(Uint8Array.from(UTF16le_BOM));
}
}
const leftovers = encoder.end();
if (leftovers && leftovers.length > 0) {
return VSBuffer.wrap(leftovers);
}
return null;
}
bytesRead += chunk.length;
return VSBuffer.wrap(encoder.write(chunk));
}
};
}
export async function encodingExists(encoding: string): Promise<boolean> {
const iconv = await import('iconv-lite');
return iconv.encodingExists(toNodeEncoding(encoding));
}
function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
return iconv.decodeStream(toNodeEncoding(encoding));
}
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream {
return iconv.encodeStream(toNodeEncoding(encoding), options);
}
export function toNodeEncoding(enc: string | null): string {
if (enc === UTF8_with_bom || enc === null) {
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
@@ -158,7 +189,7 @@ export function toNodeEncoding(enc: string | null): string {
return enc;
}
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
export function detectEncodingByBOMFromBuffer(buffer: VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
if (!buffer || bytesRead < UTF16be_BOM.length) {
return null;
}
@@ -200,10 +231,10 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32'];
/**
* Guesses the encoding from buffer.
*/
async function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
async function guessEncodingByBuffer(buffer: VSBuffer): Promise<string | null> {
const jschardet = await import('jschardet');
const guessed = jschardet.detect(buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES)); // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
const guessed = jschardet.detect(Buffer.from(buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES).buffer)); // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
if (!guessed || !guessed.encoding) {
return null;
}
@@ -271,7 +302,7 @@ export interface IDetectedEncodingResult {
}
export interface IReadResult {
buffer: Buffer | null;
buffer: VSBuffer | null;
bytesRead: number;
}
@@ -298,7 +329,7 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, aut
// that is using 4 bytes to encode a character).
for (let i = 0; i < bytesRead && i < ZERO_BYTE_DETECTION_BUFFER_MAX_LEN; i++) {
const isEndian = (i % 2 === 1); // assume 2-byte sequences typical for UTF-16
const isZeroByte = (buffer.readInt8(i) === 0);
const isZeroByte = (buffer.readUInt8(i) === 0);
if (isZeroByte) {
containsZeroByte = true;
-112
View File
@@ -1,112 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer';
import { Readable } from 'stream';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding';
export function streamToNodeReadable(stream: VSBufferReadableStream): Readable {
return new class extends Readable {
private listening = false;
_read(size?: number): void {
if (!this.listening) {
this.listening = true;
// Data
stream.on('data', data => {
try {
if (!this.push(data.buffer)) {
stream.pause(); // pause the stream if we should not push anymore
}
} catch (error) {
this.emit(error);
}
});
// End
stream.on('end', () => {
try {
this.push(null); // signal EOS
} catch (error) {
this.emit(error);
}
});
// Error
stream.on('error', error => this.emit('error', error));
}
// ensure the stream is flowing
stream.resume();
}
_destroy(error: Error | null, callback: (error: Error | null) => void): void {
stream.destroy();
callback(null);
}
};
}
export function nodeReadableToString(stream: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
let result = '';
stream.on('data', chunk => result += chunk);
stream.on('error', reject);
stream.on('end', () => resolve(result));
});
}
export function nodeStreamToVSBufferReadable(stream: NodeJS.ReadWriteStream, addBOM?: { encoding: UTF_ENCODING }): VSBufferReadable {
let bytesRead = 0;
let done = false;
return {
read(): VSBuffer | null {
if (done) {
return null;
}
const res = stream.read();
if (isUndefinedOrNull(res)) {
done = true;
// If we are instructed to add a BOM but we detect that no
// bytes have been read, we must ensure to return the BOM
// ourselves so that we comply with the contract.
if (bytesRead === 0 && addBOM) {
switch (addBOM.encoding) {
case UTF8:
case UTF8_with_bom:
return VSBuffer.wrap(Buffer.from(UTF8_BOM));
case UTF16be:
return VSBuffer.wrap(Buffer.from(UTF16be_BOM));
case UTF16le:
return VSBuffer.wrap(Buffer.from(UTF16le_BOM));
}
}
return null;
}
// Handle String
if (typeof res === 'string') {
bytesRead += res.length;
return VSBuffer.fromString(res);
}
// Handle Buffer
else {
bytesRead += res.byteLength;
return VSBuffer.wrap(res);
}
}
};
}
@@ -719,13 +719,13 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
break;
case KeyCode.Home:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
this.ui.list.focus(QuickInputListFocus.First);
dom.EventHelper.stop(event, true);
}
break;
case KeyCode.End:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
this.ui.list.focus(QuickInputListFocus.Last);
dom.EventHelper.stop(event, true);
}
@@ -283,7 +283,6 @@ export class QuickInputList {
const accessibilityProvider = new QuickInputAccessibilityProvider();
this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], {
identityProvider: { getId: element => element.saneLabel },
openController: { shouldOpen: () => false }, // Workaround #58124
setRowLineHeight: false,
multipleSelectionSupport: false,
horizontalScrolling: false,
+9
View File
@@ -342,5 +342,14 @@ suite('Arrays', () => {
arrays.coalesceInPlace(sparse);
assert.equal(sparse.length, 5);
});
test('insert, remove', function () {
const array: string[] = [];
const remove = arrays.insert(array, 'foo');
assert.equal(array[0], 'foo');
remove();
assert.equal(array.length, 0);
});
});
+15
View File
@@ -48,6 +48,21 @@ suite('Lifecycle', () => {
assert(disposable.isDisposed);
assert(disposable2.isDisposed);
});
test('Action bar has broken accessibility #100273', function () {
let array = [{ dispose() { } }, { dispose() { } }];
let array2 = dispose(array);
assert.equal(array.length, 2);
assert.equal(array2.length, 0);
assert.ok(array !== array2);
let set = new Set<IDisposable>([{ dispose() { } }, { dispose() { } }]);
let setValues = set.values();
let setValues2 = dispose(setValues);
assert.ok(setValues === setValues2);
});
});
suite('Reference Collection', () => {
+20 -19
View File
@@ -6,6 +6,7 @@
import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator } from 'vs/base/common/map';
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { extUriIgnorePathCase } from 'vs/base/common/resources';
suite('Map', () => {
@@ -811,32 +812,32 @@ suite('Map', () => {
assert.equal(map.get(uncFile), 'true');
});
// test('ResourceMap - files (ignorecase)', function () {
// const map = new ResourceMap<any>(true);
test('ResourceMap - files (ignorecase)', function () {
const map = new ResourceMap<any>(uri => extUriIgnorePathCase.getComparisonKey(uri));
// const fileA = URI.parse('file://some/filea');
// const fileB = URI.parse('some://some/other/fileb');
// const fileAUpper = URI.parse('file://SOME/FILEA');
const fileA = URI.parse('file://some/filea');
const fileB = URI.parse('some://some/other/fileb');
const fileAUpper = URI.parse('file://SOME/FILEA');
// map.set(fileA, 'true');
// assert.equal(map.get(fileA), 'true');
map.set(fileA, 'true');
assert.equal(map.get(fileA), 'true');
// assert.equal(map.get(fileAUpper), 'true');
assert.equal(map.get(fileAUpper), 'true');
// assert.ok(!map.get(fileB));
assert.ok(!map.get(fileB));
// map.set(fileAUpper, 'false');
// assert.equal(map.get(fileAUpper), 'false');
map.set(fileAUpper, 'false');
assert.equal(map.get(fileAUpper), 'false');
// assert.equal(map.get(fileA), 'false');
assert.equal(map.get(fileA), 'false');
// const windowsFile = URI.file('c:\\test with %25\\c#code');
// const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json');
const windowsFile = URI.file('c:\\test with %25\\c#code');
const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json');
// map.set(windowsFile, 'true');
// map.set(uncFile, 'true');
map.set(windowsFile, 'true');
map.set(uncFile, 'true');
// assert.equal(map.get(windowsFile), 'true');
// assert.equal(map.get(uncFile), 'true');
// });
assert.equal(map.get(windowsFile), 'true');
assert.equal(map.get(uncFile), 'true');
});
});
+176 -17
View File
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, consumeReadableWithLimit, consumeStream, ReadableStream, toStream, toReadable, transform, consumeStreamWithLimit } from 'vs/base/common/stream';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream } from 'vs/base/common/stream';
import { timeout } from 'vs/base/common/async';
suite('Stream', () => {
@@ -13,7 +14,16 @@ suite('Stream', () => {
assert.ok(isReadableStream(newWriteableStream(d => d)));
});
test('WriteableStream', () => {
test('isReadableBufferedStream', async () => {
assert.ok(!isReadableBufferedStream(Object.create(null)));
const stream = newWriteableStream(d => d);
stream.end();
const bufferedStream = await peekStream(stream, 1);
assert.ok(isReadableBufferedStream(bufferedStream));
});
test('WriteableStream - basics', () => {
const stream = newWriteableStream<string>(strings => strings.join());
let error = false;
@@ -66,17 +76,92 @@ suite('Stream', () => {
assert.equal(chunks.length, 4);
});
test('WriteableStream - removeListener', () => {
const stream = newWriteableStream<string>(strings => strings.join());
let error = false;
const errorListener = (e: Error) => {
error = true;
};
stream.on('error', errorListener);
let data = false;
const dataListener = () => {
data = true;
};
stream.on('data', dataListener);
stream.write('Hello');
assert.equal(data, true);
data = false;
stream.removeListener('data', dataListener);
stream.write('World');
assert.equal(data, false);
stream.error(new Error());
assert.equal(error, true);
error = false;
stream.removeListener('error', errorListener);
stream.error(new Error());
assert.equal(error, false);
});
test('WriteableStream - highWaterMark', async () => {
const stream = newWriteableStream<string>(strings => strings.join(), { highWaterMark: 3 });
let res = stream.write('1');
assert.ok(!res);
res = stream.write('2');
assert.ok(!res);
res = stream.write('3');
assert.ok(!res);
let promise1 = stream.write('4');
assert.ok(promise1 instanceof Promise);
let promise2 = stream.write('5');
assert.ok(promise2 instanceof Promise);
let drained1 = false;
(async () => {
await promise1;
drained1 = true;
})();
let drained2 = false;
(async () => {
await promise2;
drained2 = true;
})();
let data: string | undefined = undefined;
stream.on('data', chunk => {
data = chunk;
});
assert.ok(data);
await timeout(0);
assert.equal(drained1, true);
assert.equal(drained2, true);
});
test('consumeReadable', () => {
const readable = arrayToReadable(['1', '2', '3', '4', '5']);
const consumed = consumeReadable(readable, strings => strings.join());
assert.equal(consumed, '1,2,3,4,5');
});
test('consumeReadableWithLimit', () => {
test('peekReadable', () => {
for (let i = 0; i < 5; i++) {
const readable = arrayToReadable(['1', '2', '3', '4', '5']);
const consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), i);
const consumedOrReadable = peekReadable(readable, strings => strings.join(), i);
if (typeof consumedOrReadable === 'string') {
assert.fail('Unexpected result');
} else {
@@ -86,14 +171,75 @@ suite('Stream', () => {
}
let readable = arrayToReadable(['1', '2', '3', '4', '5']);
let consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 5);
let consumedOrReadable = peekReadable(readable, strings => strings.join(), 5);
assert.equal(consumedOrReadable, '1,2,3,4,5');
readable = arrayToReadable(['1', '2', '3', '4', '5']);
consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 6);
consumedOrReadable = peekReadable(readable, strings => strings.join(), 6);
assert.equal(consumedOrReadable, '1,2,3,4,5');
});
test('peekReadable - error handling', async () => {
// 0 Chunks
let stream = newWriteableStream(data => data);
let error: Error | undefined = undefined;
let promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.error(new Error());
await promise;
assert.ok(error);
// 1 Chunk
stream = newWriteableStream(data => data);
error = undefined;
promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.write('foo');
stream.error(new Error());
await promise;
assert.ok(error);
// 2 Chunks
stream = newWriteableStream(data => data);
error = undefined;
promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.write('foo');
stream.write('bar');
stream.error(new Error());
await promise;
assert.ok(!error);
stream.on('error', err => error = err);
stream.on('data', chunk => { });
assert.ok(error);
});
function arrayToReadable<T>(array: T[]): Readable<T> {
return {
read: () => array.shift() || null
@@ -122,26 +268,39 @@ suite('Stream', () => {
assert.equal(consumed, '1,2,3,4,5');
});
test('consumeStreamWithLimit', async () => {
test('peekStream', async () => {
for (let i = 0; i < 5; i++) {
const readable = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
const consumedOrStream = await consumeStreamWithLimit(readable, strings => strings.join(), i);
if (typeof consumedOrStream === 'string') {
assert.fail('Unexpected result');
const result = await peekStream(stream, i);
assert.equal(stream, result.stream);
if (result.ended) {
assert.fail('Unexpected result, stream should not have ended yet');
} else {
const consumed = await consumeStream(consumedOrStream, strings => strings.join());
assert.equal(consumed, '1,2,3,4,5');
assert.equal(result.buffer.length, i + 1, `maxChunks: ${i}`);
const additionalResult: string[] = [];
await consumeStream(stream, strings => {
additionalResult.push(...strings);
return strings.join();
});
assert.equal([...result.buffer, ...additionalResult].join(), '1,2,3,4,5');
}
}
let stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
let consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 5);
assert.equal(consumedOrStream, '1,2,3,4,5');
let result = await peekStream(stream, 5);
assert.equal(stream, result.stream);
assert.equal(result.buffer.join(), '1,2,3,4,5');
assert.equal(result.ended, true);
stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 6);
assert.equal(consumedOrStream, '1,2,3,4,5');
result = await peekStream(stream, 6);
assert.equal(stream, result.stream);
assert.equal(result.buffer.join(), '1,2,3,4,5');
assert.equal(result.ended, true);
});
test('toStream', async () => {
+9 -1
View File
@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream, bufferedStreamToBuffer } from 'vs/base/common/buffer';
import { timeout } from 'vs/base/common/async';
import { peekStream } from 'vs/base/common/stream';
suite('Buffer', () => {
@@ -29,6 +30,13 @@ suite('Buffer', () => {
assert.equal((await streamToBuffer(stream)).toString(), content);
});
test('bufferedStreamToBuffer', async () => {
const content = 'Hello World';
const stream = await peekStream(bufferToStream(VSBuffer.fromString(content)), 1);
assert.equal((await bufferedStreamToBuffer(stream)).toString(), content);
});
test('bufferWriteableStream - basics (no error)', async () => {
const stream = newWriteableBufferStream();
+123 -53
View File
@@ -7,9 +7,10 @@ import * as assert from 'assert';
import * as fs from 'fs';
import * as encoding from 'vs/base/node/encoding';
import * as terminalEncoding from 'vs/base/node/terminalEncoding';
import { Readable } from 'stream';
import * as streams from 'vs/base/common/stream';
import * as iconv from 'iconv-lite';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer';
export async function detectEncodingByBOM(file: string): Promise<typeof encoding.UTF16be | typeof encoding.UTF16le | typeof encoding.UTF8_with_bom | null> {
try {
@@ -22,7 +23,7 @@ export async function detectEncodingByBOM(file: string): Promise<typeof encoding
}
interface ReadResult {
buffer: Buffer | null;
buffer: VSBuffer | null;
bytesRead: number;
}
@@ -43,7 +44,7 @@ function readExactlyByFile(file: string, totalBytes: number): Promise<ReadResult
return reject(err); // we want to bubble this error up (file is actually a folder)
}
return resolve({ buffer: resultBuffer, bytesRead });
return resolve({ buffer: resultBuffer ? VSBuffer.wrap(resultBuffer) : null, bytesRead });
});
}
@@ -128,7 +129,7 @@ suite('Encoding', () => {
process.env['VSCODE_CLI_ENCODING'] = 'utf16le';
const enc = await terminalEncoding.resolveTerminalEncoding();
assert.ok(encoding.encodingExists(enc));
assert.ok(await encoding.encodingExists(enc));
assert.equal(enc, 'utf16le');
});
@@ -231,32 +232,33 @@ suite('Encoding', () => {
});
}
async function readAllAsString(stream: NodeJS.ReadableStream) {
return new Promise<string>((resolve, reject) => {
let all = '';
stream.on('data', chunk => {
all += chunk;
assert.equal(typeof chunk, 'string');
function newTestReadableStream(buffers: Buffer[]): VSBufferReadableStream {
const stream = newWriteableBufferStream();
buffers
.map(VSBuffer.wrap)
.forEach(buffer => {
setTimeout(() => {
stream.write(buffer);
});
});
stream.on('end', () => {
resolve(all);
});
stream.on('error', reject);
setTimeout(() => {
stream.end();
});
return stream;
}
async function readAllAsString(stream: streams.ReadableStream<string>) {
return streams.consumeStream(stream, strings => strings.join(''));
}
test('toDecodeStream - some stream', async function () {
const source = newTestReadableStream([
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
]);
let source = new Readable({
read(size) {
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(null);
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -266,17 +268,13 @@ suite('Encoding', () => {
});
test('toDecodeStream - some stream, expect too much data', async function () {
const source = newTestReadableStream([
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
]);
let source = new Readable({
read(size) {
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(null);
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -286,14 +284,10 @@ suite('Encoding', () => {
});
test('toDecodeStream - some stream, no data', async function () {
const source = newWriteableBufferStream();
source.end();
let source = new Readable({
read(size) {
this.push(null); // empty
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -304,29 +298,105 @@ suite('Encoding', () => {
test('toDecodeStream - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = streamToBufferReadableStream(fs.createReadStream(path));
let path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.equal(detected.encoding, 'utf16be');
assert.equal(detected.seemsBinary, false);
let expected = await readAndDecodeFromDisk(path, detected.encoding);
let actual = await readAllAsString(stream);
const expected = await readAndDecodeFromDisk(path, detected.encoding);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toDecodeStream - empty file', async function () {
const path = getPathFromAmdModule(require, './fixtures/empty.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path));
const { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
let path = getPathFromAmdModule(require, './fixtures/empty.txt');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
let expected = await readAndDecodeFromDisk(path, detected.encoding);
let actual = await readAllAsString(stream);
const expected = await readAndDecodeFromDisk(path, detected.encoding);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toDecodeStream - decodes buffer entirely', async function () {
const emojis = Buffer.from('🖥️💻💾');
const incompleteEmojis = emojis.slice(0, emojis.length - 1);
const buffers: Buffer[] = [];
for (let i = 0; i < incompleteEmojis.length; i++) {
buffers.push(incompleteEmojis.slice(i, i + 1));
}
const source = newTestReadableStream(buffers);
const { stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
const expected = incompleteEmojis.toString(encoding.UTF8);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toEncodeReadable - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = await readAndDecodeFromDisk(path, encoding.UTF16be);
const expected = VSBuffer.wrap(
iconv.encode(source, encoding.toNodeEncoding(encoding.UTF16be))
).toString();
const actual = streams.consumeReadable(
await encoding.toEncodeReadable(streams.toReadable(source), encoding.UTF16be),
VSBuffer.concat
).toString();
assert.equal(actual, expected);
});
test('toEncodeReadable - empty readable to utf8', async function () {
const source: streams.Readable<string> = {
read() {
return null;
}
};
const actual = streams.consumeReadable(
await encoding.toEncodeReadable(source, encoding.UTF8),
VSBuffer.concat
).toString();
assert.equal(actual, '');
});
[{
utfEncoding: encoding.UTF8,
relatedBom: encoding.UTF8_BOM
}, {
utfEncoding: encoding.UTF8_with_bom,
relatedBom: encoding.UTF8_BOM
}, {
utfEncoding: encoding.UTF16be,
relatedBom: encoding.UTF16be_BOM,
}, {
utfEncoding: encoding.UTF16le,
relatedBom: encoding.UTF16le_BOM
}].forEach(({ utfEncoding, relatedBom }) => {
test(`toEncodeReadable - empty readable to ${utfEncoding} with BOM`, async function () {
const source: streams.Readable<string> = {
read() {
return null;
}
};
const encodedReadable = encoding.toEncodeReadable(source, utfEncoding, { addBOM: true });
const expected = VSBuffer.wrap(Buffer.from(relatedBom)).toString();
const actual = streams.consumeReadable(await encodedReadable, VSBuffer.concat).toString();
assert.equal(actual, expected);
});
});
});
+5 -11
View File
@@ -164,8 +164,7 @@ suite('Paths (Node Implementation)', () => {
os = 'posix';
}
const message =
`path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
`path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected && actualAlt !== expected) {
failures.push(`\n${message}`);
}
@@ -319,8 +318,7 @@ suite('Paths (Node Implementation)', () => {
os = 'posix';
}
const actual = extname(input);
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}
@@ -328,8 +326,7 @@ suite('Paths (Node Implementation)', () => {
{
const input = `C:${test[0].replace(slashRE, '\\')}`;
const actual = path.win32.extname(input);
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}
@@ -416,8 +413,7 @@ suite('Paths (Node Implementation)', () => {
const expected = test[1];
const message =
`path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
`path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected && actualAlt !== expected) {
failures.push(`\n${message}`);
}
@@ -585,9 +581,7 @@ suite('Paths (Node Implementation)', () => {
const actual = relative(test[0], test[1]);
const expected = test[2];
const os = relative === path.win32.relative ? 'win32' : 'posix';
const message = `path.${os}.relative(${
test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.${os}.relative(${test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}
+1 -1
View File
@@ -35,7 +35,7 @@
display: flex;
flex-direction: column;
color: white;
font-family: "Segoe UI", "Helvetica Neue", "Helvetica", Arial, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif;
background-color: #373277;
}
@@ -8,7 +8,7 @@ import { ElectronService, IElectronService } from 'vs/platform/electron/electron
import { ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import * as os from 'os';
import * as browser from 'vs/base/browser/browser';
import { $, windowOpenNoOpener } from 'vs/base/browser/dom';
import { $, windowOpenNoOpener, addClass } from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
@@ -55,6 +55,9 @@ export interface IssueReporterConfiguration extends INativeWindowConfiguration {
}
export function startup(configuration: IssueReporterConfiguration) {
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
addClass(document.body, platformClass); // used by our fonts
document.body.innerHTML = BaseHtml();
const issueReporter = new IssueReporter(configuration);
issueReporter.render();
@@ -95,26 +95,30 @@ textarea, input, select {
}
html {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
color: #CCCCCC;
height: 100%;
}
html:lang(zh-Hans) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
}
/* Font Families (with CJK support) */
html:lang(zh-Hant) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
}
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
html:lang(ja) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
}
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
html:lang(ko) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
}
/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
body {
margin: 0;
@@ -4,25 +4,29 @@
*--------------------------------------------------------------------------------------------*/
html {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-size: 13px;
}
html:lang(zh-Hans) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
}
/* Font Families (with CJK support) */
html:lang(zh-Hant) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
}
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
html:lang(ja) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
}
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
html:lang(ko) {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
}
/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
body {
margin: 0;
@@ -16,7 +16,7 @@ import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
import { addDisposableListener } from 'vs/base/browser/dom';
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
@@ -389,6 +389,9 @@ function createCloseListener(): void {
}
export function startup(data: ProcessExplorerData): void {
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
addClass(document.body, platformClass); // used by our fonts
applyStyles(data.styles);
applyZoom(data.zoomLevel);
createCloseListener();
+2 -2
View File
@@ -18,7 +18,7 @@
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif;
font-size: 10pt;
background-color: #F3F3F3;
}
@@ -58,7 +58,7 @@
}
input {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif !important;
}
</style>
</head>
@@ -49,10 +49,10 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
@@ -63,9 +63,8 @@ import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/electron-browser/authenticationIpc';
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
@@ -95,7 +94,7 @@ class MainProcessService implements IMainProcessService {
private mainRouter: StaticRouter
) { }
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.mainRouter);
@@ -199,14 +198,14 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService));
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService));
services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService));
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
registerConfiguration();
@@ -234,9 +233,9 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
const authTokenService = accessor.get(IAuthenticationTokenService);
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
const authTokenService = accessor.get(IUserDataSyncAccountService);
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
server.registerChannel('userDataSyncAccount', authTokenChannel);
const userDataSyncService = accessor.get(IUserDataSyncService);
const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService, logService);
@@ -6,7 +6,29 @@
//@ts-check
'use strict';
const perf = require('../../../base/common/performance');
const perf = (function () {
let sharedObj;
if (typeof global === 'object') {
// nodejs
sharedObj = global;
} else if (typeof self === 'object') {
// browser
sharedObj = self;
} else {
sharedObj = {};
}
// @ts-ignore
sharedObj._performanceEntries = sharedObj._performanceEntries || [];
return {
/**
* @param {string} name
*/
mark(name) {
sharedObj._performanceEntries.push(name, Date.now());
}
};
})();
perf.mark('renderer/started');
const bootstrapWindow = require('../../../../bootstrap-window');
@@ -150,7 +172,7 @@ function showPartsSplash(configuration) {
* @returns {Promise<void>}
*/
function getLazyEnv() {
// @ts-ignore
const ipc = require('electron').ipcRenderer;
return new Promise(function (resolve) {
+11 -36
View File
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
@@ -26,7 +26,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IURLService } from 'vs/platform/url/common/url';
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
import { ITelemetryService, machineIdKey, trueMachineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
@@ -261,13 +261,6 @@ export class CodeApplication extends Disposable {
}
});
ipc.on('vscode:exit', (event: Event, code: number) => {
this.logService.trace('IPC#vscode:exit', code);
this.dispose();
this.lifecycleMainService.kill(code);
});
ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
const webContents = event.sender;
@@ -294,13 +287,6 @@ export class CodeApplication extends Disposable {
(async () => {
await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
// After waking up from sleep (after window opened)
powerMonitor.on('resume', () => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
// Keyboard layout changes (after window opened)
const nativeKeymap = await import('native-keymap');
nativeKeymap.onDidChangeKeyboardLayout(() => {
@@ -365,8 +351,8 @@ export class CodeApplication extends Disposable {
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const { machineId, trueMachineId } = await this.resolveMachineId();
this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);
const machineId = await this.resolveMachineId();
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process after the first window has opened and 3s have passed
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
@@ -387,7 +373,7 @@ export class CodeApplication extends Disposable {
});
// Services
const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessReady);
const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
// Create driver
if (this.environmentService.driverHandle) {
@@ -412,32 +398,21 @@ export class CodeApplication extends Disposable {
}
}
private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: string }> {
private async resolveMachineId(): Promise<string> {
// We cache the machineId for faster lookups on startup
// and resolve it only once initially if not cached
// and resolve it only once initially if not cached or we need to replace the macOS iBridge device
let machineId = this.stateService.getItem<string>(machineIdKey);
if (!machineId) {
if (!machineId || (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead')) {
machineId = await getMachineId();
this.stateService.setItem(machineIdKey, machineId);
}
// Check if machineId is hashed iBridge Device
let trueMachineId: string | undefined;
if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') {
trueMachineId = this.stateService.getItem<string>(trueMachineIdKey);
if (!trueMachineId) {
trueMachineId = await getMachineId();
this.stateService.setItem(trueMachineIdKey, trueMachineId);
}
}
return { machineId, trueMachineId };
return machineId;
}
private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
const services = new ServiceCollection();
switch (process.platform) {
@@ -489,7 +464,7 @@ export class CodeApplication extends Disposable {
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId, sendErrorTelemetry: true };
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
+3 -2
View File
@@ -23,7 +23,7 @@ type Credentials = {
export class ProxyAuthHandler extends Disposable {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private retryCount = 0;
@@ -60,7 +60,8 @@ export class ProxyAuthHandler extends Disposable {
webPreferences: {
nodeIntegration: true,
webviewTag: true,
enableWebSQL: false
enableWebSQL: false,
nativeWindowOpen: true
}
};
+5
View File
@@ -47,6 +47,9 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
import { IProductService } from 'vs/platform/product/common/productService';
class ExpectedError extends Error {
readonly isExpected = true;
@@ -162,6 +165,8 @@ class CodeMain {
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
services.set(ISignService, new SyncDescriptor(SignService));
services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService));
services.set(IProductService, { _serviceBrand: undefined, ...product });
services.set(ITunnelService, new SyncDescriptor(TunnelService));
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
}
@@ -47,6 +47,7 @@ export class SharedProcess implements ISharedProcess {
nodeIntegration: true,
webgl: false,
enableWebSQL: false,
nativeWindowOpen: true,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});
+29 -12
View File
@@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects';
import * as nls from 'vs/nls';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event } from 'electron';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
@@ -91,15 +91,18 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
private readonly _onLoad = this._register(new Emitter<void>());
readonly onLoad = this._onLoad.event;
private readonly _onReady = this._register(new Emitter<void>());
readonly onReady = this._onReady.event;
private readonly _onClose = this._register(new Emitter<void>());
readonly onClose = this._onClose.event;
private readonly _onDestroy = this._register(new Emitter<void>());
readonly onDestroy = this._onDestroy.event;
private readonly _onLoad = this._register(new Emitter<void>());
readonly onLoad = this._onLoad.event;
private hiddenTitleBarStyle: boolean | undefined;
private showTimeoutHandle: NodeJS.Timeout | undefined;
private _lastFocusTime: number;
@@ -167,7 +170,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
nodeIntegration: true,
nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
webviewTag: true,
enableWebSQL: false
enableWebSQL: false,
nativeWindowOpen: true
}
};
@@ -346,6 +350,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
while (this.whenReadyCallbacks.length) {
this.whenReadyCallbacks.pop()!(this);
}
// Events
this._onReady.fire();
}
ready(): Promise<ICodeWindow> {
@@ -431,24 +438,34 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._lastFocusTime = Date.now();
});
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
// we need to detect when display metrics change or displays are added/removed and toggle the
// fullscreen manually.
if (isMacintosh) {
const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
const displayChangedScheduler = this._register(new RunOnceScheduler(() => {
if (!this._win) {
return; // disposed
}
// Notify renderers about displays changed
this.sendWhenReady('vscode:displayChanged');
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
// we need to detect when display metrics change or displays are added/removed and toggle the
// fullscreen manually.
if (!this.useNativeFullScreen() && this.isFullScreen) {
this.setFullScreen(false);
this.setFullScreen(true);
}
this.sendWhenReady('vscode:displayChanged');
}, 100));
const displayChangedListener = () => simpleFullScreenScheduler.schedule();
const displayChangedListener = (event: Event, display: Display, changedMetrics?: string[]) => {
if (Array.isArray(changedMetrics) && changedMetrics.length === 1 && changedMetrics[0] === 'workArea') {
// Electron will emit 'display-metrics-changed' events even when actually
// going fullscreen, because the dock hides. However, we do not want to
// react on this event as there is no change in display bounds.
return;
}
displayChangedScheduler.schedule();
};
screen.on('display-metrics-changed', displayChangedListener);
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));
+8 -8
View File
@@ -86,7 +86,7 @@ export class Main {
} else if (argv['list-extensions']) {
await this.listExtensions(!!argv['show-versions'], argv['category']);
} else if (argv['install-extension']) {
await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['donot-sync']);
await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['do-not-sync']);
} else if (argv['uninstall-extension']) {
await this.uninstallExtension(argv['uninstall-extension']);
} else if (argv['locate-extension']) {
@@ -126,7 +126,7 @@ export class Main {
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
}
private async installExtensions(extensions: string[], force: boolean, donotSync: boolean): Promise<void> {
private async installExtensions(extensions: string[], force: boolean, doNotSync: boolean): Promise<void> {
const failed: string[] = [];
const installedExtensionsManifests: IExtensionManifest[] = [];
if (extensions.length) {
@@ -135,7 +135,7 @@ export class Main {
for (const extension of extensions) {
try {
const manifest = await this.installExtension(extension, force, donotSync);
const manifest = await this.installExtension(extension, force, doNotSync);
if (manifest) {
installedExtensionsManifests.push(manifest);
}
@@ -150,7 +150,7 @@ export class Main {
return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve();
}
private async installExtension(extension: string, force: boolean, donotSync: boolean): Promise<IExtensionManifest | null> {
private async installExtension(extension: string, force: boolean, doNotSync: boolean): Promise<IExtensionManifest | null> {
if (/\.vsix$/i.test(extension)) {
extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension);
@@ -158,7 +158,7 @@ export class Main {
const valid = await this.validate(manifest, force);
if (valid) {
return this.extensionManagementService.install(URI.file(extension), donotSync).then(id => {
return this.extensionManagementService.install(URI.file(extension), doNotSync).then(id => {
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(extension)));
return manifest;
}, error => {
@@ -205,7 +205,7 @@ export class Main {
}
console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, extension.version));
}
await this.installFromGallery(id, extension, donotSync);
await this.installFromGallery(id, extension, doNotSync);
return manifest;
}));
}
@@ -227,11 +227,11 @@ export class Main {
return true;
}
private async installFromGallery(id: string, extension: IGalleryExtension, donotSync: boolean): Promise<void> {
private async installFromGallery(id: string, extension: IGalleryExtension, doNotSync: boolean): Promise<void> {
console.log(localize('installing', "Installing extension '{0}' v{1}...", id, extension.version));
try {
await this.extensionManagementService.installFromGallery(extension, donotSync);
await this.extensionManagementService.installFromGallery(extension, doNotSync);
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, extension.version));
} catch (error) {
if (isPromiseCanceledError(error)) {
@@ -958,7 +958,7 @@ export namespace CoreNavigationCommands {
viewModel.setCursorStates(
args.source,
CursorChangeReason.Explicit,
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode, args.sticky || false)
);
viewModel.revealPrimaryCursor(args.source, true);
}
@@ -969,10 +969,27 @@ export namespace CoreNavigationCommands {
id: 'cursorEnd',
precondition: undefined,
kbOpts: {
args: { sticky: false },
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.End,
mac: { primary: KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow] }
},
description: {
description: `Go to End`,
args: [{
name: 'args',
schema: {
type: 'object',
properties: {
'sticky': {
description: nls.localize('stickydesc', "Stick to the end even when going to longer lines"),
type: 'boolean',
default: false
}
}
}
}]
}
}));
@@ -981,10 +998,27 @@ export namespace CoreNavigationCommands {
id: 'cursorEndSelect',
precondition: undefined,
kbOpts: {
args: { sticky: false },
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.End,
mac: { primary: KeyMod.Shift | KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow] }
},
description: {
description: `Select to End`,
args: [{
name: 'args',
schema: {
type: 'object',
properties: {
'sticky': {
description: nls.localize('stickydesc', "Stick to the end even when going to longer lines"),
type: 'boolean',
default: false
}
}
}
}]
}
}));
@@ -380,7 +380,7 @@ export class TextAreaInput extends Disposable {
}
private _installSelectionChangeListener(): IDisposable {
// See https://github.com/Microsoft/vscode/issues/27216
// See https://github.com/Microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256
// When using a Braille display, it is possible for users to reposition the
// system caret. This is reflected in Chrome as a `selectionchange` event.
//
@@ -406,8 +406,8 @@ export class TextAreaInput extends Disposable {
if (this._isDoingComposition) {
return;
}
if (!browser.isChrome || !platform.isWindows) {
// Support only for Chrome on Windows until testing happens on other browsers + OS configurations
if (!browser.isChrome) {
// Support only for Chrome until testing happens on other browsers
return;
}
@@ -17,7 +17,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
const IEditorCancellationTokens = createDecorator<IEditorCancellationTokens>('IEditorCancelService');
interface IEditorCancellationTokens {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void;
cancel(editor: ICodeEditor): void;
}
@@ -26,7 +26,7 @@ const ctxCancellableOperation = new RawContextKey('cancellableOperation', false)
registerSingleton(IEditorCancellationTokens, class implements IEditorCancellationTokens {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _tokens = new WeakMap<ICodeEditor, { key: IContextKey<boolean>, tokens: LinkedList<CancellationTokenSource> }>();
@@ -43,6 +43,10 @@ export interface IDiffEditorContributionDescription {
export interface ICommandKeybindingsOptions extends IKeybindings {
kbExpr?: ContextKeyExpression | null;
weight: number;
/**
* the default keybinding arguments
*/
args?: any;
}
export interface ICommandMenuOptions {
menuId: MenuId;
@@ -96,6 +100,7 @@ export abstract class Command {
id: this.id,
handler: (accessor, args) => this.runCommand(accessor, args),
weight: this._kbOpts.weight,
args: this._kbOpts.args,
when: kbWhen,
primary: this._kbOpts.primary,
secondary: this._kbOpts.secondary,
@@ -10,10 +10,11 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
export abstract class AbstractCodeEditorService extends Disposable implements ICodeEditorService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _onCodeEditorAdd: Emitter<ICodeEditor> = this._register(new Emitter<ICodeEditor>());
public readonly onCodeEditorAdd: Event<ICodeEditor> = this._onCodeEditorAdd.event;
@@ -94,6 +95,29 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions;
private readonly _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {};
private readonly _modelProperties = new Map<string, Map<string, any>>();
public setModelProperty(resource: URI, key: string, value: any): void {
const key1 = resource.toString();
let dest: Map<string, any>;
if (this._modelProperties.has(key1)) {
dest = this._modelProperties.get(key1)!;
} else {
dest = new Map<string, any>();
this._modelProperties.set(key1, dest);
}
dest.set(key, value);
}
public getModelProperty(resource: URI, key: string): any {
const key1 = resource.toString();
if (this._modelProperties.has(key1)) {
const innerMap = this._modelProperties.get(key1)!;
return innerMap.get(key);
}
return undefined;
}
public setTransientModelProperty(model: ITextModel, key: string, value: any): void {
const uri = model.uri.toString();
@@ -26,7 +26,7 @@ export interface IBulkEditResult {
export type IBulkEditPreviewHandler = (edit: WorkspaceEdit, options?: IBulkEditOptions) => Promise<WorkspaceEdit>;
export interface IBulkEditService {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
hasPreviewHandler(): boolean;
@@ -9,11 +9,12 @@ import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
export interface ICodeEditorService {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
readonly onCodeEditorAdd: Event<ICodeEditor>;
readonly onCodeEditorRemove: Event<ICodeEditor>;
@@ -41,6 +42,9 @@ export interface ICodeEditorService {
removeDecorationType(key: string): void;
resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions;
setModelProperty(resource: URI, key: string, value: any): void;
getModelProperty(resource: URI, key: string): any;
setTransientModelProperty(model: ITextModel, key: string, value: any): void;
getTransientModelProperty(model: ITextModel, key: string): any;
getTransientModelProperties(model: ITextModel): [string, any][] | undefined;
@@ -85,7 +85,7 @@ class EditorOpener implements IOpener {
export class OpenerService implements IOpenerService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _openers = new LinkedList<IOpener>();
private readonly _validators = new LinkedList<IValidator>();
+1 -1
View File
@@ -752,7 +752,7 @@ export class DiffReview extends Disposable {
switch (type) {
case DiffEntryType.Equal:
if (originalLine === modifiedLine) {
ariaLabel = nls.localize('unchangedLine', "{0} unchanged line {1}", lineContent, originalLine);
ariaLabel = nls.localize({ key: 'unchangedLine', comment: ['The placholders are contents of the line and should not be translated.'] }, "{0} unchanged line {1}", lineContent, originalLine);
} else {
ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine);
}
+17 -8
View File
@@ -94,10 +94,10 @@ export interface IEditorOptions {
*/
renderFinalNewline?: boolean;
/**
* Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
* Defaults to true.
* Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS).
* Defaults to 'prompt'.
*/
removeUnusualLineTerminators?: boolean;
unusualLineTerminators?: 'off' | 'prompt' | 'auto';
/**
* Should the corresponding line be selected when clicking on the line number?
* Defaults to true.
@@ -3560,7 +3560,6 @@ export const enum EditorOption {
quickSuggestions,
quickSuggestionsDelay,
readOnly,
removeUnusualLineTerminators,
renameOnType,
renderControlCharacters,
renderIndentGuides,
@@ -3590,6 +3589,7 @@ export const enum EditorOption {
suggestOnTriggerCharacters,
suggestSelection,
tabCompletion,
unusualLineTerminators,
useTabStops,
wordSeparators,
wordWrap,
@@ -3979,10 +3979,6 @@ export const EditorOptions = {
readOnly: register(new EditorBooleanOption(
EditorOption.readOnly, 'readOnly', false,
)),
removeUnusualLineTerminators: register(new EditorBooleanOption(
EditorOption.removeUnusualLineTerminators, 'removeUnusualLineTerminators', true,
{ description: nls.localize('removeUnusualLineTerminators', "Remove unusual line terminators that might cause problems.") }
)),
renameOnType: register(new EditorBooleanOption(
EditorOption.renameOnType, 'renameOnType', false,
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }
@@ -4152,6 +4148,19 @@ export const EditorOptions = {
description: nls.localize('tabCompletion', "Enables tab completions.")
}
)),
unusualLineTerminators: register(new EditorStringEnumOption(
EditorOption.unusualLineTerminators, 'unusualLineTerminators',
'prompt' as 'off' | 'prompt' | 'auto',
['off', 'prompt', 'auto'] as const,
{
enumDescriptions: [
nls.localize('unusualLineTerminators.off', "Unusual line terminators are ignored."),
nls.localize('unusualLineTerminators.prompt', "Unusual line terminators prompt to be removed."),
nls.localize('unusualLineTerminators.auto', "Unusual line terminators are automatically removed."),
],
description: nls.localize('unusualLineTerminators', "Remove unusual line terminators that might cause problems.")
}
)),
useTabStops: register(new EditorBooleanOption(
EditorOption.useTabStops, 'useTabStops', true,
{ description: nls.localize('useTabStops', "Inserting and deleting whitespace follows tab stops.") }
@@ -80,17 +80,17 @@ export class CursorMoveCommands {
);
}
public static moveToEndOfLine(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean): PartialCursorState[] {
public static moveToEndOfLine(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, sticky: boolean): PartialCursorState[] {
let result: PartialCursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = this._moveToLineEnd(viewModel, cursor, inSelectionMode);
result[i] = this._moveToLineEnd(viewModel, cursor, inSelectionMode, sticky);
}
return result;
}
private static _moveToLineEnd(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
private static _moveToLineEnd(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
const viewStatePosition = cursor.viewState.position;
const viewModelMaxColumn = viewModel.getLineMaxColumn(viewStatePosition.lineNumber);
const isEndOfViewLine = viewStatePosition.column === viewModelMaxColumn;
@@ -100,21 +100,21 @@ export class CursorMoveCommands {
const isEndLineOfWrappedLine = viewModelMaxColumn - viewStatePosition.column === modelMaxColumn - modelStatePosition.column;
if (isEndOfViewLine || isEndLineOfWrappedLine) {
return this._moveToLineEndByModel(viewModel, cursor, inSelectionMode);
return this._moveToLineEndByModel(viewModel, cursor, inSelectionMode, sticky);
} else {
return this._moveToLineEndByView(viewModel, cursor, inSelectionMode);
return this._moveToLineEndByView(viewModel, cursor, inSelectionMode, sticky);
}
}
private static _moveToLineEndByView(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
private static _moveToLineEndByView(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
return CursorState.fromViewState(
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode)
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, sticky)
);
}
private static _moveToLineEndByModel(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
private static _moveToLineEndByModel(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
return CursorState.fromModelState(
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel.model, cursor.modelState, inSelectionMode)
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel.model, cursor.modelState, inSelectionMode, sticky)
);
}
@@ -404,13 +404,14 @@ export class CursorMoveCommands {
}
private static _moveLeft(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): PartialCursorState[] {
const hasMultipleCursors = (cursors.length > 1);
let result: PartialCursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
let newViewState = MoveOperations.moveLeft(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
// moved over to the previous view line
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
@@ -436,12 +437,14 @@ export class CursorMoveCommands {
}
private static _moveRight(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): PartialCursorState[] {
const hasMultipleCursors = (cursors.length > 1);
let result: PartialCursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
let newViewState = MoveOperations.moveRight(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
// moved over to the next view line
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
@@ -222,10 +222,10 @@ export class MoveOperations {
return cursor.move(inSelectionMode, lineNumber, column, 0);
}
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, sticky: boolean): SingleCursorState {
let lineNumber = cursor.position.lineNumber;
let maxColumn = model.getLineMaxColumn(lineNumber);
return cursor.move(inSelectionMode, lineNumber, maxColumn, Constants.MAX_SAFE_SMALL_INTEGER - maxColumn);
return cursor.move(inSelectionMode, lineNumber, maxColumn, sticky ? Constants.MAX_SAFE_SMALL_INTEGER - maxColumn : 0);
}
public static moveToBeginningOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
@@ -36,6 +36,8 @@ export namespace EditorContextKeys {
export const canUndo = new RawContextKey<boolean>('canUndo', false);
export const canRedo = new RawContextKey<boolean>('canRedo', false);
export const hoverVisible = new RawContextKey<boolean>('editorHoverVisible', false);
/**
* A context key that is set when an editor is part of a larger editor, like notebooks or
* (future) a diff editor
+1 -1
View File
@@ -552,7 +552,7 @@ export interface ITextModel {
mightContainRTL(): boolean;
/**
* If true, the text model might contain LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
* If true, the text model might contain LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS).
* If false, the text model definitely does not contain these.
* @internal
*/

Some files were not shown because too many files have changed in this diff Show More