Skip to content

feat: support open from git scm #2094

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
Dec 16, 2022
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
122 changes: 121 additions & 1 deletion packages/core-browser/src/monaco/merge-editor-widget.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { URI, UriComponents } from '@opensumi/ide-core-common';
import type { ICodeEditor } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
import type { IEditor } from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorCommon';
import type { IEditorModel } from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorCommon';
Expand All @@ -6,6 +7,125 @@ export interface IMergeEditorEditor extends IEditor {
getOursEditor(): ICodeEditor;
getResultEditor(): ICodeEditor;
getTheirsEditor(): ICodeEditor;
open(oursTextModel: IEditorModel, resultTextModel: IEditorModel, theirsTextModel: IEditorModel): Promise<void>;
open(openMergeEditorArgs: IOpenMergeEditorArgs): Promise<void>;
compare(): Promise<void>;
}

export class MergeEditorInputData {
static from(data: string): MergeEditorInputData {
try {
const obj: MergeEditorInputData = JSON.parse(data);
return new MergeEditorInputData(obj.uri, obj.detail, obj.description);
} catch (error) {
throw Error('invalid MergeEditorInputData parse');
}
}

private _textModel: IEditorModel;
public get textModel(): IEditorModel {
return this._textModel;
}

constructor(readonly uri: URI, readonly detail?: string | undefined, readonly description?: string | undefined) {}

public toString(): string {
return JSON.stringify({
uri: this.uri.toString(),
detail: this.detail,
description: this.description,
});
}

public setTextModel(model: IEditorModel): this {
this._textModel = model;
return this;
}
}

export interface IOpenMergeEditorArgs {
ancestor: {
uri: URI;
textModel: IEditorModel;
};
input1: MergeEditorInputData;
input2: MergeEditorInputData;
output: {
uri: URI;
textModel: IEditorModel;
};
}

interface IValidateOpenArgs {
ancestor: URI;
input1: MergeEditorInputData;
input2: MergeEditorInputData;
output: URI;
}

export namespace IRelaxedOpenMergeEditorArgs {
export const validate = (args: unknown): IValidateOpenArgs => {
if (!args || typeof args !== 'object') {
throw new TypeError('invalid argument');
}

const obj = args as IValidateOpenArgs;
const ancestor = toUri(obj.ancestor);
const output = toUri(obj.output);
const input1 = toInputData(obj.input1);
const input2 = toInputData(obj.input2);
return { ancestor, input1, input2, output };
};

export const toString = (args: IValidateOpenArgs): string => {
const { ancestor, input1, input2, output } = args;

return JSON.stringify({
ancestor: ancestor.toString(),
input1: input1.toString(),
input2: input2.toString(),
output: output.toString(),
});
};

const toInputData = (args: unknown): MergeEditorInputData => {
if (typeof args === 'string') {
return new MergeEditorInputData(URI.parse(args), undefined, undefined);
}
if (!args || typeof args !== 'object') {
throw new TypeError('invalid argument');
}

if (isUriComponents(args)) {
return new MergeEditorInputData(URI.from(args), undefined, undefined);
}

const obj = args as MergeEditorInputData;
const uri = toUri(obj.uri);
const detail = obj.detail;
const description = obj.description;
return new MergeEditorInputData(uri, detail, description);
};

const toUri = (args: unknown): URI => {
if (typeof args === 'string') {
return URI.parse(args);
} else if (args && typeof args === 'object') {
return URI.from(args as UriComponents);
}
throw new TypeError('invalid argument');
};

const isUriComponents = (args: unknown): args is UriComponents => {
if (!args || typeof args !== 'object') {
return false;
}
const obj = args as UriComponents;
return (
typeof obj.scheme === 'string' &&
typeof obj.authority === 'string' &&
typeof obj.path === 'string' &&
typeof obj.query === 'string' &&
typeof obj.fragment === 'string'
);
};
}
18 changes: 6 additions & 12 deletions packages/editor/src/browser/editor.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
URI,
Domain,
localize,
formatLocalize,
MonacoService,
ServiceNames,
MonacoContribution,
Expand Down Expand Up @@ -40,6 +41,7 @@ import { ComponentContribution, ComponentRegistry } from '@opensumi/ide-core-bro
import { MenuContribution, IMenuRegistry, MenuId } from '@opensumi/ide-core-browser/lib/menu/next';
import { AbstractContextMenuService } from '@opensumi/ide-core-browser/lib/menu/next/menu.interface';
import { ICtxMenuRenderer } from '@opensumi/ide-core-browser/lib/menu/next/renderer/ctxmenu/base';
import { IRelaxedOpenMergeEditorArgs } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget';
import { isWindows, isOSX, PreferenceScope, ILogger, OnEvent, WithEventBus } from '@opensumi/ide-core-common';
import { IElectronMainUIService } from '@opensumi/ide-core-common/lib/electron';
import { ITextmateTokenizer, ITextmateTokenizerService } from '@opensumi/ide-monaco/lib/browser/contrib/tokenizer';
Expand Down Expand Up @@ -550,22 +552,14 @@ export class EditorContribution
});

commands.registerCommand(EDITOR_COMMANDS.OPEN_MERGEEDITOR, {
execute: () => {
/**
* (DEV)
*/
const current = URI.parse(`${this.appConfig.workspaceDir}/merge-editor/c.json`);
const incoming = URI.parse(`${this.appConfig.workspaceDir}/merge-editor/b.json`);
const result = URI.parse(`${this.appConfig.workspaceDir}/merge-editor/a.json`);
const name = `${current.displayName} <=> ${result.displayName} <=> ${incoming.displayName}`;
execute: (args: unknown) => {
const validatedArgs = IRelaxedOpenMergeEditorArgs.validate(args);
this.workbenchEditorService.open(
URI.from({
scheme: 'mergeEditor',
query: URI.stringifyQuery({
name,
current,
incoming,
result,
name: formatLocalize('mergeEditor.workbench.tab.name', validatedArgs.output.displayName),
openMetadata: IRelaxedOpenMergeEditorArgs.toString(validatedArgs),
}),
}),
);
Expand Down
36 changes: 22 additions & 14 deletions packages/editor/src/browser/merge-editor/merge-editor.provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable, Autowired } from '@opensumi/di';
import { URI, WithEventBus, MaybePromise, getIcon, LabelService } from '@opensumi/ide-core-browser';
import { URI, WithEventBus, MaybePromise, LabelService } from '@opensumi/ide-core-browser';

import { IResourceProvider, IResource } from '../../common';

Expand All @@ -11,18 +11,26 @@ export class MergeEditorResourceProvider extends WithEventBus implements IResour
private readonly labelService: LabelService;

public provideResource(uri: URI): MaybePromise<IResource<any>> {
const { current, incoming, result, name } = uri.getParsedQuery();
const resultEditorUri = new URI(result);
const icon = this.labelService.getIcon(resultEditorUri);
return {
name,
icon,
uri,
metadata: {
current,
incoming,
result,
},
};
const { openMetadata, name } = uri.getParsedQuery();

try {
const parseMetaData = JSON.parse(openMetadata);
const { ancestor, input1, input2, output } = parseMetaData;
const resultEditorUri = new URI(output);
const icon = this.labelService.getIcon(resultEditorUri);
return {
name,
icon,
uri,
metadata: {
ancestor,
input1,
input2,
output,
},
};
} catch (error) {
throw Error('invalid merge editor resource parse');
}
}
}
33 changes: 23 additions & 10 deletions packages/editor/src/browser/workbench-editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
toMarkdown,
} from '@opensumi/ide-core-browser';
import { ResourceContextKey } from '@opensumi/ide-core-browser/lib/contextkey/resource';
import { IMergeEditorEditor } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget';
import { IMergeEditorEditor, MergeEditorInputData } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget';
import { isUndefinedOrNull, Schemes, REPORT_NAME, match, localize, MessageType } from '@opensumi/ide-core-common';
import {
CommandService,
Expand Down Expand Up @@ -1636,18 +1636,31 @@ export class EditorGroup extends WithEventBus implements IGridEditorGroup {
if (!metadata) {
return;
}
const [current, result, incoming] = await Promise.all([
this.getDocumentModelRef(metadata.current),
this.getDocumentModelRef(metadata.result),
this.getDocumentModelRef(metadata.incoming),

const { ancestor, input1, input2, output } = metadata;
const input1Data = MergeEditorInputData.from(input1);
const input2Data = MergeEditorInputData.from(input2);

const [ancestorRef, input1Ref, outputRef, input2Ref] = await Promise.all([
this.getDocumentModelRef(URI.parse(ancestor)),
this.getDocumentModelRef(input1Data.uri),
this.getDocumentModelRef(URI.parse(output)),
this.getDocumentModelRef(input2Data.uri),
]);

await this.mergeEditorReady.onceReady(async () => {
await this.mergeEditor.open(
current.instance.getMonacoModel(),
result.instance.getMonacoModel(),
incoming.instance.getMonacoModel(),
);
await this.mergeEditor.open({
ancestor: {
uri: URI.parse(metadata.ancestor),
textModel: ancestorRef.instance.getMonacoModel(),
},
input1: input1Data.setTextModel(input1Ref.instance.getMonacoModel()),
input2: input2Data.setTextModel(input2Ref.instance.getMonacoModel()),
output: {
uri: URI.parse(metadata.output),
textModel: outputRef.instance.getMonacoModel(),
},
});
});
} else {
return; // other type not handled
Expand Down
7 changes: 6 additions & 1 deletion packages/editor/src/common/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,10 @@ export enum AskSaveResult {
}

// #region merge editor
export type IMergeEditorResource = IResource<{ current: URI; result: URI; incoming: URI }>;
export type IMergeEditorResource = IResource<{
ancestor: string;
input1: string;
input2: string;
output: string;
}>;
// #endregion merge editor
6 changes: 0 additions & 6 deletions packages/extension/src/browser/extension.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,6 @@ export class ExtensionCommandContribution implements CommandContribution {
},
);

registry.registerCommand(VSCodeBuiltinCommands.OPEN_MERGEEDITOR, {
execute: (args: unknown[]) => {
// 这里拦截 vscode 内置的 _open.mergeEditor 命令
},
});

[
// layout builtin commands
VSCodeBuiltinCommands.LAYOUT_COMMAND_MAXIMIZE_EDITOR,
Expand Down
3 changes: 1 addition & 2 deletions packages/extension/src/browser/vscode/builtin-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,9 @@ export const LAYOUT_COMMAND_MAXIMIZE_EDITOR: Command = {

export const WALKTHROUGHS_COMMAND_GET_STARTED: Command = {
id: 'walkthroughs.get.started',
}
};

export const OPEN_MERGEEDITOR: Command = {
id: '_open.mergeEditor',
label: '_open.mergeEditor(DEV)',
delegate: EDITOR_COMMANDS.OPEN_MERGEEDITOR.id,
};
4 changes: 4 additions & 0 deletions packages/i18n/src/common/en-US.lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,10 @@ export const localizationBundle = {
'walkthroughs.welcome': 'Welcome',
'walkthroughs.get.started': "Open the 'Getting Started' walkthrough",
// #endregion walkthrough

// #region merge editor
'mergeEditor.workbench.tab.name': 'Merging: {0}',
// #endregion merge editor
...browserViews,
},
};
4 changes: 4 additions & 0 deletions packages/i18n/src/common/zh-CN.lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,10 @@ export const localizationBundle = {
'walkthroughs.welcome': '欢迎使用',
'walkthroughs.get.started': '打开 `入门` 演示',
// #endregion walkthrough

// #region merge editor
'mergeEditor.workbench.tab.name': '正在合并: {0}',
// #endregion merge editor
...browserViews,
},
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';

import { Injectable, Autowired, Injector, INJECTOR_TOKEN } from '@opensumi/di';
import { Injectable, Autowired } from '@opensumi/di';
import { AppConfig, ConfigProvider } from '@opensumi/ide-core-browser';
import { IMergeEditorEditor } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget';
import { IMergeEditorEditor, IOpenMergeEditorArgs } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget';
import { Disposable, IRange, ISelection } from '@opensumi/ide-core-common';
import { Selection } from '@opensumi/monaco-editor-core';
import { IDisposable } from '@opensumi/monaco-editor-core/esm/vs/base/common/lifecycle';
Expand Down Expand Up @@ -36,9 +36,6 @@ let MERGE_EDITOR_ID = 0;

@Injectable({ multiple: true })
export class MergeEditorWidget extends Disposable implements IMergeEditorEditor {
@Autowired(INJECTOR_TOKEN)
private readonly injector: Injector;

@Autowired(AppConfig)
private readonly configContext: AppConfig;

Expand All @@ -59,11 +56,11 @@ export class MergeEditorWidget extends Disposable implements IMergeEditorEditor
this.layout();
}

open(oursTextModel: ITextModel, resultTextModel: ITextModel, theirsTextModel: ITextModel): Promise<void> {
open({ ancestor, input1, input2 }: IOpenMergeEditorArgs): Promise<void> {
this.setModel({
ours: oursTextModel,
result: resultTextModel,
theirs: theirsTextModel,
ours: input1.textModel as ITextModel,
result: ancestor.textModel as ITextModel,
theirs: input2.textModel as ITextModel,
});

this.compare();
Expand Down