提交 cd0b4fa3 编写于 作者: J Joao Moreno

Merge branch 'master' into cli

......@@ -1904,13 +1904,13 @@
},
{
"c": "octicon",
"t": "attribute-name.entity.html.other",
"t": "html.string",
"r": {
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.other.attribute-name rgb(156, 220, 254)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.other.attribute-name rgb(255, 0, 0)",
"dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.other.attribute-name rgb(156, 220, 254)",
"light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.entity.other.attribute-name rgb(255, 0, 0)",
"hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.entity.other.attribute-name rgb(156, 220, 254)"
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.string rgb(206, 145, 120)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.string.html rgb(0, 0, 255)",
"dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.string rgb(206, 145, 120)",
"light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.string.html rgb(0, 0, 255)",
"hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.string rgb(206, 145, 120)"
}
},
{
......
......@@ -12,12 +12,14 @@ suite('env-namespace', () => {
test('env is set', function() {
assert.equal(typeof env.language, 'string');
assert.equal(typeof env.appName, 'string');
assert.equal(typeof env.machineId, 'string');
assert.equal(typeof env.sessionId, 'string');
});
test('env is readonly', function() {
assert.throws(() => env.language = '234');
assert.throws(() => env.appName = '234');
assert.throws(() => env.machineId = '234');
assert.throws(() => env.sessionId = '234');
});
......
......@@ -11078,13 +11078,13 @@
},
{
"c": "$width:",
"t": "property-name.sass.support.type",
"t": "sass.variable",
"r": {
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.support.type.property-name rgb(156, 220, 254)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.support.type.property-name.sass rgb(255, 0, 0)",
"dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.support.type.property-name rgb(156, 220, 254)",
"light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.support.type.property-name.sass rgb(255, 0, 0)",
"hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.support.type.property-name rgb(212, 212, 212)"
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.variable rgb(156, 220, 254)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.variable rgb(0, 16, 128)",
"dark_vs": ".vs-dark .token rgb(212, 212, 212)",
"light_vs": ".vs .token rgb(0, 0, 0)",
"hc_black": ".hc-black .token rgb(255, 255, 255)"
}
},
{
......@@ -14279,13 +14279,13 @@
},
{
"c": "$a:",
"t": "property-name.sass.support.type",
"t": "sass.variable",
"r": {
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.support.type.property-name rgb(156, 220, 254)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.support.type.property-name.sass rgb(255, 0, 0)",
"dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.support.type.property-name rgb(156, 220, 254)",
"light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.support.type.property-name.sass rgb(255, 0, 0)",
"hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.support.type.property-name rgb(212, 212, 212)"
"dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.variable rgb(156, 220, 254)",
"light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.variable rgb(0, 16, 128)",
"dark_vs": ".vs-dark .token rgb(212, 212, 212)",
"light_vs": ".vs .token rgb(0, 0, 0)",
"hc_black": ".hc-black .token rgb(255, 255, 255)"
}
},
{
......
......@@ -238,6 +238,11 @@ export class State extends AbstractState {
return { type: htmlTokenTypes.ATTRIB_VALUE };
}
} else {
let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/);
if (attributeValue.length > 0) {
this.kind = States.WithinTag;
return { type: htmlTokenTypes.ATTRIB_VALUE };
}
var ch = stream.peek();
if (ch === '\'' || ch === '"') {
this.attributeValueQuote = ch;
......
......@@ -167,12 +167,14 @@ suite('HTML - worker', () => {
assertSuggestion(completion, 'color', null, '"color"');
assertSuggestion(completion, 'checkbox', null, '"checkbox"');
}),
testSuggestionsFor('<input src="c" type="color|" ').then((completion) => {
assert.equal(completion.currentWord, 'color');
assertSuggestion(completion, 'color', null, 'color');
}),
testSuggestionsFor('<input src="c" type=color| ').then((completion) => {
assert.equal(completion.currentWord, 'color');
assertSuggestion(completion, 'color', null, 'color');
}),
testSuggestionsFor('<div dir=|></div>').then((completion) => {
assert.equal(completion.currentWord, '');
assertSuggestion(completion, 'ltr', null, '"ltr"');
......
......@@ -483,7 +483,7 @@ suite('Colorizing - HTML', () => {
test('Tag with Attributes', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '<abc foo="bar" bar="foo">',
line: '<abc foo="bar" bar=\'foo\'>',
tokens: [
{ startIndex:0, type: DELIM_START },
{ startIndex:1, type: getTag('abc') },
......@@ -500,6 +500,25 @@ suite('Colorizing - HTML', () => {
]);
});
test('Tag with Attributes, no quotes', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '<abc foo=bar bar=help-me>',
tokens: [
{ startIndex:0, type: DELIM_START },
{ startIndex:1, type: getTag('abc') },
{ startIndex:4, type: '' },
{ startIndex:5, type: ATTRIB_NAME },
{ startIndex:8, type: DELIM_ASSIGN },
{ startIndex:9, type: ATTRIB_VALUE },
{ startIndex:12, type: '' },
{ startIndex:13, type: ATTRIB_NAME },
{ startIndex:16, type: DELIM_ASSIGN },
{ startIndex:17, type: ATTRIB_VALUE },
{ startIndex:24, type: DELIM_START }
]}
]);
});
test('Tag with Attribute And Whitespace', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '<abc foo= "bar">',
......
......@@ -203,7 +203,7 @@ export var language = <Types.ILanguage>{
],
parameterdeclaration: [
['\\$@identifier@ws:', sassTokenTypes.TOKEN_PROPERTY],
['\\$@identifier@ws:', 'variable'],
['\\.\\.\\.', 'keyword.operator'], // var args in declaration
[',', 'punctuation'],
{ include: '@term' },
......
......@@ -1299,7 +1299,7 @@ suite('Sass Colorizer', () => {
{ startIndex: 19, type: 'variable.ref.sass' },
{ startIndex: 25, type: 'punctuation.sass' },
{ startIndex: 26, type: '' },
{ startIndex: 27, type: sassTokenTypes.TOKEN_PROPERTY + '.sass' },
{ startIndex: 27, type: 'variable.sass' },
{ startIndex: 34, type: '' },
{ startIndex: 35, type: 'constant.numeric.sass' },
{ startIndex: 38, type: 'support.function.name.sass' },
......@@ -1574,7 +1574,7 @@ suite('Sass Colorizer', () => {
{ startIndex: 0, type: sassTokenTypes.TOKEN_AT_KEYWORD + '.sass' },
{ startIndex: 6, type: '' },
{ startIndex: 7, type: 'support.function.name.sass' },
{ startIndex: 13, type: sassTokenTypes.TOKEN_PROPERTY + '.sass' },
{ startIndex: 13, type: 'variable.sass' },
{ startIndex: 16, type: '' },
{ startIndex: 17, type: sassTokenTypes.TOKEN_VALUE + '.sass' },
{ startIndex: 22, type: 'support.function.name.sass' },
......
......@@ -1212,6 +1212,12 @@ declare namespace vscode {
* A short title like 'Retry', 'Open Log' etc.
*/
title: string;
/**
* Indicates that this item replaces the default
* 'Close' action.
*/
isCloseAffordance?: boolean;
}
/**
......@@ -2809,6 +2815,13 @@ declare namespace vscode {
*/
export namespace env {
/**
* The application name of the editor, like 'VS Code'.
*
* @readonly
*/
export let appName: string;
/**
* Represents the preferred user-language, like `de-CH`, `fr`, or `en-US`.
*
......
......@@ -158,7 +158,8 @@ export class ExtHostAPIImplementation {
this.env = Object.freeze({
get machineId() { return telemetryInfo.machineId; },
get sessionId() { return telemetryInfo.sessionId; },
get language() { return Platform.language; }
get language() { return Platform.language; },
get appName() { return contextService.getConfiguration().env.appName; }
});
telemetryService.getTelemetryInfo().then(info => telemetryInfo = info, errors.onUnexpectedError);
......
......@@ -331,7 +331,9 @@ export class ExtHostDocumentData extends MirrorModel2 {
const text = this._lines[line];
const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length;
const range = new Range(line, 0, line, text.length);
const rangeIncludingLineBreak = new Range(line, 0, line + 1, 0);
const rangeIncludingLineBreak = line < this._lines.length - 1
? new Range(line, 0, line + 1, 0)
: range;
result = Object.freeze({
lineNumber: line,
......
......@@ -22,14 +22,15 @@ export class ExtHostMessageService {
showMessage(severity: Severity, message: string, commands: (string|vscode.MessageItem)[]): Thenable<string|vscode.MessageItem> {
const items: { title: string; handle: number; }[] = [];
const items: { title: string; isCloseAffordance: boolean; handle: number; }[] = [];
for (let handle = 0; handle < commands.length; handle++) {
let command = commands[handle];
if (typeof command === 'string') {
items.push({ title: command, handle });
items.push({ title: command, handle, isCloseAffordance: false });
} else {
items.push({ title: command.title, handle });
let {title, isCloseAffordance} = command;
items.push({ title, isCloseAffordance, handle });
}
}
......@@ -50,23 +51,29 @@ export class MainThreadMessageService {
this._messageService = messageService;
}
$showMessage(severity: Severity, message: string, commands: { title: string; handle: number;}[]): Thenable<number> {
$showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number;}[]): Thenable<number> {
let hide: (handle?: number) => void;
let actions: Action[] = [];
actions.push(new Action('__close', nls.localize('close', "Close"), undefined, true, () => {
hide();
return Promise.as(undefined);
}));
let hasCloseAffordance = false;
commands.forEach(command => {
if (command.isCloseAffordance === true) {
hasCloseAffordance = true;
}
actions.push(new Action('_extension_message_handle_' + command.handle, command.title, undefined, true, () => {
hide(command.handle);
return Promise.as(undefined);
}));
});
if (!hasCloseAffordance) {
actions.unshift(new Action('__close', nls.localize('close', "Close"), undefined, true, () => {
hide();
return Promise.as(undefined);
}));
}
return new Promise<number>((c) => {
let messageHide: Function;
......
......@@ -586,29 +586,36 @@ export class VSCodeMenu {
].forEach((item) => macWindowMenu.append(item));
}
private toggleDevTools(): void {
let w = this.windowsManager.getFocusedWindow();
if (w && w.win) {
w.win.webContents.toggleDevTools();
}
}
private setHelpMenu(helpMenu: Electron.Menu): void {
let toggleDevToolsItem = new MenuItem({
label: mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")),
accelerator: this.getAccelerator('workbench.action.toggleDevTools'),
click: toggleDevTools,
click: () => this.toggleDevTools(),
enabled: (this.windowsManager.getWindowCount() > 0)
});
arrays.coalesce([
this.envService.product.documentationUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")), click: () => openUrl(this.envService.product.documentationUrl, 'openDocumentationUrl') }) : null,
this.envService.product.releaseNotesUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")), click: () => openUrl(this.envService.product.releaseNotesUrl, 'openReleaseNotesUrl') }) : null,
this.envService.product.documentationUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")), click: () => this.openUrl(this.envService.product.documentationUrl, 'openDocumentationUrl') }) : null,
this.envService.product.releaseNotesUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")), click: () => this.openUrl(this.envService.product.releaseNotesUrl, 'openReleaseNotesUrl') }) : null,
(this.envService.product.documentationUrl || this.envService.product.releaseNotesUrl) ? __separator__() : null,
this.envService.product.twitterUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => openUrl(this.envService.product.twitterUrl, 'openTwitterUrl') }) : null,
this.envService.product.requestFeatureUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Request Features")), click: () => openUrl(this.envService.product.requestFeatureUrl, 'openUserVoiceUrl') }) : null,
this.envService.product.reportIssueUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReportIssues', comment: ['&& denotes a mnemonic'] }, "Report &&Issues")), click: () => openUrl(this.envService.product.reportIssueUrl, 'openReportIssues') }) : null,
this.envService.product.twitterUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(this.envService.product.twitterUrl, 'openTwitterUrl') }) : null,
this.envService.product.requestFeatureUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Request Features")), click: () => this.openUrl(this.envService.product.requestFeatureUrl, 'openUserVoiceUrl') }) : null,
this.envService.product.reportIssueUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReportIssues', comment: ['&& denotes a mnemonic'] }, "Report &&Issues")), click: () => this.openUrl(this.envService.product.reportIssueUrl, 'openReportIssues') }) : null,
(this.envService.product.twitterUrl || this.envService.product.requestFeatureUrl || this.envService.product.reportIssueUrl) ? __separator__() : null,
this.envService.product.licenseUrl ? new MenuItem({
label: mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "&&View License")), click: () => {
if (platform.language) {
let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
openUrl(`${this.envService.product.licenseUrl}${queryArgChar}lang=${platform.language}`, 'openLicenseUrl');
this.openUrl(`${this.envService.product.licenseUrl}${queryArgChar}lang=${platform.language}`, 'openLicenseUrl');
} else {
openUrl(this.envService.product.licenseUrl, 'openLicenseUrl');
this.openUrl(this.envService.product.licenseUrl, 'openLicenseUrl');
}
}
}) : null,
......@@ -616,9 +623,9 @@ export class VSCodeMenu {
label: mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
if (platform.language) {
let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
openUrl(`${this.envService.product.privacyStatementUrl}${queryArgChar}lang=${platform.language}`, 'openPrivacyStatement');
this.openUrl(`${this.envService.product.privacyStatementUrl}${queryArgChar}lang=${platform.language}`, 'openPrivacyStatement');
} else {
openUrl(this.envService.product.privacyStatementUrl, 'openPrivacyStatement');
this.openUrl(this.envService.product.privacyStatementUrl, 'openPrivacyStatement');
}
}
}) : null,
......@@ -634,7 +641,7 @@ export class VSCodeMenu {
}
helpMenu.append(__separator__());
helpMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: openAboutDialog }));
helpMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.openAboutDialog() }));
}
}
......@@ -647,7 +654,7 @@ export class VSCodeMenu {
let update = this.updateManager.availableUpdate;
return [new MenuItem({
label: nls.localize('miRestartToUpdate', "Restart To Update..."), click: () => {
reportMenuActionTelemetry('RestartToUpdate');
this.reportMenuActionTelemetry('RestartToUpdate');
update.quitAndUpdate();
}
})];
......@@ -674,7 +681,7 @@ export class VSCodeMenu {
default:
let result = [new MenuItem({
label: nls.localize('miCheckForUpdates', "Check For Updates..."), click: () => setTimeout(() => {
reportMenuActionTelemetry('CheckForUpdate');
this.reportMenuActionTelemetry('CheckForUpdate');
this.updateManager.checkForUpdates(true);
}, 0)
})];
......@@ -747,45 +754,38 @@ export class VSCodeMenu {
return void (0);
}
}
function openAboutDialog(): void {
let lastActiveWindow = this.windowsManager.getFocusedWindow() || this.windowsManager.getLastActiveWindow();
dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
title: this.envService.product.nameLong,
type: 'info',
message: this.envService.product.nameLong,
detail: nls.localize('aboutDetail',
"\nVersion {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}",
app.getVersion(),
this.envService.product.commit || 'Unknown',
this.envService.product.date || 'Unknown',
process.versions['electron'],
process.versions['chrome'],
process.versions['node']
),
buttons: [nls.localize('okButton', "OK")],
noLink: true
}, (result) => null);
reportMenuActionTelemetry('showAboutDialog');
}
function openUrl(url: string, id: string): void {
shell.openExternal(url);
reportMenuActionTelemetry(id);
}
private openAboutDialog(): void {
let lastActiveWindow = this.windowsManager.getFocusedWindow() || this.windowsManager.getLastActiveWindow();
dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
title: this.envService.product.nameLong,
type: 'info',
message: this.envService.product.nameLong,
detail: nls.localize('aboutDetail',
"\nVersion {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}",
app.getVersion(),
this.envService.product.commit || 'Unknown',
this.envService.product.date || 'Unknown',
process.versions['electron'],
process.versions['chrome'],
process.versions['node']
),
buttons: [nls.localize('okButton', "OK")],
noLink: true
}, (result) => null);
this.reportMenuActionTelemetry('showAboutDialog');
}
function toggleDevTools(): void {
let w = this.windowsManager.getFocusedWindow();
if (w && w.win) {
w.win.webContents.toggleDevTools();
private openUrl(url: string, id: string): void {
shell.openExternal(url);
this.reportMenuActionTelemetry(id);
}
}
function reportMenuActionTelemetry(id: string): void {
this.windowsManager.sendToFocused('vscode:telemetry', { eventName: 'workbenchActionExecuted', data: { id, from: 'menu' } });
private reportMenuActionTelemetry(id: string): void {
this.windowsManager.sendToFocused('vscode:telemetry', { eventName: 'workbenchActionExecuted', data: { id, from: 'menu' } });
}
}
function __separator__(): Electron.MenuItem {
......
......@@ -86,6 +86,25 @@ suite('ExtHostDocument', () => {
assert.equal(line.firstNonWhitespaceCharacterIndex, 2);
});
test('line, issue #5704', function () {
let line = data.document.lineAt(0);
let {range, rangeIncludingLineBreak} = line;
assert.equal(range.end.line, 0);
assert.equal(range.end.character, 16);
assert.equal(rangeIncludingLineBreak.end.line, 1);
assert.equal(rangeIncludingLineBreak.end.character, 0);
line = data.document.lineAt(data.document.lineCount - 1);
range = line.range;
rangeIncludingLineBreak = line.rangeIncludingLineBreak;
assert.equal(range.end.line, 3);
assert.equal(range.end.character, 29);
assert.equal(rangeIncludingLineBreak.end.line, 3);
assert.equal(rangeIncludingLineBreak.end.character, 29);
});
test('offsetAt', function() {
assertOffsetAt(0, 0, 0);
assertOffsetAt(0, 1, 1);
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import {Action} from 'vs/base/common/actions';
import {MainThreadMessageService} from 'vs/workbench/api/node/extHostMessageService';
suite('ExtHostMessageService', function () {
test('propagte handle on select', function () {
let service = new MainThreadMessageService(<any>{
show(sev: number, m: { message; actions: Action[] }) {
assert.equal(m.actions.length, 1);
setImmediate(() => m.actions[0].run());
return () => { };
}
});
return service.$showMessage(1, 'h', [{ handle: 42, title: 'a thing', isCloseAffordance: true }]).then(handle => {
assert.equal(handle, 42);
});
});
test('isCloseAffordance', function () {
let actions: Action[];
let service = new MainThreadMessageService(<any>{
show(sev: number, m: { message; actions: Action[] }) {
actions = m.actions;
}
});
// default close action
service.$showMessage(1, '', [{ title: 'a thing', isCloseAffordance: false, handle: 0 }]);
assert.equal(actions.length, 2);
let [first, second] = actions;
assert.equal(first.label, 'Close');
assert.equal(second.label, 'a thing');
// override close action
service.$showMessage(1, '', [{ title: 'a thing', isCloseAffordance: true, handle: 0 }]);
assert.equal(actions.length, 1);
first = actions[0];
assert.equal(first.label, 'a thing');
});
test('hide on select', function () {
let actions: Action[];
let c: number;
let service = new MainThreadMessageService(<any>{
show(sev: number, m: { message; actions: Action[] }) {
c = 0;
actions = m.actions;
return () => {
c += 1;
};
}
});
service.$showMessage(1, '', [{ title: 'a thing', isCloseAffordance: true, handle: 0 }]);
assert.equal(actions.length, 1);
actions[0].run();
assert.equal(c, 1);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册