Skip to content

Commit

Permalink
GH-7347: Added scroll-lock to the Output view.
Browse files Browse the repository at this point in the history
Closes #7347.
Closes #7008.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta committed May 26, 2020
1 parent c4e7651 commit 75318d7
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 152 deletions.
15 changes: 15 additions & 0 deletions packages/output/src/browser/output-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-con
import { Widget, KeybindingRegistry, KeybindingContext, ApplicationShell } from '@theia/core/lib/browser';
import { OUTPUT_WIDGET_KIND, OutputWidget } from './output-widget';
import { Command, CommandRegistry } from '@theia/core/lib/common';
import { OutputChannelManager } from '../common/output-channel';

export namespace OutputCommands {

Expand All @@ -37,6 +38,12 @@ export namespace OutputCommands {
label: 'Select All'
};

export const SCROLL_LOCK: Command = {
id: 'output:scrollLock',
label: 'Toggle Auto Scroll in Selected Channel',
category: OUTPUT_CATEGORY
};

}

/**
Expand Down Expand Up @@ -64,6 +71,9 @@ export class OutputContribution extends AbstractViewContribution<OutputWidget> {
@inject(OutputWidgetIsActiveContext)
protected readonly outputIsActiveContext: OutputWidgetIsActiveContext;

@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

constructor() {
super({
widgetId: OUTPUT_WIDGET_KIND,
Expand All @@ -88,6 +98,11 @@ export class OutputContribution extends AbstractViewContribution<OutputWidget> {
isVisible: () => this.outputIsActiveContext.isEnabled(),
execute: widget => this.withWidget(widget, outputWidget => outputWidget.selectAll())
});
commands.registerCommand(OutputCommands.SCROLL_LOCK, {
isEnabled: () => this.outputIsActiveContext.isEnabled(),
isVisible: () => this.outputIsActiveContext.isEnabled(),
execute: () => this.outputChannelManager.toggleScrollLock()
});
}

registerKeybindings(registry: KeybindingRegistry): void {
Expand Down
3 changes: 2 additions & 1 deletion packages/output/src/browser/output-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { ContainerModule } from 'inversify';
import { OutputWidget, OUTPUT_WIDGET_KIND } from './output-widget';
import { WidgetFactory, bindViewContribution, KeybindingContext } from '@theia/core/lib/browser';
import { WidgetFactory, bindViewContribution, KeybindingContext, FrontendApplicationContribution } from '@theia/core/lib/browser';
import { OutputContribution, OutputWidgetIsActiveContext } from './output-contribution';
import { OutputToolbarContribution } from './output-toolbar-contribution';
import { OutputChannelManager } from '../common/output-channel';
Expand All @@ -27,6 +27,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bindOutputPreferences(bind);
bind(OutputWidget).toSelf();
bind(OutputChannelManager).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(OutputChannelManager);

bind(WidgetFactory).toDynamicValue(context => ({
id: OUTPUT_WIDGET_KIND,
Expand Down
92 changes: 87 additions & 5 deletions packages/output/src/browser/output-toolbar-contribution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import { inject, injectable } from 'inversify';
import { OutputWidget } from './output-widget';
import { OutputChannelManager } from '../common/output-channel';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { OutputCommands } from './output-contribution';
import { OutputCommands, OutputContribution } from './output-contribution';
import * as React from 'react';

@injectable()
Expand All @@ -27,20 +28,30 @@ export class OutputToolbarContribution implements TabBarToolbarContribution {
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

@inject(OutputContribution)
protected readonly outputContribution: OutputContribution;

async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
toolbarRegistry.registerItem({
id: 'channels',
render: () => this.renderChannelSelector(),
isVisible: widget => (widget instanceof OutputWidget),
isVisible: widget => widget instanceof OutputWidget,
onDidChange: this.outputChannelManager.onListOrSelectionChange
});

toolbarRegistry.registerItem({
id: OutputCommands.CLEAR_OUTPUT_TOOLBAR.id,
command: OutputCommands.CLEAR_OUTPUT_TOOLBAR.id,
tooltip: 'Clear Output',
priority: 1,
});
toolbarRegistry.registerItem({
id: OutputCommands.SCROLL_LOCK.id,
render: () => <ScrollLockToolbarItem
key={OutputCommands.SCROLL_LOCK.id}
outputChannelManager={this.outputChannelManager} />,
isVisible: widget => widget instanceof OutputWidget,
priority: 2
});
}

protected readonly NONE = '<no channels>';
Expand All @@ -55,8 +66,8 @@ export class OutputToolbarContribution implements TabBarToolbarContribution {
}
return <select
className='theia-select'
id={OutputWidget.IDs.CHANNEL_LIST}
key={OutputWidget.IDs.CHANNEL_LIST}
id='outputChannelList'
key='outputChannelList'
value={this.outputChannelManager.selectedChannel ? this.outputChannelManager.selectedChannel.name : this.NONE}
onChange={this.changeChannel}
>
Expand All @@ -71,3 +82,74 @@ export class OutputToolbarContribution implements TabBarToolbarContribution {
}
};
}

export namespace ScrollLockToolbarItem {
export interface Props {
readonly outputChannelManager: OutputChannelManager;
}
export interface State {
readonly lockedChannels: Array<string>;
}
}
class ScrollLockToolbarItem extends React.Component<ScrollLockToolbarItem.Props, ScrollLockToolbarItem.State> {

protected readonly toDispose = new DisposableCollection();

constructor(props: Readonly<ScrollLockToolbarItem.Props>) {
super(props);
const lockedChannels = this.manager.getChannels().filter(({ isLocked }) => isLocked).map(({ name }) => name);
this.state = { lockedChannels };
}

componentDidMount(): void {
this.toDispose.pushAll([
// Update when the selected channel changes.
this.manager.onSelectedChannelChange(() => this.setState({ lockedChannels: this.state.lockedChannels })),
// Update when the selected channel's scroll-lock state changes.
this.manager.onLockChange(({ name, isLocked }) => {
const lockedChannels = this.state.lockedChannels.slice();
if (isLocked) {
lockedChannels.push(name);
} else {
const index = lockedChannels.indexOf(name);
if (index === -1) {
console.warn(`Could not unlock channel '${name}'. It was not locked.`);
} else {
lockedChannels.splice(index, 1);
}
}
this.setState({ lockedChannels });
}),
]);
}

componentWillUnmount(): void {
this.toDispose.dispose();
}

render(): React.ReactNode {
const { selectedChannel } = this.manager;
if (!selectedChannel) {
return undefined;
}
return <div
key='output:toggleScrollLock'
className={`fa fa-${selectedChannel.isLocked ? 'lock' : 'unlock-alt'} item enabled`}
title={`Turn Auto Scrolling ${selectedChannel.isLocked ? 'On' : 'Off'}`}
onClick={this.toggleScrollLock} />;
}

protected readonly toggleScrollLock = (e: React.MouseEvent<HTMLElement>) => this.doToggleScrollLock(e);
protected doToggleScrollLock(e: React.MouseEvent<HTMLElement>): void {
const { selectedChannel } = this.manager;
if (selectedChannel) {
selectedChannel.toggleLocked();
e.stopPropagation();
}
}

private get manager(): OutputChannelManager {
return this.props.outputChannelManager;
}

}
Loading

0 comments on commit 75318d7

Please sign in to comment.