Skip to content

ATL-1106: Made all non-workspace editors read-only. #264

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

Merged
merged 1 commit into from
Mar 25, 2021
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
11 changes: 5 additions & 6 deletions arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
import { ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { EditorManager as TheiaEditorManager, EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser';
import { EditorManager } from './theia/editor/editor-manager';
import { EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser';
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
import {
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
Expand Down Expand Up @@ -153,6 +152,8 @@ import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/
import { SearchInWorkspaceWidget } from './theia/search-in-workspace/search-in-workspace-widget';
import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget';
import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider';
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';

const ElementQueries = require('css-element-queries/src/ElementQueries');

Expand Down Expand Up @@ -305,6 +306,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(TheiaOutputChannelRegistryMainImpl).toService(OutputChannelRegistryMainImpl);
bind(MonacoTextModelService).toSelf().inSingletonScope();
rebind(TheiaMonacoTextModelService).toService(MonacoTextModelService);
bind(MonacoEditorProvider).toSelf().inSingletonScope();
rebind(TheiaMonacoEditorProvider).toService(MonacoEditorProvider);

bind(SearchInWorkspaceWidget).toSelf();
rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget);
Expand All @@ -321,10 +324,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
rebind(TheiaFrontendConnectionStatusService).toService(FrontendConnectionStatusService);

// Editor customizations. Sets the editor to `readOnly` if under the data dir.
bind(EditorManager).toSelf().inSingletonScope();
rebind(TheiaEditorManager).toService(EditorManager);

// Decorator customizations
bind(TabBarDecoratorService).toSelf().inSingletonScope();
rebind(TheiaTabBarDecoratorService).toService(TabBarDecoratorService);
Expand Down
31 changes: 0 additions & 31 deletions arduino-ide-extension/src/browser/theia/editor/editor-manager.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { inject, injectable } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';

type CancelablePromise = Promise<monaco.referenceSearch.ReferencesModel> & { cancel: () => void };
interface EditorFactory {
(override: monaco.editor.IEditorOverrideServices, toDispose: DisposableCollection): Promise<MonacoEditor>;
}

@injectable()
export class MonacoEditorProvider extends TheiaMonacoEditorProvider {

@inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl;

protected async doCreateEditor(uri: URI, factory: EditorFactory): Promise<MonacoEditor> {
const editor = await super.doCreateEditor(uri, factory);
const toDispose = new DisposableCollection();
toDispose.push(this.installCustomReferencesController(editor));
toDispose.push(editor.onDispose(() => toDispose.dispose()));
return editor;
}

private installCustomReferencesController(editor: MonacoEditor): Disposable {
const control = editor.getControl();
const referencesController = control._contributions['editor.contrib.referencesController'];
const originalToggleWidget = referencesController.toggleWidget;
const toDispose = new DisposableCollection();
const toDisposeBeforeToggleWidget = new DisposableCollection();
referencesController.toggleWidget = (range: monaco.Range, modelPromise: CancelablePromise, peekMode: boolean) => {
toDisposeBeforeToggleWidget.dispose();
originalToggleWidget.bind(referencesController)(range, modelPromise, peekMode);
if (referencesController._widget) {
if ('onDidClose' in referencesController._widget) {
toDisposeBeforeToggleWidget.push((referencesController._widget as any).onDidClose(() => toDisposeBeforeToggleWidget.dispose()));
}
const preview = (referencesController._widget as any)._preview as monaco.editor.ICodeEditor;
if (preview) {
toDisposeBeforeToggleWidget.push(preview.onDidChangeModel(() => this.updateReadOnlyState(preview)))
this.updateReadOnlyState(preview);
}
}
};
toDispose.push(Disposable.create(() => toDisposeBeforeToggleWidget.dispose()));
toDispose.push(Disposable.create(() => referencesController.toggleWidget = originalToggleWidget));
return toDispose;
}

private updateReadOnlyState(editor: monaco.editor.ICodeEditor | undefined): void {
if (!editor) {
return;
}
const model = editor.getModel();
if (!model) {
return;
}
const readOnly = this.sketchesServiceClient.isReadOnly(model.uri);
editor.updateOptions({ readOnly });
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { injectable } from 'inversify';
import { inject, injectable } from 'inversify';
import { Resource } from '@theia/core/lib/common/resource';
import { MaybePromise } from '@theia/core/lib/common/types';
import { Log, Loggable } from '@theia/core/lib/common/logger';
import { ILogger, Log, Loggable } from '@theia/core/lib/common/logger';
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
import { EditorPreferences } from '@theia/editor/lib/browser/editor-preferences';
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';

@injectable()
export class MonacoTextModelService extends TheiaMonacoTextModelService {

protected createModel(resource: Resource): MaybePromise<MonacoEditorModel> {
@inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl;

protected async createModel(resource: Resource): Promise<MonacoEditorModel> {
const factory = this.factories.getContributions().find(({ scheme }) => resource.uri.scheme === scheme);
return factory ? factory.createModel(resource) : new SilentMonacoEditorModel(resource, this.m2p, this.p2m, this.logger);
const readOnly = this.sketchesServiceClient.isReadOnly(resource.uri);
return factory ? factory.createModel(resource) : new MaybeReadonlyMonacoEditorModel(resource, this.m2p, this.p2m, this.logger, undefined, readOnly);
}

}

// https://github.com/eclipse-theia/theia/pull/8491
export class SilentMonacoEditorModel extends MonacoEditorModel {
class SilentMonacoEditorModel extends MonacoEditorModel {

protected trace(loggable: Loggable): void {
if (this.logger) {
Expand All @@ -27,3 +34,41 @@ export class SilentMonacoEditorModel extends MonacoEditorModel {
}

}

class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {

constructor(
protected readonly resource: Resource,
protected readonly m2p: MonacoToProtocolConverter,
protected readonly p2m: ProtocolToMonacoConverter,
protected readonly logger?: ILogger,
protected readonly editorPreferences?: EditorPreferences,
protected readonly _readOnly?: boolean,
) {
super(resource, m2p, p2m, logger, editorPreferences)
}

get readOnly(): boolean {
if (typeof this._readOnly === 'boolean') {
return this._readOnly;
}
return this.resource.saveContents === undefined;
}

protected setDirty(dirty: boolean): void {
if (this._readOnly === true) {
// NOOP
return;
}
if (dirty === this._dirty) {
return;
}
this._dirty = dirty;
if (dirty === false) {
(this as any).updateSavedVersionId();
}
this.onDirtyChangedEmitter.fire(undefined);
}


}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { inject, injectable } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event';
import { notEmpty } from '@theia/core/lib/common/objects';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FileChangeType } from '@theia/filesystem/lib/common/files';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { Sketch, SketchesService } from '../../common/protocol';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { ConfigService } from './config-service';
import { DisposableCollection, Emitter } from '@theia/core';
import { FileChangeType } from '@theia/filesystem/lib/browser';
import { SketchContainer } from './sketches-service';

@injectable()
Expand Down Expand Up @@ -129,4 +130,13 @@ export class SketchesServiceClientImpl implements FrontendApplicationContributio
}, 100);
}

/**
* `true` if the `uri` is not contained in any of the opened workspaces. Otherwise, `false`.
*/
isReadOnly(uri: URI | monaco.Uri | string): boolean {
const toCheck = uri instanceof URI ? uri : new URI(uri);
const readOnly = !this.workspaceService.tryGetRoots().some(({ resource }) => resource.isEqualOrParent(toCheck));
return readOnly;
}

}