Skip to content

Commit

Permalink
Focus the viewpane on the next tick (#2903)
Browse files Browse the repository at this point in the history
  • Loading branch information
lionel- authored May 2, 2024
1 parent 209b6c0 commit e1a7a96
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 73 deletions.
79 changes: 79 additions & 0 deletions src/vs/workbench/browser/positronViewPane/positronViewPane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
*--------------------------------------------------------------------------------------------*/

import { disposableTimeout } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewDescriptorService } from 'vs/workbench/common/views';

export abstract class PositronViewPane extends ViewPane {
private _disposableStore: DisposableStore;

/**
* Drive focus to an element inside the view.
* Called automatically by `focus()`.
*/
focusElement?(): void;

constructor(
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IInstantiationService instantiationService: IInstantiationService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(
options,
keybindingService,
contextMenuService,
configurationService,
contextKeyService,
viewDescriptorService,
instantiationService,
openerService,
themeService,
telemetryService
);
this._disposableStore = this._register(new DisposableStore());

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;
}

override focus(): void {
// We focus at the next tick because in some cases `focus()` is called
// when the viewpane is not visible yet (don't trust `this.isBodyVisible()`).
// In this case the `focus()` call fails (don't trust `this.onFocus()`).
// This happens for instance with `workbench.action.togglePanel` or
// `workbench.action.toggleSecondarySideBar`. Not doing it at the next tick
// would result in broken viewpane toggling, see
// https://github.com/posit-dev/positron/pull/2867
const focus = () => {
// The base class focuses the whole pane (we set its tabIndex to make this possible).
super.focus();

// Drive focus to an inner element if any. Also needs to be at the next tick,
// and after the `super.focus()` call.
this.focusElement?.();
};
disposableTimeout(focus, 0, this._disposableStore);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ export const ConsoleInstance = (props: ConsoleInstanceProps) => {
const scrollable = () =>
consoleInstanceRef.current.scrollHeight > consoleInstanceRef.current.clientHeight;

// Scrolls to the bottom.
// Scroll to the bottom.
// Wrapped in a `useCallback()` because the function is used as dependency
// in a `useEffect()`. Caching it prevents the `useEffect()` from being
// called on every rerender.
const scrollToBottom = useCallback(() => {
props.positronConsoleInstance.scrollLocked = false;
setIgnoreNextScrollEvent(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { PositronConsole } from 'vs/workbench/contrib/positronConsole/browser/positronConsole';
Expand All @@ -33,13 +33,12 @@ import { IExecutionHistoryService } from 'vs/workbench/contrib/executionHistory/
import { IPositronConsoleService } from 'vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService';
import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService';
import { IRuntimeStartupService } from 'vs/workbench/services/runtimeStartup/common/runtimeStartupService';
import { disposableTimeout } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronConsoleViewPane class.
*/
export class PositronConsoleViewPane extends ViewPane implements IReactComponentContainer {
export class PositronConsoleViewPane extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

/**
Expand Down Expand Up @@ -94,8 +93,6 @@ export class PositronConsoleViewPane extends ViewPane implements IReactComponent
*/
private _positronConsoleFocusedContextKey: IContextKey<boolean>;

private _disposableStore: DisposableStore;

//#endregion Private Properties

//#region IReactComponentContainer
Expand Down Expand Up @@ -232,13 +229,6 @@ export class PositronConsoleViewPane extends ViewPane implements IReactComponent
themeService,
telemetryService);

// Make the view pane focusable even when there are no components
// available to take the focus (such as the console input). This happens
// when no interpreter has been started yet. The viewpane must be able
// to take focus at all times because otherwise blurring events do not
// occur and the viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

// Bind the PositronConsoleFocused context key.
this._positronConsoleFocusedContextKey = PositronConsoleFocused.bindTo(contextKeyService);

Expand All @@ -247,8 +237,6 @@ export class PositronConsoleViewPane extends ViewPane implements IReactComponent
// Relay event for our `IReactComponentContainer` implementation
this._onVisibilityChangedEmitter.fire(visible);
}));

this._disposableStore = this._register(new DisposableStore());
}

/**
Expand Down Expand Up @@ -306,22 +294,14 @@ export class PositronConsoleViewPane extends ViewPane implements IReactComponent
}

/**
* focus override method.
* Drive focus to inner element.
* Called by `super.focus()`.
*/
override focus(): void {
// Call the base class's method.
super.focus();

override focusElement(): void {
// Trigger event that eventually causes console input widgets (main
// input, readline input, or restart buttons) to focus. Must be after
// the super call.
//
// We do this at the next tick because in some cases `focus()` is called
// when the viewpane is not visible yet (don't trust `this.isBodyVisible()`).
// In this case the `focus()` call fails (don't trust `this.onFocus()`).
// This happens for instance with `workbench.action.togglePanel` or
// `workbench.action.toggleSecondarySideBar`.
disposableTimeout(() => this.positronConsoleService.activePositronConsoleInstance?.focusInput(), 0, this._disposableStore);
this.positronConsoleService.activePositronConsoleInstance?.focusInput();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ import { IHelpEntry } from 'vs/workbench/contrib/positronHelp/browser/helpEntry'
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { ActionBars } from 'vs/workbench/contrib/positronHelp/browser/components/actionBars';
import { IPositronHelpService } from 'vs/workbench/contrib/positronHelp/browser/positronHelpService';
import { IReactComponentContainer, ISize, PositronReactRenderer } from 'vs/base/browser/positronReactRenderer';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronHelpView class.
*/
export class PositronHelpView extends ViewPane implements IReactComponentContainer {
export class PositronHelpView extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

// The width. This value is set in layoutBody and is used to implement the
Expand Down Expand Up @@ -190,12 +191,6 @@ export class PositronHelpView extends ViewPane implements IReactComponentContain
telemetryService
);

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

// Create containers.
this.positronHelpContainer = DOM.$('.positron-help-container');
this.helpActionBarsContainer = DOM.$('.help-action-bars-container');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { PositronPlots } from 'vs/workbench/contrib/positronPlots/browser/positronPlots';
import { ILanguageRuntimeService } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService';
import { IElementPosition, IReactComponentContainer, ISize, PositronReactRenderer } from 'vs/base/browser/positronReactRenderer';
import { IPositronPlotsService } from 'vs/workbench/services/positronPlots/common/positronPlots';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronPlotsViewPane class.
*/
export class PositronPlotsViewPane extends ViewPane implements IReactComponentContainer {
export class PositronPlotsViewPane extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

// The onSizeChanged emitter.
Expand Down Expand Up @@ -163,12 +164,6 @@ export class PositronPlotsViewPane extends ViewPane implements IReactComponentCo
// Call the base class's constructor.
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

// Register the onDidChangeBodyVisibility event handler.
this._register(this.onDidChangeBodyVisibility(visible => {
this._onVisibilityChangedEmitter.fire(visible);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ import { IElementPosition, IReactComponentContainer, ISize, PositronReactRendere
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { Event, Emitter } from 'vs/base/common/event';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IPositronPreviewService } from 'vs/workbench/contrib/positronPreview/browser/positronPreviewSevice';
import { PositronPreview } from 'vs/workbench/contrib/positronPreview/browser/positronPreview';
import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronPreviewViewPane class.
*/
export class PositronPreviewViewPane extends ViewPane implements IReactComponentContainer {
export class PositronPreviewViewPane extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

// The PositronReactRenderer.
Expand Down Expand Up @@ -91,12 +92,6 @@ export class PositronPreviewViewPane extends ViewPane implements IReactComponent

super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

this._register(this.onDidChangeBodyVisibility(() => this.onDidChangeVisibility(this.isBodyVisible())));
this._positronPreviewContainer = DOM.$('.positron-preview-container');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { PositronSessions } from 'vs/workbench/contrib/positronRuntimeSessions/browser/positronRuntimeSessions';
import { IReactComponentContainer, ISize, PositronReactRenderer } from 'vs/base/browser/positronReactRenderer';
import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronSessionsViewPane class.
*/
export class PositronRuntimeSessionsViewPane extends ViewPane implements IReactComponentContainer {
export class PositronRuntimeSessionsViewPane extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

/**
Expand Down Expand Up @@ -194,12 +195,6 @@ export class PositronRuntimeSessionsViewPane extends ViewPane implements IReactC
themeService,
telemetryService);

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

// Register the onDidChangeBodyVisibility event handler.
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { PositronVariables } from 'vs/workbench/contrib/positronVariables/browser/positronVariables';
import { ILanguageRuntimeService } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService';
import { IReactComponentContainer, ISize, PositronReactRenderer } from 'vs/base/browser/positronReactRenderer';
import { IPositronVariablesService } from 'vs/workbench/services/positronVariables/common/interfaces/positronVariablesService';
import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService';
import { PositronViewPane } from 'vs/workbench/browser/positronViewPane/positronViewPane';

/**
* PositronVariablesViewPane class.
*/
export class PositronVariablesViewPane extends ViewPane implements IReactComponentContainer {
export class PositronVariablesViewPane extends PositronViewPane implements IReactComponentContainer {
//#region Private Properties

/**
Expand Down Expand Up @@ -205,12 +206,6 @@ export class PositronVariablesViewPane extends ViewPane implements IReactCompone
themeService,
telemetryService);

// Make the viewpane focusable even when there are no components
// available to take the focus. The viewpane must be able to take focus
// at all times because otherwise blurring events do not occur and the
// viewpane management state becomes confused on toggle.
this.element.tabIndex = 0;

// Register the onDidChangeBodyVisibility event handler.
this._register(this.onDidChangeBodyVisibility(visible => {
// The browser will automatically set scrollTop to 0 on child components that have been
Expand Down Expand Up @@ -275,12 +270,10 @@ export class PositronVariablesViewPane extends ViewPane implements IReactCompone
}

/**
* focus override method.
* Drive focus to inner element.
* Called by `super.focus()`.
*/
override focus(): void {
// Call the base class's method.
super.focus();

override focusElement(): void {
// Trigger event that eventually causes variable pane widgets to focus
this._positronVariablesService.activePositronVariablesInstance?.focusElement();
}
Expand Down

0 comments on commit e1a7a96

Please sign in to comment.