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 <[email protected]>
  • Loading branch information
Akos Kitta committed May 27, 2020
1 parent 99a7aa3 commit c924c1e
Show file tree
Hide file tree
Showing 23 changed files with 1,203 additions and 351 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class SampleOutputChannelWithSeverity
channel.appendLine('hello info2', OutputChannelSeverity.Info);
channel.appendLine('hello error', OutputChannelSeverity.Error);
channel.appendLine('hello warning', OutputChannelSeverity.Warning);
channel.append('inlineInfo1 ');
channel.append('inlineWarning ', OutputChannelSeverity.Warning);
channel.append('inlineError ', OutputChannelSeverity.Error);
channel.append('inlineInfo2', OutputChannelSeverity.Info);
}
}
export const bindSampleOutputChannelWithSeverity = (bind: interfaces.Bind) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,4 @@
"vscode-eslint": "https://open-vsx.org/api/dbaeumer/vscode-eslint/2.1.1/file/dbaeumer.vscode-eslint-2.1.1.vsix",
"vscode-references-view": "https://open-vsx.org/api/ms-vscode/references-view/0.0.47/file/ms-vscode.references-view-0.0.47.vsix"
}
}
}
3 changes: 0 additions & 3 deletions packages/languages/compile.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
{
"path": "../core/compile.tsconfig.json"
},
{
"path": "../output/compile.tsconfig.json"
},
{
"path": "../process/compile.tsconfig.json"
},
Expand Down
3 changes: 1 addition & 2 deletions packages/languages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"@theia/application-package": "^1.1.0",
"@theia/core": "^1.1.0",
"@theia/monaco-editor-core": "^0.19.3",
"@theia/output": "^1.1.0",
"@theia/process": "^1.1.0",
"@theia/workspace": "^1.1.0",
"@types/uuid": "^7.0.3",
Expand Down Expand Up @@ -51,4 +50,4 @@
"nyc": {
"extends": "../../configs/nyc.json"
}
}
}
43 changes: 23 additions & 20 deletions packages/languages/src/browser/window-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { MessageService } from '@theia/core/lib/common';
import { MessageService, CommandRegistry } from '@theia/core/lib/common';
import { Window, OutputChannel, MessageActionItem, MessageType } from 'monaco-languageclient/lib/services';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';

@injectable()
export class WindowImpl implements Window {

private canAccessOutput: boolean | undefined;
protected static readonly NOOP_CHANNEL: OutputChannel = {
append: () => { },
appendLine: () => { },
dispose: () => { },
show: () => { }
};

@inject(MessageService) protected readonly messageService: MessageService;
@inject(OutputChannelManager) protected readonly outputChannelManager: OutputChannelManager;
@inject(OutputContribution) protected readonly outputContribution: OutputContribution;
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;

showMessage<T extends MessageActionItem>(type: MessageType, message: string, ...actions: T[]): Thenable<T | undefined> {
const originalActions = new Map((actions || []).map(action => [action.title, action] as [string, T]));
Expand All @@ -52,22 +57,20 @@ export class WindowImpl implements Window {
}

createOutputChannel(name: string): OutputChannel {
const outputChannel = this.outputChannelManager.getChannel(name);
// Note: alternatively, we could add `@theia/output` as a `devDependency` and check, for instance,
// the manager for the output channels can be injected or not with `@optional()` but this approach has the same effect.
// The `@theia/languages` extension will be removed anyway: https://github.com/eclipse-theia/theia/issues/7100
if (this.canAccessOutput === undefined) {
this.canAccessOutput = !!this.commandRegistry.getCommand('output:append');
}
if (!this.canAccessOutput) {
return WindowImpl.NOOP_CHANNEL;
}
return {
append: outputChannel.append.bind(outputChannel),
appendLine: outputChannel.appendLine.bind(outputChannel),
show: async (preserveFocus?: boolean) => {
const options = Object.assign({
preserveFocus: false,
}, { preserveFocus });
const activate = !options.preserveFocus;
const reveal = options.preserveFocus;
await this.outputContribution.openView({ activate, reveal });
outputChannel.setVisibility(true);
},
dispose: () => {
this.outputChannelManager.deleteChannel(outputChannel.name);
}
append: text => this.commandRegistry.executeCommand('output:append', { name, text }),
appendLine: text => this.commandRegistry.executeCommand('output:appendLine', { name, text }),
dispose: () => this.commandRegistry.executeCommand('output:dispose', { name }),
show: (preserveFocus: boolean = false) => this.commandRegistry.executeCommand('output:show', { name, options: { preserveFocus } })
};
}
}
11 changes: 10 additions & 1 deletion packages/monaco/src/browser/monaco-context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { MenuPath } from '@theia/core/lib/common/menu';
import { EDITOR_CONTEXT_MENU } from '@theia/editor/lib/browser';
import { ContextMenuRenderer, toAnchor } from '@theia/core/lib/browser';
import IContextMenuService = monaco.editor.IContextMenuService;
Expand All @@ -35,7 +36,11 @@ export class MonacoContextMenuService implements IContextMenuService {
// Actions for editor context menu come as 'MenuItemAction' items
// In case of 'Quick Fix' actions come as 'CodeActionAction' items
if (actions.length > 0 && actions[0] instanceof monaco.actions.MenuItemAction) {
this.contextMenuRenderer.render(EDITOR_CONTEXT_MENU, anchor, () => delegate.onHide(false));
this.contextMenuRenderer.render({
menuPath: this.menuPath(),
anchor,
onHide: () => delegate.onHide(false)
});
} else {
const commands = new CommandRegistry();
const menu = new Menu({
Expand All @@ -61,4 +66,8 @@ export class MonacoContextMenuService implements IContextMenuService {
}
}

protected menuPath(): MenuPath {
return EDITOR_CONTEXT_MENU;
}

}
41 changes: 41 additions & 0 deletions packages/monaco/src/browser/monaco-editor-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { inject, injectable, named } from 'inversify';
import { Position } from 'vscode-languageserver-types';
import { TextDocumentSaveReason, TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol';
import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from 'monaco-languageclient';
import { MaybePromise } from '@theia/core/lib/common';
import { TextEditorDocument } from '@theia/editor/lib/browser';
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { ContributionProvider, Prioritizeable } from '@theia/core';
import { CancellationTokenSource, CancellationToken } from '@theia/core/lib/common/cancellation';
import { Resource, ResourceError, ResourceVersion } from '@theia/core/lib/common/resource';
import { Range } from 'vscode-languageserver-types';
Expand All @@ -42,6 +45,44 @@ export interface MonacoModelContentChangedEvent {
readonly contentChanges: TextDocumentContentChangeEvent[];
}

export const MonacoEditorModelFactoryHandler = Symbol('MonacoEditorModelFactoryHandler');
export interface MonacoEditorModelFactoryHandler {

canHandle(resource: Resource): MaybePromise<number>;

createModel(
resource: Resource,
m2p: MonacoToProtocolConverter,
p2m: ProtocolToMonacoConverter,
options?: { encoding?: string | undefined }
): MaybePromise<MonacoEditorModel>;

}

@injectable()
export class MonacoEditorModelFactory {

@inject(ContributionProvider)
@named(MonacoEditorModelFactoryHandler)
protected readonly contributions: ContributionProvider<MonacoEditorModelFactoryHandler>;

@inject(MonacoToProtocolConverter)
protected readonly m2p: MonacoToProtocolConverter;

@inject(ProtocolToMonacoConverter)
protected readonly p2m: ProtocolToMonacoConverter;

async createModel(resource: Resource, options?: { encoding?: string | undefined }): Promise<MonacoEditorModel> {
const contributions = this.contributions.getContributions();
const handler = (await Prioritizeable.prioritizeAll(contributions, c => c.canHandle(resource))).map(({ value }) => value).shift();
if (handler) {
return handler.createModel(resource, this.m2p, this.p2m, options);
}
return new MonacoEditorModel(resource, this.m2p, this.p2m, options);
}

}

export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument {

autoSave: 'on' | 'off' = 'on';
Expand Down
72 changes: 60 additions & 12 deletions packages/monaco/src/browser/monaco-editor-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
import URI from '@theia/core/lib/common/uri';
import { EditorPreferenceChange, EditorPreferences, TextEditor, DiffNavigator } from '@theia/editor/lib/browser';
import { DiffUris } from '@theia/core/lib/browser/diff-uris';
import { inject, injectable } from 'inversify';
import { inject, injectable, named } from 'inversify';
import { DisposableCollection, deepClone, Disposable, } from '@theia/core/lib/common';
import { MonacoToProtocolConverter, ProtocolToMonacoConverter, TextDocumentSaveReason } from 'monaco-languageclient';
import { MonacoCommandServiceFactory } from './monaco-command-service';
import { MonacoCommandServiceFactory, MonacoCommandService } from './monaco-command-service';
import { MonacoContextMenuService } from './monaco-context-menu';
import { MonacoDiffEditor } from './monaco-diff-editor';
import { MonacoDiffNavigatorFactory } from './monaco-diff-navigator-factory';
Expand All @@ -35,11 +35,22 @@ import { MonacoBulkEditService } from './monaco-bulk-edit-service';

import IEditorOverrideServices = monaco.editor.IEditorOverrideServices;
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
import { OS } from '@theia/core';
import { OS, MaybePromise, ContributionProvider, Prioritizeable } from '@theia/core';
import { KeybindingRegistry, OpenerService, open, WidgetOpenerOptions } from '@theia/core/lib/browser';
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handler';

export const MonacoEditorOptionsProvider = Symbol('MonacoEditorOptionsProvider');
export interface MonacoEditorOptionsProvider {
canHandle(model: MonacoEditorModel): MaybePromise<number>;
create(model: MonacoEditorModel, defaultOptions: MonacoEditor.IOptions): MonacoEditor.IOptions;
}
export const MonacoOverrideServicesProvider = Symbol('MonacoOverrideServicesProvider');
export interface MonacoOverrideServicesProvider {
canHandle(uri: URI): MaybePromise<number>;
create(uri: URI): MaybePromise<Partial<IEditorOverrideServices>>;
}

@injectable()
export class MonacoEditorProvider {

Expand All @@ -55,6 +66,14 @@ export class MonacoEditorProvider {
@inject(OpenerService)
protected readonly openerService: OpenerService;

@inject(ContributionProvider)
@named(MonacoEditorOptionsProvider)
protected readonly editorOptionsProvider: ContributionProvider<MonacoEditorOptionsProvider>;

@inject(ContributionProvider)
@named(MonacoOverrideServicesProvider)
protected readonly overrideServicesProvider: ContributionProvider<MonacoOverrideServicesProvider>;

private isWindowsBackend: boolean = false;

protected _current: MonacoEditor | undefined;
Expand Down Expand Up @@ -123,35 +142,53 @@ export class MonacoEditorProvider {

async get(uri: URI): Promise<MonacoEditor> {
await this.editorPreferences.ready;
return this.doCreateEditor((override, toDispose) => this.createEditor(uri, override, toDispose));
return this.doCreateEditor(uri, (override, toDispose) => this.createEditor(uri, override, toDispose));
}

protected async doCreateEditor(factory: (override: IEditorOverrideServices, toDispose: DisposableCollection) => Promise<MonacoEditor>): Promise<MonacoEditor> {
protected async getOverrideServices(uri: URI): Promise<IEditorOverrideServices> {
const commandService = this.commandServiceFactory();
const contextKeyService = this.contextKeyService.createScoped();
const { codeEditorService, textModelService, contextMenuService } = this;
const IWorkspaceEditService = this.bulkEditService;
const toDispose = new DisposableCollection(commandService);
const openerService = new monaco.services.OpenerService(codeEditorService, commandService);
openerService.registerOpener({
open: (uri, options) => this.interceptOpen(uri, options)
open: (u, options) => this.interceptOpen(u, options)
});
const editor = await factory({
let override: IEditorOverrideServices = {
codeEditorService,
textModelService,
contextMenuService,
commandService,
IWorkspaceEditService,
contextKeyService,
openerService
}, toDispose);
};
const contributions = this.overrideServicesProvider.getContributions();
const prioritized = (await Prioritizeable.prioritizeAll(contributions, c => c.canHandle(uri))).map(({ value }) => value);
for (const contribution of prioritized.reverse()) {
const refinedOverride = await contribution.create(uri);
override = { ...override, ...refinedOverride };
}
return override;
}

protected async doCreateEditor(uri: URI, factory: (override: IEditorOverrideServices, toDispose: DisposableCollection) => Promise<MonacoEditor>): Promise<MonacoEditor> {
const override = await this.getOverrideServices(uri);
const commandService = override.commandService;
const toDispose = new DisposableCollection();
if (commandService instanceof MonacoCommandService) {
toDispose.push(commandService);
}
const editor = await factory({ ...override }, toDispose);
editor.onDispose(() => toDispose.dispose());

this.suppressMonacoKeybindingListener(editor);
this.injectKeybindingResolver(editor);

const standaloneCommandService = new monaco.services.StandaloneCommandService(editor.instantiationService);
commandService.setDelegate(standaloneCommandService);
if (commandService instanceof MonacoCommandService) {
commandService.setDelegate(standaloneCommandService);
}
toDispose.push(this.installQuickOpenService(editor));
toDispose.push(this.installReferencesController(editor));

Expand Down Expand Up @@ -242,9 +279,13 @@ export class MonacoEditorProvider {
protected get preferencePrefixes(): string[] {
return ['editor.'];
}

protected async createMonacoEditor(uri: URI, override: IEditorOverrideServices, toDispose: DisposableCollection): Promise<MonacoEditor> {
const model = await this.getModel(uri, toDispose);
const options = this.createMonacoEditorOptions(model);
const contributions = this.editorOptionsProvider.getContributions();
const optionsProvider = (await Prioritizeable.prioritizeAll(contributions, c => c.canHandle(model))).map(({ value }) => value).shift();
const defaultOptions = this.createMonacoEditorOptions(model);
const options = optionsProvider ? optionsProvider.create(model, defaultOptions) : defaultOptions;
const editor = new MonacoEditor(uri, model, document.createElement('div'), this.services, options, override);
toDispose.push(this.editorPreferences.onPreferenceChanged(event => {
if (event.affects(uri.toString(), model.languageId)) {
Expand All @@ -255,12 +296,14 @@ export class MonacoEditorProvider {
editor.document.onWillSaveModel(event => event.waitUntil(this.formatOnSave(editor, event)));
return editor;
}

protected createMonacoEditorOptions(model: MonacoEditorModel): MonacoEditor.IOptions {
const options = this.createOptions(this.preferencePrefixes, model.uri, model.languageId);
options.model = model.textEditorModel;
options.readOnly = model.readOnly;
return options;
}

protected updateMonacoEditorOptions(editor: MonacoEditor, event?: EditorPreferenceChange): void {
if (event) {
const preferenceName = event.preferenceName;
Expand Down Expand Up @@ -295,6 +338,7 @@ export class MonacoEditorProvider {
protected get diffPreferencePrefixes(): string[] {
return [...this.preferencePrefixes, 'diffEditor.'];
}

protected async createMonacoDiffEditor(uri: URI, override: IEditorOverrideServices, toDispose: DisposableCollection): Promise<MonacoDiffEditor> {
const [original, modified] = DiffUris.decode(uri);

Expand All @@ -318,12 +362,14 @@ export class MonacoEditorProvider {
toDispose.push(editor.onLanguageChanged(() => this.updateMonacoDiffEditorOptions(editor)));
return editor;
}

protected createMonacoDiffEditorOptions(original: MonacoEditorModel, modified: MonacoEditorModel): MonacoDiffEditor.IOptions {
const options = this.createOptions(this.diffPreferencePrefixes, modified.uri, modified.languageId);
options.originalEditable = !original.readOnly;
options.readOnly = modified.readOnly;
return options;
}

protected updateMonacoDiffEditorOptions(editor: MonacoDiffEditor, event?: EditorPreferenceChange, resourceUri?: string): void {
if (event) {
const preferenceName = event.preferenceName;
Expand Down Expand Up @@ -353,6 +399,7 @@ export class MonacoEditorProvider {
this.doSetOption(options, value, optionName.split('.'));
return options;
}

protected toOptionName(preferenceName: string, prefixes: string[]): string {
for (const prefix of prefixes) {
if (preferenceName.startsWith(prefix)) {
Expand All @@ -361,6 +408,7 @@ export class MonacoEditorProvider {
}
return preferenceName;
}

protected doSetOption(obj: { [name: string]: any }, value: any, names: string[], idx: number = 0): void {
const name = names[idx];
if (!obj[name]) {
Expand Down Expand Up @@ -471,7 +519,7 @@ export class MonacoEditorProvider {
}

async createInline(uri: URI, node: HTMLElement, options?: MonacoEditor.IOptions): Promise<MonacoEditor> {
return this.doCreateEditor(async (override, toDispose) => {
return this.doCreateEditor(uri, async (override, toDispose) => {
override.contextMenuService = {
showContextMenu: () => {/* no-op*/ }
};
Expand Down
Loading

0 comments on commit c924c1e

Please sign in to comment.