提交 b2693bb7 编写于 作者: R rebornix

remove legacy kernel on content provider

上级 95f0ea4c
......@@ -17,7 +17,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, IEditor, INotebookDocumentFilter, INotebookKernelInfo, INotebookKernelInfoDto, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, IEditor, INotebookDocumentFilter, INotebookKernelInfo, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol';
......@@ -376,9 +376,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
// }
}
async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise<void> {
async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise<void> {
const controller: IMainNotebookController = {
kernel: _kernel,
supportBackup: _supportBackup,
options: options,
reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => {
......@@ -427,24 +426,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.executeNotebookByAttachedKernel(viewType, uri, undefined);
},
cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.cancelNotebookByAttachedKernel(viewType, uri, undefined);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
removeNotebookDocument: async (uri: URI) => {
return this.removeNotebookTextModel(uri);
},
executeNotebookCell: async (uri: URI, handle: number) => {
return this.executeNotebookByAttachedKernel(_viewType, uri, handle);
},
cancelNotebookCell: async (uri: URI, handle: number) => {
return this.cancelNotebookByAttachedKernel(_viewType, uri, handle);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(_viewType, uri, token);
},
......@@ -567,16 +554,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
}
async executeNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
this.logService.debug('MainthreadNotebooks#executeNotebookByAttachedKernel', uri.path, handle);
return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, handle);
}
async cancelNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
this.logService.debug('MainthreadNotebooks#cancelNotebookByAttachedKernel', uri.path, handle);
return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, handle);
}
async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean> {
const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined;
if (editor?.isNotebookEditor) {
......
......@@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
......@@ -722,7 +722,7 @@ export type NotebookCellOutputsSplice = [
export type INotebookCellStatusBarEntryDto = Dto<INotebookCellStatusBarEntry>;
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise<void>;
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise<void>;
$onNotebookChange(viewType: string, resource: UriComponents): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void>;
......@@ -1658,8 +1658,6 @@ export interface ExtHostNotebookShape {
$resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void>;
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
$executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
......
......@@ -862,7 +862,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private static _notebookKernelProviderHandlePool: number = 0;
private readonly _proxy: MainThreadNotebookShape;
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider & { kernel?: vscode.NotebookKernel }, readonly extension: IExtensionDescription; }>();
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernelProviders = new Map<number, ExtHostNotebookKernelProviderAdapter>();
private readonly _documents = new ResourceMap<ExtHostNotebookDocument>();
......@@ -948,7 +948,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
registerNotebookContentProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.NotebookContentProvider & { kernel?: vscode.NotebookKernel },
provider: vscode.NotebookContentProvider,
options?: {
transientOutputs: boolean;
transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean };
......@@ -959,10 +959,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
throw new Error(`Notebook provider for '${viewType}' already registered`);
}
// if ((<any>provider).executeCell) {
// throw new Error('NotebookContentKernel.executeCell is removed, please use vscode.notebook.registerNotebookKernel instead.');
// }
this._notebookContentProviders.set(viewType, { extension, provider });
const listener = provider.onDidChangeNotebook
......@@ -984,7 +980,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const supportBackup = !!provider.backupNotebook;
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} });
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} });
return new extHostTypes.Disposable(() => {
listener.dispose();
......@@ -1123,48 +1119,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm);
}
async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
const document = this._documents.get(URI.revive(uri));
if (!document) {
return;
}
if (this._notebookContentProviders.has(viewType)) {
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
const provider = this._notebookContentProviders.get(viewType)!.provider;
if (provider.kernel) {
if (cell) {
return withToken(token => (provider.kernel!.executeCell as any)(document, cell, token));
} else {
return withToken(token => (provider.kernel!.executeAllCells as any)(document, token));
}
}
}
}
async $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
const document = this._documents.get(URI.revive(uri));
if (!document) {
return;
}
if (this._notebookContentProviders.has(viewType)) {
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
const provider = this._notebookContentProviders.get(viewType)!.provider;
if (provider.kernel) {
if (cell) {
return provider.kernel.cancelCellExecution(document.notebookDocument, cell.cell);
} else {
return provider.kernel.cancelAllCellsExecution(document.notebookDocument);
}
}
}
}
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
await this._withAdapter(handle, uri, async (adapter, document) => {
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
......
......@@ -76,27 +76,6 @@ registerAction2(class extends Action2 {
};
});
const provider = notebookService.getContributedNotebookProviders(editor.viewModel!.uri)[0];
if (provider.kernel) {
picks.unshift({
id: provider.id,
label: provider.displayName,
picked: !activeKernel, // no active kernel, the builtin kernel of the provider is used
description: activeKernel === undefined
? nls.localize('currentActiveBuiltinKernel', " (Currently Active)")
: '',
kernelProviderId: provider.providerExtensionId,
run: () => {
editor.activeKernel = undefined;
},
buttons: [{
iconClass: 'codicon-settings-gear',
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel!.viewType)
}]
});
}
const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>();
picker.items = picks;
picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[];
......
......@@ -56,9 +56,6 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IAction, Separator } from 'vs/base/common/actions';
import { isMacintosh, isNative } from 'vs/base/common/platform';
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd';
const $ = DOM.$;
......@@ -222,7 +219,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
@IStorageService storageService: IStorageService,
@INotebookService private notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IContextKeyService readonly contextKeyService: IContextKeyService,
@ILayoutService private readonly layoutService: ILayoutService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
......@@ -704,10 +700,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return;
}
if (provider.kernel && (availableKernels.length + availableKernels2.length) > 0) {
this._notebookHasMultipleKernels!.set(true);
this.multipleKernelsAvailable = true;
} else if ((availableKernels.length + availableKernels2.length) > 1) {
if ((availableKernels.length + availableKernels2.length) > 1) {
this._notebookHasMultipleKernels!.set(true);
this.multipleKernelsAvailable = true;
} else {
......@@ -715,14 +708,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.multipleKernelsAvailable = false;
}
// @deprecated
if (provider && provider.kernel) {
// it has a builtin kernel, don't automatically choose a kernel
await this._loadKernelPreloads(provider.providerExtensionLocation, provider.kernel);
tokenSource.dispose();
return;
}
const activeKernelStillExist = [...availableKernels2, ...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined);
if (activeKernelStillExist) {
......@@ -1429,15 +1414,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private async _cancelNotebookExecution(): Promise<void> {
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
if (provider) {
const viewType = provider.id;
const notebookUri = this._notebookViewModel!.uri;
if (this._activeKernel) {
await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined);
} else if (provider.kernel) {
return await this.notebookService.cancelNotebook(viewType, notebookUri);
}
if (provider && this._activeKernel) {
await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined);
}
}
......@@ -1451,23 +1429,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private async _executeNotebook(): Promise<void> {
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
if (provider) {
const viewType = provider.id;
const notebookUri = this._notebookViewModel!.uri;
if (this._activeKernel) {
// TODO@rebornix temp any cast, should be removed once we remove legacy kernel support
if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) {
if (this._activeKernelResolvePromise) {
await this._activeKernelResolvePromise;
}
await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined);
} else {
await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id);
if (provider && this._activeKernel) {
// TODO@rebornix temp any cast, should be removed once we remove legacy kernel support
if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) {
if (this._activeKernelResolvePromise) {
await this._activeKernelResolvePromise;
}
} else if (provider.kernel) {
return await this.notebookService.executeNotebook(viewType, notebookUri);
await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined);
} else {
await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id);
}
}
}
......@@ -1491,15 +1462,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private async _cancelNotebookCell(cell: ICellViewModel): Promise<void> {
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
if (provider) {
const viewType = provider.id;
const notebookUri = this._notebookViewModel!.uri;
if (this._activeKernel) {
return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle);
} else if (provider.kernel) {
return await this.notebookService.cancelNotebookCell(viewType, notebookUri, cell.handle);
}
if (provider && this._activeKernel) {
return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle);
}
}
......@@ -1530,9 +1494,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return await this.notebookService.executeNotebookCell2(viewType, notebookUri, cell.handle, this._activeKernel.id);
}
} else if (provider.kernel) {
return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle);
}
}
}
......
......@@ -548,7 +548,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) {
this._notebookProviders.set(viewType, { extensionData, controller });
this.notebookProviderInfoStore.get(viewType)!.kernel = controller.kernel;
this._onDidChangeViewTypes.fire();
}
......@@ -820,40 +819,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return this.notebookRenderersInfoStore.getContributedRenderer(mimeType);
}
async executeNotebook(viewType: string, uri: URI): Promise<void> {
const provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.controller.executeNotebookByAttachedKernel(viewType, uri);
}
return;
}
async executeNotebookCell(viewType: string, uri: URI, handle: number): Promise<void> {
const provider = this._notebookProviders.get(viewType);
if (provider) {
await provider.controller.executeNotebookCell(uri, handle);
}
}
async cancelNotebook(viewType: string, uri: URI): Promise<void> {
const provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.controller.cancelNotebookByAttachedKernel(viewType, uri);
}
return;
}
async cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise<void> {
const provider = this._notebookProviders.get(viewType);
if (provider) {
await provider.controller.cancelNotebookCell(uri, handle);
}
}
async executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise<void> {
const kernel = this._notebookKernels.get(kernelId);
if (kernel) {
......
......@@ -36,7 +36,6 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor {
readonly providerDescription?: string;
readonly providerDisplayName: string;
readonly providerExtensionLocation: URI;
kernel?: INotebookKernelInfoDto;
constructor(descriptor: NotebookEditorDescriptor) {
this.id = descriptor.id;
......
......@@ -9,7 +9,7 @@ import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/noteb
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Event } from 'vs/base/common/event';
import {
INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, INotebookKernelInfoDto,
INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo,
IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata
} from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
......@@ -22,17 +22,12 @@ import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common
export const INotebookService = createDecorator<INotebookService>('notebookService');
export interface IMainNotebookController {
kernel: INotebookKernelInfoDto | undefined;
supportBackup: boolean;
options: { transientOutputs: boolean; transientMetadata: TransientMetadata; };
createNotebook(textModel: NotebookTextModel, editorId?: string, backupId?: string): Promise<void>;
reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise<void>;
resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void>;
executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void>;
cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void>;
onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void;
executeNotebookCell(uri: URI, handle: number): Promise<void>;
cancelNotebookCell(uri: URI, handle: number): Promise<void>;
removeNotebookDocument(uri: URI): Promise<void>;
save(uri: URI, token: CancellationToken): Promise<boolean>;
saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
......@@ -65,10 +60,6 @@ export interface INotebookService {
resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined>;
getNotebookTextModel(uri: URI): NotebookTextModel | undefined;
executeNotebook(viewType: string, uri: URI): Promise<void>;
cancelNotebook(viewType: string, uri: URI): Promise<void>;
executeNotebookCell(viewType: string, uri: URI, handle: number): Promise<void>;
cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise<void>;
executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise<void>;
executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string): Promise<void>;
getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册