Merge remote-tracking branch 'origin/main' into hediet/tokenization

This commit is contained in:
Alex Dima
2022-04-05 21:35:06 +02:00
198 changed files with 4537 additions and 3176 deletions

View File

@@ -304,7 +304,7 @@ export class MainThreadDocumentsAndEditors {
this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService, pathService));
extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments);
this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService, instantiationService));
this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService));
extHostContext.set(MainContext.MainThreadTextEditors, this._mainThreadEditors);
// It is expected that the ctor of the state computer calls our `_onDelta`.

View File

@@ -561,5 +561,22 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
// TODO @jrieken This isn't quite right how can we say true for some but not others?
return results.every(result => result);
}
async $closeGroup(groupIds: number[], preserveFocus?: boolean): Promise<boolean> {
const groupCloseResults: boolean[] = [];
for (const groupId of groupIds) {
const group = this._editorGroupsService.getGroup(groupId);
if (group) {
// TODO @lramos15 change this to use group.closeAllEditors once it
// is enriched to return a boolean
groupCloseResults.push(await group.closeEditors([...group.editors], { preserveFocus }));
// Make sure group is empty but still there before removing it
if (group.count === 0 && this._editorGroupsService.getGroup(group.id)) {
this._editorGroupsService.removeGroup(group);
}
}
}
return groupCloseResults.every(result => result);
}
//#endregion
}

View File

@@ -14,7 +14,7 @@ import { IDecorationOptions, IDecorationRenderOptions } from 'vs/editor/common/e
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution } from 'vs/platform/editor/common/editor';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor';
import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
@@ -27,13 +27,6 @@ import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { IEditorControl } from 'vs/workbench/common/editor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import { IPosition } from 'vs/editor/common/core/position';
import { IDataTransfer, IDataTransferItem } from 'vs/workbench/common/dnd';
import { extractEditorsDropData } from 'vs/workbench/browser/dnd';
import { Mimes } from 'vs/base/common/mime';
import { distinct } from 'vs/base/common/arrays';
import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
export interface IMainThreadEditorLocator {
getEditor(id: string): MainThreadTextEditor | undefined;
@@ -51,7 +44,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
private _textEditorsListenersMap: { [editorId: string]: IDisposable[] };
private _editorPositionData: ITextEditorPositionData | null;
private _registeredDecorationTypes: { [decorationType: string]: boolean };
private readonly _dropIntoEditorListeners = new Map<ICodeEditor, IDisposable>();
constructor(
private readonly _editorLocator: IMainThreadEditorLocator,
@@ -59,7 +51,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._instanceId = String(++MainThreadTextEditors.INSTANCE_COUNT);
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors);
@@ -71,22 +62,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
this._toDispose.add(this._editorGroupService.onDidRemoveGroup(() => this._updateActiveAndVisibleTextEditors()));
this._toDispose.add(this._editorGroupService.onDidMoveGroup(() => this._updateActiveAndVisibleTextEditors()));
const registerDropListenerOnEditor = (editor: ICodeEditor) => {
this._dropIntoEditorListeners.get(editor)?.dispose();
this._dropIntoEditorListeners.set(editor, editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)));
};
this._toDispose.add(_codeEditorService.onCodeEditorAdd(registerDropListenerOnEditor));
this._toDispose.add(_codeEditorService.onCodeEditorRemove(editor => {
this._dropIntoEditorListeners.get(editor)?.dispose();
this._dropIntoEditorListeners.delete(editor);
}));
for (const editor of this._codeEditorService.listCodeEditors()) {
registerDropListenerOnEditor(editor);
}
this._registeredDecorationTypes = Object.create(null);
}
@@ -99,8 +74,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
for (let decorationType in this._registeredDecorationTypes) {
this._codeEditorService.removeDecorationType(decorationType);
}
dispose(this._dropIntoEditorListeners.values());
this._dropIntoEditorListeners.clear();
this._registeredDecorationTypes = Object.create(null);
}
@@ -140,59 +113,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
return result;
}
private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) {
if (!dragEvent.dataTransfer || !editor.hasModel()) {
return;
}
const id = this._editorLocator.getIdOfCodeEditor(editor);
if (typeof id !== 'string') {
return;
}
const modelVersionNow = editor.getModel().getVersionId();
const textEditorDataTransfer: IDataTransfer = new Map<string, IDataTransferItem>();
for (const item of dragEvent.dataTransfer.items) {
if (item.kind === 'string') {
const type = item.type;
const asStringValue = new Promise<string>(resolve => item.getAsString(resolve));
textEditorDataTransfer.set(type, {
asString: () => asStringValue,
value: undefined
});
}
}
if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) {
const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent))
.filter(input => input.resource)
.map(input => input.resource!.toString());
if (editorData.length) {
const str = distinct(editorData).join('\n');
textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), {
asString: () => Promise.resolve(str),
value: undefined
});
}
}
if (textEditorDataTransfer.size === 0) {
return;
}
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(textEditorDataTransfer);
const edits = await this._proxy.$textEditorHandleDrop(id, position, dataTransferDto);
if (edits.length === 0) {
return;
}
if (editor.getModel().getVersionId() === modelVersionNow) {
const [first] = edits; // TODO@jrieken define how to pick the "one snippet edit";
performSnippetEdit(editor, first);
}
}
// --- from extension host process
async $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise<string | undefined> {

View File

@@ -3,43 +3,49 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import * as languages from 'vs/editor/common/languages';
import * as search from 'vs/workbench/contrib/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, ILocationLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto, IInlayHintDto } from '../common/extHost.protocol';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { Selection } from 'vs/editor/common/core/selection';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { mixin } from 'vs/base/common/objects';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
import { revive } from 'vs/base/common/marshalling';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { mixin } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import * as languages from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IndentationRule, LanguageConfiguration, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as search from 'vs/workbench/contrib/search/common/search';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape {
private readonly _proxy: ExtHostLanguageFeaturesShape;
private readonly _registrations = new Map<number, IDisposable>();
private readonly _dropIntoEditorListeners = new Map<ICodeEditor, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@ILanguageService private readonly _languageService: ILanguageService,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures);
if (this._languageService) {
@@ -71,11 +77,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
}
dispose(): void {
override dispose(): void {
for (const registration of this._registrations.values()) {
registration.dispose();
}
this._registrations.clear();
dispose(this._dropIntoEditorListeners.values());
this._dropIntoEditorListeners.clear();
super.dispose();
}
$unregister(handle: number): void {
@@ -850,6 +861,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}));
}
// --- document drop Edits
$registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, this._languageFeaturesService.documentOnDropEditProvider.register(selector, {
provideDocumentOnDropEdits: async (model, position, dataTransfer, token) => {
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(dataTransfer);
return this._proxy.$provideDocumentOnDropEdits(handle, model.uri, position, dataTransferDto, token);
}
}));
}
}
export class MainThreadDocumentSemanticTokensProvider implements languages.DocumentSemanticTokensProvider {

View File

@@ -510,7 +510,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
containerTitle: item.contextualTitle || viewContainer?.title,
canToggleVisibility: true,
canMoveView: viewContainer?.id !== REMOTE,
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name) : undefined,
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name, extension.description.identifier.value) : undefined,
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,

View File

@@ -532,6 +532,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
return extHostLanguages.createLanguageStatusItem(extension, id, selector);
},
registerDocumentOnDropProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'textEditorDrop');
return extHostLanguageFeatures.registerDocumentOnDropProvider(extension, selector, provider);
}
};
@@ -669,7 +673,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'editorInsets');
return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension);
},
createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: readonly string[] | string): vscode.Terminal {
if (typeof nameOrOptions === 'object') {
if ('pty' in nameOrOptions) {
return extHostTerminalService.createExtensionTerminal(nameOrOptions);
@@ -872,10 +876,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
onWillSaveTextDocument: (listener, thisArgs?, disposables?) => {
return extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension)(listener, thisArgs, disposables);
},
onWillDropOnTextEditor: (listener, thisArgs?, disposables?) => {
checkProposedApiEnabled(extension, 'textEditorDrop');
return extHostEditors.onWillDropOnTextEditor(listener, thisArgs, disposables);
},
get notebookDocuments(): vscode.NotebookDocument[] {
return extHostNotebook.notebookDocuments.map(d => d.apiNotebook);
},
@@ -1131,6 +1131,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
createConcatTextDocument(notebook, selector) {
checkProposedApiEnabled(extension, 'notebookConcatTextDocument');
extHostApiDeprecation.report('notebookConcatTextDocument', extension, 'This proposal is not on track for finalization and will be removed.');
return new ExtHostNotebookConcatDocument(extHostNotebookDocuments, extHostDocuments, notebook, selector);
},
};

View File

@@ -257,7 +257,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
}
export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise<void>;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise<void>;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise<void>;
$reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void>;
$setMessage(treeViewId: string, message: string): void;
@@ -390,6 +390,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void;
$setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void;
}
@@ -676,6 +677,7 @@ export interface MainThreadEditorTabsShape extends IDisposable {
// manage tabs: move, close, rearrange etc
$moveTab(tabId: string, index: number, viewColumn: EditorGroupColumn, preserveFocus?: boolean): void;
$closeTab(tabIds: string[], preserveFocus?: boolean): Promise<boolean>;
$closeGroup(groupIds: number[], preservceFocus?: boolean): Promise<boolean>;
}
export interface IEditorTabGroupDto {
@@ -1329,7 +1331,6 @@ export interface ISelectionChangeEvent {
export interface ExtHostEditorsShape {
$acceptEditorPropertiesChanged(id: string, props: IEditorPropertiesChangeData): void;
$acceptEditorPositionData(data: ITextEditorPositionData): void;
$textEditorHandleDrop(id: string, position: IPosition, dataTransferDto: DataTransferDTO): Promise<Dto<languages.SnippetTextEdit[]>>;
}
export interface IDocumentsAndEditorsDelta {
@@ -1734,6 +1735,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$releaseTypeHierarchy(handle: number, sessionId: string): void;
$provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined>;
}
export interface ExtHostQuickOpenShape {

View File

@@ -953,7 +953,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ
if (activeEditor) {
return activeEditor.document.uri;
}
const activeTab = editorTabs.tabGroups.groups.find(group => group.isActive)?.activeTab;
const activeTab = editorTabs.tabGroups.all.find(group => group.isActive)?.activeTab;
if (activeTab !== undefined) {
// Resolve a resource from the tab
if (activeTab.kind instanceof TextDiffTabInput || activeTab.kind instanceof NotebookDiffEditorTabInput) {

View File

@@ -36,9 +36,9 @@ class ExtHostEditorTab {
}
get apiObject(): vscode.Tab {
// Don't want to lose reference to parent `this` in the getters
const that = this;
if (!this._apiObject) {
// Don't want to lose reference to parent `this` in the getters
const that = this;
const obj: vscode.Tab = {
get isActive() {
// We use a getter function here to always ensure at most 1 active tab per group and prevent iteration for being required
@@ -120,9 +120,9 @@ class ExtHostEditorTabGroup {
}
get apiObject(): vscode.TabGroup {
// Don't want to lose reference to parent `this` in the getters
const that = this;
if (!this._apiObject) {
// Don't want to lose reference to parent `this` in the getters
const that = this;
const obj: vscode.TabGroup = {
get isActive() {
// We use a getter function here to always ensure at most 1 active group and prevent iteration for being required
@@ -201,10 +201,8 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadEditorTabsShape;
private readonly _onDidChangeTabs = new Emitter<vscode.Tab[]>();
private readonly _onDidChangeActiveTab = new Emitter<vscode.Tab>();
private readonly _onDidChangeTabs = new Emitter<vscode.TabChangeEvent>();
private readonly _onDidChangeTabGroups = new Emitter<vscode.TabGroup[]>();
private readonly _onDidChangeActiveTabGroup = new Emitter<vscode.TabGroup>();
// Have to use ! because this gets initialized via an RPC proxy
private _activeGroupId!: number;
@@ -223,11 +221,9 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
const obj: vscode.TabGroups = {
// never changes -> simple value
onDidChangeTabGroups: that._onDidChangeTabGroups.event,
onDidChangeActiveTabGroup: that._onDidChangeActiveTabGroup.event,
onDidChangeTabs: that._onDidChangeTabs.event,
onDidChangeActiveTab: that._onDidChangeActiveTab.event,
// dynamic -> getters
get groups() {
get all() {
return Object.freeze(that._extHostTabGroups.map(group => group.apiObject));
},
get activeTabGroup() {
@@ -235,17 +231,18 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
const activeTabGroup = assertIsDefined(that._extHostTabGroups.find(candidate => candidate.groupId === activeTabGroupId)?.apiObject);
return activeTabGroup;
},
close: async (tab: vscode.Tab | vscode.Tab[], preserveFocus?: boolean) => {
const tabs = Array.isArray(tab) ? tab : [tab];
const extHostTabIds: string[] = [];
for (const tab of tabs) {
const extHostTab = this._findExtHostTabFromApi(tab);
if (!extHostTab) {
throw new Error('Tab close: Invalid tab not found!');
}
extHostTabIds.push(extHostTab.tabId);
close: async (tabOrTabGroup: vscode.Tab | readonly vscode.Tab[] | vscode.TabGroup | readonly vscode.TabGroup[], preserveFocus?: boolean) => {
const tabsOrTabGroups = Array.isArray(tabOrTabGroup) ? tabOrTabGroup : [tabOrTabGroup];
if (!tabsOrTabGroups.length) {
return true;
}
// Check which type was passed in and call the appropriate close
// Casting is needed as typescript doesn't seem to infer enough from this
if (isTabGroup(tabsOrTabGroups[0])) {
return this._closeGroups(tabsOrTabGroups as vscode.TabGroup[], preserveFocus);
} else {
return this._closeTabs(tabsOrTabGroups as vscode.Tab[], preserveFocus);
}
return this._proxy.$closeTab(extHostTabIds, preserveFocus);
},
move: async (tab: vscode.Tab, viewColumn: ViewColumn, index: number, preservceFocus?: boolean) => {
const extHostTab = this._findExtHostTabFromApi(tab);
@@ -261,17 +258,6 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
return this._apiObject;
}
private _findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
for (const group of this._extHostTabGroups) {
for (const tab of group.tabs) {
if (tab.apiObject === apiTab) {
return tab;
}
}
}
return;
}
$acceptEditorTabModel(tabGroups: IEditorTabGroupDto[]): void {
this._extHostTabGroups = tabGroups.map(tabGroup => {
@@ -283,7 +269,6 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
const activeTabGroupId = assertIsDefined(tabGroups.find(group => group.isActive === true)?.groupId);
if (activeTabGroupId !== undefined && this._activeGroupId !== activeTabGroupId) {
this._activeGroupId = activeTabGroupId;
this._onDidChangeActiveTabGroup.fire(this.tabGroups.activeTabGroup);
}
this._onDidChangeTabGroups.fire(this._extHostTabGroups.map(g => g.apiObject));
}
@@ -295,11 +280,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
}
group.acceptGroupDtoUpdate(groupDto);
if (groupDto.isActive) {
const oldActiveGroupId = this._activeGroupId;
this._activeGroupId = groupDto.groupId;
if (oldActiveGroupId !== this._activeGroupId) {
this._onDidChangeActiveTabGroup.fire(group.apiObject);
}
}
this._onDidChangeTabGroups.fire([group.apiObject]);
}
@@ -310,12 +291,79 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
throw new Error('Update Tabs IPC call received before group creation.');
}
const tab = group.acceptTabOperation(operation);
// We don't want to fire a change event with a closed tab to prevent an invalid tabs from being received
if (operation.kind !== TabModelOperationKind.TAB_CLOSE) {
this._onDidChangeTabs.fire([tab.apiObject]);
if (tab.apiObject.isActive) {
this._onDidChangeActiveTab.fire(tab.apiObject);
}
// Construct the tab change event based on the operation
switch (operation.kind) {
case TabModelOperationKind.TAB_OPEN:
this._onDidChangeTabs.fire({
added: [tab.apiObject],
removed: [],
changed: []
});
return;
case TabModelOperationKind.TAB_CLOSE:
this._onDidChangeTabs.fire({
added: [],
removed: [tab.apiObject],
changed: []
});
return;
case TabModelOperationKind.TAB_UPDATE:
this._onDidChangeTabs.fire({
added: [],
removed: [],
changed: [tab.apiObject]
});
return;
}
}
private _findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
for (const group of this._extHostTabGroups) {
for (const tab of group.tabs) {
if (tab.apiObject === apiTab) {
return tab;
}
}
}
return;
}
private _findExtHostTabGroupFromApi(apiTabGroup: vscode.TabGroup): ExtHostEditorTabGroup | undefined {
return this._extHostTabGroups.find(candidate => candidate.apiObject === apiTabGroup);
}
private async _closeTabs(tabs: vscode.Tab[], preserveFocus?: boolean): Promise<boolean> {
const extHostTabIds: string[] = [];
for (const tab of tabs) {
const extHostTab = this._findExtHostTabFromApi(tab);
if (!extHostTab) {
throw new Error('Tab close: Invalid tab not found!');
}
extHostTabIds.push(extHostTab.tabId);
}
return this._proxy.$closeTab(extHostTabIds, preserveFocus);
}
private async _closeGroups(groups: vscode.TabGroup[], preserverFoucs?: boolean): Promise<boolean> {
const extHostGroupIds: number[] = [];
for (const group of groups) {
const extHostGroup = this._findExtHostTabGroupFromApi(group);
if (!extHostGroup) {
throw new Error('Group close: Invalid group not found!');
}
extHostGroupIds.push(extHostGroup.groupId);
}
return this._proxy.$closeGroup(extHostGroupIds, preserverFoucs);
}
}
//#region Utils
function isTabGroup(obj: unknown): obj is vscode.TabGroup {
const tabGroup = obj as vscode.TabGroup;
if (tabGroup.tabs !== undefined) {
return true;
}
return false;
}
//#endregion

View File

@@ -35,6 +35,8 @@ import { isCancellationError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { raceCancellationError } from 'vs/base/common/async';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
// --- adapter
@@ -1712,6 +1714,27 @@ class TypeHierarchyAdapter {
return map?.get(itemId);
}
}
class DocumentOnDropAdapter {
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.DocumentOnDropProvider
) { }
async provideDocumentOnDropEdits(uri: URI, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto);
const edit = await this._provider.provideDocumentOnDropEdits(doc, pos, dataTransfer, token);
if (!edit) {
return undefined;
}
return typeConvert.SnippetTextEdit.from(edit);
}
}
type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
@@ -1720,7 +1743,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
| SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter
| DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter
| EvaluatableExpressionAdapter | InlineValuesAdapter
| LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter | InlineCompletionAdapterNew;
| LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter | InlineCompletionAdapterNew
| DocumentOnDropAdapter;
class AdapterData {
constructor(
@@ -2341,6 +2365,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined, undefined);
}
// --- Document on drop
registerDocumentOnDropProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider) {
const handle = this._addNewAdapter(new DocumentOnDropAdapter(this._documents, provider), extension);
this._proxy.$registerDocumentOnDropProvider(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
$provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
return this._withAdapter(handle, DocumentOnDropAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
}
// --- configuration
private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto {

View File

@@ -38,7 +38,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
onDidChangeTerminalState: Event<vscode.Terminal>;
onDidWriteTerminalData: Event<vscode.TerminalDataWriteEvent>;
createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal;
createTerminal(name?: string, shellPath?: string, shellArgs?: readonly string[] | string): vscode.Terminal;
createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal;
createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal;
attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void;

View File

@@ -3,20 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event';
import * as arrays from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/common/extHostTextEditor';
import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { SnippetTextEdit, TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes';
import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes';
import * as vscode from 'vscode';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer';
import { IPosition } from 'vs/editor/common/core/position';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import * as languages from 'vs/editor/common/languages';
export class ExtHostEditors implements ExtHostEditorsShape {
@@ -26,7 +21,6 @@ export class ExtHostEditors implements ExtHostEditorsShape {
private readonly _onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor | undefined>();
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
private readonly _onWillDropOnTextEditor = new AsyncEmitter<vscode.TextEditorDropEvent>();
readonly onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent> = this._onDidChangeTextEditorSelection.event;
readonly onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent> = this._onDidChangeTextEditorOptions.event;
@@ -34,7 +28,6 @@ export class ExtHostEditors implements ExtHostEditorsShape {
readonly onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent> = this._onDidChangeTextEditorViewColumn.event;
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
readonly onWillDropOnTextEditor: Event<vscode.TextEditorDropEvent> = this._onWillDropOnTextEditor.event;
private readonly _proxy: MainThreadTextEditorsShape;
@@ -166,33 +159,4 @@ export class ExtHostEditors implements ExtHostEditorsShape {
getDiffInformation(id: string): Promise<vscode.LineChange[]> {
return Promise.resolve(this._proxy.$getDiffInformation(id));
}
// --- Text editor drag and drop
async $textEditorHandleDrop(id: string, position: IPosition, dataTransferDto: DataTransferDTO): Promise<Dto<languages.SnippetTextEdit[]>> {
const textEditor = this._extHostDocumentsAndEditors.getEditor(id);
if (!textEditor) {
throw new Error('Unknown text editor');
}
const pos = TypeConverters.Position.to(position);
const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto);
const event = Object.freeze({
editor: textEditor.value,
position: pos,
dataTransfer: dataTransfer
});
const edits: SnippetTextEdit[] = [];
await this._onWillDropOnTextEditor.fireAsync(event, CancellationToken.None, async p => {
const value = await p;
if (value instanceof SnippetTextEdit) {
edits.push(value);
}
});
return edits.map(TypeConverters.SnippetTextEdit.from);
}
}

View File

@@ -1594,7 +1594,10 @@ export class CompletionList {
@es5ClassCompat
export class InlineSuggestion implements vscode.InlineCompletionItem {
insertText?: string;
insertText?: string | SnippetString;
filterText?: string;
/**
* @deprecated Use `insertText` instead. Will be removed eventually.
@@ -1604,7 +1607,7 @@ export class InlineSuggestion implements vscode.InlineCompletionItem {
range?: Range;
command?: vscode.Command;
constructor(insertText: string, range?: Range, command?: vscode.Command) {
constructor(insertText: string | SnippetString, range?: Range, command?: vscode.Command) {
this.insertText = insertText;
this.range = range;
this.command = command;

View File

@@ -35,7 +35,7 @@ suite('ExtHostEditorTabs', function () {
})
);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 0);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 0);
// Active group should never be undefined (there is always an active group). Ensure accessing it undefined throws.
// TODO @lramos15 Add a throw on the main side when a model is sent without an active group
assert.throws(() => extHostEditorTabs.tabGroups.activeTabGroup);
@@ -63,8 +63,8 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tab]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
const [first] = extHostEditorTabs.tabGroups.groups;
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
const [first] = extHostEditorTabs.tabGroups.all;
assert.ok(first.activeTab);
assert.strictEqual(first.tabs.indexOf(first.activeTab), 0);
@@ -75,8 +75,8 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tab]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
const [first] = extHostEditorTabs.tabGroups.groups;
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
const [first] = extHostEditorTabs.tabGroups.all;
assert.ok(first.activeTab);
assert.strictEqual(first.tabs.indexOf(first.activeTab), 0);
}
@@ -95,8 +95,8 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: []
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
const [first] = extHostEditorTabs.tabGroups.groups;
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
const [first] = extHostEditorTabs.tabGroups.all;
assert.strictEqual(first.activeTab, undefined);
assert.strictEqual(first.tabs.length, 0);
});
@@ -121,7 +121,7 @@ suite('ExtHostEditorTabs', function () {
}]);
assert.ok(extHostEditorTabs.tabGroups.activeTabGroup);
const activeTabGroup: vscode.TabGroup = extHostEditorTabs.tabGroups.activeTabGroup;
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(activeTabGroup.tabs.length, 0);
assert.strictEqual(count, 1);
});
@@ -147,76 +147,14 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tab]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
const [first] = extHostEditorTabs.tabGroups.groups;
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
const [first] = extHostEditorTabs.tabGroups.all;
assert.ok(first.activeTab);
assert.strictEqual(first.tabs.indexOf(first.activeTab), 0);
assert.strictEqual(first.activeTab, first.tabs[0]);
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup, first);
});
// TODO @lramos15 Change this test because now it only fires when id changes
test.skip('onDidChangeActiveTabGroup fires properly', function () {
const extHostEditorTabs = new ExtHostEditorTabs(
SingleProxyRPCProtocol(new class extends mock<MainThreadEditorTabsShape>() {
// override/implement $moveTab or $closeTab
})
);
let count = 0;
let activeTabGroupFromEvent: vscode.TabGroup | undefined = undefined;
extHostEditorTabs.tabGroups.onDidChangeActiveTabGroup((tabGroup) => {
count++;
activeTabGroupFromEvent = tabGroup;
});
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 0);
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup, undefined);
assert.strictEqual(count, 0);
const tabModel = [{
isActive: true,
viewColumn: 0,
groupId: 12,
tabs: [],
activeTab: undefined
}];
extHostEditorTabs.$acceptEditorTabModel(tabModel);
assert.ok(extHostEditorTabs.tabGroups.activeTabGroup);
let activeTabGroup: vscode.TabGroup = extHostEditorTabs.tabGroups.activeTabGroup;
assert.strictEqual(count, 1);
assert.strictEqual(activeTabGroup, activeTabGroupFromEvent);
// Firing again with same model shouldn't cause a change
extHostEditorTabs.$acceptEditorTabModel(tabModel);
assert.strictEqual(count, 1);
// Changing a property should fire a change
tabModel[0].viewColumn = 1;
extHostEditorTabs.$acceptEditorTabModel(tabModel);
assert.strictEqual(count, 2);
activeTabGroup = extHostEditorTabs.tabGroups.activeTabGroup;
assert.strictEqual(activeTabGroup, activeTabGroupFromEvent);
// Changing the active tab group should fire a change
tabModel[0].isActive = false;
tabModel.push({
isActive: true,
viewColumn: 0,
groupId: 13,
tabs: [],
activeTab: undefined
});
extHostEditorTabs.$acceptEditorTabModel(tabModel);
assert.strictEqual(count, 3);
activeTabGroup = extHostEditorTabs.tabGroups.activeTabGroup;
assert.strictEqual(activeTabGroup, activeTabGroupFromEvent);
// Empty tab model should fire a change and return undefined
extHostEditorTabs.$acceptEditorTabModel([]);
assert.strictEqual(count, 4);
activeTabGroup = extHostEditorTabs.tabGroups.activeTabGroup;
assert.strictEqual(activeTabGroup, undefined);
assert.strictEqual(activeTabGroup, activeTabGroupFromEvent);
});
test('Ensure reference stability', function () {
const extHostEditorTabs = new ExtHostEditorTabs(
@@ -234,7 +172,7 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tabDto]
}]);
let all = extHostEditorTabs.tabGroups.groups.map(group => group.tabs).flat();
let all = extHostEditorTabs.tabGroups.all.map(group => group.tabs).flat();
assert.strictEqual(all.length, 1);
const apiTab1 = all[0];
assert.ok(apiTab1.kind instanceof TextTabInput);
@@ -255,7 +193,7 @@ suite('ExtHostEditorTabs', function () {
groupId: 12
});
all = extHostEditorTabs.tabGroups.groups.map(group => group.tabs).flat();
all = extHostEditorTabs.tabGroups.all.map(group => group.tabs).flat();
assert.strictEqual(all.length, 1);
const apiTab2 = all[0];
assert.ok(apiTab1.kind instanceof TextTabInput);
@@ -301,7 +239,7 @@ suite('ExtHostEditorTabs', function () {
tabs: [tabDtoAAA, tabDtoBBB]
}]);
let all = extHostEditorTabs.tabGroups.groups.map(group => group.tabs).flat();
let all = extHostEditorTabs.tabGroups.all.map(group => group.tabs).flat();
assert.strictEqual(all.length, 2);
const activeTab1 = extHostEditorTabs.tabGroups.activeTabGroup?.activeTab;
@@ -341,7 +279,7 @@ suite('ExtHostEditorTabs', function () {
});
assert.throws(() => {
// @ts-expect-error write to readonly prop
extHostEditorTabs.tabGroups.groups.length = 0;
extHostEditorTabs.tabGroups.all.length = 0;
});
assert.throws(() => {
// @ts-expect-error write to readonly prop
@@ -379,7 +317,7 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tab]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
const activeTab = extHostEditorTabs.tabGroups.activeTabGroup?.activeTab;
assert.ok(activeTab);
extHostEditorTabs.tabGroups.close(activeTab, false);
@@ -418,12 +356,12 @@ suite('ExtHostEditorTabs', function () {
tabs: [tabDto]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 1);
const tab = extHostEditorTabs.tabGroups.groups[0].tabs[0];
const tab = extHostEditorTabs.tabGroups.all[0].tabs[0];
const p = new Promise<vscode.Tab[]>(resolve => extHostEditorTabs.tabGroups.onDidChangeTabs(resolve));
const p = new Promise<vscode.TabChangeEvent>(resolve => extHostEditorTabs.tabGroups.onDidChangeTabs(resolve));
extHostEditorTabs.$acceptTabOperation({
groupId: 12,
@@ -432,7 +370,7 @@ suite('ExtHostEditorTabs', function () {
tabDto: { ...tabDto, label: 'NEW LABEL' }
});
const changedTab = (await p)[0];
const changedTab = (await p).changed[0];
assert.ok(tab === changedTab);
assert.strictEqual(changedTab.label, 'NEW LABEL');
@@ -472,8 +410,8 @@ suite('ExtHostEditorTabs', function () {
tabs: [tab1, tab2, tab3]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 3);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 3);
// Active tab is correct
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup?.activeTab, extHostEditorTabs.tabGroups.activeTabGroup?.tabs[0]);
@@ -503,8 +441,8 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: [tab3]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup?.activeTab, extHostEditorTabs.tabGroups.activeTabGroup?.tabs[0]);
// Closing out all tabs returns undefine active tab
@@ -514,94 +452,11 @@ suite('ExtHostEditorTabs', function () {
groupId: 12,
tabs: []
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 0);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 0);
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup?.activeTab, undefined);
});
test('Active tab change event', function () {
const extHostEditorTabs = new ExtHostEditorTabs(
SingleProxyRPCProtocol(new class extends mock<MainThreadEditorTabsShape>() {
// override/implement $moveTab or $closeTab
})
);
let activeTabChangeCount = 0;
extHostEditorTabs.tabGroups.onDidChangeActiveTab((activeTab) => {
if (activeTab.isActive === false) {
throw new Error('Active tab changed fired on inactive tab');
}
activeTabChangeCount++;
});
const tab1: IEditorTabDto = createTabDto({
id: 'uniquestring',
isActive: true,
label: 'label1',
});
const tab2: IEditorTabDto = createTabDto({
isActive: false,
id: 'uniquestring2',
label: 'label2',
});
const tab3: IEditorTabDto = createTabDto({
isActive: false,
id: 'uniquestring3',
label: 'label3',
});
extHostEditorTabs.$acceptEditorTabModel([{
isActive: true,
viewColumn: 0,
groupId: 12,
tabs: [tab1, tab2, tab3]
}]);
// Accepting a model doesn't fire an active tab change event
assert.strictEqual(activeTabChangeCount, 0);
// Switching active tab works
tab1.isActive = false;
tab2.isActive = true;
extHostEditorTabs.$acceptTabOperation({
groupId: 12,
index: 0,
kind: TabModelOperationKind.TAB_UPDATE,
tabDto: tab1
});
extHostEditorTabs.$acceptTabOperation({
groupId: 12,
index: 1,
kind: TabModelOperationKind.TAB_UPDATE,
tabDto: tab2
});
// The active tab changed so it is fired once
assert.strictEqual(activeTabChangeCount, 1);
//Closing tabs out works
tab3.isActive = true;
extHostEditorTabs.$acceptEditorTabModel([{
isActive: true,
viewColumn: 0,
groupId: 12,
tabs: [tab3]
}]);
// Accepting a model doesn't fire an active tab change event
assert.strictEqual(activeTabChangeCount, 1);
tab3.label = 'Foobar';
extHostEditorTabs.$acceptTabOperation({
groupId: 12,
index: 0,
kind: TabModelOperationKind.TAB_UPDATE,
tabDto: tab3
});
// Something related to the active tab changed
assert.strictEqual(activeTabChangeCount, 2);
});
test('Tab operations patches open and close correctly', function () {
const extHostEditorTabs = new ExtHostEditorTabs(
SingleProxyRPCProtocol(new class extends mock<MainThreadEditorTabsShape>() {
@@ -634,8 +489,8 @@ suite('ExtHostEditorTabs', function () {
tabs: [tab1, tab2, tab3]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 3);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 3);
// Close tab 2
extHostEditorTabs.$acceptTabOperation({
@@ -644,8 +499,8 @@ suite('ExtHostEditorTabs', function () {
kind: TabModelOperationKind.TAB_CLOSE,
tabDto: tab2
});
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 2);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 2);
// Close active tab and update tab 3 to be active
extHostEditorTabs.$acceptTabOperation({
@@ -654,8 +509,8 @@ suite('ExtHostEditorTabs', function () {
kind: TabModelOperationKind.TAB_CLOSE,
tabDto: tab1
});
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 1);
tab3.isActive = true;
extHostEditorTabs.$acceptTabOperation({
groupId: 12,
@@ -663,9 +518,9 @@ suite('ExtHostEditorTabs', function () {
kind: TabModelOperationKind.TAB_UPDATE,
tabDto: tab3
});
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups[0]?.activeTab?.label, 'label3');
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all[0]?.activeTab?.label, 'label3');
// Open tab 2 back
extHostEditorTabs.$acceptTabOperation({
@@ -674,8 +529,8 @@ suite('ExtHostEditorTabs', function () {
kind: TabModelOperationKind.TAB_OPEN,
tabDto: tab2
});
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.map(g => g.tabs).flat().length, 2);
assert.strictEqual(extHostEditorTabs.tabGroups.groups[0]?.tabs[1]?.label, 'label2');
assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
assert.strictEqual(extHostEditorTabs.tabGroups.all.map(g => g.tabs).flat().length, 2);
assert.strictEqual(extHostEditorTabs.tabGroups.all[0]?.tabs[1]?.label, 'label2');
});
});