import { ScriptEditor } from "./ScriptEditor"; const ace = require('brace'); require('brace/mode/javascript'); require('./AceNetscriptMode'); require('brace/theme/chaos'); require('brace/theme/chrome'); require('brace/theme/monokai'); require('brace/theme/solarized_dark'); require('brace/theme/solarized_light'); require('brace/theme/terminal'); require('brace/theme/twilight'); require('brace/theme/xcode'); require("brace/keybinding/vim"); require("brace/keybinding/emacs"); require("brace/ext/language_tools"); import { NetscriptFunctions } from "../NetscriptFunctions"; import { Settings } from "../Settings/Settings"; import { AceKeybindingSetting } from "../Settings/SettingEnums"; import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners"; import { createElement } from "../../utils/uiHelpers/createElement"; import { createOptionElement } from "../../utils/uiHelpers/createOptionElement"; import { getSelectText, getSelectValue } from "../../utils/uiHelpers/getSelectData"; import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; // Wrapper for Ace editor const Keybindings = { ace: null, vim: "ace/keyboard/vim", emacs: "ace/keyboard/emacs", }; function validateInitializationParamters(params) { if (params.saveAndCloseFn == null) { return false; } // Save & close button function if (params.quitFn == null) { return false; } // Quitting editor, aka Engine.loadTerminalContent return true; } class AceEditorWrapper extends ScriptEditor { constructor() { super(); this.vimCommandDisplayWrapper = null; } init(params) { if (this.editor != null) { console.error(`AceEditor.init() called when it's already initialized`); return false; } // Validate/Sanitize input if (!validateInitializationParamters(params)) { console.error(`'params' argument passed into initAceEditor() does not have proper properties`); return false; } // Store the filename input this.filenameInput = document.getElementById("script-editor-filename"); if (this.filenameInput == null) { console.error(`Could not get Script Editor filename element (id=script-editor-filename)`); return false; } // Initialize ACE Script editor this.editor = ace.edit('ace-editor'); this.editor.getSession().setMode('ace/mode/netscript'); this.editor.setTheme('ace/theme/monokai'); const editorElement = document.getElementById('ace-editor'); if (editorElement == null) { return false; } editorElement.style.fontSize = '16px'; this.editor.setOption("showPrintMargin", false); // Configure some of the VIM keybindings ace.config.loadModule('ace/keyboard/vim', function(module) { var VimApi = module.CodeMirror.Vim; VimApi.defineEx('write', 'w', function(cm, input) { params.saveAndCloseFn(); }); VimApi.defineEx('quit', 'q', function(cm, input) { params.quitFn(); }); VimApi.defineEx('xwritequit', 'x', function(cm, input) { params.saveAndCloseFn(); }); VimApi.defineEx('wqwritequit', 'wq', function(cm, input) { params.saveAndCloseFn(); }); }); // Store a reference to the VIM command display this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper"); if (this.vimCommandDisplayWrapper == null) { console.error(`Could not get Vim Command Display element (id=codemirror-vim-command-display-wrapper)`); return false; } //Function autocompleter this.editor.setOption("enableBasicAutocompletion", true); var autocompleter = { getCompletions: function(editor, session, pos, prefix, callback) { if (prefix.length === 0) {callback(null, []); return;} var words = []; var fns = NetscriptFunctions(null); for (let name in fns) { if (fns.hasOwnProperty(name)) { words.push({ name: name, value: name, }); //Get functions from namespaces const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang", "sleeve"]; if (namespaces.includes(name)) { let namespace = fns[name]; if (typeof namespace !== "object") {continue;} let namespaceFns = Object.keys(namespace); for (let i = 0; i < namespaceFns.length; ++i) { words.push({ name: namespaceFns[i], value: namespaceFns[i], }); } } } } callback(null, words); }, } this.editor.completers = [autocompleter]; return true; } initialized() { return (this.editor != null); } // Create the configurable Options for this Editor create() { function safeGetElementById(id, whatFor="") { const elem = document.getElementById(id); if (elem == null) { throw new Error(`Could not find ${whatFor} DOM element(id=${id})`); } return elem; } function safeClearEventListeners(id, whatFor="") { const elem = clearEventListeners(id); if (elem == null) { throw new Error(`Could not find ${whatFor} DOM element(id=${id})`); } return elem; } try { const optionsPanel = safeGetElementById("script-editor-options-panel", "Script Editor Options Panel"); // Set editor to visible const elem = document.getElementById("ace-editor"); if (elem instanceof HTMLElement) { elem.style.display = "block"; } // Make sure the Vim command display from CodeMirror is invisible if (this.vimCommandDisplayWrapper instanceof HTMLElement) { this.vimCommandDisplayWrapper.style.display = "none"; } // Theme const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector"); removeChildrenFromElement(themeDropdown); themeDropdown.add(createOptionElement("Chaos")); themeDropdown.add(createOptionElement("Chrome")); themeDropdown.add(createOptionElement("Monokai")); themeDropdown.add(createOptionElement("Solarized Dark", "Solarized_Dark")); themeDropdown.add(createOptionElement("Solarized Light", "Solarized_Light")); themeDropdown.add(createOptionElement("Terminal")); themeDropdown.add(createOptionElement("Twilight")); themeDropdown.add(createOptionElement("XCode")); if (Settings.EditorTheme) { var initialIndex = 2; for (var i = 0; i < themeDropdown.options.length; ++i) { if (themeDropdown.options[i].value === Settings.EditorTheme) { initialIndex = i; break; } } themeDropdown.selectedIndex = initialIndex; } else { themeDropdown.selectedIndex = 2; } themeDropdown.onchange = () => { const val = themeDropdown.value; Settings.EditorTheme = val; const themePath = "ace/theme/" + val.toLowerCase(); this.editor.setTheme(themePath); }; themeDropdown.onchange(); // Keybinding const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keybinding Selector"); removeChildrenFromElement(keybindingDropdown); keybindingDropdown.add(createOptionElement("Ace", AceKeybindingSetting.Ace)); keybindingDropdown.add(createOptionElement("Vim", AceKeybindingSetting.Vim)); keybindingDropdown.add(createOptionElement("Emacs", AceKeybindingSetting.Emacs)); if (Settings.EditorKeybinding) { // Sanitize the Keybinding setting if (!(Object.values(AceKeybindingSetting).includes(Settings.EditorKeybinding))) { Settings.EditorKeybinding = AceKeybindingSetting.Ace; } var initialIndex = 0; for (var i = 0; i < keybindingDropdown.options.length; ++i) { if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) { initialIndex = i; break; } } keybindingDropdown.selectedIndex = initialIndex; } else { keybindingDropdown.selectedIndex = 0; } keybindingDropdown.onchange = () => { var val = keybindingDropdown.value; Settings.EditorKeybinding = val; this.editor.setKeyboardHandler(Keybindings[val.toLowerCase()]); }; keybindingDropdown.onchange(); // Highlight Active line const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox"); highlightActiveChkBox.onchange = () => { this.editor.setHighlightActiveLine(highlightActiveChkBox.checked); }; // Show Invisibles const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox"); showInvisiblesChkBox.onchange = () => { this.editor.setShowInvisibles(showInvisiblesChkBox.checked); }; // Use Soft Tab const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox"); softTabChkBox.onchange = () => { this.editor.getSession().setUseSoftTabs(softTabChkBox.checked); }; // Some helper functions for dealing with flexible options function resetFlexibleOption(id) { const fieldset = safeGetElementById(id); removeChildrenFromElement(fieldset); fieldset.style.display = "block"; return fieldset; } function removeFlexibleOption(id) { // This doesn't really remove it, just sets it to invisible const fieldset = resetFlexibleOption(id); fieldset.style.display = "none"; return fieldset; } // Jshint Maxerr (Flex 1) const flex1Fieldset = resetFlexibleOption("script-editor-option-flex1-fieldset"); const flex1Id = "script-editor-option-maxerr"; const flex1ValueLabel = createElement("em", { innerText: "200" }); flex1Fieldset.appendChild(createElement("label", { for: flex1Id, innerText: "Max Error Count", })); const flex1Input = createElement("input", { id: flex1Id, max: "1000", min: "50", name: flex1Id, step: "1", type: "range", value: "200", changeListener: () => { this.editor.getSession().$worker.send("changeOptions", [{maxerr:flex1Input.value}]); flex1ValueLabel.innerText = flex1Input.value; } }); flex1Fieldset.appendChild(flex1Input); flex1Fieldset.appendChild(flex1ValueLabel); // Nothing for Flex Options 2-4 removeFlexibleOption("script-editor-option-flex2-fieldset"); removeFlexibleOption("script-editor-option-flex3-fieldset"); removeFlexibleOption("script-editor-option-flex4-fieldset"); } catch(e) { console.error(`Exception caught: ${e}`); return false; } } isFocused() { if (this.editor == null) { return false; } return this.editor.isFocused(); } // Sets the editor to be invisible. Does not require this class to be initialized setInvisible() { const elem = document.getElementById("ace-editor"); if (elem instanceof HTMLElement) { elem.style.display = "none"; } } } export const AceEditor = new AceEditorWrapper();