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

Focus the viewpane on the next tick #2903

Merged
merged 4 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course this exists in async.ts! Good find!

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,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
Loading