提交 3d30e66e 编写于 作者: B Benjamin Pasero

editors input factory - add canSerialize() to find out if editor can be serialized

上级 acaf2503
......@@ -119,6 +119,10 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) { }
canSerialize(editorInput: EditorInput): boolean {
return this.filesConfigurationService.isHotExitEnabled;
}
serialize(editorInput: EditorInput): string | undefined {
if (!this.filesConfigurationService.isHotExitEnabled) {
return undefined; // never restore untitled unless hot exit is enabled
......@@ -169,6 +173,20 @@ interface ISerializedSideBySideEditorInput {
// Register Side by Side Editor Input Factory
class SideBySideEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
const input = <SideBySideEditorInput>editorInput;
if (input.details && input.master) {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
return !!(detailsInputFactory?.canSerialize(input.details) && masterInputFactory?.canSerialize(input.master));
}
return false;
}
serialize(editorInput: EditorInput): string | undefined {
const input = <SideBySideEditorInput>editorInput;
......
......@@ -198,6 +198,11 @@ export interface IEditorInputFactoryRegistry {
export interface IEditorInputFactory {
/**
* Determines wether the given editor input can be serialized by the factory.
*/
canSerialize(editorInput: IEditorInput): boolean;
/**
* Returns a string representation of the provided editor input that contains enough information
* to deserialize back to the original editor input from the deserialize() method.
......
......@@ -44,6 +44,9 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(runtimeExtensionsEditorDescriptor, [new SyncDescriptor(RuntimeExtensionsInput)]);
class RuntimeExtensionsInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
return '';
}
......
......@@ -131,6 +131,10 @@ interface ISerializedFileInput {
// Register Editor Input Factory
class FileEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
const fileEditorInput = <FileEditorInput>editorInput;
const resource = fileEditorInput.getResource();
......
......@@ -26,6 +26,9 @@ Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkb
Registry.as<IEditorInputFactoryRegistry>(Input.EditorInputFactories).registerEditorInputFactory(
PerfviewInput.Id,
class implements IEditorInputFactory {
canSerialize(): boolean {
return true;
}
serialize(): string {
return '';
}
......
......@@ -87,6 +87,20 @@ interface ISerializedPreferencesEditorInput {
// Register Preferences Editor Input Factory
class PreferencesEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
const input = <PreferencesEditorInput>editorInput;
if (input.details && input.master) {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
return !!(detailsInputFactory?.canSerialize(input.details) && masterInputFactory?.canSerialize(input.master));
}
return false;
}
serialize(editorInput: EditorInput): string | undefined {
const input = <PreferencesEditorInput>editorInput;
......@@ -137,6 +151,10 @@ class PreferencesEditorInputFactory implements IEditorInputFactory {
class KeybindingsEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
const input = <KeybindingsEditorInput>editorInput;
return JSON.stringify({
......@@ -150,21 +168,18 @@ class KeybindingsEditorInputFactory implements IEditorInputFactory {
}
}
interface ISerializedSettingsEditor2EditorInput {
}
class SettingsEditor2InputFactory implements IEditorInputFactory {
serialize(input: SettingsEditor2Input): string {
const serialized: ISerializedSettingsEditor2EditorInput = {
};
canSerialize(editorInput: EditorInput): boolean {
return true;
}
return JSON.stringify(serialized);
serialize(input: SettingsEditor2Input): string {
return '{}';
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SettingsEditor2Input {
return instantiationService.createInstance(
SettingsEditor2Input);
return instantiationService.createInstance(SettingsEditor2Input);
}
}
......@@ -175,6 +190,10 @@ interface ISerializedDefaultPreferencesEditorInput {
// Register Default Preferences Editor Input Factory
class DefaultPreferencesEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
const input = <DefaultPreferencesEditorInput>editorInput;
......
......@@ -36,6 +36,10 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService
) { }
public canSerialize(input: WebviewInput): boolean {
return this._webviewWorkbenchService.shouldPersist(input);
}
public serialize(input: WebviewInput): string | undefined {
if (!this._webviewWorkbenchService.shouldPersist(input)) {
return undefined;
......
......@@ -596,6 +596,10 @@ export class WelcomeInputFactory implements IEditorInputFactory {
static readonly ID = welcomeInputTypeId;
public canSerialize(editorInput: EditorInput): boolean {
return true;
}
public serialize(editorInput: EditorInput): string {
return '{}';
}
......
......@@ -50,6 +50,10 @@ export class EditorWalkThroughInputFactory implements IEditorInputFactory {
static readonly ID = typeId;
public canSerialize(editorInput: EditorInput): boolean {
return true;
}
public serialize(editorInput: EditorInput): string {
return '{}';
}
......
......@@ -61,6 +61,10 @@ suite('EditorGroupsService', () => {
class TestEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
const testEditorInput = <TestEditorInput>editorInput;
const testInput: ISerializedTestEditorInput = {
......
......@@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IEditorModel, EditorActivation } from 'vs/platform/editor/common/editor';
import { EditorActivation } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { workbenchInstantiationService, TestStorageService, TestEditorInput } from 'vs/workbench/test/workbenchTestServices';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService';
......@@ -16,15 +16,12 @@ import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement }
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { timeout } from 'vs/base/common/async';
import { toResource } from 'vs/base/test/common/utils';
import { IFileService } from 'vs/platform/files/common/files';
......@@ -32,8 +29,11 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { CancellationToken } from 'vscode';
export class TestEditorControl extends BaseEditor {
class TestEditorControl extends BaseEditor {
constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); }
......@@ -48,54 +48,6 @@ export class TestEditorControl extends BaseEditor {
createEditor(): any { }
}
export class TestEditorInput extends EditorInput implements IFileEditorInput {
public gotDisposed = false;
public gotSaved = false;
public gotSavedAs = false;
public gotReverted = false;
public dirty = false;
private fails = false;
constructor(private resource: URI) { super(); }
getTypeId() { return 'testEditorInputForEditorService'; }
resolve(): Promise<IEditorModel | null> { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); }
matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; }
setEncoding(encoding: string) { }
getEncoding() { return undefined; }
setPreferredEncoding(encoding: string) { }
setMode(mode: string) { }
setPreferredMode(mode: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
setFailToOpen(): void {
this.fails = true;
}
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
this.gotSaved = true;
return Promise.resolve(true);
}
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
this.gotSavedAs = true;
return Promise.resolve(true);
}
revert(options?: IRevertOptions): Promise<boolean> {
this.gotReverted = true;
this.gotSaved = false;
this.gotSavedAs = false;
return Promise.resolve(true);
}
isDirty(): boolean {
return this.dirty;
}
isReadonly(): boolean {
return false;
}
dispose(): void {
super.dispose();
this.gotDisposed = true;
}
}
class FileServiceProvider extends Disposable {
constructor(scheme: string, @IFileService fileService: IFileService) {
super();
......@@ -137,7 +89,7 @@ suite('EditorService', () => {
});
let didCloseEditorListenerCounter = 0;
const didCloseEditorListener = service.onDidCloseEditor(editor => {
const didCloseEditorListener = service.onDidCloseEditor(() => {
didCloseEditorListenerCounter++;
});
......@@ -181,6 +133,8 @@ suite('EditorService', () => {
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
didCloseEditorListener.dispose();
part.dispose();
});
test('openEditors() / replaceEditors()', async () => {
......@@ -208,6 +162,8 @@ suite('EditorService', () => {
await service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup);
assert.equal(part.activeGroup.count, 2);
assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0);
part.dispose();
});
test('caching', function () {
......@@ -354,7 +310,7 @@ suite('EditorService', () => {
const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined);
const delegate = instantiationService.createInstance(DelegatingEditorService);
delegate.setEditorOpenHandler((delegate, group, input, options?) => {
delegate.setEditorOpenHandler((delegate, group, input) => {
assert.strictEqual(input, inp);
done();
......@@ -398,6 +354,8 @@ suite('EditorService', () => {
await rightGroup.closeEditor(input);
assert.equal(input.isDisposed(), true);
part.dispose();
});
test('open to the side', async () => {
......@@ -430,6 +388,8 @@ suite('EditorService', () => {
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor!.group, part.groups[1]);
part.dispose();
});
test('editor group activation', async () => {
......@@ -471,6 +431,8 @@ suite('EditorService', () => {
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup);
assert.equal(part.activeGroup, sideGroup);
part.dispose();
});
test('active editor change / visible editor change events', async function () {
......@@ -681,6 +643,8 @@ suite('EditorService', () => {
// cleanup
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
part.dispose();
});
test('openEditor returns NULL when opening fails or is inactive', async function () {
......@@ -709,6 +673,8 @@ suite('EditorService', () => {
let failingEditor = await service.openEditor(failingInput);
assert.ok(!failingEditor);
part.dispose();
});
test('save, saveAll, revertAll', async function () {
......@@ -750,5 +716,7 @@ suite('EditorService', () => {
await service.saveAll({ saveAs: true });
assert.equal(input1.gotSavedAs, true);
assert.equal(input2.gotSavedAs, true);
part.dispose();
});
});
......@@ -12,7 +12,7 @@ import * as Platform from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { workbenchInstantiationService, TestEditorGroup, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { URI } from 'vs/base/common/uri';
......@@ -50,6 +50,10 @@ export class MyOtherEditor extends BaseEditor {
class MyInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(input: EditorInput): string {
return input.toString();
}
......@@ -98,7 +102,7 @@ suite('Workbench base editor', () => {
await e.setInput(input, options, CancellationToken.None);
assert.strictEqual(input, e.input);
assert.strictEqual(options, e.options);
const group = new TestEditorGroup(1);
const group = new TestEditorGroupView(1);
e.setVisible(true, group);
assert(e.isVisible());
assert.equal(e.group, group);
......@@ -187,14 +191,14 @@ suite('Workbench base editor', () => {
});
test('EditorMemento - basics', function () {
const testGroup0 = new TestEditorGroup(0);
const testGroup1 = new TestEditorGroup(1);
const testGroup4 = new TestEditorGroup(4);
const testGroup0 = new TestEditorGroupView(0);
const testGroup1 = new TestEditorGroupView(1);
const testGroup4 = new TestEditorGroupView(4);
const editorGroupService = new TestEditorGroupsService([
testGroup0,
testGroup1,
new TestEditorGroup(2)
new TestEditorGroupView(2)
]);
interface TestViewState {
......@@ -255,7 +259,7 @@ suite('Workbench base editor', () => {
});
test('EditoMemento - use with editor input', function () {
const testGroup0 = new TestEditorGroup(0);
const testGroup0 = new TestEditorGroupView(0);
interface TestViewState {
line: number;
......
......@@ -138,6 +138,10 @@ interface ISerializedTestInput {
class TestEditorInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(editorInput: EditorInput): string {
let testEditorInput = <TestEditorInput>editorInput;
let testInput: ISerializedTestInput = {
......
......@@ -11,8 +11,8 @@ import * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions } from 'vs/workbench/common/editor';
import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, EditorInput, IFileEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor';
import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
......@@ -20,7 +20,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur
import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor';
import { IEditorOptions, IResourceInput, IEditorModel } from 'vs/platform/editor/common/editor';
import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace';
import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
......@@ -311,7 +311,7 @@ export function workbenchInstantiationService(): IInstantiationService {
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
instantiationService.stub(IThemeService, new TestThemeService());
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroup(0)]));
instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroupView(0)]));
instantiationService.stub(ILabelService, <ILabelService>instantiationService.createInstance(LabelService));
const editorService = new TestEditorService();
instantiationService.stub(IEditorService, editorService);
......@@ -683,7 +683,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
_serviceBrand: undefined;
constructor(public groups: TestEditorGroup[] = []) { }
constructor(public groups: TestEditorGroupView[] = []) { }
onDidActiveGroupChange: Event<IEditorGroup> = Event.None;
onDidActivateGroup: Event<IEditorGroup> = Event.None;
......@@ -773,7 +773,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
}
}
export class TestEditorGroup implements IEditorGroupView {
export class TestEditorGroupView implements IEditorGroupView {
constructor(public id: number) { }
......@@ -873,6 +873,76 @@ export class TestEditorGroup implements IEditorGroupView {
relayout() { }
}
export class TestEditorGroupAccessor implements IEditorGroupsAccessor {
groups: IEditorGroupView[] = [];
activeGroup!: IEditorGroupView;
partOptions: IEditorPartOptions = {};
onDidEditorPartOptionsChange = Event.None;
onDidVisibilityChange = Event.None;
getGroup(identifier: number): IEditorGroupView | undefined { throw new Error('Method not implemented.'); }
getGroups(order: GroupsOrder): IEditorGroupView[] { throw new Error('Method not implemented.'); }
activateGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); }
restoreGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); }
addGroup(location: number | IEditorGroupView, direction: GroupDirection, options?: IAddGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); }
mergeGroup(group: number | IEditorGroupView, target: number | IEditorGroupView, options?: IMergeGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); }
moveGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); }
copyGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); }
removeGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); }
arrangeGroups(arrangement: GroupsArrangement, target?: number | IEditorGroupView | undefined): void { throw new Error('Method not implemented.'); }
}
export class TestEditorInput extends EditorInput implements IFileEditorInput {
public gotDisposed = false;
public gotSaved = false;
public gotSavedAs = false;
public gotReverted = false;
public dirty = false;
private fails = false;
constructor(private resource: URI) { super(); }
getTypeId() { return 'testEditorInputForEditorService'; }
resolve(): Promise<IEditorModel | null> { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); }
matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; }
setEncoding(encoding: string) { }
getEncoding() { return undefined; }
setPreferredEncoding(encoding: string) { }
setMode(mode: string) { }
setPreferredMode(mode: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
setFailToOpen(): void {
this.fails = true;
}
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
this.gotSaved = true;
return Promise.resolve(true);
}
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
this.gotSavedAs = true;
return Promise.resolve(true);
}
revert(options?: IRevertOptions): Promise<boolean> {
this.gotReverted = true;
this.gotSaved = false;
this.gotSavedAs = false;
return Promise.resolve(true);
}
isDirty(): boolean {
return this.dirty;
}
isReadonly(): boolean {
return false;
}
dispose(): void {
super.dispose();
this.gotDisposed = true;
}
}
export class TestEditorService implements EditorServiceImpl {
_serviceBrand: undefined;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册