Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debt - some multi-tabs select cleanup #213161

Merged
merged 17 commits into from
May 22, 2024
65 changes: 33 additions & 32 deletions src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,14 +889,15 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
if (e.shiftKey) {
let anchor;
let anchor: EditorInput;
if (this.lastSingleSelectSelectedEditor && this.tabsModel.isSelected(this.lastSingleSelectSelectedEditor)) {
// The last selected editor is the anchor
anchor = this.lastSingleSelectSelectedEditor;
} else {
// The active editor is the anchor
this.lastSingleSelectSelectedEditor = this.groupView.activeEditor!;
anchor = this.groupView.activeEditor!;
const activeEditor = assertIsDefined(this.groupView.activeEditor);
this.lastSingleSelectSelectedEditor = activeEditor;
anchor = activeEditor;
}
await this.selectEditorsBetween(editor, anchor);
} else if ((e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh)) {
Expand Down Expand Up @@ -1292,7 +1293,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
throw new BugIndicatingError();
}

const selection = this.groupView.selectedEditors;
let selection = this.groupView.selectedEditors;

// Unselect editors on other side of anchor in relation to the target
let currentIndex = anchorIndex;
Expand All @@ -1308,7 +1309,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
break;
}

selection.filter(editor => !editor.matches(currentEditor));
selection = selection.filter(editor => !editor.matches(currentEditor));
}

// Select editors between anchor and target
Expand All @@ -1334,7 +1335,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return;
}

let newActiveEditor = this.groupView.activeEditor!;
let newActiveEditor = assertIsDefined(this.groupView.activeEditor);

// If active editor is bing unselected then find the most recently opened selected editor
// that is not the editor being unselected
Expand All @@ -1355,7 +1356,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {

private async unselectAllEditors(): Promise<void> {
if (this.groupView.selectedEditors.length > 1) {
await this.groupView.setSelection(this.groupView.activeEditor!, []);
const activeEditor = assertIsDefined(this.groupView.activeEditor);
await this.groupView.setSelection(activeEditor, []);
}
}

Expand Down Expand Up @@ -1643,7 +1645,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}

private doRedrawTabActive(isGroupActive: boolean, allowBorderTop: boolean, editor: EditorInput, tabContainer: HTMLElement, tabActionBar: ActionBar): void {

const isActive = this.tabsModel.isActive(editor);
const isSelected = this.tabsModel.isSelected(editor);

Expand All @@ -1657,7 +1658,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
if (isActive) {
const activeTabBorderColorBottom = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER);
tabContainer.classList.toggle('tab-border-bottom', !!activeTabBorderColorBottom);
tabContainer.style.setProperty('--tab-border-bottom-color', activeTabBorderColorBottom?.toString() ?? '');
tabContainer.style.setProperty('--tab-border-bottom-color', activeTabBorderColorBottom ?? '');
}

// Set border TOP if theme defined color
Expand Down Expand Up @@ -2219,30 +2220,30 @@ export class MultiEditorTabsControl extends EditorTabsControl {
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data) && data.length > 0) {
const sourceGroup = data.length ? this.editorPartsView.getGroup(data[0].identifier.groupId) : undefined;
const isLocalMove = sourceGroup === this.groupView;

// Keep the same order when moving / copying editors within the same group
for (const de of data) {
const editor = de.identifier.editor;

// Only allow moving/copying from a single group source
if (!sourceGroup || sourceGroup.id !== de.identifier.groupId) {
continue;
}

const sourceEditorIndex = sourceGroup.getIndexOfEditor(editor);
if (isLocalMove && sourceEditorIndex < targetEditorIndex) {
targetEditorIndex--;
}

if (this.isMoveOperation(e, de.identifier.groupId, editor)) {
sourceGroup.moveEditor(editor, this.groupView, { ...options, index: targetEditorIndex });
} else {
sourceGroup.copyEditor(editor, this.groupView, { ...options, index: targetEditorIndex });
const sourceGroup = this.editorPartsView.getGroup(data[0].identifier.groupId);
if (sourceGroup) {
for (const de of data) {
const editor = de.identifier.editor;

// Only allow moving/copying from a single group source
if (sourceGroup.id !== de.identifier.groupId) {
continue;
}

// Keep the same order when moving / copying editors within the same group
const sourceEditorIndex = sourceGroup.getIndexOfEditor(editor);
if (sourceGroup === this.groupView && sourceEditorIndex < targetEditorIndex) {
targetEditorIndex--;
}

if (this.isMoveOperation(e, de.identifier.groupId, editor)) {
sourceGroup.moveEditor(editor, this.groupView, { ...options, index: targetEditorIndex });
} else {
sourceGroup.copyEditor(editor, this.groupView, { ...options, index: targetEditorIndex });
}

targetEditorIndex++;
}

targetEditorIndex++;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont
return this.stickyEditorTabsControl.getHeight() + this.unstickyEditorTabsControl.getHeight();
}

public override dispose(): void {
override dispose(): void {
this.parent.classList.toggle('two-tab-bars', false);

super.dispose();
Expand Down
41 changes: 22 additions & 19 deletions src/vs/workbench/common/editor/editorGroupModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {

private locked = false;

private selection: EditorInput[] = []; // editors in selected state, first one is active
private selection: EditorInput[] = []; // editors in selected state, first one is active TODO align this with transient editors and use a Set<EditorInput> instead
bpasero marked this conversation as resolved.
Show resolved Hide resolved

private set active(editor: EditorInput | null) {
private set active(editor: EditorInput | null) { // TODO: this misses an event when selection changes and should be a method?
bpasero marked this conversation as resolved.
Show resolved Hide resolved
this.selection = editor ? [editor] : [];
}
private get active(): EditorInput | null {
Expand Down Expand Up @@ -413,7 +413,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
};
this._onDidModelChange.fire(event);

// Handle active & selection
// Handle active & selection TODO why do we call this when `makeActive` is false?
bpasero marked this conversation as resolved.
Show resolved Hide resolved
this.setSelection(makeActive ? newEditor : this.activeEditor, options?.inactiveSelection ?? []);

return {
Expand All @@ -434,7 +434,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
this.doPin(existingEditor, existingEditorIndex);
}

// Activate / select it
// Activate / select it TODO why do we call this when `makeActive` is false?
bpasero marked this conversation as resolved.
Show resolved Hide resolved
this.setSelection(makeActive ? existingEditor : this.activeEditor, options?.inactiveSelection ?? []);

// Respect index
Expand Down Expand Up @@ -570,22 +570,22 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
}
}

const newSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor));
this.doSetSelection(newActive, this.editors.indexOf(newActive), newSelection);
const newInactiveSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor));
this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelection);
}

// One Editor
else {
this.active = null;
this.active = null; // TODO this potentially changes selection without a related event. and is it expected to change the selection?
bpasero marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Remove from selection
else if (!isActiveEditor) {
const wasSelected = !!this.selection.find(selected => this.matches(selected, editor));
if (wasSelected) {
const newSelection = this.selection.filter(selected => !selected.matches(editor));
this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newSelection);
const newInactiveSelection = this.selection.filter(selected => !selected.matches(editor));
this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newInactiveSelection);
}
}

Expand Down Expand Up @@ -692,11 +692,11 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {

private doSetActive(editor: EditorInput, editorIndex: number): void {
if (this.matches(this.active, editor)) {
this.selection = [editor];
this.selection = [editor]; // TODO this potentially changes selection without a related event. and is it expected to change the selection?
benibenj marked this conversation as resolved.
Show resolved Hide resolved
return; // already active
}

this.active = editor;
this.active = editor; // TODO this potentially changes selection without a related event. and is it expected to change the selection?
bpasero marked this conversation as resolved.
Show resolved Hide resolved

// Bring to front in MRU list
const mruIndex = this.indexOf(editor, this.mru);
Expand All @@ -712,14 +712,17 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
this._onDidModelChange.fire(event);
}

public get selectedEditors(): EditorInput[] {
get selectedEditors(): EditorInput[] {
// Return selected editors in sequential order
return this.editors.filter(editor => this.isSelected(editor));
return this.editors.filter(editor => this.isSelected(editor)); // TODO I would have assumed `this.selection` to be in sequential order
bpasero marked this conversation as resolved.
Show resolved Hide resolved
}

isSelected(editor: EditorInput | number): boolean {
if (typeof editor === 'number') {
editor = this.editors[editor];
isSelected(editorOrIndex: EditorInput | number): boolean { // TODO align with how isTransient() works
benibenj marked this conversation as resolved.
Show resolved Hide resolved
let editor: EditorInput;
if (typeof editorOrIndex === 'number') {
editor = this.editors[editorOrIndex];
} else {
editor = editorOrIndex;
}

return !!this.selection.find(selectedEditor => this.matches(selectedEditor, editor));
Expand All @@ -728,7 +731,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void {
const res = this.findEditor(activeSelectedEditor);
if (!res) {
return;
return; // not found
}

const [newActiveEditor, newActiveEditorIndex] = res;
Expand All @@ -739,14 +742,14 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void {
this.doSetActive(newActiveEditor, activeEditorIndex);

for (const candidate of inactiveSelectedEditors) {
for (const candidate of inactiveSelectedEditors) { // TODO should this move up to the public API? the idea is that we do not trust editor inputs from public API but internal is fine
benibenj marked this conversation as resolved.
Show resolved Hide resolved
const editor = this.findEditor(candidate)?.[0];
if (editor && !this.isSelected(editor)) {
this.selection.push(editor);
}
}
bpasero marked this conversation as resolved.
Show resolved Hide resolved

// Event
// Event TODO what if selection did not change?
benibenj marked this conversation as resolved.
Show resolved Hide resolved
const event: IGroupModelChangeEvent = {
kind: GroupModelChangeKind.EDITORS_SELECTION,
};
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/common/editor/filteredEditorGroupModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE
isTransient(editorOrIndex: EditorInput | number): boolean { return this.model.isTransient(editorOrIndex); }
isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); }
isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); }
isSelected(editor: EditorInput | number): boolean { return this.model.isSelected(editor); }
isSelected(editorOrIndex: EditorInput | number): boolean { return this.model.isSelected(editorOrIndex); }

isFirst(editor: EditorInput): boolean {
return this.model.isFirst(editor, this.getEditors(EditorsOrder.SEQUENTIAL));
Expand Down
Loading