Archived
Merge branch 'ads-main-vscode-2020-08-22T06-06-45' into main
This commit is contained in:
Vendored
+8
@@ -23,6 +23,7 @@
|
||||
}(this, function () {
|
||||
const preloadGlobals = globals();
|
||||
const sandbox = preloadGlobals.context.sandbox;
|
||||
const webFrame = preloadGlobals.webFrame;
|
||||
const safeProcess = sandbox ? preloadGlobals.process : process;
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,7 @@
|
||||
/**
|
||||
* // configuration: INativeWindowConfiguration
|
||||
* @type {{
|
||||
* zoomLevel?: number,
|
||||
* extensionDevelopmentPath?: string[],
|
||||
* extensionTestsPath?: string,
|
||||
* userEnv?: { [key: string]: string | undefined },
|
||||
@@ -43,6 +45,12 @@
|
||||
* }} */
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
// Apply zoom level early to avoid glitches
|
||||
const zoomLevel = configuration.zoomLevel;
|
||||
if (typeof zoomLevel === 'number' && zoomLevel !== 0) {
|
||||
webFrame.setZoomLevel(zoomLevel);
|
||||
}
|
||||
|
||||
// Error handler
|
||||
safeProcess.on('uncaughtException', function (error) {
|
||||
onUnexpectedError(error, enableDeveloperTools);
|
||||
|
||||
+45
-64
@@ -18,7 +18,11 @@ const bootstrap = require('./bootstrap');
|
||||
const paths = require('./paths');
|
||||
/** @type {any} */
|
||||
const product = require('../product.json');
|
||||
const { app, protocol } = require('electron');
|
||||
const { app, protocol, crashReporter } = require('electron');
|
||||
|
||||
// Disable render process reuse, we still have
|
||||
// non-context aware native modules in the renderer.
|
||||
app.allowRendererProcessReuse = false;
|
||||
|
||||
// Enable portable support
|
||||
const portable = bootstrap.configurePortable(product);
|
||||
@@ -38,13 +42,13 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}}
|
||||
const userDataPath = getUserDataPath(args);
|
||||
app.setPath('userData', userDataPath);
|
||||
|
||||
// Set temp directory based on crash-reporter-directory CLI argument
|
||||
// The crash reporter will store crashes in temp folder so we need
|
||||
// to change that location accordingly.
|
||||
// Configure static command line arguments
|
||||
const argvConfig = configureCommandlineSwitchesSync(args);
|
||||
|
||||
// If a crash-reporter-directory is specified we setup the crash reporter
|
||||
// right from the beginning as early as possible to monitor all processes.
|
||||
// If a crash-reporter-directory is specified we store the crash reports
|
||||
// in the specified directory and don't upload them to the crash server.
|
||||
let crashReporterDirectory = args['crash-reporter-directory'];
|
||||
let submitURL = '';
|
||||
if (crashReporterDirectory) {
|
||||
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
||||
|
||||
@@ -62,23 +66,41 @@ if (crashReporterDirectory) {
|
||||
}
|
||||
}
|
||||
|
||||
// Crashes are stored in the temp directory by default, so we
|
||||
// Crashes are stored in the crashDumps directory by default, so we
|
||||
// need to change that directory to the provided one
|
||||
console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`);
|
||||
app.setPath('temp', 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: companyName,
|
||||
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
|
||||
submitURL: '',
|
||||
uploadToServer: false
|
||||
});
|
||||
console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
|
||||
app.setPath('crashDumps', crashReporterDirectory);
|
||||
} else {
|
||||
const appCenter = product.appCenter;
|
||||
// Disable Appcenter crash reporting if
|
||||
// * --crash-reporter-directory is specified
|
||||
// * enable-crash-reporter runtime argument is set to 'false'
|
||||
// * --disable-crash-reporter command line parameter is set
|
||||
if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) {
|
||||
const isWindows = (process.platform === 'win32');
|
||||
const isLinux = (process.platform === 'linux');
|
||||
const crashReporterId = argvConfig['crash-reporter-id'];
|
||||
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
if (uuidPattern.test(crashReporterId)) {
|
||||
submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin;
|
||||
submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
|
||||
// Send the id for child node process that are explicitly starting crash reporter.
|
||||
// For vscode this is ExtensionHost process currently.
|
||||
process.argv.push('--crash-reporter-id', crashReporterId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start crash reporter for all processes
|
||||
const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort;
|
||||
const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft';
|
||||
crashReporter.start({
|
||||
companyName: companyName,
|
||||
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
|
||||
submitURL,
|
||||
uploadToServer: !crashReporterDirectory
|
||||
});
|
||||
|
||||
// Set logs path before app 'ready' event if running portable
|
||||
// to ensure that no 'logs' folder is created on disk at a
|
||||
// location outside of the portable directory
|
||||
@@ -117,9 +139,6 @@ registerListeners();
|
||||
// Cached data
|
||||
const nodeCachedDataDir = getNodeCachedDir();
|
||||
|
||||
// Configure static command line arguments
|
||||
const argvConfig = configureCommandlineSwitchesSync(args);
|
||||
|
||||
// Remove env set by snap https://github.com/microsoft/vscode/issues/85344
|
||||
if (process.env['SNAP']) {
|
||||
delete process.env['GDK_PIXBUF_MODULE_FILE'];
|
||||
@@ -261,9 +280,6 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
app.commandLine.appendSwitch('js-flags', jsFlags);
|
||||
}
|
||||
|
||||
// TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873
|
||||
app.commandLine.appendSwitch('disable-features', 'LayoutNG');
|
||||
|
||||
return argvConfig;
|
||||
}
|
||||
|
||||
@@ -304,21 +320,10 @@ function createDefaultArgvConfigSync(argvConfigPath) {
|
||||
fs.mkdirSync(argvConfigPathDirname);
|
||||
}
|
||||
|
||||
// Migrate over legacy locale
|
||||
const localeConfigPath = path.join(userDataPath, 'User', 'locale.json');
|
||||
const legacyLocale = getLegacyUserDefinedLocaleSync(localeConfigPath);
|
||||
if (legacyLocale) {
|
||||
try {
|
||||
fs.unlinkSync(localeConfigPath);
|
||||
} catch (error) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Default argv content
|
||||
const defaultArgvConfigContent = [
|
||||
'// This configuration file allows you to pass permanent command line arguments to VS Code.',
|
||||
'// Only a subset of arguments is currently supported to reduce the likelyhood of breaking',
|
||||
'// Only a subset of arguments is currently supported to reduce the likelihood of breaking',
|
||||
'// the installation.',
|
||||
'//',
|
||||
'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
|
||||
@@ -331,19 +336,10 @@ function createDefaultArgvConfigSync(argvConfigPath) {
|
||||
'',
|
||||
' // Enabled by default by VS Code to resolve color issues in the renderer',
|
||||
' // See https://github.com/Microsoft/vscode/issues/51791 for details',
|
||||
' "disable-color-correct-rendering": true'
|
||||
' "disable-color-correct-rendering": true',
|
||||
'}'
|
||||
];
|
||||
|
||||
if (legacyLocale) {
|
||||
defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing ","
|
||||
|
||||
defaultArgvConfigContent.push('');
|
||||
defaultArgvConfigContent.push(' // Display language of VS Code');
|
||||
defaultArgvConfigContent.push(` "locale": "${legacyLocale}"`);
|
||||
}
|
||||
|
||||
defaultArgvConfigContent.push('}');
|
||||
|
||||
// Create initial argv.json with default content
|
||||
fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n'));
|
||||
} catch (error) {
|
||||
@@ -601,19 +597,4 @@ function getUserDefinedLocale(argvConfig) {
|
||||
return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} localeConfigPath
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getLegacyUserDefinedLocaleSync(localeConfigPath) {
|
||||
try {
|
||||
const content = stripComments(fs.readFileSync(localeConfigPath).toString());
|
||||
|
||||
const value = JSON.parse(content).locale;
|
||||
return value && typeof value === 'string' ? value.toLowerCase() : undefined;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -993,8 +993,6 @@ export class Table<T> implements IDisposable {
|
||||
// if (!filter || filter(this.view.element(index))) {
|
||||
return index;
|
||||
// }
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
@@ -1010,9 +1008,7 @@ export class Table<T> implements IDisposable {
|
||||
|
||||
// if (!filter || filter(this.view.element(index))) {
|
||||
return index;
|
||||
// }
|
||||
|
||||
index--;
|
||||
//
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import * as assert from 'assert';
|
||||
import { SelectBox, SelectOptionItemSQL } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { deepClone, equals } from 'vs/base/common/objects';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
|
||||
const options: SelectOptionItemSQL[] = [
|
||||
{ text: 't1', value: 'v1' },
|
||||
@@ -55,18 +54,4 @@ suite('Select Box tests', () => {
|
||||
|
||||
assert(equals(sb.values, newOptions.map(s => s.text)));
|
||||
});
|
||||
|
||||
test('value did not contain label', () => {
|
||||
const newOptions = deepClone(options).map(s => { return { text: s.text, value: s.text }; });
|
||||
delete newOptions[0].text;
|
||||
const sb = new SelectBox(newOptions, undefined!, undefined!, undefined!, undefined!);
|
||||
|
||||
|
||||
sb.onSelect({
|
||||
index: 0,
|
||||
selected: options[0].value
|
||||
});
|
||||
|
||||
assert(isUndefined(sb.label));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export class AccountPickerViewModel {
|
||||
try {
|
||||
const accounts = await this._accountManagementService.getAccounts();
|
||||
return accounts;
|
||||
} catch{
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,6 @@ class TabImpl extends ModelViewPanelImpl implements azdata.window.DialogTab {
|
||||
|
||||
public title: string;
|
||||
public content: string;
|
||||
public handle: number;
|
||||
|
||||
public setModelViewId(value: string) {
|
||||
super.setModelViewId(value);
|
||||
|
||||
@@ -171,12 +171,12 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
* Build and render the modal, will call {@link Modal#renderBody}
|
||||
*/
|
||||
public render() {
|
||||
let builderClass = 'modal fade';
|
||||
let builderClass = '.modal.fade';
|
||||
if (this._modalOptions.isFlyout) {
|
||||
builderClass += ' flyout-dialog';
|
||||
builderClass += '.flyout-dialog';
|
||||
}
|
||||
|
||||
this._bodyContainer = DOM.$(`.${builderClass}`, { role: 'dialog', 'aria-label': this._title });
|
||||
this._bodyContainer = DOM.$(`${builderClass}`, { role: 'dialog', 'aria-label': this._title });
|
||||
const top = this.layoutService.offset?.top ?? 0;
|
||||
this._bodyContainer.style.top = `${top}px`;
|
||||
this._modalDialog = DOM.append(this._bodyContainer, DOM.$('.modal-dialog'));
|
||||
|
||||
@@ -55,7 +55,7 @@ export class BackupDialog extends Modal {
|
||||
* Get the bootstrap params and perform the bootstrap
|
||||
*/
|
||||
private bootstrapAngular(bodyContainer: HTMLElement) {
|
||||
this._instantiationService.invokeFunction(bootstrapAngular,
|
||||
this._instantiationService.invokeFunction<void, any[]>(bootstrapAngular,
|
||||
BackupModule,
|
||||
bodyContainer,
|
||||
BACKUP_SELECTOR,
|
||||
|
||||
@@ -257,7 +257,7 @@ suite('Notebook Actions', function (): void {
|
||||
assert.strictEqual(actualCmdId, NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID);
|
||||
});
|
||||
|
||||
suite('Kernels dropdown', async () => {
|
||||
suite.skip('Kernels dropdown', async () => {
|
||||
let kernelsDropdown: KernelsDropdown;
|
||||
let contextViewProvider: ContextViewProviderStub;
|
||||
let container: HTMLElement;
|
||||
@@ -460,4 +460,3 @@ function verifyUpdateKernelForKernelDefinedAndReadyCase(notebookModel: TestNoteb
|
||||
assert.ok(setOptionsSpy.calledOnce, `setOptions should be be called exactly once when kernel is not defined or ready and clientSession is in error state`);
|
||||
assert.ok(setOptionsSpy.calledWithExactly(expectedSetOptionsArgs.kernels, expectedSetOptionsArgs.selected), `setOptions should be called with a options value of ${JSON.stringify(expectedSetOptionsArgs.kernels)} and selected value of ${expectedSetOptionsArgs.selected}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class NotebookModelStub extends stubs.NotebookModelStub {
|
||||
}
|
||||
}
|
||||
|
||||
suite('Test class NotebookEditor:', () => {
|
||||
suite.skip('Test class NotebookEditor:', () => {
|
||||
let instantiationService = <TestInstantiationService>workbenchInstantiationService();
|
||||
let workbenchThemeService = instantiationService.createInstance(WorkbenchThemeService);
|
||||
let notebookEditor: NotebookEditor;
|
||||
@@ -171,7 +171,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
});
|
||||
}
|
||||
|
||||
test('Verifies that getCellEditor() returns a valid text editor object for valid guid input', async () => {
|
||||
test.skip('Verifies that getCellEditor() returns a valid text editor object for valid guid input', async () => {
|
||||
await setupNotebookEditor(notebookEditor, untitledNotebookInput);
|
||||
const result = notebookEditor.getCellEditor(cellTextEditorGuid);
|
||||
assert.strictEqual(result, queryTextEditor, 'notebookEditor.getCellEditor() should return an expected QueryTextEditor when a guid corresponding to that editor is passed in.');
|
||||
@@ -409,7 +409,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
const searchString = getRandomString(1, 10);
|
||||
const matchCase = true;
|
||||
const wholeWord = true;
|
||||
const searchScope = new NotebookRange(<ICellModel>{}, 1, 1, 1, 1);
|
||||
const searchScope = [new NotebookRange(<ICellModel>{}, 1, 1, 1, 1)];
|
||||
const currentMatch = <NotebookRange>{};
|
||||
test(`Verifies _onFindStateChange callback when searchScope is defined, visibility='${visibility}', searchString='${searchString}', matchCase='${matchCase}', wholeWord='${wholeWord}'`, async () => {
|
||||
const { findReplaceStateChangedEvent, notebookFindModelMock, findDecorationsMock, notebookFindModel, notebookEditor } = await findStateChangeSetup(instantiationService, workbenchThemeService, notebookService, untitledNotebookInput, undefined, currentMatch, searchString, wholeWord, matchCase, searchScope);
|
||||
@@ -444,7 +444,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
const searchString = getRandomString(1, 10);
|
||||
const matchCase = true;
|
||||
const wholeWord = true;
|
||||
const searchScope = new NotebookRange(<ICellModel>{}, 1, 1, 1, 1);
|
||||
const searchScope = [new NotebookRange(<ICellModel>{}, 1, 1, 1, 1)];
|
||||
const currentMatch = <NotebookRange>{};
|
||||
const { notebookFindModelMock, notebookEditor } = await findStateChangeSetup(instantiationService, workbenchThemeService, notebookService, untitledNotebookInput, undefined, currentMatch, searchString, wholeWord, matchCase, searchScope);
|
||||
notebookFindModelMock.setup(x => x.getIndexByRange(TypeMoq.It.isAny())).returns((_range: NotebookRange) => {
|
||||
@@ -469,7 +469,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
const searchString = getRandomString(1, 10);
|
||||
const matchCase = true;
|
||||
const wholeWord = true;
|
||||
const searchScope = new NotebookRange(<ICellModel>{}, 1, 1, 1, 1);
|
||||
const searchScope = [new NotebookRange(<ICellModel>{}, 1, 1, 1, 1)];
|
||||
const currentMatch = <NotebookRange>{};
|
||||
const { notebookEditor } = await findStateChangeSetup(instantiationService, workbenchThemeService, notebookService, untitledNotebookInput, undefined, currentMatch, searchString, wholeWord, matchCase, searchScope);
|
||||
untitledNotebookInput.notebookFindModel.notebookModel = undefined; // clear preexisting notebookModel
|
||||
@@ -499,7 +499,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
searchString: getRandomString(1, 10),
|
||||
matchCase: true,
|
||||
wholeWord: true,
|
||||
searchScope: <NotebookRange>{}
|
||||
searchScope: [<NotebookRange>{}]
|
||||
};
|
||||
findState.change(newState, false);
|
||||
untitledNotebookInput.notebookFindModel.notebookModel = undefined; // clear preexisting notebookModel
|
||||
@@ -530,7 +530,7 @@ suite('Test class NotebookEditor:', () => {
|
||||
searchString: getRandomString(1, 10),
|
||||
matchCase: true,
|
||||
wholeWord: true,
|
||||
searchScope: <NotebookRange>{}
|
||||
searchScope: [<NotebookRange>{}]
|
||||
};
|
||||
findState.change(newState, false); //installs _updateFinderMatchState as event handler for onFindCountChange event
|
||||
let updateFinderMatchStateCalled = false;
|
||||
@@ -596,7 +596,7 @@ async function verifyFindCallsWhenFindStateChangeCallbackFires(instantiationServ
|
||||
findDecorationsMock.verify(x => x.clearDecorations(), TypeMoq.Times.once());
|
||||
}
|
||||
|
||||
async function findStateChangeSetup(instantiationService: TestInstantiationService, workbenchThemeService: any, notebookService: NotebookService, untitledNotebookInput: UntitledNotebookInput, modelFindExpression: string, currentMatch: NotebookRange, searchString: string, wholeWord: boolean, matchCase: boolean, searchScope: NotebookRange | null = undefined, findMatches: Array<NotebookFindMatch> = []) {
|
||||
async function findStateChangeSetup(instantiationService: TestInstantiationService, workbenchThemeService: any, notebookService: NotebookService, untitledNotebookInput: UntitledNotebookInput, modelFindExpression: string, currentMatch: NotebookRange, searchString: string, wholeWord: boolean, matchCase: boolean, searchScope: NotebookRange[] | null = undefined, findMatches: Array<NotebookFindMatch> = []) {
|
||||
const findReplaceStateChangedEvent: FindReplaceStateChangedEvent = {
|
||||
searchString: searchString !== undefined,
|
||||
matchCase: matchCase,
|
||||
@@ -752,4 +752,3 @@ function createEditor(notebookEditor: NotebookEditor) {
|
||||
let parentHtmlElement = document.createElement('div');
|
||||
notebookEditor.create(parentHtmlElement); // adds notebookEditor to new htmlElement as parent
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ suite('ProviderDescriptor:', () => {
|
||||
});
|
||||
});
|
||||
|
||||
suite('NotebookService:', function (): void {
|
||||
suite.skip('NotebookService:', function (): void {
|
||||
let notebookService: NotebookService;
|
||||
let lifecycleService: TestLifecycleService;
|
||||
let storageService: TestStorageService;
|
||||
|
||||
@@ -21,13 +21,9 @@ export class NotebookEditorStub extends stubs.NotebookEditorStub {
|
||||
model: INotebookModel | undefined;
|
||||
cells?: ICellModel[] = [];
|
||||
|
||||
get id(): string {
|
||||
return this.notebookParams?.notebookUri?.toString();
|
||||
}
|
||||
public readonly id = this.notebookParams?.notebookUri?.toString();
|
||||
|
||||
get modelReady(): Promise<INotebookModel> {
|
||||
return Promise.resolve(this.model);
|
||||
}
|
||||
public readonly modelReady: Promise<INotebookModel> = Promise.resolve(this.model);
|
||||
|
||||
// Normally one needs to provide either the editor or the instantiationService as the constructor parameter
|
||||
constructor({ cellGuid, instantiationService, editor, model, notebookParams }: { cellGuid?: string; instantiationService?: IInstantiationService; editor?: QueryTextEditor; model?: INotebookModel, notebookParams?: INotebookParams } = {}) {
|
||||
|
||||
@@ -165,7 +165,10 @@ class ServiceAccessor {
|
||||
}
|
||||
|
||||
class MockEditorService extends TestEditorService {
|
||||
public readonly activeEditor: IEditorInput | undefined = undefined;
|
||||
private __activeEditor: IEditorInput | undefined = undefined;
|
||||
public get activeEditor(): IEditorInput | undefined {
|
||||
return this.__activeEditor;
|
||||
}
|
||||
|
||||
constructor(instantiationService?: IInstantiationService) {
|
||||
super();
|
||||
@@ -174,7 +177,7 @@ class MockEditorService extends TestEditorService {
|
||||
const accessor = workbenchinstantiationService.createInstance(ServiceAccessor);
|
||||
const service = accessor.untitledTextEditorService;
|
||||
const untitledInput = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: URI.file('/test/file') }));
|
||||
this.activeEditor = instantiationService.createInstance(UntitledQueryEditorInput, '', untitledInput, undefined);
|
||||
this.__activeEditor = instantiationService.createInstance(UntitledQueryEditorInput, '', untitledInput, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,9 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
|
||||
this.viewContainer = container;
|
||||
this.viewContainerModel = viewDescriptorService.getViewContainerModel(container);
|
||||
}
|
||||
getActionsContext(): unknown {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the available connection providers, this is called when new providers are registered
|
||||
|
||||
@@ -114,7 +114,7 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
* Bootstrap angular for the dialog's model view controller with the given model view ID
|
||||
*/
|
||||
private initializeModelViewContainer(bodyContainer: HTMLElement, modelViewId: string, tab?: DialogTab) {
|
||||
this._instantiationService.invokeFunction(bootstrapAngular,
|
||||
this._instantiationService.invokeFunction<void, any[]>(bootstrapAngular,
|
||||
DialogModule,
|
||||
bodyContainer,
|
||||
'dialog-modelview-container',
|
||||
|
||||
@@ -98,7 +98,7 @@ export class ServerTreeRenderer implements IRenderer {
|
||||
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
|
||||
const connectionTemplate: IObjectExplorerTemplateData = Object.create(null);
|
||||
connectionTemplate.root = dom.append(container, dom.$('.connection-tile'));
|
||||
connectionTemplate.icon = dom.append(connectionTemplate.root, dom.$('div.icon server-page'));
|
||||
connectionTemplate.icon = dom.append(connectionTemplate.root, dom.$('div.icon.server-page'));
|
||||
connectionTemplate.label = dom.append(connectionTemplate.root, dom.$('div.label'));
|
||||
return connectionTemplate;
|
||||
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"strictNullChecks": false,
|
||||
"strictPropertyInitialization": false,
|
||||
"strict": false,
|
||||
"allowUnreachableCode": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare namespace NodeJS {
|
||||
|
||||
export interface Process {
|
||||
|
||||
/**
|
||||
* The lazy enviroment is a promise that resolves to `process.env`
|
||||
* once the process is resolved. The use-case is VS Code running
|
||||
* on Linux/macOS when being launched via a launcher. Then the env
|
||||
* (as defined in .bashrc etc) isn't properly set and needs to be
|
||||
* resolved lazy.
|
||||
*/
|
||||
lazyEnv: Thenable<typeof process.env> | undefined;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ export function clearNode(node: HTMLElement): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `node.remove()` instead
|
||||
*/
|
||||
export function removeNode(node: HTMLElement): void {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
@@ -1004,7 +1007,7 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
|
||||
return child;
|
||||
}
|
||||
|
||||
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
|
||||
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;
|
||||
|
||||
export enum Namespace {
|
||||
HTML = 'http://www.w3.org/1999/xhtml',
|
||||
|
||||
@@ -17,6 +17,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
|
||||
import { resolvePath } from 'vs/base/common/resources';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
export interface MarkedOptions extends marked.MarkedOptions {
|
||||
baseUrl?: never;
|
||||
@@ -185,25 +186,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
|
||||
const actionHandler = options.actionHandler;
|
||||
if (actionHandler) {
|
||||
actionHandler.disposeables.add(DOM.addStandardDisposableListener(element, 'click', event => {
|
||||
let target: HTMLElement | null = event.target;
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
if (!target || target.tagName !== 'A') {
|
||||
[DOM.EventType.CLICK, DOM.EventType.AUXCLICK].forEach(event => {
|
||||
actionHandler.disposeables.add(DOM.addDisposableListener(element, event, (e: MouseEvent) => {
|
||||
const mouseEvent = new StandardMouseEvent(e);
|
||||
if (!mouseEvent.leftButton && !mouseEvent.middleButton) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const href = target.dataset['href'];
|
||||
if (href) {
|
||||
actionHandler.callback(href, event);
|
||||
|
||||
let target: HTMLElement | null = mouseEvent.target;
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
if (!target || target.tagName !== 'A') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
} finally {
|
||||
event.preventDefault();
|
||||
}
|
||||
}));
|
||||
try {
|
||||
const href = target.dataset['href'];
|
||||
if (href) {
|
||||
actionHandler.callback(href, mouseEvent);
|
||||
}
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
} finally {
|
||||
mouseEvent.preventDefault();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Use our own sanitizer so that we can let through only spans.
|
||||
|
||||
@@ -285,6 +285,10 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
index++;
|
||||
}
|
||||
});
|
||||
if (this.focusedItem) {
|
||||
// After a clear actions might be re-added to simply toggle some actions. We should preserve focus #97128
|
||||
this.focus(this.focusedItem);
|
||||
}
|
||||
}
|
||||
|
||||
getWidth(index: number): number {
|
||||
|
||||
@@ -187,14 +187,14 @@ class Label {
|
||||
|
||||
if (typeof label === 'string') {
|
||||
if (!this.singleLabel) {
|
||||
this.container.innerHTML = '';
|
||||
this.container.innerText = '';
|
||||
dom.removeClass(this.container, 'multiple');
|
||||
this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId }));
|
||||
}
|
||||
|
||||
this.singleLabel.textContent = label;
|
||||
} else {
|
||||
this.container.innerHTML = '';
|
||||
this.container.innerText = '';
|
||||
dom.addClass(this.container, 'multiple');
|
||||
this.singleLabel = undefined;
|
||||
|
||||
@@ -250,7 +250,7 @@ class LabelWithHighlights {
|
||||
|
||||
if (typeof label === 'string') {
|
||||
if (!this.singleLabel) {
|
||||
this.container.innerHTML = '';
|
||||
this.container.innerText = '';
|
||||
dom.removeClass(this.container, 'multiple');
|
||||
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
|
||||
}
|
||||
@@ -258,7 +258,7 @@ class LabelWithHighlights {
|
||||
this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
|
||||
} else {
|
||||
|
||||
this.container.innerHTML = '';
|
||||
this.container.innerText = '';
|
||||
dom.addClass(this.container, 'multiple');
|
||||
this.singleLabel = undefined;
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ export class InputBox extends Widget {
|
||||
this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;
|
||||
|
||||
this.mirror = dom.append(wrapper, $('div.mirror'));
|
||||
this.mirror.innerHTML = ' ';
|
||||
this.mirror.innerText = '\u00a0';
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
|
||||
|
||||
@@ -563,7 +563,7 @@ export class InputBox extends Widget {
|
||||
if (mirrorTextContent) {
|
||||
this.mirror.textContent = value + suffix;
|
||||
} else {
|
||||
this.mirror.innerHTML = ' ';
|
||||
this.mirror.innerText = '\u00a0';
|
||||
}
|
||||
|
||||
this.layout();
|
||||
|
||||
@@ -602,6 +602,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return this.items[index].element;
|
||||
}
|
||||
|
||||
indexOf(element: T): number {
|
||||
return this.items.findIndex(item => item.element === element);
|
||||
}
|
||||
|
||||
domElement(index: number): HTMLElement | null {
|
||||
const row = this.items[index].row;
|
||||
return row && row.domNode;
|
||||
|
||||
@@ -1323,6 +1323,10 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
return this.view.element(index);
|
||||
}
|
||||
|
||||
indexOf(element: T): number {
|
||||
return this.view.indexOf(element);
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this.view.length;
|
||||
}
|
||||
|
||||
@@ -324,8 +324,7 @@ export class Menu extends ActionBar {
|
||||
if (action instanceof Separator) {
|
||||
return new MenuSeparatorActionViewItem(options.context, action, { icon: true });
|
||||
} else if (action instanceof SubmenuAction) {
|
||||
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
|
||||
const menuActionViewItem = new SubmenuMenuActionViewItem(action, actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) });
|
||||
const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) });
|
||||
|
||||
if (options.enableMnemonics) {
|
||||
const mnemonic = menuActionViewItem.getMnemonic();
|
||||
@@ -791,7 +790,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
|
||||
private cleanupExistingSubmenu(force: boolean): void {
|
||||
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
|
||||
this.parentData.submenu.dispose();
|
||||
|
||||
// disposal may throw if the submenu has already been removed
|
||||
try {
|
||||
this.parentData.submenu.dispose();
|
||||
} catch { }
|
||||
|
||||
this.parentData.submenu = undefined;
|
||||
this.updateAriaExpanded('false');
|
||||
if (this.submenuContainer) {
|
||||
@@ -835,7 +839,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
|
||||
if (!this.parentData.submenu) {
|
||||
this.updateAriaExpanded('true');
|
||||
this.submenuContainer = document.createElement('div.monaco-submenu');
|
||||
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
|
||||
addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
|
||||
|
||||
// Set the top value of the menu container before construction
|
||||
@@ -853,8 +857,6 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
this.parentData.submenu.style(this.menuStyle);
|
||||
}
|
||||
|
||||
this.element.appendChild(this.submenuContainer);
|
||||
|
||||
// layout submenu
|
||||
const entryBox = this.element.getBoundingClientRect();
|
||||
const entryBoxUpdated = {
|
||||
|
||||
@@ -217,8 +217,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
|
||||
// Intercept keyboard handling
|
||||
|
||||
// React on KEY_UP since the actionBar also reacts on KEY_UP so that appropriate events get canceled
|
||||
this._register(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
this._register(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let showDropDown = false;
|
||||
|
||||
@@ -235,7 +234,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
|
||||
if (showDropDown) {
|
||||
this.showSelectDropDown();
|
||||
dom.EventHelper.stop(e, true);
|
||||
dom.EventHelper.stop(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface IToolBarOptions {
|
||||
actionRunner?: IActionRunner;
|
||||
toggleMenuTitle?: string;
|
||||
anchorAlignmentProvider?: () => AnchorAlignment;
|
||||
renderDropdownAsChildElement?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,7 @@ export class ToolBar extends Disposable {
|
||||
private submenuActionViewItems: DropdownMenuActionViewItem[] = [];
|
||||
private hasSecondaryActions: boolean = false;
|
||||
private lookupKeybindings: boolean;
|
||||
private element: HTMLElement;
|
||||
|
||||
private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>());
|
||||
readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event;
|
||||
@@ -52,11 +54,11 @@ export class ToolBar extends Disposable {
|
||||
|
||||
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle));
|
||||
|
||||
let element = document.createElement('div');
|
||||
element.className = 'monaco-toolbar';
|
||||
container.appendChild(element);
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'monaco-toolbar';
|
||||
container.appendChild(this.element);
|
||||
|
||||
this.actionBar = this._register(new ActionBar(element, {
|
||||
this.actionBar = this._register(new ActionBar(this.element, {
|
||||
orientation: options.orientation,
|
||||
ariaLabel: options.ariaLabel,
|
||||
actionRunner: options.actionRunner,
|
||||
@@ -72,7 +74,7 @@ export class ToolBar extends Disposable {
|
||||
keybindingProvider: this.options.getKeyBinding,
|
||||
classNames: toolBarMoreIcon.classNames,
|
||||
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
|
||||
menuAsChild: true
|
||||
menuAsChild: !!this.options.renderDropdownAsChildElement
|
||||
}
|
||||
);
|
||||
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
|
||||
@@ -90,10 +92,9 @@ export class ToolBar extends Disposable {
|
||||
}
|
||||
|
||||
if (action instanceof SubmenuAction) {
|
||||
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
|
||||
const result = new DropdownMenuActionViewItem(
|
||||
action,
|
||||
actions,
|
||||
action.actions,
|
||||
contextMenuProvider,
|
||||
{
|
||||
actionViewItemProvider: this.options.actionViewItemProvider,
|
||||
@@ -134,8 +135,8 @@ export class ToolBar extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
getContainer(): HTMLElement {
|
||||
return this.actionBar.getContainer();
|
||||
getElement(): HTMLElement {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
getItemsWidth(): number {
|
||||
|
||||
@@ -881,7 +881,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
this.messageDomNode.textContent = localize('empty', "No elements found");
|
||||
this._empty = true;
|
||||
} else {
|
||||
this.messageDomNode.innerHTML = '';
|
||||
this.messageDomNode.innerText = '';
|
||||
this._empty = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -256,7 +256,12 @@ export class Separator extends Action {
|
||||
export type SubmenuActions = IAction[] | (() => IAction[]);
|
||||
|
||||
export class SubmenuAction extends Action {
|
||||
constructor(id: string, label: string, readonly actions: SubmenuActions, cssClass?: string) {
|
||||
|
||||
get actions(): IAction[] {
|
||||
return Array.isArray(this._actions) ? this._actions : this._actions();
|
||||
}
|
||||
|
||||
constructor(id: string, label: string, private _actions: SubmenuActions, cssClass?: string) {
|
||||
super(id, label, cssClass, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +590,9 @@ export function asArray<T>(x: T | T[]): T[] {
|
||||
return Array.isArray(x) ? x : [x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `Array.from` or `[...iter]`
|
||||
*/
|
||||
export function toArray<T>(iterable: IterableIterator<T>): T[] {
|
||||
const result: T[] = [];
|
||||
for (let element of iterable) {
|
||||
|
||||
@@ -33,8 +33,7 @@ const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Coll
|
||||
return {
|
||||
collator: collator
|
||||
};
|
||||
});
|
||||
|
||||
});/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
|
||||
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
@@ -49,36 +48,16 @@ export function compareFileNames(one: string | null, other: string | null, caseS
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
|
||||
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
/** Compares filenames without distinguishing the name from the extension. Disambiguates by length, not unicode comparison. */
|
||||
export function compareFileNamesDefault(one: string | null, other: string | null): number {
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
one = one || '';
|
||||
other = other || '';
|
||||
|
||||
// Check for name differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate the extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Compare the entire filename - both name and extension - and disambiguate by length if needed
|
||||
return compareAndDisambiguateByLength(collatorNumeric, one, other);
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
@@ -123,10 +102,12 @@ export function compareFileExtensions(one: string | null, other: string | null):
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
|
||||
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
/** Compares filenames by extenson, then by full filename */
|
||||
export function compareFileExtensionsDefault(one: string | null, other: string | null): number {
|
||||
one = one || '';
|
||||
other = other || '';
|
||||
const oneExtension = extractExtension(one);
|
||||
const otherExtension = extractExtension(other);
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
@@ -137,20 +118,12 @@ export function compareFileExtensionsNumeric(one: string | null, other: string |
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compare names.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Compare full filenames
|
||||
return compareAndDisambiguateByLength(collatorNumeric, one, other);
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
|
||||
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
||||
@@ -166,6 +139,13 @@ function extractNameAndExtension(str?: string | null, dotfilesAsNames = false):
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */
|
||||
function extractExtension(str?: string | null): string {
|
||||
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
||||
|
||||
return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || '';
|
||||
}
|
||||
|
||||
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
|
||||
// Check for differences
|
||||
let result = collator.compare(one, other);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters';
|
||||
import { matchesPrefix, IMatch, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters';
|
||||
import { sep } from 'vs/base/common/path';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
@@ -168,7 +168,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
|
||||
score += 1;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLower[queryIndex]} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
|
||||
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLowerCharAtIndex} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
|
||||
// }
|
||||
|
||||
// Consecutive match bonus
|
||||
@@ -176,7 +176,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
|
||||
score += (matchesSequenceLength * 5);
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Consecutive match bonus: ' + (matchesSequenceLength * 5));
|
||||
// console.log(`Consecutive match bonus: +${matchesSequenceLength * 5}`);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -206,16 +206,16 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
|
||||
score += separatorBonus;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('After separtor bonus: +4');
|
||||
// console.log(`After separtor bonus: +${separatorBonus}`);
|
||||
// }
|
||||
}
|
||||
|
||||
// Inside word upper case bonus (camel case)
|
||||
else if (isUpper(target.charCodeAt(targetIndex))) {
|
||||
score += 1;
|
||||
score += 2;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Inside word upper case bonus: +1');
|
||||
// console.log('Inside word upper case bonus: +2');
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -371,8 +371,7 @@ export interface IItemAccessor<T> {
|
||||
const PATH_IDENTITY_SCORE = 1 << 18;
|
||||
const LABEL_PREFIX_SCORE_MATCHCASE = 1 << 17;
|
||||
const LABEL_PREFIX_SCORE_IGNORECASE = 1 << 16;
|
||||
const LABEL_CAMELCASE_SCORE = 1 << 15;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 14;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 15;
|
||||
|
||||
export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
|
||||
if (!item || !query.normalized) {
|
||||
@@ -386,11 +385,17 @@ export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean
|
||||
|
||||
const description = accessor.getItemDescription(item);
|
||||
|
||||
// in order to speed up scoring, we cache the score with a unique hash based on:
|
||||
// - label
|
||||
// - description (if provided)
|
||||
// - query (normalized)
|
||||
// - number of query pieces (i.e. 'hello world' and 'helloworld' are different)
|
||||
// - wether fuzzy matching is enabled or not
|
||||
let cacheHash: string;
|
||||
if (description) {
|
||||
cacheHash = `${label}${description}${query.normalized}${fuzzy}`;
|
||||
cacheHash = `${label}${description}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
|
||||
} else {
|
||||
cacheHash = `${label}${query.normalized}${fuzzy}`;
|
||||
cacheHash = `${label}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
|
||||
}
|
||||
|
||||
const cached = cache[cacheHash];
|
||||
@@ -465,13 +470,7 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined,
|
||||
return { score: prefixLabelMatchStrictCase ? LABEL_PREFIX_SCORE_MATCHCASE : LABEL_PREFIX_SCORE_IGNORECASE, labelMatch: prefixLabelMatchStrictCase || prefixLabelMatchIgnoreCase };
|
||||
}
|
||||
|
||||
// Treat camelcase matches on the label second highest
|
||||
const camelcaseLabelMatch = matchesCamelCase(query.normalized, label);
|
||||
if (camelcaseLabelMatch) {
|
||||
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
|
||||
}
|
||||
|
||||
// Prefer scores on the label if any
|
||||
// Second, score fuzzy
|
||||
const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy);
|
||||
if (labelScore) {
|
||||
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
|
||||
@@ -594,7 +593,7 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
const scoreA = itemScoreA.score;
|
||||
const scoreB = itemScoreB.score;
|
||||
|
||||
// 1.) prefer identity matches
|
||||
// 1.) identity matches have highest score
|
||||
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === PATH_IDENTITY_SCORE ? -1 : 1;
|
||||
@@ -631,44 +630,32 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
}
|
||||
}
|
||||
|
||||
// 4.) prefer camelcase matches
|
||||
if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) {
|
||||
// 4.) matches on label are considered higher compared to label+description matches
|
||||
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1;
|
||||
return scoreA > scoreB ? -1 : 1;
|
||||
}
|
||||
|
||||
const labelA = accessor.getItemLabel(itemA) || '';
|
||||
const labelB = accessor.getItemLabel(itemB) || '';
|
||||
|
||||
// prefer more compact camel case matches over longer
|
||||
// prefer more compact matches over longer in label
|
||||
const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch);
|
||||
if (comparedByMatchLength !== 0) {
|
||||
return comparedByMatchLength;
|
||||
}
|
||||
|
||||
// prefer shorter names when both match on label camelcase
|
||||
// prefer shorter labels over longer labels
|
||||
const labelA = accessor.getItemLabel(itemA) || '';
|
||||
const labelB = accessor.getItemLabel(itemB) || '';
|
||||
if (labelA.length !== labelB.length) {
|
||||
return labelA.length - labelB.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 5.) prefer label scores
|
||||
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
|
||||
if (scoreB < LABEL_SCORE_THRESHOLD) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (scoreA < LABEL_SCORE_THRESHOLD) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 6.) compare by score
|
||||
// 5.) compare by score in label+description
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA > scoreB ? -1 : 1;
|
||||
}
|
||||
|
||||
// 7.) prefer matches in label over non-label matches
|
||||
// 6.) scores are identical: prefer matches in label over non-label matches
|
||||
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
|
||||
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
|
||||
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
|
||||
@@ -677,15 +664,14 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 8.) scores are identical, prefer more compact matches (label and description)
|
||||
// 7.) scores are identical: prefer more compact matches (label and description)
|
||||
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
|
||||
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
|
||||
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
|
||||
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
|
||||
}
|
||||
|
||||
// 9.) at this point, scores are identical and match compactness as well
|
||||
// for both items so we start to use the fallback compare
|
||||
// 8.) scores are identical: start to use the fallback compare
|
||||
return fallbackCompare(itemA, itemB, query, accessor);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ import * as strings from 'vs/base/common/strings';
|
||||
/**
|
||||
* Return a hash value for an object.
|
||||
*/
|
||||
export function hash(obj: any, hashVal = 0): number {
|
||||
export function hash(obj: any): number {
|
||||
return doHash(obj, 0);
|
||||
}
|
||||
|
||||
|
||||
export function doHash(obj: any, hashVal: number): number {
|
||||
switch (typeof obj) {
|
||||
case 'object':
|
||||
if (obj === null) {
|
||||
@@ -24,9 +29,9 @@ export function hash(obj: any, hashVal = 0): number {
|
||||
case 'number':
|
||||
return numberHash(obj, hashVal);
|
||||
case 'undefined':
|
||||
return numberHash(0, 937);
|
||||
return numberHash(937, hashVal);
|
||||
default:
|
||||
return numberHash(0, 617);
|
||||
return numberHash(617, hashVal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +53,14 @@ export function stringHash(s: string, hashVal: number) {
|
||||
|
||||
function arrayHash(arr: any[], initialHashVal: number): number {
|
||||
initialHashVal = numberHash(104579, initialHashVal);
|
||||
return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal);
|
||||
return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal);
|
||||
}
|
||||
|
||||
function objectHash(obj: any, initialHashVal: number): number {
|
||||
initialHashVal = numberHash(181387, initialHashVal);
|
||||
return Object.keys(obj).sort().reduce((hashVal, key) => {
|
||||
hashVal = stringHash(key, hashVal);
|
||||
return hash(obj[key], hashVal);
|
||||
return doHash(obj[key], hashVal);
|
||||
}, initialHashVal);
|
||||
}
|
||||
|
||||
@@ -68,7 +73,7 @@ export class Hasher {
|
||||
}
|
||||
|
||||
hash(obj: any): number {
|
||||
this._value = hash(obj, this._value);
|
||||
this._value = doHash(obj, this._value);
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ function trackDisposable<T extends IDisposable>(x: T): T {
|
||||
return x;
|
||||
}
|
||||
|
||||
export class MultiDisposeError extends Error {
|
||||
constructor(
|
||||
public readonly errors: any[]
|
||||
) {
|
||||
super(`Encounter errors while disposing of store. Errors: [${errors.join(', ')}]`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
@@ -60,12 +68,25 @@ export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
|
||||
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
|
||||
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
|
||||
if (Iterable.is(arg)) {
|
||||
for (let d of arg) {
|
||||
let errors: any[] = [];
|
||||
|
||||
for (const d of arg) {
|
||||
if (d) {
|
||||
markTracked(d);
|
||||
d.dispose();
|
||||
try {
|
||||
d.dispose();
|
||||
} catch (e) {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 1) {
|
||||
throw errors[0];
|
||||
} else if (errors.length > 1) {
|
||||
throw new MultiDisposeError(errors);
|
||||
}
|
||||
|
||||
return Array.isArray(arg) ? [] : arg;
|
||||
} else if (arg) {
|
||||
markTracked(arg);
|
||||
@@ -116,8 +137,11 @@ export class DisposableStore implements IDisposable {
|
||||
* Dispose of all registered disposables but do not mark this object as disposed.
|
||||
*/
|
||||
public clear(): void {
|
||||
this._toDispose.forEach(item => item.dispose());
|
||||
this._toDispose.clear();
|
||||
try {
|
||||
dispose(this._toDispose.values());
|
||||
} finally {
|
||||
this._toDispose.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public add<T extends IDisposable>(t: T): T {
|
||||
|
||||
+2402
-1489
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,8 @@ export namespace Schemas {
|
||||
|
||||
export const vscodeNotebook = 'vscode-notebook';
|
||||
|
||||
export const vscodeNotebookCell = 'vscode-notebook-cell';
|
||||
|
||||
export const vscodeSettings = 'vscode-settings';
|
||||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
|
||||
@@ -855,10 +855,6 @@ export function stripUTF8BOM(str: string): string {
|
||||
return startsWithUTF8BOM(str) ? str.substr(1) : str;
|
||||
}
|
||||
|
||||
export function safeBtoa(str: string): string {
|
||||
return btoa(encodeURIComponent(str)); // we use encodeURIComponent because btoa fails for non Latin 1 values
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated ES6
|
||||
*/
|
||||
|
||||
@@ -219,7 +219,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
// Set numeric locale to ensure '.' is used as the decimal separator
|
||||
exec(`${ps} ${args}`, { maxBuffer: 1000 * 1024, env: { LC_NUMERIC: 'en_US.UTF-8' } }, (err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
// Silently ignoring the screen size is bogus error. See https://github.com/microsoft/vscode/issues/98590
|
||||
if (err || (stderr && !stderr.includes('screen size is bogus'))) {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
parsePsOutput(stdout, addToTree);
|
||||
@@ -246,4 +247,4 @@ function parsePsOutput(stdout: string, addToTree: (pid: number, ppid: number, cm
|
||||
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } from 'electron';
|
||||
import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export function registerContextMenuListener(): void {
|
||||
ipcMain.on(CONTEXT_MENU_CHANNEL, (event: IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => {
|
||||
const menu = createMenu(event, onClickChannel, items);
|
||||
|
||||
menu.popup({
|
||||
window: BrowserWindow.fromWebContents(event.sender),
|
||||
window: withNullAsUndefined(BrowserWindow.fromWebContents(event.sender)),
|
||||
x: options ? options.x : undefined,
|
||||
y: options ? options.y : undefined,
|
||||
positioningItem: options ? options.positioningItem : undefined,
|
||||
|
||||
@@ -278,7 +278,7 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
if (title && this.ui.title.textContent !== title) {
|
||||
this.ui.title.textContent = title;
|
||||
} else if (!title && this.ui.title.innerHTML !== ' ') {
|
||||
this.ui.title.innerHTML = ' ';
|
||||
this.ui.title.innerText = '\u00a0;';
|
||||
}
|
||||
const description = this.getDescription();
|
||||
if (this.ui.description.textContent !== description) {
|
||||
@@ -381,7 +381,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
|
||||
|
||||
private _value = '';
|
||||
private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL;
|
||||
private _ariaLabel: string | undefined;
|
||||
private _placeholder: string | undefined;
|
||||
private readonly onDidChangeValueEmitter = this._register(new Emitter<string>());
|
||||
private readonly onDidAcceptEmitter = this._register(new Emitter<IQuickPickAcceptEvent>());
|
||||
@@ -435,8 +435,8 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
|
||||
filterValue = (value: string) => value;
|
||||
|
||||
set ariaLabel(ariaLabel: string) {
|
||||
this._ariaLabel = ariaLabel || QuickPick.DEFAULT_ARIA_LABEL;
|
||||
set ariaLabel(ariaLabel: string | undefined) {
|
||||
this._ariaLabel = ariaLabel;
|
||||
this.update();
|
||||
}
|
||||
|
||||
@@ -884,8 +884,11 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}
|
||||
if (inputShownJustForScreenReader) {
|
||||
this.ui.inputBox.ariaLabel = '';
|
||||
} else if (this.ui.inputBox.ariaLabel !== this.ariaLabel) {
|
||||
this.ui.inputBox.ariaLabel = this.ariaLabel;
|
||||
} else {
|
||||
const ariaLabel = this.ariaLabel || this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;
|
||||
if (this.ui.inputBox.ariaLabel !== ariaLabel) {
|
||||
this.ui.inputBox.ariaLabel = ariaLabel;
|
||||
}
|
||||
}
|
||||
this.ui.list.matchOnDescription = this.matchOnDescription;
|
||||
this.ui.list.matchOnDetail = this.matchOnDetail;
|
||||
@@ -1384,9 +1387,6 @@ export class QuickInputController extends Disposable {
|
||||
];
|
||||
input.canSelectMany = !!options.canPickMany;
|
||||
input.placeholder = options.placeHolder;
|
||||
if (options.placeHolder) {
|
||||
input.ariaLabel = options.placeHolder;
|
||||
}
|
||||
input.ignoreFocusOut = !!options.ignoreFocusLost;
|
||||
input.matchOnDescription = !!options.matchOnDescription;
|
||||
input.matchOnDetail = !!options.matchOnDetail;
|
||||
|
||||
@@ -199,7 +199,7 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
*/
|
||||
filterValue: (value: string) => string;
|
||||
|
||||
ariaLabel: string;
|
||||
ariaLabel: string | undefined;
|
||||
|
||||
placeholder: string | undefined;
|
||||
|
||||
|
||||
@@ -209,37 +209,6 @@ export interface SaveDialogReturnValue {
|
||||
bookmark?: string;
|
||||
}
|
||||
|
||||
export interface CrashReporterStartOptions {
|
||||
companyName: string;
|
||||
/**
|
||||
* URL that crash reports will be sent to as POST.
|
||||
*/
|
||||
submitURL: string;
|
||||
/**
|
||||
* Defaults to `app.name`.
|
||||
*/
|
||||
productName?: string;
|
||||
/**
|
||||
* Whether crash reports should be sent to the server. Default is `true`.
|
||||
*/
|
||||
uploadToServer?: boolean;
|
||||
/**
|
||||
* Default is `false`.
|
||||
*/
|
||||
ignoreSystemCrashHandler?: boolean;
|
||||
/**
|
||||
* An object you can define that will be sent along with the report. Only string
|
||||
* properties are sent correctly. Nested objects are not supported. When using
|
||||
* Windows, the property names and values must be fewer than 64 characters.
|
||||
*/
|
||||
extra?: Record<string, string>;
|
||||
/**
|
||||
* Directory to store the crash reports temporarily (only used when the crash
|
||||
* reporter is started via `process.crashReporter.start`).
|
||||
*/
|
||||
crashesDirectory?: string;
|
||||
}
|
||||
|
||||
export interface FileFilter {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/file-filter
|
||||
@@ -281,3 +250,62 @@ export interface MouseInputEvent extends InputEvent {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface CrashReporterStartOptions {
|
||||
/**
|
||||
* URL that crash reports will be sent to as POST.
|
||||
*/
|
||||
submitURL: string;
|
||||
/**
|
||||
* Defaults to `app.name`.
|
||||
*/
|
||||
productName?: string;
|
||||
/**
|
||||
* Deprecated alias for `{ globalExtra: { _companyName: ... } }`.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
companyName?: string;
|
||||
/**
|
||||
* Whether crash reports should be sent to the server. If false, crash reports will
|
||||
* be collected and stored in the crashes directory, but not uploaded. Default is
|
||||
* `true`.
|
||||
*/
|
||||
uploadToServer?: boolean;
|
||||
/**
|
||||
* If true, crashes generated in the main process will not be forwarded to the
|
||||
* system crash handler. Default is `false`.
|
||||
*/
|
||||
ignoreSystemCrashHandler?: boolean;
|
||||
/**
|
||||
* If true, limit the number of crashes uploaded to 1/hour. Default is `false`.
|
||||
*
|
||||
* @platform darwin,win32
|
||||
*/
|
||||
rateLimit?: boolean;
|
||||
/**
|
||||
* If true, crash reports will be compressed and uploaded with `Content-Encoding:
|
||||
* gzip`. Not all collection servers support compressed payloads. Default is
|
||||
* `false`.
|
||||
*
|
||||
* @platform darwin,win32
|
||||
*/
|
||||
compress?: boolean;
|
||||
/**
|
||||
* Extra string key/value annotations that will be sent along with crash reports
|
||||
* that are generated in the main process. Only string values are supported.
|
||||
* Crashes generated in child processes will not contain these extra parameters to
|
||||
* crash reports generated from child processes, call `addExtraParameter` from the
|
||||
* child process.
|
||||
*/
|
||||
extra?: Record<string, string>;
|
||||
/**
|
||||
* Extra string key/value annotations that will be sent along with any crash
|
||||
* reports generated in any process. These annotations cannot be changed once the
|
||||
* crash reporter has been started. If a key is present in both the global extra
|
||||
* parameters and the process-specific extra parameters, then the global one will
|
||||
* take precedence. By default, `productName` and the app version are included, as
|
||||
* well as the Electron version.
|
||||
*/
|
||||
globalExtra?: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -74,15 +74,16 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Support for subset of methods of Electron's `crashReporter` type.
|
||||
* Support for subset of methods of Electron's `crashReporter` type.
|
||||
*/
|
||||
crashReporter: {
|
||||
|
||||
/**
|
||||
* @param {Electron.CrashReporterStartOptions} options
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
start(options) {
|
||||
crashReporter.start(options);
|
||||
addExtraParameter(key, value) {
|
||||
crashReporter.addExtraParameter(key, value);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
|
||||
export const ipcRenderer = (window as any).vscode.ipcRenderer as {
|
||||
|
||||
/**
|
||||
@@ -54,32 +52,23 @@ export const webFrame = (window as any).vscode.webFrame as {
|
||||
export const crashReporter = (window as any).vscode.crashReporter as {
|
||||
|
||||
/**
|
||||
* You are required to call this method before using any other `crashReporter` APIs
|
||||
* and in each process (main/renderer) from which you want to collect crash
|
||||
* reports. You can pass different options to `crashReporter.start` when calling
|
||||
* from different processes.
|
||||
* Set an extra parameter to be sent with the crash report. The values specified
|
||||
* here will be sent in addition to any values set via the `extra` option when
|
||||
* `start` was called.
|
||||
*
|
||||
* **Note** Child processes created via the `child_process` module will not have
|
||||
* access to the Electron modules. Therefore, to collect crash reports from them,
|
||||
* use `process.crashReporter.start` instead. Pass the same options as above along
|
||||
* with an additional one called `crashesDirectory` that should point to a
|
||||
* directory to store the crash reports temporarily. You can test this out by
|
||||
* calling `process.crash()` to crash the child process.
|
||||
* Parameters added in this fashion (or via the `extra` parameter to
|
||||
* `crashReporter.start`) are specific to the calling process. Adding extra
|
||||
* parameters in the main process will not cause those parameters to be sent along
|
||||
* with crashes from renderer or other child processes. Similarly, adding extra
|
||||
* parameters in a renderer process will not result in those parameters being sent
|
||||
* with crashes that occur in other renderer processes or in the main process.
|
||||
*
|
||||
* **Note:** If you need send additional/updated `extra` parameters after your
|
||||
* first call `start` you can call `addExtraParameter` on macOS or call `start`
|
||||
* again with the new/updated `extra` parameters on Linux and Windows.
|
||||
*
|
||||
* **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash
|
||||
* collection and reporting. If you want to enable crash reporting, initializing
|
||||
* `crashpad` from the main process using `crashReporter.start` is required
|
||||
* regardless of which process you want to collect crashes from. Once initialized
|
||||
* this way, the crashpad handler collects crashes from all processes. You still
|
||||
* have to call `crashReporter.start` from the renderer or child process, otherwise
|
||||
* crashes from them will get reported without `companyName`, `productName` or any
|
||||
* of the `extra` information.
|
||||
* **Note:** Parameters have limits on the length of the keys and values. Key names
|
||||
* must be no longer than 39 bytes, and values must be no longer than 127 bytes.
|
||||
* Keys with names longer than the maximum will be silently ignored. Key values
|
||||
* longer than the maximum length will be truncated.
|
||||
*/
|
||||
start(options: CrashReporterStartOptions): void;
|
||||
addExtraParameter(key: string, value: string): void;
|
||||
};
|
||||
|
||||
export const process = (window as any).vscode.process as {
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as Lifecycle from 'vs/base/common/lifecycle';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as Diff from 'vs/base/common/diff/diff';
|
||||
import * as Touch from 'vs/base/browser/touch';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as Mouse from 'vs/base/browser/mouseEvent';
|
||||
import * as Keyboard from 'vs/base/browser/keyboardEvent';
|
||||
import * as Model from 'vs/base/parts/tree/browser/treeModel';
|
||||
@@ -218,7 +217,7 @@ export class ViewItem implements IViewItem {
|
||||
this.element.setAttribute('aria-posinset', accessibility.getPosInSet(this.context.tree, this.model.getElement()));
|
||||
}
|
||||
if (this.model.hasTrait('focused')) {
|
||||
const base64Id = strings.safeBtoa(this.model.id);
|
||||
const base64Id = btoa(encodeURIComponent(this.model.id));
|
||||
this.element.setAttribute('id', base64Id);
|
||||
this.element.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
@@ -1061,7 +1060,7 @@ export class TreeView extends HeightMap {
|
||||
|
||||
// ARIA
|
||||
if (focus) {
|
||||
this.domNode.setAttribute('aria-activedescendant', strings.safeBtoa(this.context.dataSource.getId(this.context.tree, focus)));
|
||||
this.domNode.setAttribute('aria-activedescendant', btoa(encodeURIComponent(this.context.dataSource.getId(this.context.tree, focus))));
|
||||
} else {
|
||||
this.domNode.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareFileNames, compareFileExtensions, compareFileNamesNumeric, compareFileExtensionsNumeric } from 'vs/base/common/comparers';
|
||||
import { compareFileNames, compareFileExtensions, compareFileNamesDefault, compareFileExtensionsDefault } from 'vs/base/common/comparers';
|
||||
import * as assert from 'assert';
|
||||
|
||||
const compareLocale = (a: string, b: string) => a.localeCompare(b);
|
||||
@@ -15,7 +15,7 @@ suite('Comparers', () => {
|
||||
test('compareFileNames', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileNamesNumeric
|
||||
// Comparisons with the same results as compareFileNamesDefault
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
@@ -28,6 +28,7 @@ suite('Comparers', () => {
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename');
|
||||
assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal');
|
||||
@@ -52,7 +53,7 @@ suite('Comparers', () => {
|
||||
assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileNamesNumeric
|
||||
// Comparisons with different results than compareFileNamesDefault
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
@@ -61,9 +62,6 @@ suite('Comparers', () => {
|
||||
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
|
||||
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
|
||||
assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order');
|
||||
@@ -75,7 +73,7 @@ suite('Comparers', () => {
|
||||
test('compareFileExtensions', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileExtensionsNumeric
|
||||
// Comparisons with the same results as compareFileExtensionsDefault
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
@@ -118,12 +116,8 @@ suite('Comparers', () => {
|
||||
assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
|
||||
assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
|
||||
|
||||
// Same extension comparison that has the same result as compareFileExtensionsNumeric, but a different result than compareFileNames
|
||||
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
|
||||
assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
|
||||
|
||||
//
|
||||
// Comparisons with different results from compareFileExtensionsNumeric
|
||||
// Comparisons with different results from compareFileExtensionsDefault
|
||||
//
|
||||
|
||||
// name-only comparisions
|
||||
@@ -135,6 +129,7 @@ suite('Comparers', () => {
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale');
|
||||
assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale');
|
||||
assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions');
|
||||
@@ -152,145 +147,139 @@ suite('Comparers', () => {
|
||||
|
||||
});
|
||||
|
||||
test('compareFileNamesNumeric', () => {
|
||||
test('compareFileNamesDefault', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileNames
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNamesNumeric(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileNamesNumeric(null, 'abc') < 0, 'null should be come before real values');
|
||||
assert(compareFileNamesNumeric('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileNamesNumeric('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileNamesNumeric('z', 'A') > 0, 'z comes is after A regardless of case');
|
||||
assert(compareFileNamesNumeric('Z', 'a') > 0, 'Z comes after a regardless of case');
|
||||
assert(compareFileNamesDefault(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileNamesDefault(null, 'abc') < 0, 'null should be come before real values');
|
||||
assert(compareFileNamesDefault('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileNamesDefault('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileNamesDefault('z', 'A') > 0, 'z comes is after A regardless of case');
|
||||
assert(compareFileNamesDefault('Z', 'a') > 0, 'Z comes after a regardless of case');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNamesNumeric('file.ext', 'file.ext') === 0, 'equal full names should be equal');
|
||||
assert(compareFileNamesNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileNamesNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileNamesNumeric('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently');
|
||||
assert(compareFileNamesDefault('file.ext', 'file.ext') === 0, 'equal full names should be equal');
|
||||
assert(compareFileNamesDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileNamesDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileNamesDefault('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently');
|
||||
assert(compareFileNamesDefault('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole filename in locale order');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileNamesNumeric('.abc', '.abc') === 0, 'equal dotfile names should be equal');
|
||||
assert(compareFileNamesNumeric('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
|
||||
assert(compareFileNamesNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileNamesNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
assert(compareFileNamesNumeric('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
|
||||
assert(compareFileNamesDefault('.abc', '.abc') === 0, 'equal dotfile names should be equal');
|
||||
assert(compareFileNamesDefault('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
|
||||
assert(compareFileNamesDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileNamesDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
assert(compareFileNamesDefault('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileNamesNumeric(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileNamesNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileNamesNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileNamesNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
assert(compareFileNamesNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
assert(compareFileNamesDefault(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileNamesDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileNamesDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileNamesDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
assert(compareFileNamesDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNamesNumeric('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileNamesNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileNamesNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileNamesNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileNamesNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileNamesNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
assert(compareFileNamesDefault('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileNamesDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileNamesDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileNamesDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileNamesDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileNamesDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileNames
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNamesNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
|
||||
assert(compareFileNamesNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesNumeric), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// name plus extensions comparisons
|
||||
assert(compareFileNamesNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'compares the name first, then the extension');
|
||||
assert(compareFileNamesDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
|
||||
assert(compareFileNamesDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesDefault), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNamesNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first');
|
||||
assert(compareFileNamesNumeric('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first');
|
||||
assert(compareFileNamesNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
assert(compareFileNamesDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first');
|
||||
assert(compareFileNamesDefault('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first');
|
||||
assert(compareFileNamesDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
|
||||
});
|
||||
|
||||
test('compareFileExtensionsNumeric', () => {
|
||||
test('compareFileExtensionsDefault', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same result as compareFileExtensions
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensionsNumeric(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileExtensionsNumeric(null, 'abc') < 0, 'null should come before real files without extensions');
|
||||
assert(compareFileExtensionsNumeric('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileExtensionsNumeric('z', 'A') > 0, 'z comes after A');
|
||||
assert(compareFileExtensionsNumeric('Z', 'a') > 0, 'Z comes after a');
|
||||
assert(compareFileExtensionsDefault(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileExtensionsDefault(null, 'abc') < 0, 'null should come before real files without extensions');
|
||||
assert(compareFileExtensionsDefault('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileExtensionsDefault('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileExtensionsDefault('z', 'A') > 0, 'z comes after A');
|
||||
assert(compareFileExtensionsDefault('Z', 'a') > 0, 'Z comes after a');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensionsNumeric('file.ext', 'file.ext') === 0, 'equal full filenames should be equal');
|
||||
assert(compareFileExtensionsNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileExtensionsNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileExtensionsNumeric('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first');
|
||||
assert(compareFileExtensionsNumeric('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
|
||||
assert(compareFileExtensionsNumeric('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
|
||||
assert(compareFileExtensionsNumeric('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
|
||||
assert(compareFileExtensionsDefault('file.ext', 'file.ext') === 0, 'equal full filenames should be equal');
|
||||
assert(compareFileExtensionsDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileExtensionsDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileExtensionsDefault('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first');
|
||||
assert(compareFileExtensionsDefault('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
|
||||
assert(compareFileExtensionsDefault('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.abc', '.abc') === 0, 'equal dotfiles should be equal');
|
||||
assert(compareFileExtensionsNumeric('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
|
||||
assert(compareFileExtensionsDefault('.abc', '.abc') === 0, 'equal dotfiles should be equal');
|
||||
assert(compareFileExtensionsDefault('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileExtensionsNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileExtensionsNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
assert(compareFileExtensionsDefault(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileExtensionsDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileExtensionsDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensionsNumeric('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order');
|
||||
assert(compareFileExtensionsNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileExtensionsNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
assert(compareFileExtensionsNumeric('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
|
||||
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileExtensionsNumeric('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
|
||||
assert(compareFileExtensionsNumeric('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
|
||||
|
||||
// Same extension comparison that has the same result as compareFileExtensions, but a different result than compareFileNames
|
||||
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
|
||||
assert(compareFileExtensionsNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
|
||||
assert(compareFileExtensionsDefault('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileExtensionsDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileExtensionsDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order');
|
||||
assert(compareFileExtensionsDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileExtensionsDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
assert(compareFileExtensionsDefault('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
|
||||
assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsDefault('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileExtensionsDefault('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
|
||||
assert(compareFileExtensionsDefault('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileExtensions
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensionsNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
|
||||
assert(compareFileExtensionsNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsNumeric), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
|
||||
assert(compareFileExtensionsDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
|
||||
assert(compareFileExtensionsDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsDefault), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensionsNumeric('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale');
|
||||
assert(compareFileExtensionsNumeric('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale');
|
||||
assert(compareFileExtensionsDefault('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale');
|
||||
assert(compareFileExtensionsDefault('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale');
|
||||
assert(compareFileExtensionsDefault('aggregate.go', 'aggregate_repo.go') > 0, 'names with the same extension sort in full filename locale order');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileExtensionsNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
assert(compareFileExtensionsDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileExtensionsDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileExtensionsNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
assert(compareFileExtensionsDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileExtensionsDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensionsNumeric('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order');
|
||||
assert(compareFileExtensionsNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
assert(compareFileExtensionsNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first');
|
||||
assert(compareFileExtensionsNumeric('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first');
|
||||
assert(compareFileExtensionsDefault('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order');
|
||||
assert(compareFileExtensionsDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
assert(compareFileExtensionsDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first');
|
||||
assert(compareFileExtensionsDefault('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,6 +93,22 @@ suite('dom', () => {
|
||||
assert(!div.firstChild);
|
||||
});
|
||||
|
||||
test('should buld nodes with id', () => {
|
||||
const div = $('div#foo');
|
||||
assert(div);
|
||||
assert(div instanceof HTMLElement);
|
||||
assert.equal(div.tagName, 'DIV');
|
||||
assert.equal(div.id, 'foo');
|
||||
});
|
||||
|
||||
test('should buld nodes with class-name', () => {
|
||||
const div = $('div.foo');
|
||||
assert(div);
|
||||
assert(div instanceof HTMLElement);
|
||||
assert.equal(div.tagName, 'DIV');
|
||||
assert.equal(div.className, 'foo');
|
||||
});
|
||||
|
||||
test('should build nodes with attributes', () => {
|
||||
let div = $('div', { class: 'test' });
|
||||
assert.equal(div.className, 'test');
|
||||
@@ -111,5 +127,12 @@ suite('dom', () => {
|
||||
|
||||
assert.equal(div.firstChild && div.firstChild.textContent, 'hello');
|
||||
});
|
||||
|
||||
test('should build nodes with text children', () => {
|
||||
let div = $('div', undefined, 'foobar');
|
||||
let firstChild = div.firstChild as HTMLElement;
|
||||
assert.equal(firstChild.tagName, undefined);
|
||||
assert.equal(firstChild.textContent, 'foobar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ suite('MarkdownRenderer', () => {
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
@@ -29,7 +28,6 @@ suite('MarkdownRenderer', () => {
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
|
||||
@@ -110,10 +110,10 @@ suite('Fuzzy Scorer', () => {
|
||||
scores.push(_doScore(target, 'hw', true)); // direct mix-case prefix (multiple)
|
||||
scores.push(_doScore(target, 'H', true)); // direct case prefix
|
||||
scores.push(_doScore(target, 'h', true)); // direct mix-case prefix
|
||||
scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)
|
||||
scores.push(_doScore(target, 'W', true)); // direct case word prefix
|
||||
scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix
|
||||
scores.push(_doScore(target, 'Ld', true)); // in-string case match (multiple)
|
||||
scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)
|
||||
scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix
|
||||
scores.push(_doScore(target, 'L', true)); // in-string case match
|
||||
scores.push(_doScore(target, 'l', true)); // in-string mix-case match
|
||||
scores.push(_doScore(target, '4', true)); // no match
|
||||
@@ -123,13 +123,13 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.deepEqual(scores, sortedScores);
|
||||
|
||||
// Assert scoring positions
|
||||
let positions = scores[0][1];
|
||||
assert.equal(positions.length, 'HelLo-World'.length);
|
||||
// let positions = scores[0][1];
|
||||
// assert.equal(positions.length, 'HelLo-World'.length);
|
||||
|
||||
positions = scores[2][1];
|
||||
assert.equal(positions.length, 'HW'.length);
|
||||
assert.equal(positions[0], 0);
|
||||
assert.equal(positions[1], 6);
|
||||
// positions = scores[2][1];
|
||||
// assert.equal(positions.length, 'HW'.length);
|
||||
// assert.equal(positions[0], 0);
|
||||
// assert.equal(positions[1], 6);
|
||||
});
|
||||
|
||||
test('score (non fuzzy)', function () {
|
||||
@@ -626,6 +626,21 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer camel case matches', function () {
|
||||
const resourceA = URI.file('config/test/NullPointerException.java');
|
||||
const resourceB = URI.file('config/test/nopointerexception.java');
|
||||
|
||||
for (const query of ['npe', 'NPE']) {
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
}
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer more compact camel case matches', function () {
|
||||
const resourceA = URI.file('config/test/openthisAnythingHandler.js');
|
||||
const resourceB = URI.file('config/test/openthisisnotsorelevantforthequeryAnyHand.js');
|
||||
@@ -925,6 +940,91 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter match (bug #103052) - foo bar', function () {
|
||||
const resourceA = URI.file('app/emails/foo.bar.js');
|
||||
const resourceB = URI.file('app/emails/other-footer.other-bar.js');
|
||||
|
||||
for (const query of ['foo bar', 'foobar']) {
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
}
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter match (bug #103052) - payment model', function () {
|
||||
const resourceA = URI.file('app/components/payment/payment.model.js');
|
||||
const resourceB = URI.file('app/components/online-payments-history/online-payments-history.model.js');
|
||||
|
||||
for (const query of ['payment model', 'paymentmodel']) {
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
}
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter match (bug #103052) - color', function () {
|
||||
const resourceA = URI.file('app/constants/color.js');
|
||||
const resourceB = URI.file('app/components/model/input/pick-avatar-color.js');
|
||||
|
||||
for (const query of ['color js', 'colorjs']) {
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
}
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer strict case prefix', function () {
|
||||
const resourceA = URI.file('app/constants/color.js');
|
||||
const resourceB = URI.file('app/components/model/input/Color.js');
|
||||
|
||||
let query = 'Color';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
query = 'color';
|
||||
|
||||
res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer prefix (bug #103052)', function () {
|
||||
const resourceA = URI.file('test/smoke/src/main.ts');
|
||||
const resourceB = URI.file('src/vs/editor/common/services/semantikTokensProviderStyling.ts');
|
||||
|
||||
let query = 'smoke main.ts';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
});
|
||||
|
||||
test('prepareQuery', () => {
|
||||
assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa');
|
||||
assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts');
|
||||
|
||||
@@ -32,12 +32,18 @@ suite('Hash', () => {
|
||||
assert.equal(hash([1, 2, 3]), hash([1, 2, 3]));
|
||||
assert.equal(hash(['foo', 'bar']), hash(['foo', 'bar']));
|
||||
assert.equal(hash([]), hash([]));
|
||||
assert.equal(hash([]), hash(new Array()));
|
||||
assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo']));
|
||||
assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo', null]));
|
||||
assert.notEqual(hash(['foo', 'bar', null]), hash(['bar', 'foo', null]));
|
||||
assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo', undefined]));
|
||||
assert.notEqual(hash(['foo', 'bar', undefined]), hash(['bar', 'foo', undefined]));
|
||||
assert.notEqual(hash(['foo', 'bar', null]), hash(['foo', 'bar', undefined]));
|
||||
});
|
||||
|
||||
test('object', () => {
|
||||
assert.equal(hash({}), hash({}));
|
||||
assert.equal(hash({}), hash(Object.create(null)));
|
||||
assert.equal(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar' }));
|
||||
assert.equal(hash({ 'foo': 'bar', 'foo2': undefined }), hash({ 'foo2': undefined, 'foo': 'bar' }));
|
||||
assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' }));
|
||||
@@ -45,14 +51,26 @@ suite('Hash', () => {
|
||||
});
|
||||
|
||||
test('array - unexpected collision', function () {
|
||||
this.skip();
|
||||
const a = hash([undefined, undefined, undefined, undefined, undefined]);
|
||||
const b = hash([undefined, undefined, 'HHHHHH', [{ line: 0, character: 0 }, { line: 0, character: 0 }], undefined]);
|
||||
// console.log(a);
|
||||
// console.log(b);
|
||||
assert.notEqual(a, b);
|
||||
});
|
||||
|
||||
test('all different', () => {
|
||||
const candidates: any[] = [
|
||||
null, undefined, {}, [], 0, false, true, '', ' ', [null], [undefined], [undefined, undefined], { '': undefined }, { [' ']: undefined },
|
||||
'ab', 'ba', ['ab']
|
||||
];
|
||||
const hashes: number[] = candidates.map(hash);
|
||||
for (let i = 0; i < hashes.length; i++) {
|
||||
assert.equal(hashes[i], hash(candidates[i])); // verify that repeated invocation returns the same hash
|
||||
for (let k = i + 1; k < hashes.length; k++) {
|
||||
assert.notEqual(hashes[i], hashes[k], `Same hash ${hashes[i]} for ${JSON.stringify(candidates[i])} and ${JSON.stringify(candidates[k])}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function checkSHA1(strings: string[], expected: string) {
|
||||
const hash = new StringSHA1();
|
||||
for (const str of strings) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { IDisposable, dispose, ReferenceCollection } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, dispose, IDisposable, MultiDisposeError, ReferenceCollection, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
class Disposable implements IDisposable {
|
||||
isDisposed = false;
|
||||
@@ -49,6 +49,48 @@ suite('Lifecycle', () => {
|
||||
assert(disposable2.isDisposed);
|
||||
});
|
||||
|
||||
test('dispose array should dispose all if a child throws on dispose', () => {
|
||||
const disposedValues = new Set<number>();
|
||||
|
||||
let thrownError: any;
|
||||
try {
|
||||
dispose([
|
||||
toDisposable(() => { disposedValues.add(1); }),
|
||||
toDisposable(() => { throw new Error('I am error'); }),
|
||||
toDisposable(() => { disposedValues.add(3); }),
|
||||
]);
|
||||
} catch (e) {
|
||||
thrownError = e;
|
||||
}
|
||||
|
||||
assert.ok(disposedValues.has(1));
|
||||
assert.ok(disposedValues.has(3));
|
||||
assert.strictEqual(thrownError.message, 'I am error');
|
||||
});
|
||||
|
||||
test('dispose array should rethrow composite error if multiple entries throw on dispose', () => {
|
||||
const disposedValues = new Set<number>();
|
||||
|
||||
let thrownError: any;
|
||||
try {
|
||||
dispose([
|
||||
toDisposable(() => { disposedValues.add(1); }),
|
||||
toDisposable(() => { throw new Error('I am error 1'); }),
|
||||
toDisposable(() => { throw new Error('I am error 2'); }),
|
||||
toDisposable(() => { disposedValues.add(4); }),
|
||||
]);
|
||||
} catch (e) {
|
||||
thrownError = e;
|
||||
}
|
||||
|
||||
assert.ok(disposedValues.has(1));
|
||||
assert.ok(disposedValues.has(4));
|
||||
assert.ok(thrownError instanceof MultiDisposeError);
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors.length, 2);
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors[0].message, 'I am error 1');
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors[1].message, 'I am error 2');
|
||||
});
|
||||
|
||||
test('Action bar has broken accessibility #100273', function () {
|
||||
let array = [{ dispose() { } }, { dispose() { } }];
|
||||
let array2 = dispose(array);
|
||||
@@ -61,7 +103,52 @@ suite('Lifecycle', () => {
|
||||
let setValues = set.values();
|
||||
let setValues2 = dispose(setValues);
|
||||
assert.ok(setValues === setValues2);
|
||||
});
|
||||
});
|
||||
|
||||
suite('DisposableStore', () => {
|
||||
test('dispose should call all child disposes even if a child throws on dispose', () => {
|
||||
const disposedValues = new Set<number>();
|
||||
|
||||
const store = new DisposableStore();
|
||||
store.add(toDisposable(() => { disposedValues.add(1); }));
|
||||
store.add(toDisposable(() => { throw new Error('I am error'); }));
|
||||
store.add(toDisposable(() => { disposedValues.add(3); }));
|
||||
|
||||
let thrownError: any;
|
||||
try {
|
||||
store.dispose();
|
||||
} catch (e) {
|
||||
thrownError = e;
|
||||
}
|
||||
|
||||
assert.ok(disposedValues.has(1));
|
||||
assert.ok(disposedValues.has(3));
|
||||
assert.strictEqual(thrownError.message, 'I am error');
|
||||
});
|
||||
|
||||
test('dispose should throw composite error if multiple children throw on dispose', () => {
|
||||
const disposedValues = new Set<number>();
|
||||
|
||||
const store = new DisposableStore();
|
||||
store.add(toDisposable(() => { disposedValues.add(1); }));
|
||||
store.add(toDisposable(() => { throw new Error('I am error 1'); }));
|
||||
store.add(toDisposable(() => { throw new Error('I am error 2'); }));
|
||||
store.add(toDisposable(() => { disposedValues.add(4); }));
|
||||
|
||||
let thrownError: any;
|
||||
try {
|
||||
store.dispose();
|
||||
} catch (e) {
|
||||
thrownError = e;
|
||||
}
|
||||
|
||||
assert.ok(disposedValues.has(1));
|
||||
assert.ok(disposedValues.has(4));
|
||||
assert.ok(thrownError instanceof MultiDisposeError);
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors.length, 2);
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors[0].message, 'I am error 1');
|
||||
assert.strictEqual((thrownError as MultiDisposeError).errors[1].message, 'I am error 2');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -224,7 +224,6 @@ suite('PFS', function () {
|
||||
}
|
||||
catch (error) {
|
||||
assert.fail(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<script>
|
||||
// NOTE: Changes to inline scripts require update of content security policy
|
||||
self.require = {
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
recordStats: true,
|
||||
@@ -101,7 +100,6 @@
|
||||
globalThis.MonacoPerformanceMarks.push('willLoadWorkbenchMain', Date.now());
|
||||
</script>
|
||||
<script>
|
||||
// NOTE: Changes to inline scripts require update of content security policy
|
||||
require(['vs/code/browser/workbench/workbench'], function() {});
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<script>
|
||||
// NOTE: Changes to inline scripts require update of content security policy
|
||||
self.require = {
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
recordStats: true,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace } from 'vs/workbench/workbench.web.api';
|
||||
import { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, ICommand, IHomeIndicator, IProductQualityChangeHandler } from 'vs/workbench/workbench.web.api';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { streamToBuffer } from 'vs/base/common/buffer';
|
||||
@@ -276,6 +279,51 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
class WindowIndicator implements IWindowIndicator {
|
||||
|
||||
readonly onDidChange = Event.None;
|
||||
|
||||
readonly label: string;
|
||||
readonly tooltip: string;
|
||||
readonly command: string | undefined;
|
||||
|
||||
readonly commandImpl: ICommand | undefined = undefined;
|
||||
|
||||
constructor(workspace: IWorkspace) {
|
||||
let repositoryOwner: string | undefined = undefined;
|
||||
let repositoryName: string | undefined = undefined;
|
||||
|
||||
if (workspace) {
|
||||
let uri: URI | undefined = undefined;
|
||||
if (isFolderToOpen(workspace)) {
|
||||
uri = workspace.folderUri;
|
||||
} else if (isWorkspaceToOpen(workspace)) {
|
||||
uri = workspace.workspaceUri;
|
||||
}
|
||||
|
||||
if (uri?.scheme === 'github' || uri?.scheme === 'codespace') {
|
||||
[repositoryOwner, repositoryName] = uri.authority.split('+');
|
||||
}
|
||||
}
|
||||
|
||||
if (repositoryName && repositoryOwner) {
|
||||
this.label = localize('openInDesktopLabel', "$(remote) Open in Desktop");
|
||||
this.tooltip = localize('openInDesktopTooltip', "Open in Desktop");
|
||||
this.command = '_web.openInDesktop';
|
||||
this.commandImpl = {
|
||||
id: this.command,
|
||||
handler: () => {
|
||||
const protocol = product.quality === 'stable' ? 'vscode' : 'vscode-insiders';
|
||||
window.open(`${protocol}://vscode.git/clone?url=${encodeURIComponent(`https://github.com/${repositoryOwner}/${repositoryName}.git`)}`);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.label = localize('playgroundLabel', "Web Playground");
|
||||
this.tooltip = this.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
|
||||
// Find config by checking for DOM
|
||||
@@ -343,14 +391,44 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Home Indicator
|
||||
const homeIndicator: IHomeIndicator = {
|
||||
href: 'https://github.com/Microsoft/vscode',
|
||||
icon: 'code',
|
||||
title: localize('home', "Home")
|
||||
};
|
||||
|
||||
// Commands
|
||||
const commands: ICommand[] = [];
|
||||
|
||||
// Window indicator
|
||||
const windowIndicator = new WindowIndicator(workspace);
|
||||
if (windowIndicator.commandImpl) {
|
||||
commands.push(windowIndicator.commandImpl);
|
||||
}
|
||||
|
||||
// Product Quality Change Handler
|
||||
const productQualityChangeHandler: IProductQualityChangeHandler = (quality) => {
|
||||
let queryString = `quality=${quality}`;
|
||||
|
||||
// Save all other query params we might have
|
||||
const query = new URL(document.location.href).searchParams;
|
||||
query.forEach((value, key) => {
|
||||
if (key !== 'quality') {
|
||||
queryString += `&${key}=${value}`;
|
||||
}
|
||||
});
|
||||
|
||||
window.location.href = `${window.location.origin}?${queryString}`;
|
||||
};
|
||||
|
||||
// Finally create workbench
|
||||
create(document.body, {
|
||||
...config,
|
||||
homeIndicator: {
|
||||
href: 'https://github.com/Microsoft/vscode',
|
||||
icon: 'code',
|
||||
title: localize('home', "Home")
|
||||
},
|
||||
homeIndicator,
|
||||
commands,
|
||||
windowIndicator,
|
||||
productQualityChangeHandler,
|
||||
workspaceProvider: new WorkspaceProvider(workspace, payload),
|
||||
urlCallbackProvider: new PollingURLCallbackProvider(),
|
||||
credentialsProvider: new LocalStorageCredentialsProvider()
|
||||
|
||||
@@ -82,6 +82,10 @@ import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMai
|
||||
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
|
||||
import { createServer, AddressInfo } from 'net';
|
||||
import { IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { stripComments } from 'vs/base/common/json';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
@@ -134,11 +138,6 @@ export class CodeApplication extends Disposable {
|
||||
//
|
||||
// !!! DO NOT CHANGE without consulting the documentation !!!
|
||||
//
|
||||
app.on('remote-get-guest-web-contents', event => {
|
||||
this.logService.trace('App#on(remote-get-guest-web-contents): prevented');
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-require', (event, sender, module) => {
|
||||
this.logService.trace('App#on(remote-require): prevented');
|
||||
|
||||
@@ -807,7 +806,7 @@ export class CodeApplication extends Disposable {
|
||||
return { fileUri: URI.file(path) };
|
||||
}
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
private async afterWindowOpen(accessor: ServicesAccessor): Promise<void> {
|
||||
|
||||
// Signal phase: after window open
|
||||
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
|
||||
@@ -820,6 +819,34 @@ export class CodeApplication extends Disposable {
|
||||
if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) {
|
||||
updateService.initialize();
|
||||
}
|
||||
|
||||
// If enable-crash-reporter argv is undefined then this is a fresh start,
|
||||
// based on telemetry.enableCrashreporter settings, generate a UUID which
|
||||
// will be used as crash reporter id and also update the json file.
|
||||
try {
|
||||
const fileService = accessor.get(IFileService);
|
||||
const argvContent = await fileService.readFile(this.environmentService.argvResource);
|
||||
const argvString = argvContent.value.toString();
|
||||
const argvJSON = JSON.parse(stripComments(argvString));
|
||||
if (argvJSON['enable-crash-reporter'] === undefined) {
|
||||
const enableCrashReporter = this.configurationService.getValue<boolean>('telemetry.enableCrashReporter') ?? true;
|
||||
const additionalArgvContent = [
|
||||
'',
|
||||
' // Allows to disable crash reporting.',
|
||||
' // Should restart the app if the value is changed.',
|
||||
` "enable-crash-reporter": ${enableCrashReporter},`,
|
||||
'',
|
||||
' // Unique id used for correlating crash reports sent from this instance.',
|
||||
' // Do not edit this value.',
|
||||
` "crash-reporter-id": "${generateUuid()}"`,
|
||||
'}'
|
||||
];
|
||||
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
|
||||
await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private handleRemoteAuthorities(): void {
|
||||
|
||||
@@ -289,7 +289,8 @@ class CodeMain {
|
||||
|
||||
// Process Info
|
||||
if (args.status) {
|
||||
return instantiationService.invokeFunction(async accessor => {
|
||||
return instantiationService.invokeFunction(async () => {
|
||||
|
||||
// Create a diagnostic service connected to the existing shared process
|
||||
const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main');
|
||||
const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics');
|
||||
@@ -357,7 +358,10 @@ class CodeMain {
|
||||
}
|
||||
|
||||
private showStartupWarningDialog(message: string, detail: string): void {
|
||||
dialog.showMessageBox({
|
||||
// use sync variant here because we likely exit after this method
|
||||
// due to startup issues and otherwise the dialog seems to disappear
|
||||
// https://github.com/microsoft/vscode/issues/104493
|
||||
dialog.showMessageBoxSync({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
|
||||
@@ -553,7 +553,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
similarIssues.innerHTML = '';
|
||||
similarIssues.innerText = '';
|
||||
this.numberOfSearchResultsDisplayed = 0;
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
|
||||
response.json().then(result => {
|
||||
similarIssues.innerHTML = '';
|
||||
similarIssues.innerText = '';
|
||||
if (result && result.items) {
|
||||
this.displaySearchResults(result.items);
|
||||
} else {
|
||||
@@ -713,7 +713,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
sourceSelect.innerHTML = '';
|
||||
sourceSelect.innerText = '';
|
||||
if (issueType === IssueType.FeatureRequest) {
|
||||
sourceSelect.append(...[
|
||||
this.makeOption('', localize('selectSource', "Select source"), true),
|
||||
@@ -812,11 +812,14 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private validateInput(inputId: string): boolean {
|
||||
const inputElement = (<HTMLInputElement>this.getElementById(inputId));
|
||||
const inputValidationMessage = this.getElementById(`${inputId}-empty-error`);
|
||||
if (!inputElement.value) {
|
||||
inputElement.classList.add('invalid-input');
|
||||
inputValidationMessage?.classList.remove('hidden');
|
||||
return false;
|
||||
} else {
|
||||
inputElement.classList.remove('invalid-input');
|
||||
inputValidationMessage?.classList.add('hidden');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1079,7 +1082,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void {
|
||||
const target = document.querySelector('.block-extensions .block-info');
|
||||
const target = document.querySelector<HTMLElement>('.block-extensions .block-info');
|
||||
if (target) {
|
||||
if (this.configuration.disableExtensions) {
|
||||
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
|
||||
@@ -1090,7 +1093,7 @@ export class IssueReporter extends Disposable {
|
||||
extensions = extensions || [];
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none' + themeExclusionStr;
|
||||
target.innerText = 'Extensions: none' + themeExclusionStr;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1100,10 +1103,10 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private updateSearchedExtensionTable(extensions: IssueReporterExtensionData[]): void {
|
||||
const target = document.querySelector('.block-searchedExtensions .block-info');
|
||||
const target = document.querySelector<HTMLElement>('.block-searchedExtensions .block-info');
|
||||
if (target) {
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none';
|
||||
target.innerText = 'Extensions: none';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export default (): string => `
|
||||
<select id="issue-source" class="inline-form-control" required>
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
<div id="issue-source-empty-error" class="validation-error hidden" role="alert">${escape(localize('issueSourceEmptyValidation', "An issue source is required."))}</div>
|
||||
<div id="problem-source-help-text" class="instructions hidden">${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}. If the problem only reproduces when extensions are active, it is likely an issue with an extension."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="disableExtensions" class="workbenchCommand">${escape(localize('disableExtensions', "disabling all extensions and reloading the window"))}</span>`)}
|
||||
</div>
|
||||
@@ -43,6 +44,7 @@ export default (): string => `
|
||||
<div class="input-group">
|
||||
<label class="inline-label" for="issue-title">${escape(localize('issueTitleLabel', "Title"))} <span class="required-input">*</span></label>
|
||||
<input id="issue-title" type="text" class="inline-form-control" placeholder="${escape(localize('issueTitleRequired', "Please enter a title."))}" required>
|
||||
<div id="issue-title-empty-error" class="validation-error hidden" role="alert">${escape(localize('titleEmptyValidation', "A title is required."))}</div>
|
||||
<div id="issue-title-length-validation-error" class="validation-error hidden" role="alert">${escape(localize('titleLengthValidation', "The title is too long."))}</div>
|
||||
<small id="similar-issues">
|
||||
<!-- To be dynamically filled -->
|
||||
@@ -61,6 +63,7 @@ export default (): string => `
|
||||
<div class="block-info-text">
|
||||
<textarea name="description" id="description" placeholder="${escape(localize('details', "Please enter details."))}" required></textarea>
|
||||
</div>
|
||||
<div id="description-empty-error" class="validation-error hidden" role="alert">${escape(localize('descriptionEmptyValidation', "A description is required."))}</div>
|
||||
</div>
|
||||
|
||||
<div class="system-info" id="block-container">
|
||||
|
||||
@@ -201,9 +201,10 @@ select, input, textarea {
|
||||
}
|
||||
|
||||
|
||||
.validation-error {
|
||||
#issue-reporter .validation-error {
|
||||
font-size: 12px;
|
||||
margin-top: 1em;
|
||||
padding: 10px;
|
||||
border-top: 0px !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -256,8 +257,7 @@ a {
|
||||
}
|
||||
|
||||
.section .input-group .validation-error {
|
||||
margin-left: calc(15% + 5px);
|
||||
padding: 10px;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.section .inline-form-control, .section .inline-label {
|
||||
@@ -268,7 +268,7 @@ a {
|
||||
width: 95px;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
.section .inline-form-control, .section .input-group .validation-error {
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
|
||||
@@ -294,9 +294,13 @@ a {
|
||||
margin-left: calc(15% + 1em);
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
.section .inline-form-control, .section .input-group .validation-error {
|
||||
width: calc(85% - 5px);
|
||||
}
|
||||
|
||||
.section .input-group .validation-error {
|
||||
margin-left: calc(15% + 4px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
@@ -308,7 +312,7 @@ a {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
.section .inline-form-control, .section .input-group .validation-error {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ class ProcessExplorer {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
container.innerText = '';
|
||||
this.listeners.clear();
|
||||
|
||||
const tableHead = document.createElement('thead');
|
||||
|
||||
@@ -124,12 +124,12 @@ class DomCharWidthReader {
|
||||
|
||||
private static _render(testElement: HTMLElement, request: CharWidthRequest): void {
|
||||
if (request.chr === ' ') {
|
||||
let htmlString = ' ';
|
||||
let htmlString = '\u00a0';
|
||||
// Repeat character 256 (2^8) times
|
||||
for (let i = 0; i < 8; i++) {
|
||||
htmlString += htmlString;
|
||||
}
|
||||
testElement.innerHTML = htmlString;
|
||||
testElement.innerText = htmlString;
|
||||
} else {
|
||||
let testString = request.chr;
|
||||
// Repeat character 256 (2^8) times
|
||||
|
||||
@@ -559,9 +559,9 @@ export namespace CoreNavigationCommands {
|
||||
case CursorMove_.Direction.ViewPortCenter:
|
||||
case CursorMove_.Direction.ViewPortIfOutside:
|
||||
return CursorMoveCommands.viewportMove(viewModel, cursors, args.direction, inSelectionMode, value);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,14 +178,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
mode
|
||||
};
|
||||
},
|
||||
|
||||
getScreenReaderContent: (currentState: TextAreaState): TextAreaState => {
|
||||
|
||||
if (browser.isIPad) {
|
||||
// Do not place anything in the textarea for the iPad
|
||||
return TextAreaState.EMPTY;
|
||||
}
|
||||
|
||||
if (this._accessibilitySupport === AccessibilitySupport.Disabled) {
|
||||
// We know for a fact that a screen reader is not attached
|
||||
// On OSX, we write the character before the cursor to allow for "long-press" composition
|
||||
|
||||
@@ -580,6 +580,11 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
*/
|
||||
getRawOptions(): IEditorOptions;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getOverflowWidgetsDomNode(): HTMLElement | undefined;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -1055,3 +1060,14 @@ export function getCodeEditor(thing: any): ICodeEditor | null {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*@internal
|
||||
*/
|
||||
export function getIEditor(thing: any): editorCommon.IEditor | null {
|
||||
if (isCodeEditor(thing) || isDiffEditor(thing)) {
|
||||
return thing;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -109,8 +109,9 @@ export class ViewController {
|
||||
return data.ctrlKey;
|
||||
case 'metaKey':
|
||||
return data.metaKey;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _hasNonMulticursorModifier(data: IMouseDispatchData): boolean {
|
||||
@@ -121,8 +122,9 @@ export class ViewController {
|
||||
return data.altKey || data.metaKey;
|
||||
case 'metaKey':
|
||||
return data.ctrlKey || data.altKey;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public dispatchMouse(data: IMouseDispatchData): void {
|
||||
|
||||
@@ -379,6 +379,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
return this._configuration.getRawOptions();
|
||||
}
|
||||
|
||||
public getOverflowWidgetsDomNode(): HTMLElement | undefined {
|
||||
return this._overflowWidgetsDomNode;
|
||||
}
|
||||
|
||||
public getConfiguredWordAtPosition(position: Position): IWordAtPosition | null {
|
||||
if (!this._modelData) {
|
||||
return null;
|
||||
|
||||
@@ -702,7 +702,7 @@ export class DiffReview extends Disposable {
|
||||
if (originalLine !== 0) {
|
||||
originalLineNumber.appendChild(document.createTextNode(String(originalLine)));
|
||||
} else {
|
||||
originalLineNumber.innerHTML = ' ';
|
||||
originalLineNumber.innerText = '\u00a0';
|
||||
}
|
||||
cell.appendChild(originalLineNumber);
|
||||
|
||||
@@ -714,7 +714,7 @@ export class DiffReview extends Disposable {
|
||||
if (modifiedLine !== 0) {
|
||||
modifiedLineNumber.appendChild(document.createTextNode(String(modifiedLine)));
|
||||
} else {
|
||||
modifiedLineNumber.innerHTML = ' ';
|
||||
modifiedLineNumber.innerText = '\u00a0';
|
||||
}
|
||||
cell.appendChild(modifiedLineNumber);
|
||||
|
||||
@@ -724,10 +724,10 @@ export class DiffReview extends Disposable {
|
||||
if (spacerIcon) {
|
||||
const spacerCodicon = document.createElement('span');
|
||||
spacerCodicon.className = spacerIcon.classNames;
|
||||
spacerCodicon.innerHTML = '  ';
|
||||
spacerCodicon.innerText = '\u00a0\u00a0';
|
||||
spacer.appendChild(spacerCodicon);
|
||||
} else {
|
||||
spacer.innerHTML = '  ';
|
||||
spacer.innerText = '\u00a0\u00a0';
|
||||
}
|
||||
cell.appendChild(spacer);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget {
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService
|
||||
) {
|
||||
super(domElement, parentEditor.getRawOptions(), {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService);
|
||||
super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService);
|
||||
|
||||
this._parentEditor = parentEditor;
|
||||
this._overwriteOptions = options;
|
||||
|
||||
@@ -1286,6 +1286,10 @@ class EditorEmptySelectionClipboard extends EditorBooleanOption<EditorOption.emp
|
||||
* Configuration options for editor find widget
|
||||
*/
|
||||
export interface IEditorFindOptions {
|
||||
/**
|
||||
* Controls whether the cursor should move to find matches while typing.
|
||||
*/
|
||||
cursorMoveOnType?: boolean;
|
||||
/**
|
||||
* Controls if we seed search string in the Find Widget with editor selection.
|
||||
*/
|
||||
@@ -1315,6 +1319,7 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
|
||||
constructor() {
|
||||
const defaults: EditorFindOptions = {
|
||||
cursorMoveOnType: true,
|
||||
seedSearchStringFromSelection: true,
|
||||
autoFindInSelection: 'never',
|
||||
globalFindClipboard: false,
|
||||
@@ -1324,6 +1329,11 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
super(
|
||||
EditorOption.find, 'find', defaults,
|
||||
{
|
||||
'editor.find.cursorMoveOnType': {
|
||||
type: 'boolean',
|
||||
default: defaults.cursorMoveOnType,
|
||||
description: nls.localize('find.cursorMoveOnType', "Controls whether the cursor should jump to find matches while typing.")
|
||||
},
|
||||
'editor.find.seedSearchStringFromSelection': {
|
||||
type: 'boolean',
|
||||
default: defaults.seedSearchStringFromSelection,
|
||||
@@ -1367,6 +1377,7 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
}
|
||||
const input = _input as IEditorFindOptions;
|
||||
return {
|
||||
cursorMoveOnType: EditorBooleanOption.boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType),
|
||||
seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection),
|
||||
autoFindInSelection: typeof _input.autoFindInSelection === 'boolean'
|
||||
? (_input.autoFindInSelection ? 'always' : 'never')
|
||||
|
||||
@@ -317,9 +317,10 @@ export class CursorMoveCommands {
|
||||
// Move to the last non-whitespace column of the current view line
|
||||
return this._moveToViewLastNonWhitespaceColumn(viewModel, cursors, inSelectionMode);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static viewportMove(viewModel: IViewModel, cursors: CursorState[], direction: CursorMove.ViewportDirection, inSelectionMode: boolean, value: number): PartialCursorState[] | null {
|
||||
@@ -353,9 +354,9 @@ export class CursorMoveCommands {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static findPositionInViewportIfOutside(viewModel: IViewModel, cursor: CursorState, visibleViewRange: Range, inSelectionMode: boolean): PartialCursorState {
|
||||
|
||||
@@ -800,7 +800,7 @@ export interface ITextModel {
|
||||
/**
|
||||
* Search the model.
|
||||
* @param searchString The string used to search. If it is a regular expression, set `isRegex` to true.
|
||||
* @param searchScope Limit the searching to only search inside this range.
|
||||
* @param searchScope Limit the searching to only search inside these ranges.
|
||||
* @param isRegex Used to indicate that `searchString` is a regular expression.
|
||||
* @param matchCase Force the matching to match lower/upper case exactly.
|
||||
* @param wordSeparators Force the matching to match entire words only. Pass null otherwise.
|
||||
@@ -808,7 +808,7 @@ export interface ITextModel {
|
||||
* @param limitResultCount Limit the number of results
|
||||
* @return The ranges where the matches are. It is empty if no matches have been found.
|
||||
*/
|
||||
findMatches(searchString: string, searchScope: IRange, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[];
|
||||
findMatches(searchString: string, searchScope: IRange | IRange[], isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[];
|
||||
/**
|
||||
* Search the model for the next match. Loops to the beginning of the model if needed.
|
||||
* @param searchString The string used to search. If it is a regular expression, set `isRegex` to true.
|
||||
|
||||
@@ -214,8 +214,9 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
|
||||
return '\r\n';
|
||||
case EndOfLinePreference.TextDefined:
|
||||
return this.getEOL();
|
||||
default:
|
||||
throw new Error('Unknown EOL preference');
|
||||
}
|
||||
throw new Error('Unknown EOL preference');
|
||||
}
|
||||
|
||||
public setEOL(newEOL: '\r\n' | '\n'): void {
|
||||
|
||||
@@ -1121,13 +1121,35 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
|
||||
this._assertNotDisposed();
|
||||
|
||||
let searchRange: Range;
|
||||
if (Range.isIRange(rawSearchScope)) {
|
||||
searchRange = this.validateRange(rawSearchScope);
|
||||
} else {
|
||||
searchRange = this.getFullModelRange();
|
||||
let searchRanges: Range[] | null = null;
|
||||
|
||||
if (rawSearchScope !== null) {
|
||||
if (!Array.isArray(rawSearchScope)) {
|
||||
rawSearchScope = [rawSearchScope];
|
||||
}
|
||||
|
||||
if (rawSearchScope.every((searchScope: Range) => Range.isIRange(searchScope))) {
|
||||
searchRanges = rawSearchScope.map((searchScope: Range) => this.validateRange(searchScope));
|
||||
}
|
||||
}
|
||||
|
||||
if (searchRanges === null) {
|
||||
searchRanges = [this.getFullModelRange()];
|
||||
}
|
||||
|
||||
searchRanges = searchRanges.sort((d1, d2) => d1.startLineNumber - d2.startLineNumber || d1.startColumn - d2.startColumn);
|
||||
|
||||
const uniqueSearchRanges: Range[] = [];
|
||||
uniqueSearchRanges.push(searchRanges.reduce((prev, curr) => {
|
||||
if (Range.areIntersecting(prev, curr)) {
|
||||
return prev.plusRange(curr);
|
||||
}
|
||||
|
||||
uniqueSearchRanges.push(prev);
|
||||
return curr;
|
||||
}));
|
||||
|
||||
let matchMapper: (value: Range, index: number, array: Range[]) => model.FindMatch[];
|
||||
if (!isRegex && searchString.indexOf('\n') < 0) {
|
||||
// not regex, not multi line
|
||||
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
|
||||
@@ -1137,10 +1159,12 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
|
||||
matchMapper = (searchRange: Range) => this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
|
||||
} else {
|
||||
matchMapper = (searchRange: Range) => TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
|
||||
return uniqueSearchRanges.map(matchMapper).reduce((arr, matches: model.FindMatch[]) => arr.concat(matches), []);
|
||||
}
|
||||
|
||||
public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null {
|
||||
|
||||
@@ -813,12 +813,12 @@ export interface DocumentHighlightProvider {
|
||||
*/
|
||||
export interface OnTypeRenameProvider {
|
||||
|
||||
stopPattern?: RegExp;
|
||||
wordPattern?: RegExp;
|
||||
|
||||
/**
|
||||
* Provide a list of ranges that can be live-renamed together.
|
||||
*/
|
||||
provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<IRange[]>;
|
||||
provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ ranges: IRange[]; wordPattern?: RegExp; }>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} { height: ${height}px; line-height: ${lineHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;}
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} > a > .codicon { line-height: ${lineHeight}px; font-size: ${fontSize}px; }
|
||||
`;
|
||||
this._styleElement.innerHTML = newStyle;
|
||||
this._styleElement.textContent = newStyle;
|
||||
}
|
||||
|
||||
private _localDispose(): void {
|
||||
@@ -470,5 +470,3 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class CodeLensContentWidget implements IContentWidget {
|
||||
} else {
|
||||
// symbols and commands
|
||||
if (!innerHtml) {
|
||||
innerHtml = ' ';
|
||||
innerHtml = '\u00a0';
|
||||
}
|
||||
this._domNode.innerHTML = innerHtml;
|
||||
if (this._isEmpty && animate) {
|
||||
|
||||
@@ -233,12 +233,22 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
this._state.change({ searchScope: null }, true);
|
||||
} else {
|
||||
if (this._editor.hasModel()) {
|
||||
let selection = this._editor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
let selections = this._editor.getSelections();
|
||||
selections.map(selection => {
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(
|
||||
selection.endLineNumber - 1,
|
||||
this._editor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)
|
||||
);
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
return selection;
|
||||
}
|
||||
return null;
|
||||
}).filter(element => !!element);
|
||||
|
||||
if (selections.length) {
|
||||
this._state.change({ searchScope: selections }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,9 +309,9 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
}
|
||||
|
||||
if (opts.updateSearchScope) {
|
||||
let currentSelection = this._editor.getSelection();
|
||||
if (!currentSelection.isEmpty()) {
|
||||
stateChanges.searchScope = currentSelection;
|
||||
let currentSelections = this._editor.getSelections();
|
||||
if (currentSelections.some(selection => !selection.isEmpty())) {
|
||||
stateChanges.searchScope = currentSelections;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export class FindDecorations implements IDisposable {
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private _decorations: string[];
|
||||
private _overviewRulerApproximateDecorations: string[];
|
||||
private _findScopeDecorationId: string | null;
|
||||
private _findScopeDecorationIds: string[];
|
||||
private _rangeHighlightDecorationId: string | null;
|
||||
private _highlightedDecorationId: string | null;
|
||||
private _startPosition: Position;
|
||||
@@ -26,7 +26,7 @@ export class FindDecorations implements IDisposable {
|
||||
this._editor = editor;
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._findScopeDecorationIds = [];
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
this._startPosition = this._editor.getPosition();
|
||||
@@ -37,7 +37,7 @@ export class FindDecorations implements IDisposable {
|
||||
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._findScopeDecorationIds = [];
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class FindDecorations implements IDisposable {
|
||||
public reset(): void {
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._findScopeDecorationIds = [];
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
}
|
||||
@@ -54,9 +54,22 @@ export class FindDecorations implements IDisposable {
|
||||
return this._decorations.length;
|
||||
}
|
||||
|
||||
/** @deprecated use getFindScopes to support multiple selections */
|
||||
public getFindScope(): Range | null {
|
||||
if (this._findScopeDecorationId) {
|
||||
return this._editor.getModel().getDecorationRange(this._findScopeDecorationId);
|
||||
if (this._findScopeDecorationIds[0]) {
|
||||
return this._editor.getModel().getDecorationRange(this._findScopeDecorationIds[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getFindScopes(): Range[] | null {
|
||||
if (this._findScopeDecorationIds.length) {
|
||||
const scopes = this._findScopeDecorationIds.map(findScopeDecorationId =>
|
||||
this._editor.getModel().getDecorationRange(findScopeDecorationId)
|
||||
).filter(element => !!element);
|
||||
if (scopes.length) {
|
||||
return scopes as Range[];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -133,7 +146,7 @@ export class FindDecorations implements IDisposable {
|
||||
return matchPosition;
|
||||
}
|
||||
|
||||
public set(findMatches: FindMatch[], findScope: Range | null): void {
|
||||
public set(findMatches: FindMatch[], findScopes: Range[] | null): void {
|
||||
this._editor.changeDecorations((accessor) => {
|
||||
|
||||
let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION;
|
||||
@@ -195,12 +208,12 @@ export class FindDecorations implements IDisposable {
|
||||
}
|
||||
|
||||
// Find scope
|
||||
if (this._findScopeDecorationId) {
|
||||
accessor.removeDecoration(this._findScopeDecorationId);
|
||||
this._findScopeDecorationId = null;
|
||||
if (this._findScopeDecorationIds.length) {
|
||||
this._findScopeDecorationIds.forEach(findScopeDecorationId => accessor.removeDecoration(findScopeDecorationId));
|
||||
this._findScopeDecorationIds = [];
|
||||
}
|
||||
if (findScope) {
|
||||
this._findScopeDecorationId = accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION);
|
||||
if (findScopes?.length) {
|
||||
this._findScopeDecorationIds = findScopes.map(findScope => accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -253,8 +266,8 @@ export class FindDecorations implements IDisposable {
|
||||
let result: string[] = [];
|
||||
result = result.concat(this._decorations);
|
||||
result = result.concat(this._overviewRulerApproximateDecorations);
|
||||
if (this._findScopeDecorationId) {
|
||||
result.push(this._findScopeDecorationId);
|
||||
if (this._findScopeDecorationIds.length) {
|
||||
result.push(...this._findScopeDecorationIds);
|
||||
}
|
||||
if (this._rangeHighlightDecorationId) {
|
||||
result.push(this._rangeHighlightDecorationId);
|
||||
|
||||
@@ -169,26 +169,36 @@ export class FindModelBoundToEditorModel {
|
||||
return model.getFullModelRange();
|
||||
}
|
||||
|
||||
private research(moveCursor: boolean, newFindScope?: Range | null): void {
|
||||
let findScope: Range | null = null;
|
||||
private research(moveCursor: boolean, newFindScope?: Range | Range[] | null): void {
|
||||
let findScopes: Range[] | null = null;
|
||||
if (typeof newFindScope !== 'undefined') {
|
||||
findScope = newFindScope;
|
||||
} else {
|
||||
findScope = this._decorations.getFindScope();
|
||||
}
|
||||
if (findScope !== null) {
|
||||
if (findScope.startLineNumber !== findScope.endLineNumber) {
|
||||
if (findScope.endColumn === 1) {
|
||||
findScope = new Range(findScope.startLineNumber, 1, findScope.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(findScope.endLineNumber - 1));
|
||||
if (newFindScope !== null) {
|
||||
if (!Array.isArray(newFindScope)) {
|
||||
findScopes = [newFindScope as Range];
|
||||
} else {
|
||||
// multiline find scope => expand to line starts / ends
|
||||
findScope = new Range(findScope.startLineNumber, 1, findScope.endLineNumber, this._editor.getModel().getLineMaxColumn(findScope.endLineNumber));
|
||||
findScopes = newFindScope;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
findScopes = this._decorations.getFindScopes();
|
||||
}
|
||||
if (findScopes !== null) {
|
||||
findScopes = findScopes.map(findScope => {
|
||||
if (findScope.startLineNumber !== findScope.endLineNumber) {
|
||||
let endLineNumber = findScope.endLineNumber;
|
||||
|
||||
if (findScope.endColumn === 1) {
|
||||
endLineNumber = endLineNumber - 1;
|
||||
}
|
||||
|
||||
return new Range(findScope.startLineNumber, 1, endLineNumber, this._editor.getModel().getLineMaxColumn(endLineNumber));
|
||||
}
|
||||
return findScope;
|
||||
});
|
||||
}
|
||||
|
||||
let findMatches = this._findMatches(findScope, false, MATCHES_LIMIT);
|
||||
this._decorations.set(findMatches, findScope);
|
||||
let findMatches = this._findMatches(findScopes, false, MATCHES_LIMIT);
|
||||
this._decorations.set(findMatches, findScopes);
|
||||
|
||||
const editorSelection = this._editor.getSelection();
|
||||
let currentMatchesPosition = this._decorations.getCurrentMatchesPosition(editorSelection);
|
||||
@@ -205,7 +215,7 @@ export class FindModelBoundToEditorModel {
|
||||
undefined
|
||||
);
|
||||
|
||||
if (moveCursor) {
|
||||
if (moveCursor && this._editor.getOption(EditorOption.find).cursorMoveOnType) {
|
||||
this._moveToNextMatch(this._decorations.getStartPosition());
|
||||
}
|
||||
}
|
||||
@@ -467,9 +477,12 @@ export class FindModelBoundToEditorModel {
|
||||
}
|
||||
}
|
||||
|
||||
private _findMatches(findScope: Range | null, captureMatches: boolean, limitResultCount: number): FindMatch[] {
|
||||
let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope);
|
||||
return this._editor.getModel().findMatches(this._state.searchString, searchRange, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(EditorOption.wordSeparators) : null, captureMatches, limitResultCount);
|
||||
private _findMatches(findScopes: Range[] | null, captureMatches: boolean, limitResultCount: number): FindMatch[] {
|
||||
const searchRanges = (findScopes as [] || [null] as unknown as []).map((scope: Range | null) => // {{SQL CARBON EDIT}} strict-null-check
|
||||
FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), scope)
|
||||
);
|
||||
|
||||
return this._editor.getModel().findMatches(this._state.searchString, searchRanges, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(EditorOption.wordSeparators) : null, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
public replaceAll(): void {
|
||||
@@ -477,13 +490,13 @@ export class FindModelBoundToEditorModel {
|
||||
return;
|
||||
}
|
||||
|
||||
const findScope = this._decorations.getFindScope();
|
||||
const findScopes = this._decorations.getFindScopes();
|
||||
|
||||
if (findScope === null && this._state.matchesCount >= MATCHES_LIMIT) {
|
||||
if (findScopes === null && this._state.matchesCount >= MATCHES_LIMIT) {
|
||||
// Doing a replace on the entire file that is over ${MATCHES_LIMIT} matches
|
||||
this._largeReplaceAll();
|
||||
} else {
|
||||
this._regularReplaceAll(findScope);
|
||||
this._regularReplaceAll(findScopes);
|
||||
}
|
||||
|
||||
this.research(false);
|
||||
@@ -528,10 +541,10 @@ export class FindModelBoundToEditorModel {
|
||||
this._executeEditorCommand('replaceAll', command);
|
||||
}
|
||||
|
||||
private _regularReplaceAll(findScope: Range | null): void {
|
||||
private _regularReplaceAll(findScopes: Range[] | null): void {
|
||||
const replacePattern = this._getReplacePattern();
|
||||
// Get all the ranges (even more than the highlighted ones)
|
||||
let matches = this._findMatches(findScope, replacePattern.hasReplacementPatterns || this._state.preserveCase, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
let matches = this._findMatches(findScopes, replacePattern.hasReplacementPatterns || this._state.preserveCase, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
|
||||
let replaceStrings: string[] = [];
|
||||
for (let i = 0, len = matches.length; i < len; i++) {
|
||||
@@ -547,10 +560,10 @@ export class FindModelBoundToEditorModel {
|
||||
return;
|
||||
}
|
||||
|
||||
let findScope = this._decorations.getFindScope();
|
||||
let findScopes = this._decorations.getFindScopes();
|
||||
|
||||
// Get all the ranges (even more than the highlighted ones)
|
||||
let matches = this._findMatches(findScope, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
let matches = this._findMatches(findScopes, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
let selections = matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn));
|
||||
|
||||
// If one of the ranges is the editor selection, then maintain it as primary
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface INewFindReplaceState {
|
||||
matchCaseOverride?: FindOptionOverride;
|
||||
preserveCase?: boolean;
|
||||
preserveCaseOverride?: FindOptionOverride;
|
||||
searchScope?: Range | null;
|
||||
searchScope?: Range[] | null;
|
||||
loop?: boolean;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class FindReplaceState extends Disposable {
|
||||
private _matchCaseOverride: FindOptionOverride;
|
||||
private _preserveCase: boolean;
|
||||
private _preserveCaseOverride: FindOptionOverride;
|
||||
private _searchScope: Range | null;
|
||||
private _searchScope: Range[] | null;
|
||||
private _matchesPosition: number;
|
||||
private _matchesCount: number;
|
||||
private _currentMatch: Range | null;
|
||||
@@ -94,7 +94,7 @@ export class FindReplaceState extends Disposable {
|
||||
public get actualMatchCase(): boolean { return this._matchCase; }
|
||||
public get actualPreserveCase(): boolean { return this._preserveCase; }
|
||||
|
||||
public get searchScope(): Range | null { return this._searchScope; }
|
||||
public get searchScope(): Range[] | null { return this._searchScope; }
|
||||
public get matchesPosition(): number { return this._matchesPosition; }
|
||||
public get matchesCount(): number { return this._matchesCount; }
|
||||
public get currentMatch(): Range | null { return this._currentMatch; }
|
||||
@@ -238,7 +238,11 @@ export class FindReplaceState extends Disposable {
|
||||
this._preserveCase = newState.preserveCase;
|
||||
}
|
||||
if (typeof newState.searchScope !== 'undefined') {
|
||||
if (!Range.equalsRange(this._searchScope, newState.searchScope)) {
|
||||
if (!newState.searchScope?.every((newSearchScope) => {
|
||||
return this._searchScope?.some(existingSearchScope => {
|
||||
return !Range.equalsRange(existingSearchScope, newSearchScope);
|
||||
});
|
||||
})) {
|
||||
this._searchScope = newState.searchScope;
|
||||
changeEvent.searchScope = true;
|
||||
somethingChanged = true;
|
||||
|
||||
@@ -804,16 +804,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
}
|
||||
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
let selection = this._codeEditor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
const currentMatch = this._state.currentMatch;
|
||||
if (selection.startLineNumber !== selection.endLineNumber) {
|
||||
if (!Range.equalsRange(selection, currentMatch)) {
|
||||
// Reseed find scope
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
let selections = this._codeEditor.getSelections();
|
||||
|
||||
selections.map(selection => {
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(
|
||||
selection.endLineNumber - 1,
|
||||
this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)
|
||||
);
|
||||
}
|
||||
const currentMatch = this._state.currentMatch;
|
||||
if (selection.startLineNumber !== selection.endLineNumber) {
|
||||
if (!Range.equalsRange(selection, currentMatch)) {
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).filter(element => !!element);
|
||||
|
||||
if (selections.length) {
|
||||
this._state.change({ searchScope: selections as Range[] }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1028,12 +1038,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
this._register(this._toggleSelectionFind.onChange(() => {
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
if (this._codeEditor.hasModel()) {
|
||||
let selection = this._codeEditor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
let selections = this._codeEditor.getSelections();
|
||||
selections.map(selection => {
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
return selection;
|
||||
}
|
||||
return null;
|
||||
}).filter(element => !!element);
|
||||
|
||||
if (selections.length) {
|
||||
this._state.change({ searchScope: selections as Range[] }, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -309,10 +309,10 @@ suite.skip('FindController', async () => {
|
||||
assert.equal(findController.getState().searchScope, null);
|
||||
|
||||
findController.getState().change({
|
||||
searchScope: new Range(1, 1, 1, 5)
|
||||
searchScope: [new Range(1, 1, 1, 5)]
|
||||
}, false);
|
||||
|
||||
assert.deepEqual(findController.getState().searchScope, new Range(1, 1, 1, 5));
|
||||
assert.deepEqual(findController.getState().searchScope, [new Range(1, 1, 1, 5)]);
|
||||
|
||||
findController.closeFindWidget();
|
||||
assert.equal(findController.getState().searchScope, null);
|
||||
@@ -523,10 +523,8 @@ suite.skip('FindController query options persistence', async () => {
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 1, 2, 1));
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
await findController.start({
|
||||
const findConfig = {
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -534,9 +532,17 @@ suite.skip('FindController query options persistence', async () => {
|
||||
shouldAnimate: false,
|
||||
updateSearchScope: true,
|
||||
loop: true
|
||||
});
|
||||
};
|
||||
|
||||
assert.deepEqual(findController.getState().searchScope, new Selection(1, 1, 2, 1));
|
||||
editor.setSelection(new Range(1, 1, 2, 1));
|
||||
findController.start(findConfig);
|
||||
assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1)]);
|
||||
|
||||
findController.closeFindWidget();
|
||||
|
||||
editor.setSelections([new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]);
|
||||
findController.start(findConfig);
|
||||
assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -584,7 +590,7 @@ suite.skip('FindController query options persistence', async () => {
|
||||
loop: true
|
||||
});
|
||||
|
||||
assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3));
|
||||
assert.deepEqual(findController.getState().searchScope, [new Selection(1, 2, 1, 3)]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -609,7 +615,7 @@ suite.skip('FindController query options persistence', async () => {
|
||||
loop: true
|
||||
});
|
||||
|
||||
assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1));
|
||||
assert.deepEqual(findController.getState().searchScope, [new Selection(1, 6, 2, 1)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,7 +210,7 @@ suite('FindModel', () => {
|
||||
);
|
||||
|
||||
// simulate adding a search scope
|
||||
findState.change({ searchScope: new Range(8, 1, 10, 1) }, true);
|
||||
findState.change({ searchScope: [new Range(8, 1, 10, 1)] }, true);
|
||||
assertFindState(
|
||||
editor,
|
||||
[8, 14, 8, 19],
|
||||
@@ -443,7 +443,7 @@ suite('FindModel', () => {
|
||||
|
||||
findTest('find model next stays in scope', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 9, 1) }, false);
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
assertFindState(
|
||||
@@ -493,6 +493,131 @@ suite('FindModel', () => {
|
||||
findState.dispose();
|
||||
});
|
||||
|
||||
findTest('multi-selection find model next stays in scope (overlap)', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 8, 2), new Range(8, 1, 9, 1)] }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
assertFindState(
|
||||
editor,
|
||||
[1, 1, 1, 1],
|
||||
null,
|
||||
[
|
||||
[7, 14, 7, 19],
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[7, 14, 7, 19],
|
||||
[7, 14, 7, 19],
|
||||
[
|
||||
[7, 14, 7, 19],
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[8, 14, 8, 19],
|
||||
[8, 14, 8, 19],
|
||||
[
|
||||
[7, 14, 7, 19],
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[7, 14, 7, 19],
|
||||
[7, 14, 7, 19],
|
||||
[
|
||||
[7, 14, 7, 19],
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
});
|
||||
|
||||
findTest('multi-selection find model next stays in scope', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', matchCase: true, wholeWord: false, searchScope: [new Range(6, 1, 7, 38), new Range(9, 3, 9, 38)] }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
assertFindState(
|
||||
editor,
|
||||
[1, 1, 1, 1],
|
||||
null,
|
||||
[
|
||||
[6, 14, 6, 19],
|
||||
// `matchCase: false` would
|
||||
// find this match as well:
|
||||
// [6, 27, 6, 32],
|
||||
[7, 14, 7, 19],
|
||||
// `wholeWord: true` would
|
||||
// exclude this match:
|
||||
[9, 14, 9, 19],
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[6, 14, 6, 19],
|
||||
[6, 14, 6, 19],
|
||||
[
|
||||
[6, 14, 6, 19],
|
||||
[7, 14, 7, 19],
|
||||
[9, 14, 9, 19],
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[7, 14, 7, 19],
|
||||
[7, 14, 7, 19],
|
||||
[
|
||||
[6, 14, 6, 19],
|
||||
[7, 14, 7, 19],
|
||||
[9, 14, 9, 19],
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[9, 14, 9, 19],
|
||||
[9, 14, 9, 19],
|
||||
[
|
||||
[6, 14, 6, 19],
|
||||
[7, 14, 7, 19],
|
||||
[9, 14, 9, 19],
|
||||
]
|
||||
);
|
||||
|
||||
findModel.moveToNextMatch();
|
||||
assertFindState(
|
||||
editor,
|
||||
[6, 14, 6, 19],
|
||||
[6, 14, 6, 19],
|
||||
[
|
||||
[6, 14, 6, 19],
|
||||
[7, 14, 7, 19],
|
||||
[9, 14, 9, 19],
|
||||
]
|
||||
);
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
});
|
||||
|
||||
findTest('find model prev', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', wholeWord: true }, false);
|
||||
@@ -581,7 +706,7 @@ suite('FindModel', () => {
|
||||
|
||||
findTest('find model prev stays in scope', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 9, 1) }, false);
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
assertFindState(
|
||||
@@ -2073,7 +2198,7 @@ suite('FindModel', () => {
|
||||
|
||||
findTest('issue #27083. search scope works even if it is a single line', (editor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 8, 1) }, false);
|
||||
findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 8, 1)] }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
assertFindState(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { asArray, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -120,11 +120,12 @@ export abstract class FormattingConflicts {
|
||||
}
|
||||
}
|
||||
|
||||
export async function formatDocumentRangeWithSelectedProvider(
|
||||
export async function formatDocumentRangesWithSelectedProvider(
|
||||
accessor: ServicesAccessor,
|
||||
editorOrModel: ITextModel | IActiveCodeEditor,
|
||||
range: Range,
|
||||
rangeOrRanges: Range | Range[],
|
||||
mode: FormattingMode,
|
||||
progress: IProgress<DocumentRangeFormattingEditProvider>,
|
||||
token: CancellationToken
|
||||
): Promise<void> {
|
||||
|
||||
@@ -133,15 +134,16 @@ export async function formatDocumentRangeWithSelectedProvider(
|
||||
const provider = DocumentRangeFormattingEditProviderRegistry.ordered(model);
|
||||
const selected = await FormattingConflicts.select(provider, model, mode);
|
||||
if (selected) {
|
||||
await instaService.invokeFunction(formatDocumentRangeWithProvider, selected, editorOrModel, range, token);
|
||||
progress.report(selected);
|
||||
await instaService.invokeFunction(formatDocumentRangesWithProvider, selected, editorOrModel, rangeOrRanges, token);
|
||||
}
|
||||
}
|
||||
|
||||
export async function formatDocumentRangeWithProvider(
|
||||
export async function formatDocumentRangesWithProvider(
|
||||
accessor: ServicesAccessor,
|
||||
provider: DocumentRangeFormattingEditProvider,
|
||||
editorOrModel: ITextModel | IActiveCodeEditor,
|
||||
range: Range,
|
||||
rangeOrRanges: Range | Range[],
|
||||
token: CancellationToken
|
||||
): Promise<boolean> {
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
@@ -156,39 +158,53 @@ export async function formatDocumentRangeWithProvider(
|
||||
cts = new TextModelCancellationTokenSource(editorOrModel, token);
|
||||
}
|
||||
|
||||
let edits: TextEdit[] | undefined;
|
||||
try {
|
||||
const rawEdits = await provider.provideDocumentRangeFormattingEdits(
|
||||
model,
|
||||
range,
|
||||
model.getFormattingOptions(),
|
||||
cts.token
|
||||
);
|
||||
edits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
|
||||
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return true;
|
||||
// make sure that ranges don't overlap nor touch each other
|
||||
let ranges: Range[] = [];
|
||||
let len = 0;
|
||||
for (let range of asArray(rangeOrRanges).sort(Range.compareRangesUsingStarts)) {
|
||||
if (len > 0 && Range.areIntersectingOrTouching(ranges[len - 1], range)) {
|
||||
ranges[len - 1] = Range.fromPositions(ranges[len - 1].getStartPosition(), range.getEndPosition());
|
||||
} else {
|
||||
len = ranges.push(range);
|
||||
}
|
||||
|
||||
} finally {
|
||||
cts.dispose();
|
||||
}
|
||||
|
||||
if (!edits || edits.length === 0) {
|
||||
const allEdits: TextEdit[] = [];
|
||||
for (let range of ranges) {
|
||||
try {
|
||||
const rawEdits = await provider.provideDocumentRangeFormattingEdits(
|
||||
model,
|
||||
range,
|
||||
model.getFormattingOptions(),
|
||||
cts.token
|
||||
);
|
||||
const minEdits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
|
||||
if (minEdits) {
|
||||
allEdits.push(...minEdits);
|
||||
}
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
cts.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (allEdits.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCodeEditor(editorOrModel)) {
|
||||
// use editor to apply edits
|
||||
FormattingEdit.execute(editorOrModel, edits, true);
|
||||
alertFormattingEdits(edits);
|
||||
FormattingEdit.execute(editorOrModel, allEdits, true);
|
||||
alertFormattingEdits(allEdits);
|
||||
editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate);
|
||||
|
||||
} else {
|
||||
// use model to apply edits
|
||||
const [{ range }] = edits;
|
||||
const [{ range }] = allEdits;
|
||||
const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
|
||||
model.pushEditOperations([initialSelection], edits.map(edit => {
|
||||
model.pushEditOperations([initialSelection], allEdits.map(edit => {
|
||||
return {
|
||||
text: edit.text,
|
||||
range: Range.lift(edit.range),
|
||||
|
||||
@@ -16,7 +16,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { getOnTypeFormattingEdits, alertFormattingEdits, formatDocumentRangeWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
|
||||
import { getOnTypeFormattingEdits, alertFormattingEdits, formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
|
||||
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
|
||||
import * as nls from 'vs/nls';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -25,7 +25,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Progress } from 'vs/platform/progress/common/progress';
|
||||
import { Progress, IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
|
||||
class FormatOnType implements IEditorContribution {
|
||||
|
||||
@@ -202,7 +202,7 @@ class FormatOnPaste implements IEditorContribution {
|
||||
if (this.editor.getSelections().length > 1) {
|
||||
return;
|
||||
}
|
||||
this._instantiationService.invokeFunction(formatDocumentRangeWithSelectedProvider, this.editor, range, FormattingMode.Silent, CancellationToken.None).catch(onUnexpectedError);
|
||||
this._instantiationService.invokeFunction(formatDocumentRangesWithSelectedProvider, this.editor, range, FormattingMode.Silent, Progress.None, CancellationToken.None).catch(onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,11 @@ class FormatDocumentAction extends EditorAction {
|
||||
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
if (editor.hasModel()) {
|
||||
const instaService = accessor.get(IInstantiationService);
|
||||
await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, Progress.None, CancellationToken.None);
|
||||
const progressService = accessor.get(IEditorProgressService);
|
||||
await progressService.showWhile(
|
||||
instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, Progress.None, CancellationToken.None),
|
||||
250
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +271,12 @@ class FormatSelectionAction extends EditorAction {
|
||||
if (range.isEmpty()) {
|
||||
range = new Range(range.startLineNumber, 1, range.startLineNumber, model.getLineMaxColumn(range.startLineNumber));
|
||||
}
|
||||
await instaService.invokeFunction(formatDocumentRangeWithSelectedProvider, editor, range, FormattingMode.Explicit, CancellationToken.None);
|
||||
|
||||
const progressService = accessor.get(IEditorProgressService);
|
||||
await progressService.showWhile(
|
||||
instaService.invokeFunction(formatDocumentRangesWithSelectedProvider, editor, range, FormattingMode.Explicit, Progress.None, CancellationToken.None),
|
||||
250
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ class MessageWidget {
|
||||
|
||||
let relatedResource = document.createElement('a');
|
||||
dom.addClass(relatedResource, 'filename');
|
||||
relatedResource.innerHTML = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `;
|
||||
relatedResource.innerText = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `;
|
||||
relatedResource.title = getPathLabel(related.resource, undefined);
|
||||
this._relatedDiagnostics.set(relatedResource, related);
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
||||
|
||||
if (this._model.isEmpty) {
|
||||
this.setTitle('');
|
||||
this._messageContainer.innerHTML = nls.localize('noResults', "No results");
|
||||
this._messageContainer.innerText = nls.localize('noResults', "No results");
|
||||
dom.show(this._messageContainer);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -601,13 +601,15 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
}
|
||||
|
||||
if (findState.searchScope) {
|
||||
const state = findState.searchScope;
|
||||
const states = findState.searchScope;
|
||||
let inSelection: FindMatch[] | null = [];
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
if (matches[i].range.endLineNumber <= state.endLineNumber && matches[i].range.startLineNumber >= state.startLineNumber) {
|
||||
inSelection.push(matches[i]);
|
||||
}
|
||||
}
|
||||
matches.forEach((match) => {
|
||||
states.forEach((state) => {
|
||||
if (match.range.endLineNumber <= state.endLineNumber && match.range.startLineNumber >= state.startLineNumber) {
|
||||
inSelection!.push(match);
|
||||
}
|
||||
});
|
||||
});
|
||||
matches = inSelection;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
|
||||
dom.toggleClass(this.domNodes.element, 'multiple', multiple);
|
||||
this.keyMultipleSignatures.set(multiple);
|
||||
|
||||
this.domNodes.signature.innerHTML = '';
|
||||
this.domNodes.docs.innerHTML = '';
|
||||
this.domNodes.signature.innerText = '';
|
||||
this.domNodes.docs.innerText = '';
|
||||
|
||||
const signature = hints.signatures[hints.activeSignature];
|
||||
if (!signature) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
@@ -223,10 +222,10 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
|
||||
setTitle(primaryHeading: string, secondaryHeading?: string): void {
|
||||
if (this._primaryHeading && this._secondaryHeading) {
|
||||
this._primaryHeading.innerHTML = strings.escape(primaryHeading);
|
||||
this._primaryHeading.innerText = primaryHeading;
|
||||
this._primaryHeading.setAttribute('aria-label', primaryHeading);
|
||||
if (secondaryHeading) {
|
||||
this._secondaryHeading.innerHTML = strings.escape(secondaryHeading);
|
||||
this._secondaryHeading.innerText = secondaryHeading;
|
||||
} else {
|
||||
dom.clearNode(this._secondaryHeading);
|
||||
}
|
||||
@@ -236,7 +235,7 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
setMetaTitle(value: string): void {
|
||||
if (this._metaHeading) {
|
||||
if (value) {
|
||||
this._metaHeading.innerHTML = strings.escape(value);
|
||||
this._metaHeading.innerText = value;
|
||||
dom.show(this._metaHeading);
|
||||
} else {
|
||||
dom.hide(this._metaHeading);
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as nls from 'vs/nls';
|
||||
import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
@@ -16,7 +16,7 @@ import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedS
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { first, createCancelablePromise, CancelablePromise, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { first, createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
@@ -24,11 +24,12 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { isPromiseCanceledError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
|
||||
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('onTypeRenameInputVisible', false);
|
||||
|
||||
@@ -50,14 +51,19 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
|
||||
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||
|
||||
private _currentRequest: CancelablePromise<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
} | null | undefined> | null;
|
||||
private _rangeUpdateTriggerPromise: Promise<any> | null;
|
||||
private _rangeSyncTriggerPromise: Promise<any> | null;
|
||||
|
||||
private _currentRequest: CancelablePromise<any> | null;
|
||||
private _currentRequestPosition: Position | null;
|
||||
private _currentRequestModelVersion: number | null;
|
||||
|
||||
private _currentDecorations: string[]; // The one at index 0 is the reference one
|
||||
private _stopPattern: RegExp;
|
||||
private _languageWordPattern: RegExp | null;
|
||||
private _currentWordPattern: RegExp | null;
|
||||
private _ignoreChangeEvent: boolean;
|
||||
private _updateMirrors: RunOnceScheduler;
|
||||
|
||||
private readonly _localToDispose = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@@ -65,103 +71,117 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||
this._enabled = false;
|
||||
this._visibleContextKey = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
|
||||
this._currentRequest = null;
|
||||
|
||||
this._currentDecorations = [];
|
||||
this._stopPattern = /^\s/;
|
||||
this._languageWordPattern = null;
|
||||
this._currentWordPattern = null;
|
||||
this._ignoreChangeEvent = false;
|
||||
this._updateMirrors = this._register(new RunOnceScheduler(() => this._doUpdateMirrors(), 0));
|
||||
this._localToDispose = this._register(new DisposableStore());
|
||||
|
||||
this._register(this._editor.onDidChangeModel((e) => {
|
||||
this.stopAll();
|
||||
this.run();
|
||||
}));
|
||||
this._rangeUpdateTriggerPromise = null;
|
||||
this._rangeSyncTriggerPromise = null;
|
||||
|
||||
this._register(this._editor.onDidChangeConfiguration((e) => {
|
||||
this._currentRequest = null;
|
||||
this._currentRequestPosition = null;
|
||||
this._currentRequestModelVersion = null;
|
||||
|
||||
this._register(this._editor.onDidChangeModel(() => this.reinitialize()));
|
||||
|
||||
this._register(this._editor.onDidChangeConfiguration(e => {
|
||||
if (e.hasChanged(EditorOption.renameOnType)) {
|
||||
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||
this.stopAll();
|
||||
this.run();
|
||||
this.reinitialize();
|
||||
}
|
||||
}));
|
||||
this._register(OnTypeRenameProviderRegistry.onDidChange(() => this.reinitialize()));
|
||||
this._register(this._editor.onDidChangeModelLanguage(() => this.reinitialize()));
|
||||
|
||||
this._register(this._editor.onDidChangeCursorPosition((e) => {
|
||||
// no regions, run
|
||||
if (this._currentDecorations.length === 0) {
|
||||
this.run(e.position);
|
||||
}
|
||||
|
||||
// has cached regions, don't run
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||
|
||||
// just moving cursor around, don't run again
|
||||
if (Range.containsPosition(currentRanges[0], e.position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// moving cursor out of primary region, run
|
||||
this.run(e.position);
|
||||
}));
|
||||
|
||||
this._register(OnTypeRenameProviderRegistry.onDidChange(() => {
|
||||
this.run();
|
||||
}));
|
||||
|
||||
this._register(this._editor.onDidChangeModelContent((e) => {
|
||||
if (this._ignoreChangeEvent) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
if (e.isUndoing || e.isRedoing) {
|
||||
return;
|
||||
}
|
||||
if (e.changes[0] && this._stopPattern.test(e.changes[0].text)) {
|
||||
this.stopAll();
|
||||
return;
|
||||
}
|
||||
this._updateMirrors.schedule();
|
||||
}));
|
||||
this.reinitialize();
|
||||
}
|
||||
|
||||
private _doUpdateMirrors(): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
private reinitialize() {
|
||||
const model = this._editor.getModel();
|
||||
const isEnabled = model !== null && this._editor.getOption(EditorOption.renameOnType) && OnTypeRenameProviderRegistry.has(model);
|
||||
if (isEnabled === this._enabled) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
|
||||
this._enabled = isEnabled;
|
||||
|
||||
this.clearRanges();
|
||||
this._localToDispose.clear();
|
||||
|
||||
if (!isEnabled || model === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._languageWordPattern = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
this._localToDispose.add(model.onDidChangeLanguageConfiguration(() => {
|
||||
this._languageWordPattern = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
}));
|
||||
|
||||
const rangeUpdateScheduler = new Delayer(200);
|
||||
const triggerRangeUpdate = () => {
|
||||
this._rangeUpdateTriggerPromise = rangeUpdateScheduler.trigger(() => this.updateRanges());
|
||||
};
|
||||
const rangeSyncScheduler = new Delayer(0);
|
||||
const triggerRangeSync = (decorations: string[]) => {
|
||||
this._rangeSyncTriggerPromise = rangeSyncScheduler.trigger(() => this._syncRanges(decorations));
|
||||
};
|
||||
this._localToDispose.add(this._editor.onDidChangeCursorPosition((e) => {
|
||||
triggerRangeUpdate();
|
||||
}));
|
||||
this._localToDispose.add(this._editor.onDidChangeModelContent((e) => {
|
||||
if (!this._ignoreChangeEvent) {
|
||||
if (this._currentDecorations.length > 0) {
|
||||
const referenceRange = model.getDecorationRange(this._currentDecorations[0]);
|
||||
if (referenceRange && e.changes.every(c => referenceRange.intersectRanges(c.range))) {
|
||||
triggerRangeSync(this._currentDecorations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
triggerRangeUpdate();
|
||||
}));
|
||||
this._localToDispose.add({
|
||||
dispose: () => {
|
||||
rangeUpdateScheduler.cancel();
|
||||
rangeSyncScheduler.cancel();
|
||||
}
|
||||
});
|
||||
this.updateRanges();
|
||||
}
|
||||
|
||||
private _syncRanges(decorations: string[]): void {
|
||||
// dalayed invocation, make sure we're still on
|
||||
if (!this._editor.hasModel() || decorations !== this._currentDecorations || decorations.length === 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||
const referenceRange = model.getDecorationRange(decorations[0]);
|
||||
|
||||
const referenceRange = currentRanges[0];
|
||||
if (referenceRange.startLineNumber !== referenceRange.endLineNumber) {
|
||||
return this.stopAll();
|
||||
if (!referenceRange || referenceRange.startLineNumber !== referenceRange.endLineNumber) {
|
||||
return this.clearRanges();
|
||||
}
|
||||
|
||||
const referenceValue = model.getValueInRange(referenceRange);
|
||||
if (this._stopPattern.test(referenceValue)) {
|
||||
return this.stopAll();
|
||||
if (this._currentWordPattern) {
|
||||
const match = referenceValue.match(this._currentWordPattern);
|
||||
const matchLength = match ? match[0].length : 0;
|
||||
if (matchLength !== referenceValue.length) {
|
||||
return this.clearRanges();
|
||||
}
|
||||
}
|
||||
|
||||
let edits: IIdentifiedSingleEditOperation[] = [];
|
||||
for (let i = 1, len = currentRanges.length; i < len; i++) {
|
||||
const mirrorRange = currentRanges[i];
|
||||
for (let i = 1, len = decorations.length; i < len; i++) {
|
||||
const mirrorRange = model.getDecorationRange(decorations[i]);
|
||||
if (!mirrorRange) {
|
||||
continue;
|
||||
}
|
||||
if (mirrorRange.startLineNumber !== mirrorRange.endLineNumber) {
|
||||
edits.push({
|
||||
range: mirrorRange,
|
||||
@@ -207,72 +227,131 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.clearRanges();
|
||||
super.dispose();
|
||||
this.stopAll();
|
||||
}
|
||||
|
||||
stopAll(): void {
|
||||
public clearRanges(): void {
|
||||
this._visibleContextKey.set(false);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []);
|
||||
}
|
||||
|
||||
async run(position: Position | null = this._editor.getPosition(), force = false): Promise<void> {
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
if (!this._enabled && !force) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentRequest) {
|
||||
this._currentRequest.cancel();
|
||||
this._currentRequest = null;
|
||||
this._currentRequestPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
public get currentUpdateTriggerPromise(): Promise<any> {
|
||||
return this._rangeUpdateTriggerPromise || Promise.resolve();
|
||||
}
|
||||
|
||||
public get currentSyncTriggerPromise(): Promise<any> {
|
||||
return this._rangeSyncTriggerPromise || Promise.resolve();
|
||||
}
|
||||
|
||||
public async updateRanges(force = false): Promise<void> {
|
||||
if (!this._editor.hasModel()) {
|
||||
this.clearRanges();
|
||||
return;
|
||||
}
|
||||
|
||||
const position = this._editor.getPosition();
|
||||
if (!this._enabled && !force || this._editor.getSelections().length > 1) {
|
||||
// disabled or multicursor
|
||||
this.clearRanges();
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
|
||||
this._currentRequest = createCancelablePromise(token => getOnTypeRenameRanges(model, position, token));
|
||||
try {
|
||||
const response = await this._currentRequest;
|
||||
|
||||
let ranges: IRange[] = [];
|
||||
if (response?.ranges) {
|
||||
ranges = response.ranges;
|
||||
const modelVersionId = model.getVersionId();
|
||||
if (this._currentRequestPosition && this._currentRequestModelVersion === modelVersionId) {
|
||||
if (position.equals(this._currentRequestPosition)) {
|
||||
return; // same position
|
||||
}
|
||||
if (response?.stopPattern) {
|
||||
this._stopPattern = response.stopPattern;
|
||||
}
|
||||
|
||||
let foundReferenceRange = false;
|
||||
for (let i = 0, len = ranges.length; i < len; i++) {
|
||||
if (Range.containsPosition(ranges[i], position)) {
|
||||
foundReferenceRange = true;
|
||||
if (i !== 0) {
|
||||
const referenceRange = ranges[i];
|
||||
ranges.splice(i, 1);
|
||||
ranges.unshift(referenceRange);
|
||||
}
|
||||
break;
|
||||
if (this._currentDecorations && this._currentDecorations.length > 0) {
|
||||
const range = model.getDecorationRange(this._currentDecorations[0]);
|
||||
if (range && range.containsPosition(position)) {
|
||||
return; // just moving inside the existing primary range
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundReferenceRange) {
|
||||
// Cannot do on type rename if the ranges are not where the cursor is...
|
||||
this.stopAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION }));
|
||||
this._visibleContextKey.set(true);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
this.stopAll();
|
||||
}
|
||||
|
||||
this._currentRequestPosition = position;
|
||||
this._currentRequestModelVersion = modelVersionId;
|
||||
const request = createCancelablePromise(async token => {
|
||||
try {
|
||||
const response = await getOnTypeRenameRanges(model, position, token);
|
||||
if (request !== this._currentRequest) {
|
||||
return;
|
||||
}
|
||||
this._currentRequest = null;
|
||||
if (modelVersionId !== model.getVersionId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ranges: IRange[] = [];
|
||||
if (response?.ranges) {
|
||||
ranges = response.ranges;
|
||||
}
|
||||
|
||||
this._currentWordPattern = response?.wordPattern || this._languageWordPattern;
|
||||
|
||||
let foundReferenceRange = false;
|
||||
for (let i = 0, len = ranges.length; i < len; i++) {
|
||||
if (Range.containsPosition(ranges[i], position)) {
|
||||
foundReferenceRange = true;
|
||||
if (i !== 0) {
|
||||
const referenceRange = ranges[i];
|
||||
ranges.splice(i, 1);
|
||||
ranges.unshift(referenceRange);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundReferenceRange) {
|
||||
// Cannot do on type rename if the ranges are not where the cursor is...
|
||||
this.clearRanges();
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION }));
|
||||
this._visibleContextKey.set(true);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
|
||||
} catch (err) {
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
if (this._currentRequest === request || !this._currentRequest) {
|
||||
// stop if we are still the latest request
|
||||
this.clearRanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
this._currentRequest = request;
|
||||
return request;
|
||||
}
|
||||
|
||||
// private printDecorators(model: ITextModel) {
|
||||
// return this._currentDecorations.map(d => {
|
||||
// const range = model.getDecorationRange(d);
|
||||
// if (range) {
|
||||
// return this.printRange(range);
|
||||
// }
|
||||
// return 'invalid';
|
||||
// }).join(',');
|
||||
// }
|
||||
|
||||
// private printChanges(changes: IModelContentChange[]) {
|
||||
// return changes.map(c => {
|
||||
// return `${this.printRange(c.range)} - ${c.text}`;
|
||||
// }
|
||||
// ).join(',');
|
||||
// }
|
||||
|
||||
// private printRange(range: IRange) {
|
||||
// return `${range.startLineNumber},${range.startColumn}/${range.endLineNumber},${range.endColumn}`;
|
||||
// }
|
||||
}
|
||||
|
||||
export class OnTypeRenameAction extends EditorAction {
|
||||
@@ -310,10 +389,10 @@ export class OnTypeRenameAction extends EditorAction {
|
||||
return super.runCommand(accessor, args);
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const controller = OnTypeRenameContribution.get(editor);
|
||||
if (controller) {
|
||||
return Promise.resolve(controller.run(editor.getPosition(), true));
|
||||
return Promise.resolve(controller.updateRanges(true));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -323,7 +402,7 @@ const OnTypeRenameCommand = EditorCommand.bindToContribution<OnTypeRenameContrib
|
||||
registerEditorCommand(new OnTypeRenameCommand({
|
||||
id: 'cancelOnTypeRenameInput',
|
||||
precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE,
|
||||
handler: x => x.stopAll(),
|
||||
handler: x => x.clearRanges(),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
weight: KeybindingWeight.EditorContrib + 99,
|
||||
@@ -335,7 +414,7 @@ registerEditorCommand(new OnTypeRenameCommand({
|
||||
|
||||
export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
wordPattern?: RegExp
|
||||
} | undefined | null> {
|
||||
const orderedByScore = OnTypeRenameProviderRegistry.ordered(model);
|
||||
|
||||
@@ -344,16 +423,16 @@ export function getOnTypeRenameRanges(model: ITextModel, position: Position, tok
|
||||
// (good = none empty array)
|
||||
return first<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
wordPattern?: RegExp
|
||||
} | undefined>(orderedByScore.map(provider => () => {
|
||||
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((ranges) => {
|
||||
if (!ranges) {
|
||||
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((res) => {
|
||||
if (!res) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
ranges,
|
||||
stopPattern: provider.stopPattern
|
||||
ranges: res.ranges,
|
||||
wordPattern: res.wordPattern || provider.wordPattern
|
||||
};
|
||||
}, (err) => {
|
||||
onUnexpectedExternalError(err);
|
||||
|
||||
@@ -6,19 +6,29 @@
|
||||
import * as assert from 'assert';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename';
|
||||
import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
|
||||
|
||||
const mockFile = URI.parse('test:somefile.ttt');
|
||||
const mockFileSelector = { scheme: 'test' };
|
||||
const timeout = 30;
|
||||
|
||||
interface TestEditor {
|
||||
setPosition(pos: Position): Promise<any>;
|
||||
setSelection(sel: IRange): Promise<any>;
|
||||
trigger(source: string | null | undefined, handlerId: string, payload: any): Promise<any>;
|
||||
undo(): void;
|
||||
redo(): void;
|
||||
}
|
||||
|
||||
suite('On type rename', () => {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
@@ -45,26 +55,53 @@ suite('On type rename', () => {
|
||||
|
||||
function testCase(
|
||||
name: string,
|
||||
initialState: { text: string | string[], ranges: Range[], stopPattern?: RegExp },
|
||||
operations: (editor: ITestCodeEditor, contrib: OnTypeRenameContribution) => Promise<void>,
|
||||
initialState: { text: string | string[], responseWordPattern?: RegExp, providerWordPattern?: RegExp },
|
||||
operations: (editor: TestEditor) => Promise<void>,
|
||||
expectedEndText: string | string[]
|
||||
) {
|
||||
test(name, async () => {
|
||||
disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, {
|
||||
stopPattern: initialState.stopPattern || /^\s/,
|
||||
|
||||
provideOnTypeRenameRanges() {
|
||||
return initialState.ranges;
|
||||
wordPattern: initialState.providerWordPattern,
|
||||
provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) {
|
||||
const wordAtPos = model.getWordAtPosition(pos);
|
||||
if (wordAtPos) {
|
||||
const matches = model.findMatches(wordAtPos.word, false, false, true, USUAL_WORD_SEPARATORS, false);
|
||||
assert.ok(matches.length > 0);
|
||||
return { ranges: matches.map(m => m.range), wordPattern: initialState.responseWordPattern };
|
||||
}
|
||||
return { ranges: [], wordPattern: initialState.responseWordPattern };
|
||||
}
|
||||
}));
|
||||
|
||||
const editor = createMockEditor(initialState.text);
|
||||
editor.updateOptions({ renameOnType: true });
|
||||
const ontypeRenameContribution = editor.registerAndInstantiateContribution(
|
||||
OnTypeRenameContribution.ID,
|
||||
OnTypeRenameContribution
|
||||
);
|
||||
|
||||
await operations(editor, ontypeRenameContribution);
|
||||
const testEditor: TestEditor = {
|
||||
setPosition(pos: Position) {
|
||||
editor.setPosition(pos);
|
||||
return ontypeRenameContribution.currentUpdateTriggerPromise;
|
||||
},
|
||||
setSelection(sel: IRange) {
|
||||
editor.setSelection(sel);
|
||||
return ontypeRenameContribution.currentUpdateTriggerPromise;
|
||||
},
|
||||
trigger(source: string | null | undefined, handlerId: string, payload: any) {
|
||||
editor.trigger(source, handlerId, payload);
|
||||
return ontypeRenameContribution.currentSyncTriggerPromise;
|
||||
},
|
||||
undo() {
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
},
|
||||
redo() {
|
||||
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
|
||||
}
|
||||
};
|
||||
|
||||
await operations(testEditor);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
@@ -80,349 +117,322 @@ suite('On type rename', () => {
|
||||
}
|
||||
|
||||
const state = {
|
||||
text: '<ooo></ooo>',
|
||||
ranges: [
|
||||
new Range(1, 2, 1, 5),
|
||||
new Range(1, 8, 1, 11),
|
||||
]
|
||||
text: '<ooo></ooo>'
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple insertion
|
||||
*/
|
||||
testCase('Simple insert - initial', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - initial', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Simple insert - middle', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - middle', state, async (editor) => {
|
||||
const pos = new Position(1, 3);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oioo></oioo>');
|
||||
|
||||
testCase('Simple insert - end', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - end', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oooi></oooi>');
|
||||
|
||||
/**
|
||||
* Simple insertion - end
|
||||
*/
|
||||
testCase('Simple insert end - initial', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert end - initial', state, async (editor) => {
|
||||
const pos = new Position(1, 8);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Simple insert end - middle', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert end - middle', state, async (editor) => {
|
||||
const pos = new Position(1, 9);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oioo></oioo>');
|
||||
|
||||
testCase('Simple insert end - end', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert end - end', state, async (editor) => {
|
||||
const pos = new Position(1, 11);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oooi></oooi>');
|
||||
|
||||
/**
|
||||
* Boundary insertion
|
||||
*/
|
||||
testCase('Simple insert - out of boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - out of boundary', state, async (editor) => {
|
||||
const pos = new Position(1, 1);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, 'i<ooo></ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 2', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - out of boundary 2', state, async (editor) => {
|
||||
const pos = new Position(1, 6);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo>i</ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 3', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - out of boundary 3', state, async (editor) => {
|
||||
const pos = new Position(1, 7);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo><i/ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 4', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Simple insert - out of boundary 4', state, async (editor) => {
|
||||
const pos = new Position(1, 12);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo></ooo>i');
|
||||
|
||||
/**
|
||||
* Insert + Move
|
||||
*/
|
||||
testCase('Continuous insert', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Continuous insert', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iiooo></iiooo>');
|
||||
|
||||
testCase('Insert - move - insert', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Insert - move - insert', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.setPosition(new Position(1, 4));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(new Position(1, 4));
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ioioo></ioioo>');
|
||||
|
||||
testCase('Insert - move - insert outside region', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Insert - move - insert outside region', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.setPosition(new Position(1, 7));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(new Position(1, 7));
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo>i</iooo>');
|
||||
|
||||
/**
|
||||
* Selection insert
|
||||
*/
|
||||
testCase('Selection insert - simple', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Selection insert - simple', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.setSelection(new Range(1, 2, 1, 3));
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ioo></ioo>');
|
||||
|
||||
testCase('Selection insert - whole', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Selection insert - whole', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 5));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.setSelection(new Range(1, 2, 1, 5));
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<i></i>');
|
||||
|
||||
testCase('Selection insert - across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Selection insert - across boundary', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 1, 1, 3));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.setSelection(new Range(1, 1, 1, 3));
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, 'ioo></oo>');
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Undefined behavior
|
||||
*/
|
||||
// testCase('Selection insert - across two boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
// testCase('Selection insert - across two boundary', state, async (editor) => {
|
||||
// const pos = new Position(1, 2);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.setSelection(new Range(1, 4, 1, 9));
|
||||
// editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await editor.setSelection(new Range(1, 4, 1, 9));
|
||||
// await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
// }, '<ooioo>');
|
||||
|
||||
/**
|
||||
* Break out behavior
|
||||
*/
|
||||
testCase('Breakout - type space', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - type space', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
}, '<ooo ></ooo>');
|
||||
|
||||
testCase('Breakout - type space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - type space then undo', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Breakout - type space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - type space in middle', state, async (editor) => {
|
||||
const pos = new Position(1, 4);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
}, '<oo o></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - paste content starting with space', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
}, '<ooo i="i"></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - paste content starting with space then undo', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout - paste content starting with space in middle', state, async (editor) => {
|
||||
const pos = new Position(1, 4);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Paste, { text: ' i' });
|
||||
}, '<oo io></ooo>');
|
||||
|
||||
/**
|
||||
* Break out with custom stopPattern
|
||||
* Break out with custom provider wordPattern
|
||||
*/
|
||||
|
||||
const state3 = {
|
||||
...state,
|
||||
stopPattern: /^s/
|
||||
providerWordPattern: /[a-yA-Y]+/
|
||||
};
|
||||
|
||||
testCase('Breakout with stop pattern - insert', state3, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout with stop pattern - insert', state3, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - insert stop char', state3, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout with stop pattern - insert stop char', state3, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||
}, '<sooo></ooo>');
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'z' });
|
||||
}, '<zooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - paste char', state3, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout with stop pattern - paste char', state3, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: 's' });
|
||||
}, '<sooo></ooo>');
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Paste, { text: 'z' });
|
||||
}, '<zooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - paste string', state3, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout with stop pattern - paste string', state3, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: 'so' });
|
||||
}, '<soooo></ooo>');
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Paste, { text: 'zo' });
|
||||
}, '<zoooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - insert at end', state3, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Breakout with stop pattern - insert at end', state3, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||
}, '<ooos></ooo>');
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'z' });
|
||||
}, '<oooz></ooo>');
|
||||
|
||||
const state4 = {
|
||||
...state,
|
||||
providerWordPattern: /[a-yA-Y]+/,
|
||||
responseWordPattern: /[a-eA-E]+/
|
||||
};
|
||||
|
||||
testCase('Breakout with stop pattern - insert stop char, respos', state4, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></ooo>');
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*/
|
||||
testCase('Delete - left char', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - left char', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, '<oo></oo>');
|
||||
|
||||
testCase('Delete - left char then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - left char then undo', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', 'deleteLeft', {});
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Delete - left word', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - left word', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
}, '<></>');
|
||||
|
||||
testCase('Delete - left word then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - left word then undo', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
editor.undo();
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
/**
|
||||
* Todo: Fix test
|
||||
*/
|
||||
// testCase('Delete - left all', state, async (editor, ontypeRenameContribution) => {
|
||||
// testCase('Delete - left all', state, async (editor) => {
|
||||
// const pos = new Position(1, 3);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// }, '></>');
|
||||
|
||||
/**
|
||||
* Todo: Fix test
|
||||
*/
|
||||
// testCase('Delete - left all then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
// testCase('Delete - left all then undo', state, async (editor) => {
|
||||
// const pos = new Position(1, 5);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// editor.undo();
|
||||
// }, '></ooo>');
|
||||
|
||||
testCase('Delete - left all then undo twice', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - left all then undo twice', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
editor.undo();
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Delete - selection', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - selection', state, async (editor) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
await editor.setPosition(pos);
|
||||
await editor.setSelection(new Range(1, 2, 1, 3));
|
||||
await editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, '<oo></oo>');
|
||||
|
||||
testCase('Delete - selection across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Delete - selection across boundary', state, async (editor) => {
|
||||
const pos = new Position(1, 3);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 1, 1, 3));
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
await editor.setPosition(pos);
|
||||
await editor.setSelection(new Range(1, 1, 1, 3));
|
||||
await editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, 'oo></oo>');
|
||||
|
||||
/**
|
||||
* Undo / redo
|
||||
*/
|
||||
testCase('Undo/redo - simple undo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Undo/redo - simple undo', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.undo();
|
||||
editor.undo();
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Undo/redo - simple undo/redo', state, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Undo/redo - simple undo/redo', state, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.undo();
|
||||
editor.redo();
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
/**
|
||||
@@ -432,18 +442,13 @@ suite('On type rename', () => {
|
||||
text: [
|
||||
'<ooo>',
|
||||
'</ooo>'
|
||||
],
|
||||
ranges: [
|
||||
new Range(1, 2, 1, 5),
|
||||
new Range(2, 3, 2, 6),
|
||||
]
|
||||
};
|
||||
|
||||
testCase('Multiline insert', state2, async (editor, ontypeRenameContribution) => {
|
||||
testCase('Multiline insert', state2, async (editor) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
await editor.setPosition(pos);
|
||||
await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, [
|
||||
'<iooo>',
|
||||
'</iooo>'
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/sni
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
|
||||
suite('Snippet Variables Resolver', function () {
|
||||
|
||||
@@ -229,7 +229,7 @@ export class SuggestModel implements IDisposable {
|
||||
if (supports) {
|
||||
// keep existing items that where not computed by the
|
||||
// supports/providers that want to trigger now
|
||||
const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined;
|
||||
const items = this._completionModel?.adopt(supports);
|
||||
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items);
|
||||
}
|
||||
};
|
||||
@@ -556,6 +556,12 @@ export class SuggestModel implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.leadingWord.word.length !== 0 && ctx.leadingWord.startColumn > this._context.leadingWord.startColumn) {
|
||||
// started a new word while IntelliSense shows -> retrigger
|
||||
this.trigger({ auto: this._context.auto, shy: false }, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.column > this._context.column && this._completionModel.incomplete.size > 0 && ctx.leadingWord.word.length !== 0) {
|
||||
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
|
||||
const { incomplete } = this._completionModel;
|
||||
|
||||
@@ -373,7 +373,7 @@ class SuggestionDetails {
|
||||
this.docs.textContent = documentation;
|
||||
} else {
|
||||
this.docs.classList.add('markdown-docs');
|
||||
this.docs.innerHTML = '';
|
||||
this.docs.innerText = '';
|
||||
const renderedContents = this.markdownRenderer.render(documentation);
|
||||
this.renderDisposeable = renderedContents;
|
||||
this.docs.appendChild(renderedContents.element);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CompletionProviderRegistry, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
@@ -34,14 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
export interface Ctor<T> {
|
||||
new(): T;
|
||||
}
|
||||
|
||||
export function mock<T>(): Ctor<T> {
|
||||
return function () { } as any;
|
||||
}
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
|
||||
|
||||
function createMockEditor(model: TextModel): ITestCodeEditor {
|
||||
@@ -798,4 +791,68 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('Trigger (full) completions when (incomplete) completions are already active #99504', function () {
|
||||
|
||||
let countA = 0;
|
||||
let countB = 0;
|
||||
|
||||
disposables.push(CompletionProviderRegistry.register({ scheme: 'test' }, {
|
||||
provideCompletionItems(doc, pos) {
|
||||
countA += 1;
|
||||
return {
|
||||
incomplete: false, // doesn't matter if incomplete or not
|
||||
suggestions: [{
|
||||
kind: CompletionItemKind.Class,
|
||||
label: 'Z aaa',
|
||||
insertText: 'Z aaa',
|
||||
range: new Range(1, 1, pos.lineNumber, pos.column)
|
||||
}],
|
||||
};
|
||||
}
|
||||
}));
|
||||
disposables.push(CompletionProviderRegistry.register({ scheme: 'test' }, {
|
||||
provideCompletionItems(doc, pos) {
|
||||
countB += 1;
|
||||
return {
|
||||
incomplete: false,
|
||||
suggestions: [{
|
||||
kind: CompletionItemKind.Folder,
|
||||
label: 'aaa',
|
||||
insertText: 'aaa',
|
||||
range: getDefaultSuggestRange(doc, pos)
|
||||
}],
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
return withOracle(async (model, editor) => {
|
||||
|
||||
await assertEvent(model.onDidSuggest, () => {
|
||||
editor.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'Z' });
|
||||
|
||||
}, event => {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
assert.equal(event.completionModel.items[0].textLabel, 'Z aaa');
|
||||
});
|
||||
|
||||
await assertEvent(model.onDidSuggest, () => {
|
||||
// started another word: Z a|
|
||||
// item should be: Z aaa, aaa
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' a' });
|
||||
}, event => {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 2);
|
||||
assert.equal(event.completionModel.items[0].textLabel, 'Z aaa');
|
||||
assert.equal(event.completionModel.items[1].textLabel, 'aaa');
|
||||
|
||||
assert.equal(countA, 2); // should we keep the suggestions from the "active" provider?
|
||||
assert.equal(countB, 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
|
||||
import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { EditorWorkerHost, EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
@@ -81,11 +81,16 @@ suite('suggest, word distance', function () {
|
||||
|
||||
distance = await WordDistance.create(service, editor);
|
||||
|
||||
disposables.add(service);
|
||||
disposables.add(mode);
|
||||
disposables.add(model);
|
||||
disposables.add(editor);
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
function createSuggestItem(label: string, overwriteBefore: number, position: IPosition): CompletionItem {
|
||||
const suggestion: modes.CompletionItem = {
|
||||
label,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user