Skip to content

Commit

Permalink
Support workspace.save(URI) and workspace.saveAs(URI) (#13393)
Browse files Browse the repository at this point in the history
Fixes #13352

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
  • Loading branch information
tsmaeder committed Feb 20, 2024
1 parent 81111d1 commit c9bbfe5
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 26 deletions.
9 changes: 5 additions & 4 deletions packages/core/src/browser/save-resource-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { inject, injectable } from 'inversify';
import { MessageService, UNTITLED_SCHEME } from '../common';
import { MessageService, UNTITLED_SCHEME, URI } from '../common';
import { Navigatable, NavigatableWidget } from './navigatable-types';
import { Saveable, SaveableSource, SaveOptions } from './saveable';
import { Widget } from './widgets';
Expand All @@ -41,19 +41,20 @@ export class SaveResourceService {
*
* No op if the widget is not saveable.
*/
async save(widget: Widget | undefined, options?: SaveOptions): Promise<void> {
async save(widget: Widget | undefined, options?: SaveOptions): Promise<URI | undefined> {
if (this.canSaveNotSaveAs(widget)) {
await Saveable.save(widget, options);
return NavigatableWidget.getUri(widget);
} else if (this.canSaveAs(widget)) {
await this.saveAs(widget, options);
return this.saveAs(widget, options);
}
}

canSaveAs(saveable?: Widget): saveable is Widget & SaveableSource & Navigatable {
return false;
}

saveAs(sourceWidget: Widget & SaveableSource & Navigatable, options?: SaveOptions): Promise<void> {
saveAs(sourceWidget: Widget & SaveableSource & Navigatable, options?: SaveOptions): Promise<URI | undefined> {
return Promise.reject('Unsupported: The base SaveResourceService does not support saveAs action.');
}
}
4 changes: 3 additions & 1 deletion packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,9 @@ export class ApplicationShell extends Widget {
Saveable.apply(
widget,
() => this.widgets.filter((maybeSaveable): maybeSaveable is Widget & SaveableSource => !!Saveable.get(maybeSaveable)),
(toSave, options) => this.saveResourceService.save(toSave, options),
async (toSave, options) => {
await this.saveResourceService.save(toSave, options);
},
);
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
for (const toTrack of widget.getTrackableWidgets()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class FilesystemSaveResourceService extends SaveResourceService {
/**
* Save `sourceWidget` to a new file picked by the user.
*/
override async saveAs(sourceWidget: Widget & SaveableSource & Navigatable, options?: SaveOptions): Promise<void> {
override async saveAs(sourceWidget: Widget & SaveableSource & Navigatable, options?: SaveOptions): Promise<URI | undefined> {
let exist: boolean = false;
let overwrite: boolean = false;
let selected: URI | undefined;
Expand All @@ -68,10 +68,11 @@ export class FilesystemSaveResourceService extends SaveResourceService {
}
} while ((selected && exist && !overwrite) || (selected?.isEqual(uri) && !canSave));
if (selected && selected.isEqual(uri)) {
await this.save(sourceWidget, options);
return this.save(sourceWidget, options);
} else if (selected) {
try {
await this.copyAndSave(sourceWidget, selected, overwrite);
return selected;
} catch (e) {
console.warn(e);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,8 @@ export interface TextEditorsMain {
$tryApplyEdits(id: string, modelVersionId: number, edits: SingleEditOperation[], opts: ApplyEditsOptions): Promise<boolean>;
$tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto, metadata?: WorkspaceEditMetadataDto): Promise<boolean>;
$tryInsertSnippet(id: string, template: string, selections: Range[], opts: UndoStopOptions): Promise<boolean>;
$save(uri: UriComponents): PromiseLike<UriComponents | undefined>;
$saveAs(uri: UriComponents): PromiseLike<UriComponents | undefined>;
$saveAll(includeUntitled?: boolean): Promise<boolean>;
// $getDiffInformation(id: string): Promise<editorCommon.ILineChange[]>;
}
Expand Down
6 changes: 1 addition & 5 deletions packages/plugin-ext/src/common/uri-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import URI, { UriComponents } from '@theia/core/lib/common/uri';
import { UriComponents } from '@theia/core/lib/common/uri';

export { UriComponents };

Expand Down Expand Up @@ -79,7 +79,3 @@ export namespace Schemes {

export const webviewPanel = 'webview-panel';
}

export function theiaUritoUriComponents(uri: URI): UriComponents {
return uri.toComponents();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export class CustomEditorWidget extends WebviewWidget implements SaveableSource,
Saveable.apply(
this,
() => this.shell.widgets.filter(widget => !!Saveable.get(widget)),
(widget, options) => this.saveService.save(widget, options),
async (widget, options) => {
await this.saveService.save(widget, options);
},
);
}
get saveable(): Saveable {
Expand Down
32 changes: 27 additions & 5 deletions packages/plugin-ext/src/main/browser/editors-and-documents-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import { EditorModelService } from './text-editor-model-service';
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { TextEditorMain } from './text-editor-main';
import { DisposableCollection, Emitter } from '@theia/core';
import { DisposableCollection, Emitter, URI } from '@theia/core';
import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
import { SaveResourceService } from '@theia/core/lib/browser/save-resource-service';

export class EditorsAndDocumentsMain implements Disposable {

Expand All @@ -41,7 +42,8 @@ export class EditorsAndDocumentsMain implements Disposable {
private readonly textEditors = new Map<string, TextEditorMain>();

private readonly modelService: EditorModelService;
private readonly editorService: EditorManager;
private readonly editorManager: EditorManager;
private readonly saveResourceService: SaveResourceService;

private readonly onTextEditorAddEmitter = new Emitter<TextEditorMain[]>();
private readonly onTextEditorRemoveEmitter = new Emitter<string[]>();
Expand All @@ -60,10 +62,11 @@ export class EditorsAndDocumentsMain implements Disposable {
constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT);

this.editorService = container.get(EditorManager);
this.editorManager = container.get(EditorManager);
this.modelService = container.get(EditorModelService);
this.saveResourceService = container.get(SaveResourceService);

this.stateComputer = new EditorAndDocumentStateComputer(d => this.onDelta(d), this.editorService, this.modelService);
this.stateComputer = new EditorAndDocumentStateComputer(d => this.onDelta(d), this.editorManager, this.modelService);
this.toDispose.push(this.stateComputer);
this.toDispose.push(this.onTextEditorAddEmitter);
this.toDispose.push(this.onTextEditorRemoveEmitter);
Expand Down Expand Up @@ -167,12 +170,31 @@ export class EditorsAndDocumentsMain implements Disposable {
return this.textEditors.get(id);
}

async save(uri: URI): Promise<URI | undefined> {
const editor = await this.editorManager.getByUri(uri);
if (!editor) {
return undefined;
}
return this.saveResourceService.save(editor);
}

async saveAs(uri: URI): Promise<URI | undefined> {
const editor = await this.editorManager.getByUri(uri);
if (!editor) {
return undefined;
}
if (!this.saveResourceService.canSaveAs(editor)) {
return undefined;
}
return this.saveResourceService.saveAs(editor);
}

saveAll(includeUntitled?: boolean): Promise<boolean> {
return this.modelService.saveAll(includeUntitled);
}

hideEditor(id: string): Promise<void> {
for (const editorWidget of this.editorService.all) {
for (const editorWidget of this.editorManager.all) {
const monacoEditor = MonacoEditor.get(editorWidget);
if (monacoEditor) {
if (id === new EditorSnapshot(monacoEditor).id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import { UriAwareCommandHandler, UriCommandHandler } from '@theia/core/lib/common/uri-command-handler';
import URI from '@theia/core/lib/common/uri';
import { SelectionService } from '@theia/core';
import { theiaUritoUriComponents } from '../../common/uri-components';

export namespace SelectionProviderCommands {
export const GET_SELECTED_CONTEXT: Command = {
Expand All @@ -36,7 +35,7 @@ export class SelectionProviderCommandContribution implements CommandContribution
commands.registerCommand(SelectionProviderCommands.GET_SELECTED_CONTEXT, this.newMultiUriAwareCommandHandler({
isEnabled: () => true,
isVisible: () => false,
execute: (selectedUris: URI[]) => selectedUris.map(uri => theiaUritoUriComponents(uri))
execute: (selectedUris: URI[]) => selectedUris.map(uri => uri.toComponents())
}));
}

Expand Down
14 changes: 11 additions & 3 deletions packages/plugin-ext/src/main/browser/text-editors-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { URI } from '@theia/core/shared/vscode-uri';
import {
TextEditorsMain,
MAIN_RPC_CONTEXT,
Expand All @@ -40,13 +39,14 @@ import { TextEditorMain } from './text-editor-main';
import { disposed } from '../../common/errors';
import { toMonacoWorkspaceEdit } from './languages-main';
import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service';
import { theiaUritoUriComponents, UriComponents } from '../../common/uri-components';
import { UriComponents } from '../../common/uri-components';
import { Endpoint } from '@theia/core/lib/browser/endpoint';
import * as monaco from '@theia/monaco-editor-core';
import { ResourceEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
import { IDecorationRenderOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/editorCommon';
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
import { URI } from '@theia/core';

export class TextEditorsMainImpl implements TextEditorsMain, Disposable {

Expand Down Expand Up @@ -171,7 +171,7 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable {

protected toRemoteUri(uri?: UriComponents): UriComponents | undefined {
if (uri && uri.scheme === 'file') {
return theiaUritoUriComponents(this.fileEndpoint.withQuery(URI.revive(uri).toString()));
return this.fileEndpoint.withQuery(URI.fromComponents(uri).toString()).toComponents();
}
return uri;
}
Expand Down Expand Up @@ -200,6 +200,14 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable {
return Promise.resolve();
}

$save(uri: UriComponents): PromiseLike<UriComponents | undefined> {
return this.editorsAndDocuments.save(URI.fromComponents(uri)).then(u => u?.toComponents());
}

$saveAs(uri: UriComponents): PromiseLike<UriComponents | undefined> {
return this.editorsAndDocuments.saveAs(URI.fromComponents(uri)).then(u => u?.toComponents());
}

$saveAll(includeUntitled?: boolean): Promise<boolean> {
return this.editorsAndDocuments.saveAll(includeUntitled);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,13 @@ export function createAPIFactory(
callbackOrToken?: CancellationToken | ((result: theia.TextSearchResult) => void), token?: CancellationToken): Promise<theia.TextSearchComplete> {
return workspaceExt.findTextInFiles(query, optionsOrCallback, callbackOrToken, token);
},
save(uri: theia.Uri): PromiseLike<theia.Uri | undefined> {
return editors.save(uri);
},

saveAs(uri: theia.Uri): PromiseLike<theia.Uri | undefined> {
return editors.saveAs(uri);
},
saveAll(includeUntitled?: boolean): PromiseLike<boolean> {
return editors.saveAll(includeUntitled);
},
Expand Down
11 changes: 9 additions & 2 deletions packages/plugin-ext/src/plugin/text-editors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
import { EditorsAndDocumentsExtImpl } from './editors-and-documents';
import { TextEditorExt } from './text-editor';
import * as Converters from './type-converters';
import { TextEditorSelectionChangeKind } from './types-impl';
import { TextEditorSelectionChangeKind, URI } from './types-impl';
import { IdGenerator } from '../common/id-generator';

export class TextEditorsExtImpl implements TextEditorsExt {

private readonly _onDidChangeTextEditorSelection = new Emitter<theia.TextEditorSelectionChangeEvent>();
private readonly _onDidChangeTextEditorOptions = new Emitter<theia.TextEditorOptionsChangeEvent>();
private readonly _onDidChangeTextEditorVisibleRanges = new Emitter<theia.TextEditorVisibleRangesChangeEvent>();
Expand Down Expand Up @@ -124,6 +123,14 @@ export class TextEditorsExtImpl implements TextEditorsExt {
return this.proxy.$tryApplyWorkspaceEdit(dto, metadata);
}

save(uri: theia.Uri): PromiseLike<theia.Uri | undefined> {
return this.proxy.$save(uri).then(components => URI.revive(components));
}

saveAs(uri: theia.Uri): PromiseLike<theia.Uri | undefined> {
return this.proxy.$saveAs(uri).then(components => URI.revive(components));
}

saveAll(includeUntitled?: boolean): PromiseLike<boolean> {
return this.proxy.$saveAll(includeUntitled);
}
Expand Down
23 changes: 23 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7532,6 +7532,29 @@ export module '@theia/plugin' {
*/
export function findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Thenable<Uri[]>;

/**
* Saves the editor identified by the given resource and returns the resulting resource or `undefined`
* if save was not successful or no editor with the given resource was found.
*
* **Note** that an editor with the provided resource must be opened in order to be saved.
*
* @param uri the associated uri for the opened editor to save.
* @returns A thenable that resolves when the save operation has finished.
*/
export function save(uri: Uri): Thenable<Uri | undefined>;

/**
* Saves the editor identified by the given resource to a new file name as provided by the user and
* returns the resulting resource or `undefined` if save was not successful or cancelled or no editor
* with the given resource was found.
*
* **Note** that an editor with the provided resource must be opened in order to be saved as.
*
* @param uri the associated uri for the opened editor to save as.
* @returns A thenable that resolves when the save-as operation has finished.
*/
export function saveAs(uri: Uri): Thenable<Uri | undefined>;

/**
* Save all dirty files.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi
}

async saveAs(widget: Widget & SaveableSource & Navigatable): Promise<void> {
return this.saveService.saveAs(widget);
await this.saveService.saveAs(widget);
}

protected updateWorkspaceStateKey(): WorkspaceState {
Expand Down

0 comments on commit c9bbfe5

Please sign in to comment.