').text('Data');
var pre = $('').text(jsonText);
details.append(summary, pre);
li.append(details);
}
LSPLogList.append(li);
}
/**
* 添加状态底栏
*/
var statusBar = $('');
form.editorDiv.append(statusBar);
/**
* languageSocket
*/
var url = OJBetter.monaco.lsp.socketUrl;
var languageSocket = new WebSocket(url + language);
OJBetter.monaco.lsp.socket.push(languageSocket);
var languageSocketState = false;
var responseHandlers = {}; // 映射表,需要等待返回数据的请求 -> 对应的事件触发函数
languageSocket.onopen = () => {
languageSocketState = true;
lspStateButton.setButtonPopover(i18next.t('lsp.waitingAnswer', { ns: 'codeEditor' }));
pushLSPLogMessage("info", `languageSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
};
languageSocket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.id === 0 && message.result) {
// 初始化完成
lspStateButton.setButtonState('success', i18next.t('lsp.connected', { ns: 'codeEditor' }));
pushLSPLogMessage("info", `Initialization ${i18next.t('lsp.state.finished', { ns: 'logMessage' })}`);
serverInfo = message.result; // 存下服务器支持信息
OJBetter_monaco.openDocRequest(); // 打开文档
if (!OJBetter.monaco.setting.language.includes(language)) {
OJBetter.monaco.setting.language.push(language);
OJBetter_monaco.RegistrationAfterInit(); // 注册语言及功能
} else {
location.reload(); // 这里有问题,先贴个补丁
}
OJBetter_monaco.PassiveReceiveHandler(); // 注册被动接收函数
} else if (message.id === 0 && message.error) {
pushLSPLogMessage("warn", `Initialization ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`);
} else if (message.id !== undefined && responseHandlers[message.id]) {
// 如果收到带有id字段的消息,则回传给对应的事件触发函数
responseHandlers[message.id](message);
delete responseHandlers[message.id]; // 删除已处理的事件触发函数
} else if (message.method == "textDocument/publishDiagnostics") {
// 接收代码诊断推送
OJBetter_monaco.updateMarkers(message);
} else if (message.method == "workspace/applyEdit") {
// 应用服务器推送的更改
OJBetter_monaco.applyEdit(message);
}
};
languageSocket.onerror = (error) => {
pushLSPLogMessage("error", `languageSocket ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`, error);
console.warn(`Error connecting to languageSocket: ${error}`)
};
languageSocket.onclose = (event) => {
languageSocketState = false;
lspStateButton.setButtonState('error', i18next.t('lsp.error', { ns: 'codeEditor' }));
pushLSPLogMessage("warn", `languageSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`);
};
/**
* 等待LanguageSocketState
*/
async function waitForLanguageSocketState() {
return new Promise((resolve) => {
const checkInitialized = () => {
if (languageSocketState) {
resolve();
} else {
setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
}
};
checkInitialized();
});
}
// 等待lsp响应初始化结果
async function waitForInitialized() {
return new Promise((resolve) => {
const checkInitialized = () => {
if (initialized) {
resolve();
} else {
setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
}
};
checkInitialized();
});
}
/**
* 与languageSocket通信的包装方法
*/
async function sendMessage(data, requiresResponse, callback) {
if (!initialized) {
await waitForInitialized(); // 等待initialized为真
}
if (requiresResponse) {
responseHandlers[data.id] = callback; // 将事件触发函数与id关联起来
}
if (!languageSocketState) await waitForLanguageSocketState();
languageSocket.send(JSON.stringify(data));
}
// 发送消息并等待返回结果
function fetchData(params, callback) {
sendMessage(params, true, callback);
}
// 发送消息,不需要等待返回结果
function sendData(data) {
sendMessage(data, false);
}
/**
* 代码文件更新fileWebSocket
*/
var fileWebSocket = new WebSocket(url + "file");
var fileWebSocketState = false;
OJBetter.monaco.lsp.socket.push(fileWebSocket);
fileWebSocket.onopen = () => {
fileWebSocketState = true;
pushLSPLogMessage("info", `fileWebSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
};
fileWebSocket.onclose = (ev) => {
fileWebSocketState = false;
pushLSPLogMessage("warn", `fileWebSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`, ev);
};
fileWebSocket.onmessage = (ev) => {
let message = JSON.parse(ev.data);
if (message.result !== "ok")
pushLSPLogMessage("error", `update file failed: ${ev}`);
};
fileWebSocket.onerror = (error) => {
console.warn(`Error connecting to fileWebSocket: ${error}`);
};
async function updateFile(workspace, filename, fileExtension, code) {
async function waitForfileWebSocketState() {
return new Promise((resolve) => {
const checkInitialized = () => {
if (fileWebSocketState) {
resolve();
} else {
setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
}
};
checkInitialized();
});
}
if (!fileWebSocketState) await waitForfileWebSocketState();
fileWebSocket.send(
JSON.stringify({
type: "update",
workspace,
filename,
fileExtension,
code,
})
);
}
/**
* 发送初始化请求
*/
OJBetter_monaco.Initialize = () => {
//初始化initialize
const capabilities = {
workspace: {
applyEdit: true,
},
textDocument: {
publishDiagnostics: {
relatedInformation: true,
versionSupport: false,
tagSupport: {
valueSet: [1, 2],
},
codeDescriptionSupport: true,
},
completion: {
contextSupport: true,
completionItem: {
snippetSupport: true,
commitCharactersSupport: true,
documentationFormat: ["markdown", "plaintext"],
deprecatedSupport: true,
preselectSupport: true,
tagSupport: {
valueSet: [1],
},
insertReplaceSupport: true,
resolveSupport: {
properties: [
"documentation",
"detail",
"additionalTextEdits",
],
},
insertTextModeSupport: {
valueSet: [1, 2],
},
},
},
hover: {
dynamicRegistration: true,
contentFormat: ["markdown", "plaintext"],
},
signatureHelp: {
signatureInformation: {
documentationFormat: ["markdown", "plaintext"],
parameterInformation: {
labelOffsetSupport: true,
},
activeParameterSupport: true,
},
contextSupport: true,
},
definition: {
dynamicRegistration: true,
linkSupport: true,
},
references: {
dynamicRegistration: true,
},
documentHighlight: {
dynamicRegistration: true,
},
codeAction: {
codeActionLiteralSupport: {
codeActionKind: {
valueSet:
language == "java"
? []
: [
"",
"quickfix",
"refactor",
"refactor.extract",
"refactor.inline",
"refactor.rewrite",
"source",
"source.organizeImports",
],
},
},
},
rename: {
dynamicRegistration: true,
prepareSupport: true,
prepareSupportDefaultBehavior: 1,
honorsChangeAnnotations: true,
},
documentLink: {
tooltipSupport: true,
},
typeDefinition: {
dynamicRegistration: true,
linkSupport: true,
},
implementation: {
dynamicRegistration: true,
linkSupport: true,
},
colorProvider: {
dynamicRegistration: true,
},
foldingRange: {
dynamicRegistration: true,
rangeLimit: 5000,
lineFoldingOnly: true,
},
declaration: {
dynamicRegistration: true,
linkSupport: true,
},
semanticTokens: {
dynamicRegistration: true,
tokenTypes: [
"namespace",
"type",
"class",
"enum",
"interface",
"struct",
"typeParameter",
"parameter",
"variable",
"property",
"enumMember",
"event",
"function",
"method",
"macro",
"keyword",
"modifier",
"comment",
"string",
"number",
"regexp",
"operator",
],
tokenModifiers: [
"declaration",
"definition",
"readonly",
"static",
"deprecated",
"abstract",
"async",
"modification",
"documentation",
"defaultLibrary",
],
formats: ["relative"],
requests: {
range: true,
full: {
delta: true,
},
},
multilineTokenSupport: false,
overlappingTokenSupport: false,
},
callHierarchy: {
dynamicRegistration: true,
},
},
window: {
showMessage: {
messageActionItem: {
additionalPropertiesSupport: true,
},
},
showDocument: {
support: true,
},
workDoneProgress: true,
},
general: {
regularExpressions: {
engine: "ECMAScript",
version: "ES2020",
},
markdown: {
parser: "marked",
version: "1.1.0",
},
},
};
const initializeRequest = {
id: id++,
jsonrpc: "2.0",
method: "initialize",
params: {
processId: null,
clientInfo: {
name: "CFMonaco" + InstanceID,
},
locale: "zh-CN",
rootPath: null,
rootUri: null,
capabilities: capabilities,
trace: "off",
workspaceFolders: [
{
uri:
"file:///" + OJBetter.monaco.lsp.workUri + workspace,
name:
"file:///" + OJBetter.monaco.lsp.workUri + workspace,
},
],
},
};
languageSocket.send(JSON.stringify(initializeRequest));
// 打开文档函数
OJBetter_monaco.openDocRequest = function () {
const initializ = {
jsonrpc: "2.0",
method: "initialized",
params: {},
};
languageSocket.send(JSON.stringify(initializ));
const openDocRequest = {
jsonrpc: "2.0",
method: "textDocument/didOpen",
params: {
textDocument: {
uri: model.uri.toString(),
languageId: language,
version: model.getVersionId(),
text: model.getValue(),
},
},
};
languageSocket.send(JSON.stringify(openDocRequest));
initialized = true; // 初始化完成,这里确认逻辑待完善
};
// 初始化更新文件
updateFile(workspace, filename, fileExtension, model.getValue());
}
/**
* 注册语言及功能
*/
OJBetter_monaco.RegistrationAfterInit = () => {
// 注册语言
monaco.languages.register({ id: language });
// 注册"Command"
(function registerCommand() {
serverInfo.capabilities.executeCommandProvider.commands.forEach(
(item) => {
pushLSPLogMessage("info", `${i18next.t('lsp.server.regist', { ns: 'logMessage' })}`, item);
monaco.editor.registerCommand(item, (accessor, ...args) => {
sendData({
jsonrpc: "2.0",
id: id++,
method: "workspace/executeCommand",
params: {
command: item,
arguments: args,
},
});
});
}
);
})();
// 注册"增量更新"
model.onDidChangeContent((event) => {
updateFile(workspace, filename, fileExtension, model.getValue()); // 更新文件
const changeDocRequest = {
jsonrpc: "2.0",
method: "textDocument/didChange",
params: {
textDocument: {
uri: model.uri.toString(),
version: model.getVersionId(),
},
contentChanges: event.changes.map((change) => ({
range: OJBetter_monaco.MonacoRangeTolspRange(change.range),
rangeLength: change.rangeLength,
text: change.text,
})),
},
};
sendData(changeDocRequest);
});
//注册"自动补全"
monaco.languages.registerCompletionItemProvider(language, {
provideCompletionItems: (model, position, context) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/completion",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
context: {
triggerKind: context.triggerKind + 1, // 这里要+1,两边的定义不一样。。。
triggerCharacter: context.triggerCharacter,
},
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve(null);
const CompletionItems = {
suggestions: result.items.map(
({
label,
kind,
filterText,
insertText,
insertTextFormat,
sortText,
textEdit,
documentation,
additionalTextEdits,
}) => ({
additionalTextEdits: additionalTextEdits
? additionalTextEdits.map(({ newText, range }) => ({
text: newText,
range: OJBetter_monaco.lspRangeToMonacoRange(range),
}))
: [],
documentation: documentation ? documentation.value : "",
filterText,
insertText: insertText ? insertText : textEdit.newText,
insertTextRules:
insertTextFormat === 2
? monaco.languages.CompletionItemInsertTextRule
.InsertAsSnippet
: monaco.languages.CompletionItemInsertTextRule
.KeepWhitespace,
kind,
label,
sortText,
range: textEdit
? textEdit.range
? OJBetter_monaco.lspRangeToMonacoRange(textEdit.range)
: OJBetter_monaco.lspRangeToMonacoRange(textEdit.insert)
: null,
})
),
};
pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, CompletionItems);
resolve(CompletionItems);
});
});
},
});
// 注册"代码修复"
monaco.languages.registerCodeActionProvider(language, {
provideCodeActions: (model, range, context) => {
const request = {
id: id++,
jsonrpc: "2.0",
method: "textDocument/codeAction",
params: {
textDocument: {
uri: model.uri.toString(),
},
range: OJBetter_monaco.MonacoRangeTolspRange(range),
context: {
diagnostics: context.markers.map((item) => ({
range: OJBetter_monaco.MonacoRangeTolspRange({
startLineNumber: item.startLineNumber,
startColumn: item.startColumn,
endLineNumber: item.endLineNumber,
endColumn: item.endColumn,
}),
severity: OJBetter_monaco.MonacoSeverityTolspSeverity(
item.severity
),
code: item.code,
source: item.source,
message: item.message,
tags: item.tags,
relatedInformation: item.relatedInformation
? item.relatedInformation.map((item) => ({
location: {
uri: item.resource.toString(),
range: OJBetter_monaco.MonacoRangeTolspRange({
startLineNumber: item.startLineNumber,
startColumn: item.startColumn,
endLineNumber: item.endLineNumber,
endColumn: item.endColumn,
}),
},
message: item.message,
}))
: null,
})),
only: context.only ? [context.only] : [],
triggerKind: context.trigger,
},
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve(null);
const codeAction = {
actions: result.map((item) => ({
title: item.title,
kind: item.kind ? item.kind : "quickfix",
command: item.command
? item.command.command
? {
id: item.command.command,
arguments: item.command.arguments,
title: item.command.title,
}
: null
: null,
diagnostics: item.diagnostics
? item.diagnostics.map((item) => ({
code: item.code,
message: item.message,
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
severity: OJBetter_monaco.lspSeverityToMonacoSeverity(
item.severity
),
source: item.source,
}))
: null,
edit: item.edit
? OJBetter_monaco.lspEditToMonacoEdit(item.edit)
: item.arguments
? {
edits: item.arguments.flatMap(
(item1) => OJBetter_monaco.lspEditToMonacoEdit(item1).edits
),
}
: null,
})),
dispose: () => { },
};
pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, codeAction);
resolve(codeAction);
});
});
},
});
// 注册"hover提示"
monaco.languages.registerHoverProvider(language, {
provideHover: (model, position) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/hover",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
const result = response.result;
if (!result) return resolve(null);
const Hover = {
range: result.range
? OJBetter_monaco.lspRangeToMonacoRange(result.range)
: new monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
),
contents: Array.isArray(result.contents)
? result.contents.map((item) => ({
value: item.value ? item.value : item,
}))
: [
{
value: result.contents.value,
},
],
};
pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, Hover);
resolve(Hover);
});
});
},
});
// 注册"inlay提示"
if (language == "cpp" || language == "java")
monaco.languages.registerInlayHintsProvider(language, {
provideInlayHints: (model, range, token) => {
return new Promise((resolve, reject) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/inlayHint",
params: {
textDocument: {
uri: model.uri.toString(),
},
range: OJBetter_monaco.MonacoRangeTolspRange(range),
},
};
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve(null);
const inlayHints = {
hints: result.map((item) => {
return {
kind: item.kind,
label: item.label,
paddingLeft: item.paddingLeft,
paddingRight: item.paddingRight,
position: {
lineNumber: item.position.line + 1,
column: item.position.character + 1,
},
};
}),
};
pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, inlayHints);
resolve(inlayHints);
});
});
},
});
// 注册"转到定义"
monaco.languages.registerDefinitionProvider(language, {
provideDefinition: (model, position) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/definition",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (result.length == 0) return resolve(null);
const definition = result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
uri: monaco.Uri.parse(item.uri), //
}));
pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, definition);
resolve(definition);
});
});
return null; // 如果没有内容,则返回null
},
});
// 注册"转到引用"
monaco.languages.registerReferenceProvider(language, {
provideReferences: (model, position, context) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/references",
params: {
context: context,
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `references ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (result.length == 0) return resolve([]);
const references = result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
uri: monaco.Uri.parse(item.uri), //
}));
pushLSPLogMessage("info", `references ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, references);
resolve(references);
});
});
return []; // 如果没有内容,则返回空数组
},
});
// 注册"符号引用点击高亮"
monaco.languages.registerDocumentHighlightProvider(language, {
provideDocumentHighlights: (model, position) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/documentHighlight",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `documentHighlight ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result || result.length == 0) return resolve([]);
const highlights = result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
kind: item.kind,
}));
pushLSPLogMessage("info",
`documentHighlight ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
highlights
);
resolve(highlights);
});
});
return []; // 如果没有内容,则返回空数组
},
});
// 注册"文件链接"
if (language == "cpp" || language == "java")
monaco.languages.registerLinkProvider(language, {
provideLinks: (model) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/documentLink",
params: {
textDocument: {
uri: model.uri.toString(),
},
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve(null);
const links = {
links: result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
url: item.target.toString(),
tooltip: item.tooltip ? item.tooltip : null,
})),
};
pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, links);
resolve(links);
});
});
},
});
// 注册"格式化"
monaco.languages.registerDocumentFormattingEditProvider(language, {
provideDocumentFormattingEdits: (model, options, token) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/formatting",
params: {
textDocument: {
uri: model.uri.toString(),
},
options: options,
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
const TextEdit = result.map((edit) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(edit.range),
text: edit.newText,
}));
pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, TextEdit);
resolve(TextEdit);
});
});
},
});
// 注册"部分格式化"
monaco.languages.registerDocumentRangeFormattingEditProvider(language, {
provideDocumentRangeFormattingEdits: (model, range, options) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/rangeFormatting",
params: {
textDocument: {
uri: model.uri.toString(),
},
range: OJBetter_monaco.MonacoRangeTolspRange(range),
options,
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result || result.length == 0) return resolve([]);
const edits = result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
text: item.newText,
}));
pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
resolve(edits);
});
});
},
});
// 注册"重命名"
monaco.languages.registerRenameProvider(language, {
provideRenameEdits: (model, position, newName, token) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/rename",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
newName: newName,
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
const rename = OJBetter_monaco.lspEditToMonacoEdit(result);
pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, rename);
resolve(rename);
});
});
},
});
// 注册"折叠范围分析"
monaco.languages.registerFoldingRangeProvider(language, {
provideFoldingRanges: (model) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/foldingRange",
params: {
textDocument: {
uri: model.uri.toString(),
},
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve([]);
const foldingRanges = result.map((item) => ({
start: item.startLine + 1,
end: item.endLine + 1,
kind: monaco.languages.FoldingRangeKind.fromValue(item.kind),
}));
pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, foldingRanges);
resolve(foldingRanges);
});
});
},
});
// 注册"方法签名提示"
monaco.languages.registerSignatureHelpProvider(language, {
signatureHelpTriggerCharacters:
serverInfo.capabilities.signatureHelpProvider.triggerCharacters,
provideSignatureHelp: (model, position, token, context) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/signatureHelp",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: {
line: position.lineNumber - 1,
character: position.column - 1,
},
context: {
triggerKind: context.triggerKind,
triggerCharacter: context.triggerCharacter,
isRetrigger: context.isRetrigger,
activeSignatureHelp: context.activeSignatureHelp,
},
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `signatureHelp ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result) return resolve(null);
const SignatureHelpResult = {
value: {
activeParameter: result.activeParameter,
activeSignature: result.activeSignature,
signatures: result.signatures,
},
dispose: () => { },
};
pushLSPLogMessage("info",
`signatureHelp ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
SignatureHelpResult
);
resolve(SignatureHelpResult);
});
});
},
});
// 注册"渐进式自动格式化" 如果server有这个
if (serverInfo.capabilities.documentOnTypeFormattingProvider)
monaco.languages.registerOnTypeFormattingEditProvider(language, {
autoFormatTriggerCharacters: [
serverInfo.capabilities.documentOnTypeFormattingProvider
.firstTriggerCharacter,
],
provideOnTypeFormattingEdits: (model, position, ch, options) => {
const request = {
jsonrpc: "2.0",
id: id++,
method: "textDocument/onTypeFormatting",
params: {
textDocument: {
uri: model.uri.toString(),
},
position: OJBetter_monaco.MonacoPositionTolspPosition(position),
ch,
options,
},
};
return new Promise((resolve, reject) => {
fetchData(request, (response) => {
const result = response.result;
pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
if (!result || result.length == 0) return resolve([]);
const edits = result.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
text: item.newText,
}));
pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
resolve(edits);
});
});
},
});
};
/**
* 被动式接收处理
*/
OJBetter_monaco.PassiveReceiveHandler = () => {
// "实时代码诊断"
OJBetter_monaco.updateMarkers = function (message) {
const params = message.params;
pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
if (!params) return;
const markers = params.diagnostics.map((item1) => ({
code: item1.code,
message: item1.message,
...OJBetter_monaco.lspRangeToMonacoRange(item1.range),
relatedInformation: item1.relatedInformation
? item1.relatedInformation.map((item2) => ({
...(item2.location.range
? OJBetter_monaco.lspRangeToMonacoRange(item2.location.range)
: OJBetter_monaco.lspRangeToMonacoRange(item2.location)),
message: item2.message,
resource: monaco.Uri.parse(item2.location.uri),
}))
: null,
severity: OJBetter_monaco.lspSeverityToMonacoSeverity(item1.severity),
source: item1.source,
}));
pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, markers);
monaco.editor.setModelMarkers(model, "eslint", markers);
// 更新状态底栏信息
const nowMarks = monaco.editor.getModelMarkers();
warningCount = 0;
errorCount = 0;
for (const marker of nowMarks) {
if (marker.severity === monaco.MarkerSeverity.Warning) {
warningCount++;
} else if (marker.severity === monaco.MarkerSeverity.Error) {
errorCount++;
}
}
$('#OJBetter_statusBar').text(`Warnings: ${warningCount}, Errors: ${errorCount}`);
};
// "应用服务器推送的更改"(代码修复)
OJBetter_monaco.applyEdit = function (message) {
const params = message.params;
pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
if (!params) return;
const operations = Object.values(params.edit.changes)
.flat()
.map((item) => ({
range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
text: item.newText,
}));
pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, operations);
model.pushEditOperations([], operations, () => null); // 入栈编辑操作
};
}
if (!languageSocketState) await waitForLanguageSocketState();
OJBetter_monaco.Initialize();
}
// 语言更改
function changeMonacoLanguage(form) {
let nowSelect = form.selectLang.val();
// 记忆更改
GM_setValue('compilerSelection', nowSelect);
// 销毁旧的编辑器
try {
if (OJBetter.monaco.editor) OJBetter.monaco.editor.dispose();
} catch (error) {
console.warn("Encountered an error while attempting to delete the old editor, but it should not affect your regular usage.", error)
}
// 关闭旧的socket
OJBetter.monaco.lsp.socket.forEach(socket => {
socket.close();
});
// 移除相关元素
form.topRightDiv.empty();
$('#LSPLog').remove();
$('#OJBetter_statusBar').remove();
// 创建新的编辑器
if (nowSelect in value_monacoLanguageMap) {
let language = value_monacoLanguageMap[nowSelect];
if (language == "python" || language == "cpp") {
createMonacoEditor(language, form, true);
} else {
createMonacoEditor(language, form, false);
}
} else {
createMonacoEditor(null, false);
}
// 更新在线编译器参数
changeCompilerArgs(nowSelect);
}
// 收集样例数据
function getTestData() {
var testData = {};
// 从pre中获取文本信息
function getTextFromPre(node) {
let text;
if (node.find("br").length > 0) {
text = node.html().replace(/
/g, "\n"); //
作换行符的情况
} else {
text = node.text();
}
return text;
}
$('.input').each(function (index) {
var inputText = '';
if ($(this).find('pre').find('div').length > 0) {
$(this).find('pre').find('div').each(function () {
inputText += getTextFromPre($(this)) + '\n';
});
} else {
inputText = getTextFromPre($(this).find('pre'));
}
var outputText = '';
if ($('.output').eq(index).find('pre').find('div').length > 0) {
$('.output').eq(index).find('pre').find('div').each(function () {
inputText += getTextFromPre($(this)) + '\n';
});
} else {
outputText = getTextFromPre($('.output').eq(index).find('pre'));
}
testData[index + 1] = {
input: inputText.trim(),
output: outputText.trim()
};
});
return testData;
}
// 初始化自定义测试数据面板
function CustomTestInit() {
const url = window.location.href;
restoreText();
// 添加
$('#addCustomTest').click(function () {
var sampleDiv = $('
');
var inputTextarea = $('
input
');
var outputTextarea = $('
output
');
var deleteCustomTest = $(`
`);
sampleDiv.append(deleteCustomTest);
sampleDiv.append(inputTextarea);
sampleDiv.append(outputTextarea);
$('#customTests').append(sampleDiv);
});
// 实时保存文本内容到 IndexedDB 中
$(document).on('input', '.inputTextarea, .outputTextarea', function () {
OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
var objectStore = OJBetter.common.database.samplesData;
var samples = {
url: url,
samples: []
};
var index = 0;
$('.sampleDiv').each(function () {
var $sampleDiv = $(this);
var inputTextarea = $sampleDiv.find('.inputTextarea');
var outputTextarea = $sampleDiv.find('.outputTextarea');
$sampleDiv.attr('data-index', index);
inputTextarea.attr('id', 'input' + index);
outputTextarea.attr('id', 'output' + index);
var sample = {
id: index,
input: inputTextarea.val(),
output: outputTextarea.val()
};
samples.samples.push(sample);
index++;
});
objectStore.put(samples);
});
});
// 删除
$(document).on('click', '.deleteCustomTest', function () {
var $sampleDiv = $(this).closest('.sampleDiv');
OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
var objectStore = OJBetter.common.database.samplesData;
var index = parseInt($sampleDiv.attr('data-index'));
if (!isNaN(index)) {
objectStore.get(url).then(row => {
let samples = row.samples;
samples.splice(index, 1); // 移除第index个元素
objectStore.put({
url: url,
samples: samples
});
})
}
$sampleDiv.remove();
});
});
// 恢复保存的内容
function restoreText() {
OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
return OJBetter.common.database.samplesData.get(url);
}).then(function (data) {
if (data.samples && data.samples.length > 0) {
data.samples.forEach(function (item, index) {
var sampleDiv = $('
');
var inputTextarea = $(`
input
`);
var outputTextarea = $(`
output
`);
var deleteCustomTest = $(`
`);
inputTextarea.val(item.input);
outputTextarea.val(item.output);
sampleDiv.append(deleteCustomTest);
sampleDiv.append(inputTextarea);
sampleDiv.append(outputTextarea);
sampleDiv.attr('data-index', index)
$('#customTests').append(sampleDiv);
});
}
});
}
}
// 获取自定义测试数据
function getCustomTestData() {
const url = window.location.href;
return new Promise(function (resolve) {
var customTestData = {};
OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
return OJBetter.common.database.samplesData.get(url);
}).then(function (data) {
if (!data) resolve(customTestData);
if (data.samples && data.samples.length > 0) {
data.samples.forEach(function (item, index) {
customTestData[index + 1] = {
input: item.input,
output: item.output
};
});
}
resolve(customTestData);
});
});
}
// codeforces编译器参数列表
let officialLanguage = "";
function officialCompilerArgsChange(nowSelect) {
officialLanguage = nowSelect;
$('#CompilerArgsInput').prop("disabled", true);
}
// codeforces编译器通信
async function officialCompiler(code, input) {
const data = new FormData();
data.append('csrf_token', OJBetter.common.cf_csrf_token);
data.append('source', code);
data.append('tabSize', '4');
data.append('programTypeId', officialLanguage);
data.append('input', input);
data.append('output', '');
data.append('communityCode', '');
data.append('action', 'submitSourceCode');
data.append('programTypeId', officialLanguage);
data.append('sourceCode', code);
const requestOptions = {
method: 'POST',
url: `${OJBetter.common.hostAddress}/data/customtest`,
data: data,
headers: {
'X-Csrf-Token': OJBetter.common.cf_csrf_token
}
};
const result = {
Errors: '',
Result: '',
Stats: ''
};
try {
const submitResponse = await OJB_GMRequest(requestOptions);
if (submitResponse.status !== 200 || !submitResponse.response) {
result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
return result;
}
const submitResult = JSON.parse(submitResponse.response);
const customTestSubmitId = submitResult.customTestSubmitId;
const verdictResponse = await OJB_promiseRetryWrapper(
getOfficialCompilerVerdict,
{
maxRetries: 10,
retryInterval: 500
},
customTestSubmitId
);
return verdictResponse;
} catch (error) {
result.Errors = error.message;
return result;
}
}
// 获取codeforces编译器的执行结果
async function getOfficialCompilerVerdict(customTestSubmitId) {
const newdata = new FormData();
newdata.append('csrf_token', OJBetter.common.cf_csrf_token);
newdata.append('action', 'getVerdict');
newdata.append('customTestSubmitId', customTestSubmitId);
const requestOptions = {
method: 'POST',
url: `${OJBetter.common.hostAddress}/data/customtest`,
data: newdata,
headers: {
'X-Csrf-Token': OJBetter.common.cf_csrf_token
}
};
const responseDetails = await OJB_GMRequest(requestOptions);
if (responseDetails.status !== 200 || !responseDetails.response) {
throw new Error(`${i18next.t('compiler.official.getResultError', { ns: 'codeEditor' })}`);
}
const response = JSON.parse(responseDetails.response);
if (!response.stat) {
throw new Error('Verdict not ready, retrying...');
}
return {
Errors: response.verdict === "OK" ? null : response.verdict + '
' + response.output,
Result: response.output.replace(/\r\n/g, "\n"),
Stats: `Status: ${response.stat}`
};
}
// rextester编译器参数列表
let rextesterLanguage = "";
function rextesterCompilerArgsChange(nowSelect) {
let LanguageChoiceList = {
"4": "9", "6": "8", "7": "5", "9": "1", "13": "13", "19": "42", "20": "21", "28": "30", "31": "24", "32": "20",
"34": "17", "36": "4", "43": "6", "45": "7", "46": "4", "50": "7", "51": "9", "52": "27", "54": "7", "55": "23", "60": "4",
"61": "7", "65": "1", "67": "12", "70": "5", "73": "7", "74": "4", "75": "46", "77": "43", "79": "1", "80": "27", "83": "43", "87": "4"
}
let CompilerArgsList = {
"6": "-Wall -std=gnu99 -O2 -o a.out source_file.c",
"7": "-Wall -std=c++14 -O2 -o a.out source_file.cpp",
"20": "-o a.out source_file.go",
"27": "-Wall -std=c++14 -stdlib=libc++ -O2 -o a.out source_file.cpp",
"30": "source_file.d -ofa.out"
}
if (nowSelect in LanguageChoiceList) {
$('#RunTestButton').prop("disabled", false);
rextesterLanguage = LanguageChoiceList[nowSelect];
} else {
$('#RunTestButton').prop("disabled", true);
}
if (rextesterLanguage in CompilerArgsList) {
const rextesterCompilerArgs = CompilerArgsList[rextesterLanguage];
$('#CompilerArgsInput').val(rextesterCompilerArgs);
} else {
$('#CompilerArgsInput').val("");
}
}
// rextester编译器通信
async function rextesterCompiler(code, input) {
try {
const result = await OJB_promiseRetryWrapper(rextesterCompilerRequest, {
maxRetries: 5,
retryInterval: 500,
errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
}, code, input);
return result;
} catch (error) {
return { Errors: error.message, Result: '', Stats: '' };
}
}
// rextester编译器请求方法
async function rextesterCompilerRequest(code, input) {
const data = new FormData();
data.append('LanguageChoiceWrapper', rextesterLanguage);
data.append('EditorChoiceWrapper', '1');
data.append('LayoutChoiceWrapper', '1');
data.append('Program', code);
data.append('CompilerArgs', $('#CompilerArgsInput').val());
data.append('Input', input);
data.append('ShowWarnings', 'false');
data.append('IsInEditMode', 'false');
data.append('IsLive', 'false');
const responseDetails = await OJB_GMRequest({
method: 'POST',
url: 'https://rextester.com/rundotnet/Run',
data: data
});
if (responseDetails.status !== 200 || !responseDetails.response) {
throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
}
const response = JSON.parse(responseDetails.response);
return {
Errors: response.Errors || '',
Result: response.Result || '',
Stats: response.Stats || ''
};
}
// wandbox编译器参数列表
var wandboxlist = JSON.parse(GM_getResourceText("wandboxlist"));
function wandboxCompilerArgsChange(nowSelect) {
let LanguageChoiceList = {
"6": "PHP", "7": "Python", "9": "C#", "12": "Haskell", "13": "Perl", "19": "OCaml",
"20": "Scala", "28": "D", "31": "Python", "32": "Go", "34": "JavaScript", "36": "Java", "40": "Python", "41": "Python",
"43": "C++", "50": "C++", "51": "Pascal", "52": "C++", "54": "C++", "60": "Java", "61": "C++", "65": "C#", "67": "Ruby",
"70": "Python", "73": "C++", "74": "Java", "75": "Rust", "79": "C#", "80": "C++", "87": "Java"
}
// 移除旧的
$('#CompilerChange').remove();
if (nowSelect in LanguageChoiceList) {
$('#RunTestButton').prop("disabled", false);
const Languagefiltered = wandboxlist.filter(obj => obj.language === LanguageChoiceList[nowSelect]);
// 创建编译器下拉框
let CompilerChange = $('
');
$('#CompilerSetting').show().append(CompilerChange);
for (let i = 0; i < Languagefiltered.length; i++) {
let Compiler = Languagefiltered[i];
let op = $("
")
.val(Compiler.name)
.text(Compiler["display-name"] + " " + Compiler.version);
$("#CompilerChange").append(op);
}
// 编译器参数刷新
function refreshCompilerArgs() {
var flags = '';
$("#CompilerBox").find("*").each(function () {
if ($(this).is("input[type='checkbox']")) {
let flag = $(this).prop("checked") ? $(this).val() : '';
flags += flag + (flag ? ' ' : '');
} else if ($(this).is("select") || $(this).is("input") || $(this).is("textarea")) {
let flag = $(this).val();
flags += flag + (flag ? ' ' : '');
}
});
$("#CompilerArgsInput").val(flags);
$("#CompilerArgsInput").prop("readonly", true); // 只读
}
// 编译器切换监听
CompilerChange.change(function () {
let selectedName = CompilerChange.val();
let Compiler = Languagefiltered.find(
(obj) => obj.name === selectedName
);
$("#CompilerArgsInput").val(); // 初始化编译器输入框
$("#CompilerBox").remove();
let div = $("
");
let display_compile_command = $(`
`);
div.append(display_compile_command);
let switches = Compiler.switches;
for (let i = 0; i < switches.length; i++) {
let switche = switches[i];
if (switche.type == "single") {
let single = $(`
`);
div.append(single);
single.find("input").change(function () {
refreshCompilerArgs();
});
} else if (switche.type == "select") {
let select = $(`
`);
select.data('previousValue', switche.options[0]['display-flags']);
div.append(select);
for (let i = 0; i < switche.options.length; i++) {
let option = switche.options[i];
let op = $("
")
.val(option['display-flags'])
.text(option['display-name']);
select.append(op);
}
select.change(function () {
refreshCompilerArgs();
});
}
}
if (Compiler['compiler-option-raw'] == true) {
let textarea = $(`
`);
div.append(textarea);
textarea.on('input', function () {
refreshCompilerArgs();
});
}
if (Compiler['runtime-option-raw'] == true) {
let textarea = $(`
`);
div.append(textarea);
textarea.on('input', function () {
refreshCompilerArgs();
});
}
$("#CompilerSetting").append(div);
refreshCompilerArgs(); // 初始化
});
CompilerChange.trigger("change"); // 初始化
} else {
$('#RunTestButton').prop("disabled", true);
}
}
// wandbox编译器通信
async function wandboxCompiler(code, input) {
try {
const result = await OJB_promiseRetryWrapper(wandboxCompilerRequest, {
maxRetries: 5,
retryInterval: 500,
errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
}, code, input);
return result;
} catch (error) {
return { Errors: error.message, Result: '', Stats: '' };
}
}
// wandbox编译器请求方法
async function wandboxCompilerRequest(code, input) {
const data = {
code: code,
codes: [],
compiler: $('#CompilerChange').val().replace($('#compiler_option_raw').val(), '').replace($('#runtime_option_raw').val(), ''),
'compiler-option-raw': $('#compiler_option_raw').val(),
'runtime-option-raw': $('#runtime_option_raw').val(),
options: $("#CompilerArgsInput").val(),
description: '',
stdin: input,
title: ''
};
const responseDetails = await OJB_GMRequest({
method: 'POST',
url: 'https://wandbox.org/api/compile.json',
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
});
if (responseDetails.status !== 200 || !responseDetails.response) {
throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
}
const response = JSON.parse(responseDetails.response);
return {
Errors: response.compiler_error === "" ? response.signal : response.compiler_error,
Result: response.program_output || '',
Stats: response.status === "0" ? "Finish" : "Error"
};
}
// 更改编译器参数
function changeCompilerArgs(nowSelect) {
if (OJBetter.monaco.onlineCompilerChoice == "official") {
officialCompilerArgsChange(nowSelect);
} else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
rextesterCompilerArgsChange(nowSelect);
} else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
wandboxCompilerArgsChange(nowSelect);
}
}
// 在线编译器通信
async function onlineCompilerConnect(code, input) {
if (OJBetter.monaco.onlineCompilerChoice == "official") {
return await officialCompiler(code, input);
} else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
return await rextesterCompiler(code, input);
} else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
return await wandboxCompiler(code, input);
}
}
// 差异对比
function codeDiff(expectedText, actualText) {
// 将文本按行拆分
const expectedLines = expectedText ? expectedText.split('\n') : [];
const actualLines = actualText ? actualText.split('\n') : [];
const output = document.createElement('div');
const createLineElement = (lineNo, contentElement) => {
const lineDiv = document.createElement('div');
lineDiv.className = 'diffLine';
const lineNoDiv = document.createElement('div');
lineNoDiv.className = 'lineNo';
lineNoDiv.textContent = lineNo;
lineDiv.appendChild(lineNoDiv);
lineDiv.appendChild(contentElement);
return lineDiv;
};
const createContentElement = (isEquals = true, expected = null, removed = null) => {
const contentDiv = document.createElement('div');
contentDiv.className = 'lineContent';
if (isEquals) {
contentDiv.textContent = expected;
} else {
if (removed != null) {
const removedSpan = document.createElement('span');
removedSpan.className = 'removed';
removedSpan.textContent = removed;
contentDiv.appendChild(removedSpan);
}
if (expected != null) {
const addedSpan = document.createElement('span');
addedSpan.className = 'added';
addedSpan.textContent = expected;
contentDiv.appendChild(addedSpan);
}
}
return contentDiv;
};
let index = 1;
expectedLines.forEach((expectedLine, i) => {
const actualLine = actualLines[i];
if (actualLine === undefined) {
output.appendChild(createLineElement(index++, createContentElement(false, expectedLine)));
} else if (expectedLine === actualLine) {
output.appendChild(createLineElement(index++, createContentElement(true, expectedLine)));
} else {
output.appendChild(createLineElement(index++, createContentElement(false, expectedLine, actualLine)));
}
});
// 处理多余的 actualLines
for (let i = expectedLines.length; i < actualLines.length; i++) {
output.appendChild(createLineElement(index++, createContentElement(false, null, actualLines[i])));
}
return output.innerHTML;
}
// 内容类型常量
const TestCaseContentType = {
TERMINAL: 'terminal',
DIFF: 'diff',
NO_DIFF: 'no_diff',
SUCCESS: 'success'
};
// 样例测试状态类
class TestCaseStatus {
constructor(item, prefix) {
this.testCase = $(`
`);
this.item = item;
this.prefix = prefix;
this.titleElement = $(`
${this.prefix} ${this.item}
`);
this.statusElement = $(`
`);
this.contentElement = $(`
`);
this.judgeElement = $(`
`);
this.testCase.append(this.titleElement, this.statusElement, this.contentElement, this.judgeElement);
$('#statePanel').append(this.testCase);
this.setStatus('Queued', 'queued');
}
setTitle(title) {
this.titleElement.text(title);
}
setStatus(text, status) {
this.statusElement.text(text).removeClass('queued running success error').addClass(status);
}
setContent(content, type) {
// 如果内容类型为SUCCESS,隐藏内容元素并提前返回
if (type === TestCaseContentType.SUCCESS) {
this.contentElement.hide();
return;
}
// 根据内容类型创建内容元素
const createContentElementByType = (content, type) => {
let contentElement;
switch (type) {
case TestCaseContentType.TERMINAL:
// 为TERMINAL类型创建一个新的终端容器
contentElement = $(`
`);
break;
case TestCaseContentType.DIFF:
case TestCaseContentType.NO_DIFF:
// 为DIFF和NO_DIFF类型创建相应的内容元素,并添加差异说明
const className = type === TestCaseContentType.DIFF ? "output_diff" : "output_no_diff";
contentElement = $(`
${content}
`);
appendDiffNote();
break;
default:
throw new Error("Unsupported content type.");
}
return contentElement;
};
// 初始化终端
const initializeTerminal = (content, contentElement) => {
const term = new Terminal({ rows: 10, cols: 150 });
term.setOption('theme', { background: '#2d2e2c' });
term.setOption('convertEol', true); // 将换行符\n转换为\r\n
term.write(content);
term.open(contentElement.get(0));
};
// 添加差异说明
const appendDiffNote = () => {
const diffNote = $(`
${i18next.t('resultBlock.diffNote', { ns: 'codeEditor' })}
`);
this.testCase.append(diffNote);
};
// 创建并追加内容元素
const contentElement = createContentElementByType(content, type);
this.contentElement.append(contentElement);
// 如果内容类型为TERMINAL,初始化并打开终端
if (type === TestCaseContentType.TERMINAL) {
initializeTerminal(content, contentElement);
}
}
setJudge(judge) {
this.judgeElement.text(judge);
}
}
// 样例测试函数
async function runCode(event, runButton, sourceDiv, submitDiv) {
event.preventDefault();
const statePanel = $('#statePanel').show().empty();
const testData = getTestData();
const customTestData = await getCustomTestData();
const totalTests = Object.keys(customTestData).length + Object.keys(testData).length;
let passedTests = 0;
let failedTests = 0;
let hasError = false;
// 定义一个对象队列,包括创建的样例块实例和对应的样例数据
const queue = [];
// 先生成各个样例的块,并显示排队中,将创建的各个对象存到队列中,以便后面更新
for (const [item, data] of Object.entries(customTestData)) {
const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.custom', { ns: 'codeEditor' }));
queue.push({ testCase, data });
}
if (!$('#onlyCustomTest').prop('checked')) {
for (const [item, data] of Object.entries(testData)) {
const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.sample', { ns: 'codeEditor' }));
queue.push({ testCase, data });
}
}
// 测试函数
const runTest = async (testCase, data, index) => {
runButton.setButtonState('running', `${index}/${totalTests}`);
testCase.setStatus('Running', 'running');
const result = await onlineCompilerConnect(sourceDiv.val(), data.input);
if (result.Errors) {
testCase.setStatus('Compilation error or Time limit', 'error');
testCase.setContent(result.Errors, TestCaseContentType.TERMINAL);
hasError = true;
} else if (result.Result.trim() === data.output.trim()) {
testCase.setStatus('Accepted', 'success');
testCase.setContent('The output is correct.', TestCaseContentType.SUCCESS);
passedTests++;
} else {
testCase.setStatus('Wrong Answer', 'error');
const diffContent = $('#DontShowDiff').prop('checked') ? result.Result.trim() : codeDiff(data.output.trim(), result.Result.trim());
const contentType = $('#DontShowDiff').prop('checked') ? TestCaseContentType.NO_DIFF : TestCaseContentType.DIFF;
testCase.setContent(diffContent, contentType);
failedTests++;
}
const judgeStats = `${i18next.t('resultBlock.state', { ns: 'codeEditor' })}${result.Stats}`;
testCase.setJudge(judgeStats);
await OJB_delay(500); // 等待500毫秒
};
// 对队列中的对象进行测试
for (let i = 0; i < queue.length; i++) {
const { testCase, data } = queue[i];
await runTest(testCase, data, i + 1);
}
// 测试完成后更新按钮状态
if (hasError) {
runButton.setButtonState('error', i18next.t('runTestButton.error', { ns: 'codeEditor' }));
} else if (failedTests > 0) {
runButton.setButtonState('error', `${passedTests}/${totalTests} ` + i18next.t('runTestButton.partial', { ns: 'codeEditor' }));
} else {
runButton.setButtonState('success', i18next.t('runTestButton.success', { ns: 'codeEditor' }));
}
}
/**
* 添加题目页代码编辑器
* @returns
*/
async function addProblemPageCodeEditor() {
if (typeof ace === 'undefined') {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('error.codeEditor.load', { ns: 'alert' })}`, 'error');
return; // 因为Codeforces设定的是未登录时不能访问提交页,也不会加载ace库
}
// 获取提交页链接
const href = window.location.href;
let submitUrl;
if (/\/problemset\//.test(href)) {
// problemset
submitUrl = OJBetter.common.hostAddress + '/problemset/submit';
} else if (/\/gym\//.test(href)) {
// gym 题目
submitUrl = OJBetter.common.hostAddress + '/gym/' + ((href) => {
const regex = /\/gym\/(?
[0-9a-zA-Z]*?)\/problem\//;
const match = href.match(regex);
return match && match.groups.num;
})(href) + '/submit';
} else if (OJBetter.typeOfPage.is_acmsguru) {
// acmsguru 题目
submitUrl = href.replace(/\/problemsets[A-Za-z0-9\/#]*/, "/problemsets/acmsguru/submit");
} else {
submitUrl = href.replace(/\/problem[A-Za-z0-9\/#]*/, "/submit");
}
// 获取提交页HTML
let cloneHTML = await getSubmitHTML(submitUrl);
// 创建
let form = await createCodeEditorForm(submitUrl, cloneHTML);
let selectLang = form.selectLang;
let submitButton = form.submitButton;
let runButton = form.runButton;
// 初始化
CustomTestInit(); // 自定义测试数据面板
selectLang.val(OJBetter.monaco.compilerSelection);
changeMonacoLanguage(form);
selectLang.on('change', () => changeMonacoLanguage(form)); // 编辑器语言切换监听
// 样例测试
runButton.on('click', (event) => runCode(event, runButton, form.sourceDiv, form.submitDiv))
.setHoverRedo();
// 提交
submitButton.on('click', async function (event) {
event.preventDefault();
if (OJBetter.monaco.setting.isCodeSubmitDoubleConfirm) {
const submit = await OJB_createDialog(
i18next.t('submitCode.title', { ns: 'dialog' }),
i18next.t('submitCode.content', { ns: 'dialog' }),
[
i18next.t('submitCode.buttons.0', { ns: 'dialog' }),
i18next.t('submitCode.buttons.1', { ns: 'dialog' })
]
); //提交确认
if (submit) {
submitButton.after(`
`);
$('#OJBetter_SubmitForm').submit();
} else {
submitButton.addClass('disabled');
setTimeout(function () {
submitButton.removeClass('disabled');
}, 300);
}
} else {
$('#OJBetter_SubmitForm').submit();
}
});
}
/**
* 获取翻译服务目标语言的对应代码
* @param {string} serverName 服务名称
* @returns {string} 目标语言,如果没有对应代码则返回中文
*/
function getTargetLanguage(serverName) {
let targetLanguage = OJBetter.supportList.translationSupport[serverName][OJBetter.translation.targetLang];
if (targetLanguage) return targetLanguage;
else return OJBetter.supportList.translationSupport[serverName]['zh'];
}
/**
* 将文本中Markdown格式的加粗**转换成HTML格式。
* @param {string} text 文本
* @returns {string} 替换后的字符串
*/
function convertBoldMarkdownToHTML(text) {
return text.replace(/\*\*(.*?)\*\*/g, '$1');
}
/**
* 将文本中Markdown格式的链接文本转换成HTML格式。
* @param {string} text 文本
* @returns {string} 替换后的字符串
*/
function convertLinksMarkdownToHTML(text) {
return text.replace(/(?$1');
}
/**
* 将HTML格式的加粗文本转换回Markdown格式。
* @param {string} text 文本
* @returns {string} 替换后的字符串
*/
function convertBoldHTMLToMarkdown(text) {
return text.replace(/(.*?)<\/strong>/g, '**$1**');
}
/**
* 将HTML格式的链接文本转换回Markdown格式。
* @param {string} html - 包含HTML链接标签的字符串。
* @returns {string} 转换后的字符串,其中HTML链接标签被替换为Markdown的链接语法。
*/
function convertLinksHTMLToMarkdown(html) {
return html.replace(/([^<]+)<\/a>/g, '[$4]($1 "$3")');
}
/**
* DeepL翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_deepl(raw) {
const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
const data = {
jsonrpc: '2.0',
method: 'LMT_handle_texts',
id,
params: {
splitting: 'newlines',
lang: {
source_lang_user_selected: 'auto',
target_lang: getTargetLanguage('deepl'),
},
texts: [{
text: raw,
requestAlternatives: 3
}],
timestamp: getTimeStamp(raw.split('i').length - 1)
}
}
let postData = JSON.stringify(data);
if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
postData = postData.replace('"method":"', '"method" : "');
} else {
postData = postData.replace('"method":"', '"method": "');
}
const options = {
method: 'POST',
url: 'https://www2.deepl.com/jsonrpc',
data: postData,
headers: {
'Content-Type': 'application/json',
'Host': 'www2.deepl.com',
'Origin': 'https://www.deepl.com',
'Referer': 'https://www.deepl.com/',
},
anonymous: true,
nocache: true,
}
return await BaseTranslate(options, res => JSON.parse(res).result.texts[0].text, res => {
const resObj = {
status: true,
message: 'ok'
};
if (res.includes('"error":{"code":1042912,"message":"Too many requests"}')) {
resObj.status = false;
resObj.message = i18next.t('error.deepl429', { ns: 'translator' }); // Too many requests 提示
return resObj;
};
return resObj;
});
}
/**
* 使用 DeepL Free API 进行翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_deepl_api_free(raw) {
const data = JSON.stringify({
text: [raw],
target_lang: getTargetLanguage('deepl'),
split_sentences: '1',
...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
...Object.assign({}, ...OJBetter.deepl.config.data)
});
const options = {
method: "POST",
url: OJBetter.deepl.config.proxy || "https://api-free.deepl.com/v2/translate",
headers: {
"Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
"Content-Type": "application/json",
...Object.assign({}, ...OJBetter.deepl.config.header)
},
data: data,
onload: response => response.responseText,
onerror: error => console.error(error)
};
return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
}
/**
* 使用 DeepL Pro API 进行翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_deepl_api_pro(raw) {
const data = JSON.stringify({
text: [raw],
target_lang: getTargetLanguage('deepl'),
split_sentences: '1',
...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
...Object.assign({}, ...OJBetter.deepl.config.data)
});
const options = {
method: "POST",
url: OJBetter.deepl.config.proxy || "https://api.deepl.com/v2/translate",
headers: {
"Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
"Content-Type": "application/json",
...Object.assign({}, ...OJBetter.deepl.config.header)
},
data: data,
onload: response => response.responseText,
onerror: error => console.error(error)
};
return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
}
/**
* 使用 DeepLX 进行翻译
* @param {String} text 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_deeplx(text) {
const options = {
method: "POST",
url: OJBetter.deepl.config.proxy || 'https://api.deeplx.org/translate',
data: JSON.stringify({
"text": text,
"source_lang": "EN",
"target_lang": getTargetLanguage('deepl'),
}),
headers: {
'Content-Type': 'application/json',
...(OJBetter.deepl.config.key ? { Authorization: `Bearer ${OJBetter.deepl.config.key}` } : {})
},
responseType: "json",
};
return await BaseTranslate(options, res => {
const parsedResponse = JSON.parse(res);
if (parsedResponse.code === 200 && parsedResponse.data) {
return parsedResponse.data;
} else {
throw new Error('Translation failed or invalid response format.');
}
});
}
function getTimeStamp(iCount) {
const ts = Date.now();
if (iCount !== 0) {
iCount = iCount + 1;
return ts - (ts % iCount) + iCount;
} else {
return ts;
}
}
/**
* 讯飞听见翻译
* @param {String} text 要翻译的文本
* @returns {Promise} 翻译结果对象
*/
async function translate_iflyrec(text) {
const options = {
method: "POST",
url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
data: JSON.stringify({
"from": "2",
"to": getTargetLanguage('iflyrec'),
"contents": [{
"text": text,
"frontBlankLine": 0
}]
}),
anonymous: true,
headers: {
'Content-Type': 'application/json',
'Origin': 'https://www.iflyrec.com',
},
responseType: "json",
};
return await BaseTranslate(options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
}
/**
* 有道翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_youdao_mobile(raw) {
const options = {
method: "POST",
url: 'http://m.youdao.com/translate',
data: "inputtext=" + encodeURIComponent(raw) + "&type=" + getTargetLanguage('youdao'),
anonymous: true,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Host': 'm.youdao.com',
'Origin': 'http://m.youdao.com',
'Referer': 'http://m.youdao.com/translate',
}
}
return await BaseTranslate(options,
res => {
const array = /id="translateResult">\s*?([\s\S]*?)<\/li>\s*?<\/ul/.exec(res);
if (array && array.length > 1) {
return array[1];
} else {
return res;
}
},
res => {
const resObj = {
status: true,
message: 'ok'
};
if (res.includes('413 Request Entity Too Large')) {
resObj.status = false;
resObj.message = i18next.t('error.youdao413', { ns: 'translator' }); // Request Entity Too Large 提示
return resObj;
};
return resObj;
})
}
/**
* 谷歌翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_gg(raw) {
const params = `tl=${getTargetLanguage('google')}&q=${encodeURIComponent(raw)}`;
const options = {
method: "GET",
url: `https://translate.google.com/m?${params}`,
}
return await BaseTranslate(options,
res => $(res).filter('.result-container').text() || $(res).find('.result-container').text());
}
/**
* 彩云翻译预处理
*/
async function translate_caiyun_startup() {
const browser_id = CryptoJS.MD5(Math.random().toString()).toString();
sessionStorage.setItem('caiyun_id', browser_id);
const options = {
method: "POST",
url: 'https://api.interpreter.caiyunai.com/v1/user/jwt/generate',
headers: {
"Content-Type": "application/json",
"X-Authorization": "token:qgemv4jr1y38jyq6vhvi",
"Origin": "https://fanyi.caiyunapp.com",
},
data: JSON.stringify({ browser_id }),
}
const res = await OJB_GMRequest(options);
sessionStorage.setItem('caiyun_jwt', JSON.parse(res.responseText).jwt);
}
/**
* 彩云翻译
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_caiyun(raw) {
const source = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
const dic = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"].reduce((dic, current, index) => { dic[current] = source[index]; return dic }, {});
// 解码
const decodeUnicode = str => {
const decoder = new TextDecoder();
const data = Uint8Array.from(atob(str), c => c.charCodeAt(0));
return decoder.decode(data);
};
const decoder = line => decodeUnicode([...line].map(i => dic[i] || i).join(""));
const options = {
method: "POST",
url: 'https://api.interpreter.caiyunai.com/v1/translator',
data: JSON.stringify({
"source": raw.split('\n'),
"trans_type": getTargetLanguage('caiyun'),
"detect": true,
"browser_id": sessionStorage.getItem('caiyun_id')
}),
headers: {
"X-Authorization": "token:qgemv4jr1y38jyq6vhvi",
"T-Authorization": sessionStorage.getItem('caiyun_jwt')
}
}
return await BaseTranslate(options, res => JSON.parse(res).target.map(decoder).join('\n'))
}
/**
* ChatGPT
* @param {string} raw 原文
* @returns {Promise} 翻译结果对象
*/
async function translate_openai(raw) {
const modelDefault = 'gpt-3.5-turbo';
const lang = getTargetLanguage('openai');
const prompt = (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) ?
i18next.t('chatgpt_prompt.notLaTeX', { ns: 'translator', transTargetLang: lang, lng: OJBetter.translation.targetLang }) :
i18next.t('chatgpt_prompt.common', { ns: 'translator', transTargetLang: lang, lng: OJBetter.translation.targetLang });
const data = {
model: OJBetter.chatgpt.config.model || modelDefault,
messages: [{
role: "user",
content: prompt + raw
}],
temperature: 0.7,
...Object.assign({}, ...OJBetter.chatgpt.config.data)
}
const options = {
method: "POST",
url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
data: JSON.stringify(data),
responseType: 'json',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
...Object.assign({}, ...OJBetter.chatgpt.config.header)
}
}
return await BaseTranslate(options,
res => res,
undefined,
response => response.response.choices[0].message.content);
}
/**
* ChatGPT 流式传输
* @param {string} raw 原文
* @param {TranslateDiv} translateDiv 翻译结果面板
* @returns {Promise} 翻译结果对象
*/
async function translate_openai_stream(raw, translateDiv) {
const result = {
done: true,
checkPassed: null,
response: null,
responseText: null,
text: "",
error: null,
message: null
};
const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
try {
for await (const delta of openai_stream(raw)) {
result.text += delta;
// 翻译结果面板更新
translateDiv.updateTranslateDiv(result.text, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru), false);
}
return result;
} catch (err) {
console.warn(err);
result.error = {
message: err.message || null,
stack: err.stack ? err.stack.replace(/\n/g, '
').replace(/\s/g, ' ') : null,
enumerable: err,
source: 'openai_stream'
};
result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
}
return result;
}
/**
* 流式传输
* @param {string} raw 原文
* @returns {AsyncGenerator} 返回 AsyncGenerator
*/
async function* openai_stream(raw) {
const modelDefault = 'gpt-3.5-turbo';
const lang = getTargetLanguage('openai');
const prompt = (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) ?
i18next.t('chatgpt_prompt.notLaTeX', { ns: 'translator', transTargetLang: lang, lng: OJBetter.translation.targetLang }) :
i18next.t('chatgpt_prompt.common', { ns: 'translator', transTargetLang: lang, lng: OJBetter.translation.targetLang });
const data = {
model: OJBetter.chatgpt.config.model || modelDefault,
messages: [{
role: "user",
content: prompt + raw
}],
temperature: 0.7,
stream: true,
...Object.assign({}, ...OJBetter.chatgpt.config.data)
}
const options = {
method: "POST",
url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
data: JSON.stringify(data),
responseType: 'stream',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
...Object.assign({}, ...OJBetter.chatgpt.config.header)
}
}
const response = await OJB_GMRequest(options, true);
const reader = response.response.getReader();
const decoder = new TextDecoder();
let buffer = ''; // 用于累积数据片段的缓冲区
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true }); // 将新的数据片段追加到缓冲区
let lines = buffer.split("\n\n"); // 处理累积的数据
// 缓冲区的最后一行可能还未完整接收,保留在缓冲区中,-1
for (let i = 0; i < lines.length - 1; i++) {
let line = lines[i];
line = line.substring(5); // 移除 'data:' 前缀
if (line.includes('[DONE]')) {
return; // End
}
try {
let data = JSON.parse(line);
let delta = data['choices'][0]['delta'];
let content = delta['content'] ? delta['content'] : "";
yield content; // 传递数据给调用者
} catch (error) {
console.warn(`Error parsing JSON: ${error}\n\nError data: ${line}`);
}
}
// 保留最后一行在缓冲区中
buffer = lines.slice(-1);
}
return buffer;
}
/**
* @typedef {Object} CheckResponseResult
* @property {boolean} status 检查是否通过
* @property {string} message 检查失败时的消息
*/
/**
* @typedef {Object} ErrorResponse
* @property {Object} message 错误消息
* @property {Object} stack 错误堆栈
* @property {Object} enumerable 可枚举的错误属性
* @property {string} source 错误来源
*/
/**
* @typedef {Object} TranslateResult
* @property {boolean} done 操作是否完成
* @property {CheckResponseResult|null} checkPassed 检查是否通过的结果
* @property {Object|null} response 响应对象
* @property {string|null} text 处理后的文本
* @property {ErrorResponse} error 错误列表
* @property {string|null} message 可能的消息
*/
/**
* 通用翻译函数
* @param {Object} options GM_xmlhttpRequest 的参数
* @param {Function} processer 响应再处理函数,它接收响应文本,并应返回处理后的文本。
* @param {Function} checkResponse 检查文本是否符合预期的函数,它接收文本,并返回一个Object,包含状态和信息。默认为返回 { status: true, message: 'ok' }
* @param {Function} getResponseText 重写响应文本获取函数,它接收response,并返回响应文本。 默认为 response.responseText
* @returns {Promise} 返回 Promise,其解析值为翻译结果对象
*/
async function BaseTranslate(options, processer, checkResponse = () => { return { status: true, message: 'ok' } }, getResponseText = (response) => response.responseText) {
const result = {
done: false,
checkPassed: null,
response: null,
responseText: null,
text: "",
error: null,
message: null
};
const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
const toDo = async () => {
try {
result.response = await OJB_GMRequest(options);
result.responseText = result.response.responseText;
result.text = getResponseText(result.response);
} catch (err) {
console.warn(err);
result.error = {
message: err.message || null,
stack: err.stack ? err.stack.replace(/\n/g, '
').replace(/\s/g, ' ') : null,
enumerable: err,
source: 'GMRequest'
};
result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
throw result;
}
try {
result.text = processer(result.text);
} catch (err) {
console.warn(err);
result.error = {
message: err.message || null,
stack: err.stack ? err.stack.replace(/\n/g, '
').replace(/\s/g, ' ') : null,
enumerable: err,
source: 'processer'
};
result.message = `${i18next.t('error.processer', { ns: 'translator' })}${helpText}`;
throw result;
}
try {
result.checkPassed = checkResponse(result.text);
if (result.checkPassed.status) result.done = true;
else result.message = result.checkPassed.message;
return result;
} catch (err) {
console.warn(err);
result.error = {
message: err.message || null,
stack: err.stack ? err.stack.replace(/\n/g, '
').replace(/\s/g, ' ') : null,
enumerable: err,
source: 'checkResponse'
};
result.message = `${i18next.t('error.checkResponse', { ns: 'translator' })}${helpText}`;
throw result;
}
};
return await OJB_promiseRetryWrapper(toDo, {
maxRetries: 3,
errorHandler: (err, maxRetries, attemptsLeft) => {
const detailedError = {
maxRetries: maxRetries,
attemptsLeft: attemptsLeft,
...err
};
return detailedError;
}
});
}
/**
* 查询服务余额
* @param {Object} quotaConfig - 配额配置对象
* @returns {Promise} 返回包含余额信息的 Promise
*/
async function queryServerBalance(quotaConfig) {
// 确保传入了有效的配置对象
if (!quotaConfig || !quotaConfig.url) {
return Promise.reject(new Error('Quota configuration is missing.'));
}
// 准备请求选项
const requestOptions = {
method: quotaConfig.method || 'GET',
url: quotaConfig.url,
headers: {
...Object.assign({}, ...quotaConfig.header)
},
data: JSON.stringify({ ...Object.assign({}, ...quotaConfig.data) })
};
// 发送请求并返回 Promise
return OJB_GMRequest(requestOptions).then(response => {
try {
const responseData = JSON.parse(response.responseText);
// 从响应数据中提取余额
const surplusPath = quotaConfig.surplus;
const surplusValue = OJB_evaluatePathOrExpression(responseData, surplusPath);
return surplusValue;
} catch (error) {
return Promise.reject(new Error('Failed to parse balance response.'));
}
}).catch(error => {
console.warn('Error querying balance:', error);
return Promise.reject(error);
});
}
/**
* 确认 jQuery 已加载
* @param {number} retryDelay 重试延迟(毫秒)
* @returns {Promise}
*/
async function ensureJQueryIsLoaded(retryDelay = 50) {
while (typeof jQuery === 'undefined') {
console.warn(`JQuery is not loaded. Retry after ${retryDelay} ms.`);
await OJB_delay(retryDelay);
retryDelay = Math.min(retryDelay * 2, 2000);
}
}
/**
* 加载必须的函数
* @returns {Promise} 加载提示信息
*/
async function loadRequiredFunctions() {
await initVar();// 初始化全局变量
return Promise.allSettled([
initDB(), // 连接数据库
initI18next(), // i18next初始化
initButtonFunc(), // 加载按钮相关函数
checkScriptVersion(), // 更新检查
...(OJBetter.typeOfPage.is_acmsguru ? [acmsguruReblock()] : []) // 为acmsguru题面重新划分div
]);
}
/**
* DOM加载后即可执行
*/
function initOnDOMReady() {
showAnnounce(); // 显示公告
showWarnMessage(); // 显示警告消息
initSettingsPanel(); // 加载设置按钮面板
localizeWebsite(); // 网站本地化替换
addDependencyStyles(); // 添加一些依赖库的样式
addI18nStyles(); // 添加包含i18n内容的样式
if (OJBetter.basic.expandFoldingblocks) ExpandFoldingblocks(); // 折叠块展开
if (OJBetter.basic.renderPerfOpt) RenderPerfOpt(); // 折叠块渲染优化
if (OJBetter.typeOfPage.is_problem) {
const problemPageLinkbar = new ProblemPageLinkbar(); // 创建题目页相关链接栏
if (OJBetter.basic.showJumpToLuogu) CF2luogu(problemPageLinkbar); // 跳转到洛谷按钮
if (OJBetter.clist.enabled.problem) showRatingByClist_problem(problemPageLinkbar); // problem页显示Rating
}
if (OJBetter.typeOfPage.is_contest) {
if (OJBetter.clist.enabled.contest) showRatingByClist_contest(); // contest页显示Rating
}
if (OJBetter.typeOfPage.is_problemset) {
if (OJBetter.clist.enabled.problemset) showRatingByClist_problemset(); // problemset页显示Rating
}
if (OJBetter.typeOfPage.is_problem && OJBetter.monaco.enableOnProblemPage) {
addProblemPageCodeEditor(); // 添加题目页代码编辑器
}
}
/**
* 需要在页面资源完全加载后执行的函数
*/
function onResourcesReady(loadingMessage) {
if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadFunc', { ns: 'alert' })}`);
initializeInParallel(loadingMessage);
initializeSequentially(loadingMessage);
}
/**
* 可以异步并行的函数
*/
function initializeInParallel(loadingMessage) {
if (OJBetter.basic.darkMode == "dark") darkModeStyleAdjustment(); // 黑暗模式额外的处理事件
if (OJBetter.basic.commentPaging) CommentPagination(); // 评论区分页
if (OJBetter.translation.comment.transMode == "2") multiChoiceTranslation(); // 选段翻译支持
}
/**
* 必须按序执行的函数
*/
async function initializeSequentially(loadingMessage) {
await addConversionButton(); // 添加MD/复制/翻译按钮
if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
await initTransResultsRecover(); // 翻译结果恢复功能初始化
}
if (OJBetter.translation.auto.enabled) {
await initTransWhenViewable(); // 自动翻译
}
if (OJBetter.basic.standingsRecolor && OJBetter.typeOfPage.is_cfStandings) {
await recolorStandings(); // cf赛制榜单重新着色
}
if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadSuccess', { ns: 'alert' })}`, 'success', 3000);
}
/**
* 脚本开始加载
*/
document.addEventListener("DOMContentLoaded", async () => {
await ensureJQueryIsLoaded(); // 等待jQuery加载
const loadingMessage = new LoadingMessage();
await loadRequiredFunctions(); // 加载必须的函数
initOnDOMReady(); // DOM加载后即可执行的函数
if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('onload', { ns: 'alert' })}`);
// 检查页面资源是否已经完全加载
if (OJBetter.state.notWaiteLoaded) {
onResourcesReady(loadingMessage);
} else {
if (document.readyState === 'complete') {
onResourcesReady(loadingMessage);
} else {
window.addEventListener('load', () => onResourcesReady(loadingMessage));
}
}
});
// ------------------------------
// 配置自动迁移代码(将在10个小版本后移除-1.83)
// ------------------------------
{
let bottomZh_CN = GM_getValue("bottomZh_CN");
if (bottomZh_CN !== undefined) {
if (bottomZh_CN == true) {
GM_setValue("localizationLanguage", "zh");
} else {
GM_setValue("localizationLanguage", "initial");
}
GM_deleteValue("bottomZh_CN");
location.reload();
}
}
{
let config = GM_getValue("chatgpt-config");
if (config && config !== undefined) {
let index = parseInt(config.choice, 10);
if (index == -1) config.choice = "";
else config.choice = config.configurations[index].note;
config.configurations.forEach(function (item) {
item.name = item.note;
delete item.note;
});
GM_deleteValue("chatgpt-config");
GM_setValue("chatgpt_config", config);
location.reload();
}
}
{
let config = GM_getValue("Complet_config");
if (config && config.changed === undefined) {
config.changed = true; // 设置一个迁移标志
config.configurations.forEach(function (item) {
if (item.note !== undefined) {
item.name = item.note;
delete item.note;
}
});
GM_setValue("Complet_config", config);
location.reload();
}
}