debt - some multi-tabs select cleanup (#213161)

This commit is contained in:
Benjamin Pasero 2024-05-22 13:36:38 +02:00 committed by GitHub
parent ea8e9f09d6
commit 9512a2e9a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 137 additions and 110 deletions

View file

@ -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)) {
@ -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;
@ -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
@ -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
@ -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, []);
}
}
@ -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);
@ -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
@ -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;
const sourceGroup = this.editorPartsView.getGroup(data[0].identifier.groupId);
if (sourceGroup) {
for (const de of data) {
const editor = de.identifier.editor;
// 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.id !== de.identifier.groupId) {
continue;
}
// Only allow moving/copying from a single group source
if (!sourceGroup || 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++;
}
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 });
}
targetEditorIndex++;
}
}

View file

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

View file

@ -183,7 +183,7 @@ export interface IReadonlyEditorGroupModel {
isActive(editor: EditorInput | IUntypedEditorInput): boolean;
isPinned(editorOrIndex: EditorInput | number): boolean;
isSticky(editorOrIndex: EditorInput | number): boolean;
isSelected(editor: EditorInput | number): boolean;
isSelected(editorOrIndex: EditorInput | number): boolean;
isTransient(editorOrIndex: EditorInput | number): boolean;
isFirst(editor: EditorInput, editors?: EditorInput[]): boolean;
isLast(editor: EditorInput, editors?: EditorInput[]): boolean;
@ -222,9 +222,6 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
private selection: EditorInput[] = []; // editors in selected state, first one is active
private set active(editor: EditorInput | null) {
this.selection = editor ? [editor] : [];
}
private get active(): EditorInput | null {
return this.selection[0] ?? null;
}
@ -299,8 +296,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
return this.active;
}
isActive(editor: EditorInput | IUntypedEditorInput): boolean {
return this.matches(this.active, editor);
isActive(candidate: EditorInput | IUntypedEditorInput): boolean {
return this.matches(this.active, candidate);
}
get previewEditor(): EditorInput | null {
@ -311,7 +308,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
const makePinned = options?.pinned || options?.sticky;
const makeTransient = !!options?.transient;
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.preview === this.activeEditor);
const existingEditorAndIndex = this.findEditor(candidate, options);
@ -413,7 +410,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
};
this._onDidModelChange.fire(event);
// Handle active & selection
// Handle active editor / selected editors
this.setSelection(makeActive ? newEditor : this.activeEditor, options?.inactiveSelection ?? []);
return {
@ -434,7 +431,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
this.doPin(existingEditor, existingEditorIndex);
}
// Activate / select it
// Handle active editor / selected editors
this.setSelection(makeActive ? existingEditor : this.activeEditor, options?.inactiveSelection ?? []);
// Respect index
@ -553,8 +550,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
const editor = this.editors[index];
const sticky = this.isSticky(index);
// Active Editor closed
const isActiveEditor = this.matches(this.active, editor);
// Active editor closed
const isActiveEditor = this.active === editor;
if (openNext && isActiveEditor) {
// More than one editor
@ -570,27 +567,29 @@ 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);
// Select editor as active
const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== newActive);
this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelectedEditors);
}
// One Editor
// Last editor closed: clear selection
else {
this.active = null;
this.doSetSelection(null, undefined, []);
}
}
// Remove from selection
// Inactive editor closed
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);
// Remove editor from inactive selection
if (this.doIsSelected(editor)) {
const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== this.activeEditor);
this.doSetSelection(this.activeEditor, this.indexOf(this.activeEditor), newInactiveSelectedEditors);
}
}
// Preview Editor closed
if (this.matches(this.preview, editor)) {
if (this.preview === editor) {
this.preview = null;
}
@ -685,72 +684,99 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
const [editor, editorIndex] = res;
this.doSetActive(editor, editorIndex);
this.doSetSelection(editor, editorIndex, []);
return editor;
}
private doSetActive(editor: EditorInput, editorIndex: number): void {
if (this.matches(this.active, editor)) {
this.selection = [editor];
return; // already active
get selectedEditors(): EditorInput[] {
return this.editors.filter(editor => this.doIsSelected(editor)); // return in sequential order
}
isSelected(editorCandidateOrIndex: EditorInput | number): boolean {
let editor: EditorInput | undefined;
if (typeof editorCandidateOrIndex === 'number') {
editor = this.editors[editorCandidateOrIndex];
} else {
editor = this.findEditor(editorCandidateOrIndex)?.[0];
}
this.active = editor;
// Bring to front in MRU list
const mruIndex = this.indexOf(editor, this.mru);
this.mru.splice(mruIndex, 1);
this.mru.unshift(editor);
// Event
const event: IGroupEditorChangeEvent = {
kind: GroupModelChangeKind.EDITOR_ACTIVE,
editor,
editorIndex
};
this._onDidModelChange.fire(event);
return !!editor && this.doIsSelected(editor);
}
public get selectedEditors(): EditorInput[] {
// Return selected editors in sequential order
return this.editors.filter(editor => this.isSelected(editor));
private doIsSelected(editor: EditorInput): boolean {
return this.selection.includes(editor);
}
isSelected(editor: EditorInput | number): boolean {
if (typeof editor === 'number') {
editor = this.editors[editor];
}
return !!this.selection.find(selectedEditor => this.matches(selectedEditor, editor));
}
setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void {
const res = this.findEditor(activeSelectedEditor);
setSelection(activeSelectedEditorCandidate: EditorInput, inactiveSelectedEditorCandidates: EditorInput[]): void {
const res = this.findEditor(activeSelectedEditorCandidate);
if (!res) {
return;
return; // not found
}
const [newActiveEditor, newActiveEditorIndex] = res;
const [activeSelectedEditor, activeSelectedEditorIndex] = res;
this.doSetSelection(newActiveEditor, newActiveEditorIndex, inactiveSelectedEditors);
const inactiveSelectedEditors = new Set<EditorInput>();
for (const inactiveSelectedEditorCandidate of inactiveSelectedEditorCandidates) {
const res = this.findEditor(inactiveSelectedEditorCandidate);
if (!res) {
return; // not found
}
const [inactiveSelectedEditor] = res;
if (inactiveSelectedEditor === activeSelectedEditor) {
continue; // already selected
}
inactiveSelectedEditors.add(inactiveSelectedEditor);
}
this.doSetSelection(activeSelectedEditor, activeSelectedEditorIndex, Array.from(inactiveSelectedEditors));
}
private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void {
this.doSetActive(newActiveEditor, activeEditorIndex);
private doSetSelection(activeSelectedEditor: EditorInput | null, activeSelectedEditorIndex: number | undefined, inactiveSelectedEditors: EditorInput[]): void {
const previousActiveEditor = this.activeEditor;
const previousSelection = this.selection;
for (const candidate of inactiveSelectedEditors) {
const editor = this.findEditor(candidate)?.[0];
if (editor && !this.isSelected(editor)) {
this.selection.push(editor);
}
let newSelection: EditorInput[];
if (activeSelectedEditor) {
newSelection = [activeSelectedEditor, ...inactiveSelectedEditors];
} else {
newSelection = [];
}
// Event
const event: IGroupModelChangeEvent = {
kind: GroupModelChangeKind.EDITORS_SELECTION,
};
this._onDidModelChange.fire(event);
// Update selection
this.selection = newSelection;
// Update active editor if it has changed
const activeEditorChanged = activeSelectedEditor && typeof activeSelectedEditorIndex === 'number' && previousActiveEditor !== activeSelectedEditor;
if (activeEditorChanged) {
// Bring to front in MRU list
const mruIndex = this.indexOf(activeSelectedEditor, this.mru);
this.mru.splice(mruIndex, 1);
this.mru.unshift(activeSelectedEditor);
// Event
const event: IGroupEditorChangeEvent = {
kind: GroupModelChangeKind.EDITOR_ACTIVE,
editor: activeSelectedEditor,
editorIndex: activeSelectedEditorIndex
};
this._onDidModelChange.fire(event);
}
// Fire event if the selection has changed
if (
activeEditorChanged ||
previousSelection.length !== newSelection.length ||
previousSelection.some(editor => !newSelection.includes(editor))
) {
const event: IGroupModelChangeEvent = {
kind: GroupModelChangeKind.EDITORS_SELECTION
};
this._onDidModelChange.fire(event);
}
}
setIndex(index: number) {
@ -838,12 +864,12 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
}
}
isPinned(editorOrIndex: EditorInput | number): boolean {
isPinned(editorCandidateOrIndex: EditorInput | number): boolean {
let editor: EditorInput;
if (typeof editorOrIndex === 'number') {
editor = this.editors[editorOrIndex];
if (typeof editorCandidateOrIndex === 'number') {
editor = this.editors[editorCandidateOrIndex];
} else {
editor = editorOrIndex;
editor = editorCandidateOrIndex;
}
return !this.matches(this.preview, editor);
@ -980,16 +1006,16 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
this._onDidModelChange.fire(event);
}
isTransient(editorOrIndex: EditorInput | number): boolean {
isTransient(editorCandidateOrIndex: EditorInput | number): boolean {
if (this.transient.size === 0) {
return false; // no transient editor
}
let editor: EditorInput | undefined;
if (typeof editorOrIndex === 'number') {
editor = this.editors[editorOrIndex];
if (typeof editorCandidateOrIndex === 'number') {
editor = this.editors[editorCandidateOrIndex];
} else {
editor = this.findEditor(editorOrIndex)?.[0];
editor = this.findEditor(editorCandidateOrIndex)?.[0];
}
return !!editor && this.transient.has(editor);
@ -1140,7 +1166,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
clone.editors = this.editors.slice(0);
clone.mru = this.mru.slice(0);
clone.preview = this.preview;
clone.active = this.active;
clone.selection = this.selection.slice(0);
clone.sticky = this.sticky;
// Ensure to register listeners for each editor
@ -1242,7 +1268,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
this.mru = coalesce(data.mru.map(i => this.editors[i]));
this.active = this.mru[0];
this.selection = this.mru.length > 0 ? [this.mru[0]] : [];
if (typeof data.preview === 'number') {
this.preview = this.editors[data.preview];

View file

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