mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
* Editors can steal focus later when opening slowly (fix #128117) * play it a bit safer * fix tests
This commit is contained in:
parent
4c0cecf300
commit
f01a44b447
|
@ -71,9 +71,7 @@ suite('vscode API - window', () => {
|
||||||
reg.dispose();
|
reg.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('editor, onDidChangeTextEditorViewColumn (close editor)', () => {
|
test('editor, onDidChangeTextEditorViewColumn (close editor)', async () => {
|
||||||
|
|
||||||
let actualEvent: TextEditorViewColumnChangeEvent;
|
|
||||||
|
|
||||||
const registration1 = workspace.registerTextDocumentContentProvider('bikes', {
|
const registration1 = workspace.registerTextDocumentContentProvider('bikes', {
|
||||||
provideTextDocumentContent() {
|
provideTextDocumentContent() {
|
||||||
|
@ -81,33 +79,30 @@ suite('vscode API - window', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all([
|
const doc1 = await workspace.openTextDocument(Uri.parse('bikes://testing/one'));
|
||||||
workspace.openTextDocument(Uri.parse('bikes://testing/one')).then(doc => window.showTextDocument(doc, ViewColumn.One)),
|
await window.showTextDocument(doc1, ViewColumn.One);
|
||||||
workspace.openTextDocument(Uri.parse('bikes://testing/two')).then(doc => window.showTextDocument(doc, ViewColumn.Two))
|
|
||||||
]).then(async editors => {
|
|
||||||
|
|
||||||
const [one, two] = editors;
|
const doc2 = await workspace.openTextDocument(Uri.parse('bikes://testing/two'));
|
||||||
|
const two = await window.showTextDocument(doc2, ViewColumn.Two);
|
||||||
|
|
||||||
await new Promise<void>(resolve => {
|
assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.Two);
|
||||||
const registration2 = window.onDidChangeTextEditorViewColumn(event => {
|
|
||||||
actualEvent = event;
|
const actualEvent = await new Promise<TextEditorViewColumnChangeEvent>(resolve => {
|
||||||
registration2.dispose();
|
const registration2 = window.onDidChangeTextEditorViewColumn(event => {
|
||||||
resolve();
|
registration2.dispose();
|
||||||
});
|
resolve(event);
|
||||||
// close editor 1, wait a little for the event to bubble
|
|
||||||
one.hide();
|
|
||||||
});
|
});
|
||||||
assert.ok(actualEvent);
|
// close editor 1, wait a little for the event to bubble
|
||||||
assert.ok(actualEvent.textEditor === two);
|
commands.executeCommand('workbench.action.closeEditorsInOtherGroups');
|
||||||
assert.ok(actualEvent.viewColumn === two.viewColumn);
|
|
||||||
|
|
||||||
registration1.dispose();
|
|
||||||
});
|
});
|
||||||
|
assert.ok(actualEvent);
|
||||||
|
assert.ok(actualEvent.textEditor === two);
|
||||||
|
assert.ok(actualEvent.viewColumn === two.viewColumn);
|
||||||
|
|
||||||
|
registration1.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('editor, onDidChangeTextEditorViewColumn (move editor group)', () => {
|
test('editor, onDidChangeTextEditorViewColumn (move editor group)', async () => {
|
||||||
|
|
||||||
const actualEvents: TextEditorViewColumnChangeEvent[] = [];
|
|
||||||
|
|
||||||
const registration1 = workspace.registerTextDocumentContentProvider('bikes', {
|
const registration1 = workspace.registerTextDocumentContentProvider('bikes', {
|
||||||
provideTextDocumentContent() {
|
provideTextDocumentContent() {
|
||||||
|
@ -115,38 +110,38 @@ suite('vscode API - window', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all([
|
const doc1 = await workspace.openTextDocument(Uri.parse('bikes://testing/one'));
|
||||||
workspace.openTextDocument(Uri.parse('bikes://testing/one')).then(doc => window.showTextDocument(doc, ViewColumn.One)),
|
await window.showTextDocument(doc1, ViewColumn.One);
|
||||||
workspace.openTextDocument(Uri.parse('bikes://testing/two')).then(doc => window.showTextDocument(doc, ViewColumn.Two))
|
|
||||||
]).then(editors => {
|
|
||||||
|
|
||||||
const [, two] = editors;
|
const doc2 = await workspace.openTextDocument(Uri.parse('bikes://testing/two'));
|
||||||
two.show();
|
await window.showTextDocument(doc2, ViewColumn.Two);
|
||||||
|
|
||||||
return new Promise<void>(resolve => {
|
assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.Two);
|
||||||
|
|
||||||
const registration2 = window.onDidChangeTextEditorViewColumn(event => {
|
const actualEvents = await new Promise<TextEditorViewColumnChangeEvent[]>(resolve => {
|
||||||
actualEvents.push(event);
|
|
||||||
|
|
||||||
if (actualEvents.length === 2) {
|
const actualEvents: TextEditorViewColumnChangeEvent[] = [];
|
||||||
registration2.dispose();
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// move active editor group left
|
const registration2 = window.onDidChangeTextEditorViewColumn(event => {
|
||||||
return commands.executeCommand('workbench.action.moveActiveEditorGroupLeft');
|
actualEvents.push(event);
|
||||||
|
|
||||||
}).then(() => {
|
if (actualEvents.length === 2) {
|
||||||
assert.strictEqual(actualEvents.length, 2);
|
registration2.dispose();
|
||||||
|
resolve(actualEvents);
|
||||||
for (const event of actualEvents) {
|
|
||||||
assert.strictEqual(event.viewColumn, event.textEditor.viewColumn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registration1.dispose();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// move active editor group left
|
||||||
|
return commands.executeCommand('workbench.action.moveActiveEditorGroupLeft');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
assert.strictEqual(actualEvents.length, 2);
|
||||||
|
|
||||||
|
for (const event of actualEvents) {
|
||||||
|
assert.strictEqual(event.viewColumn, event.textEditor.viewColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
registration1.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('active editor not always correct... #49125', async function () {
|
test('active editor not always correct... #49125', async function () {
|
||||||
|
|
|
@ -210,7 +210,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||||
this.element.appendChild(this.editorContainer);
|
this.element.appendChild(this.editorContainer);
|
||||||
|
|
||||||
// Editor pane
|
// Editor pane
|
||||||
this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.editorContainer, this));
|
this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.element, this.editorContainer, this));
|
||||||
this._onDidChange.input = this.editorPane.onDidChangeSizeConstraints;
|
this._onDidChange.input = this.editorPane.onDidChangeSizeConstraints;
|
||||||
|
|
||||||
// Track Focus
|
// Track Focus
|
||||||
|
@ -511,6 +511,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||||
// not changed meanwhile. This prevents focus from being
|
// not changed meanwhile. This prevents focus from being
|
||||||
// stolen accidentally on startup when the user already
|
// stolen accidentally on startup when the user already
|
||||||
// clicked somewhere.
|
// clicked somewhere.
|
||||||
|
|
||||||
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
|
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
|
||||||
this.focus();
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Severity from 'vs/base/common/severity';
|
||||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, isEditorOpenError } from 'vs/workbench/common/editor';
|
import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, isEditorOpenError } from 'vs/workbench/common/editor';
|
||||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||||
import { Dimension, show, hide, IDomNodePagePosition } from 'vs/base/browser/dom';
|
import { Dimension, show, hide, IDomNodePagePosition, isAncestor } from 'vs/base/browser/dom';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser/editor';
|
import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser/editor';
|
||||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||||
|
@ -91,7 +91,8 @@ export class EditorPanes extends Disposable {
|
||||||
private readonly editorPanesRegistry = Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane);
|
private readonly editorPanesRegistry = Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private parent: HTMLElement,
|
private editorGroupParent: HTMLElement,
|
||||||
|
private editorPanesParent: HTMLElement,
|
||||||
private groupView: IEditorGroupView,
|
private groupView: IEditorGroupView,
|
||||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
|
@ -237,20 +238,57 @@ export class EditorPanes extends Disposable {
|
||||||
// Editor pane
|
// Editor pane
|
||||||
const pane = this.doShowEditorPane(descriptor);
|
const pane = this.doShowEditorPane(descriptor);
|
||||||
|
|
||||||
|
// Remember current active element for deciding to restore focus later
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
|
||||||
// Apply input to pane
|
// Apply input to pane
|
||||||
const { changed, cancelled } = await this.doSetInput(pane, editor, options, context);
|
const { changed, cancelled } = await this.doSetInput(pane, editor, options, context);
|
||||||
|
|
||||||
// Focus unless cancelled
|
// Focus only if not cancelled and not prevented
|
||||||
if (!cancelled) {
|
const focus = !options || !options.preserveFocus;
|
||||||
const focus = !options || !options.preserveFocus;
|
if (!cancelled && focus && this.shouldRestoreFocus(activeElement)) {
|
||||||
if (focus) {
|
pane.focus();
|
||||||
pane.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { pane, changed, cancelled };
|
return { pane, changed, cancelled };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldRestoreFocus(expectedActiveElement: Element | null): boolean {
|
||||||
|
if (!this.layoutService.isRestored()) {
|
||||||
|
return true; // restore focus if we are not restored yet on startup
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectedActiveElement) {
|
||||||
|
return true; // restore focus if nothing was focused
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
|
||||||
|
if (!activeElement || activeElement === document.body) {
|
||||||
|
return true; // restore focus if nothing is focused currently
|
||||||
|
}
|
||||||
|
|
||||||
|
const same = expectedActiveElement === activeElement;
|
||||||
|
if (same) {
|
||||||
|
return true; // restore focus if same element is still active
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeElement.tagName !== 'INPUT' && activeElement.tagName !== 'TEXTAREA') {
|
||||||
|
|
||||||
|
// This is to avoid regressions from not restoring focus as we used to:
|
||||||
|
// Only allow a different input element (or textarea) to remain focused
|
||||||
|
// but not other elements that do not accept text input.
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAncestor(activeElement, this.editorGroupParent)) {
|
||||||
|
return true; // restore focus if active element is still inside our editor group
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // do not restore focus
|
||||||
|
}
|
||||||
|
|
||||||
private getEditorPaneDescriptor(editor: EditorInput): IEditorPaneDescriptor {
|
private getEditorPaneDescriptor(editor: EditorInput): IEditorPaneDescriptor {
|
||||||
if (editor.hasCapability(EditorInputCapabilities.RequiresTrust) && !this.workspaceTrustService.isWorkspaceTrusted()) {
|
if (editor.hasCapability(EditorInputCapabilities.RequiresTrust) && !this.workspaceTrustService.isWorkspaceTrusted()) {
|
||||||
// Workspace trust: if an editor signals it needs workspace trust
|
// Workspace trust: if an editor signals it needs workspace trust
|
||||||
|
@ -281,7 +319,7 @@ export class EditorPanes extends Disposable {
|
||||||
|
|
||||||
// Show editor
|
// Show editor
|
||||||
const container = assertIsDefined(editorPane.getContainer());
|
const container = assertIsDefined(editorPane.getContainer());
|
||||||
this.parent.appendChild(container);
|
this.editorPanesParent.appendChild(container);
|
||||||
show(container);
|
show(container);
|
||||||
|
|
||||||
// Indicate to editor that it is now visible
|
// Indicate to editor that it is now visible
|
||||||
|
@ -403,7 +441,7 @@ export class EditorPanes extends Disposable {
|
||||||
// Remove editor pane from parent
|
// Remove editor pane from parent
|
||||||
const editorPaneContainer = this._activeEditorPane.getContainer();
|
const editorPaneContainer = this._activeEditorPane.getContainer();
|
||||||
if (editorPaneContainer) {
|
if (editorPaneContainer) {
|
||||||
this.parent.removeChild(editorPaneContainer);
|
this.editorPanesParent.removeChild(editorPaneContainer);
|
||||||
hide(editorPaneContainer);
|
hide(editorPaneContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue