mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 14:28:36 +02:00
140 lines
6.8 KiB
TypeScript
140 lines
6.8 KiB
TypeScript
import type { ContentFilePath } from "../Paths/ContentFile";
|
|
|
|
import { EventEmitter } from "../utils/EventEmitter";
|
|
import * as monaco from "monaco-editor";
|
|
import { loadThemes, makeTheme } from "./ui/themes";
|
|
import { Settings } from "../Settings/Settings";
|
|
import { NetscriptExtra } from "../NetscriptFunctions/Extra";
|
|
import * as enums from "../Enums";
|
|
import { ns } from "../NetscriptFunctions";
|
|
import { isLegacyScript } from "../Paths/ScriptFilePath";
|
|
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
|
|
|
/** Event emitter used for tracking when changes have been made to a content file. */
|
|
export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>();
|
|
|
|
export class ScriptEditor {
|
|
// TODO: This will store info about currently open scripts.
|
|
// Among other things, this will allow informing the script editor of changes made elsewhere, even if the script editor is not being rendered.
|
|
// openScripts: OpenScript[] = [];
|
|
|
|
// Currently, this object is only used for initialization.
|
|
isInitialized = false;
|
|
initialize() {
|
|
if (this.isInitialized) return;
|
|
this.isInitialized = true;
|
|
// populate API keys for adding tokenization
|
|
const apiKeys: string[] = [];
|
|
const api = { args: [], pid: 1, enums, ...ns };
|
|
const hiddenAPI = NetscriptExtra();
|
|
function populate(apiLayer: object = api) {
|
|
for (const [apiKey, apiValue] of Object.entries(apiLayer)) {
|
|
if (apiLayer === api && apiKey in hiddenAPI) continue;
|
|
apiKeys.push(apiKey);
|
|
if (typeof apiValue === "object") {
|
|
populate(apiValue as object);
|
|
}
|
|
}
|
|
}
|
|
populate();
|
|
// Add api keys to tokenization
|
|
(async function () {
|
|
// We have to improve the default js language otherwise theme sucks
|
|
const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript");
|
|
if (!jsLanguage) {
|
|
return;
|
|
}
|
|
const loader = await jsLanguage.loader();
|
|
// replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side
|
|
// this prevents the highlighter from highlighting pieces of variables that start with a reserved token name
|
|
loader.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]);
|
|
for (const symbol of apiKeys)
|
|
loader.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]);
|
|
const otherKeywords = ["let", "const", "var", "function", "arguments"];
|
|
const otherKeyvars = ["true", "false", "null", "undefined"];
|
|
otherKeywords.forEach((k) =>
|
|
loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]),
|
|
);
|
|
otherKeyvars.forEach((k) =>
|
|
loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]),
|
|
);
|
|
loader.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]);
|
|
})().catch((e) => exceptionAlert(e));
|
|
|
|
for (const [language, languageDefaults, getLanguageWorker] of [
|
|
["javascript", monaco.languages.typescript.javascriptDefaults, monaco.languages.typescript.getJavaScriptWorker],
|
|
["typescript", monaco.languages.typescript.typescriptDefaults, monaco.languages.typescript.getTypeScriptWorker],
|
|
] as const) {
|
|
languageDefaults.setCompilerOptions({
|
|
...languageDefaults.getCompilerOptions(),
|
|
// We allow direct importing of `.ts`/`.tsx` files, so tell the typescript language server that.
|
|
allowImportingTsExtensions: true,
|
|
// We use file-at-a-time transpiler. See https://www.typescriptlang.org/tsconfig/#isolatedModules
|
|
isolatedModules: true,
|
|
// We use the classic (i.e. `React.createElement`:) react runtime.
|
|
jsx: monaco.languages.typescript.JsxEmit.React,
|
|
// We define `React` and `ReactDOM` as globals. Don't mark using them as errors.
|
|
allowUmdGlobalAccess: true,
|
|
// Enable strict typechecking.
|
|
// Note that checking in javascript is disabled by default but can be enabled via `// @ts-check`.
|
|
// This enables strictNullChecks, which impacts reported types, even in javascript.
|
|
strict: true,
|
|
noImplicitAny: language === "typescript",
|
|
noImplicitReturns: true,
|
|
// Allow processing of javascript files, for handling cross-language imports.
|
|
allowJs: true,
|
|
});
|
|
languageDefaults.setDiagnosticsOptions({
|
|
...languageDefaults.getDiagnosticsOptions(),
|
|
// Show semantic errors, even in javascript.
|
|
// Note that this will only happen if checking is enabled in javascript (e.g. by `// @ts-check`)
|
|
noSemanticValidation: false,
|
|
// Ignore these errors in the editor:
|
|
diagnosticCodesToIgnore: [
|
|
// We define `React` and `ReactDOM` as globals. Don't mark using them as errors.
|
|
// Even though we set allowUmdGlobalAccess, it still shows a warning (instead of an error).
|
|
// - 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.(2686)
|
|
2686,
|
|
],
|
|
});
|
|
|
|
// Sync all javascript and typescript text models to both language servers.
|
|
//
|
|
// `monaco.languages.typescript.get{Java,Type}ScriptWorker` returns a promise that
|
|
// fires with a function that takes a list of `monaco.Uri`s and sync's them with the
|
|
// worker. (It also returns the worker, but we don't care about that.) However, it
|
|
// returns a reject promise if the language worker is not loaded yet, so we wait to
|
|
// call it until the language gets loaded.
|
|
const languageWorker = new Promise<(...uris: monaco.Uri[]) => unknown>((resolve) =>
|
|
monaco.languages.onLanguage(language, () => {
|
|
getLanguageWorker()
|
|
.then(resolve)
|
|
.catch((error) => exceptionAlert(error));
|
|
}),
|
|
);
|
|
// Whenever a model is created, arrange for it to be synced to the language server.
|
|
monaco.editor.onDidCreateModel((model) => {
|
|
if (language === "typescript" && isLegacyScript(model.uri.path)) {
|
|
// Don't sync legacy scripts to typescript worker.
|
|
return;
|
|
}
|
|
if (["javascript", "typescript"].includes(model.getLanguageId())) {
|
|
languageWorker.then((resolve) => resolve(model.uri)).catch((error) => exceptionAlert(error));
|
|
}
|
|
});
|
|
}
|
|
|
|
monaco.languages.json.jsonDefaults.setModeConfiguration({
|
|
...monaco.languages.json.jsonDefaults.modeConfiguration,
|
|
//completion should be disabled because the
|
|
//json language server tries to load a schema by default
|
|
completionItems: false,
|
|
});
|
|
// Load themes
|
|
loadThemes(monaco.editor.defineTheme);
|
|
monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
|
|
}
|
|
}
|
|
|
|
export const scriptEditor = new ScriptEditor();
|