diff --git a/static/motd.interfaces.ts b/static/motd.interfaces.ts
index c8d9e18f3..ab84df778 100644
--- a/static/motd.interfaces.ts
+++ b/static/motd.interfaces.ts
@@ -24,7 +24,7 @@
import {editor} from 'monaco-editor/';
-type Decoration = {
+export type Decoration = {
decoration: editor.IModelDecorationOptions;
filter: string[];
name: string;
diff --git a/static/multifile-service.ts b/static/multifile-service.ts
index 8c5fc75c3..5b6261b70 100644
--- a/static/multifile-service.ts
+++ b/static/multifile-service.ts
@@ -237,7 +237,7 @@ export class MultifileService {
if (file.isOpen) {
const editor = this.hub.getEditorById(file.editorId);
- return editor.getSource();
+ return editor?.getSource() ?? '';
} else {
return file.content;
}
diff --git a/static/panes/editor.interfaces.ts b/static/panes/editor.interfaces.ts
new file mode 100644
index 000000000..946e1f0af
--- /dev/null
+++ b/static/panes/editor.interfaces.ts
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+export type EditorState = {
+ filename?: string;
+ options?: {
+ readOnly?: boolean;
+ };
+ source?: string;
+ lang?: string;
+};
diff --git a/static/panes/editor.js b/static/panes/editor.js
deleted file mode 100644
index ec762931d..000000000
--- a/static/panes/editor.js
+++ /dev/null
@@ -1,1834 +0,0 @@
-// Copyright (c) 2016, Compiler Explorer Authors
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright
-// notice, this list of conditions and the following disclaimer in the
-// documentation and/or other materials provided with the distribution.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-
-'use strict';
-var _ = require('underscore');
-var $ = require('jquery');
-var colour = require('../colour');
-var loadSaveLib = require('../widgets/load-save');
-var FontScale = require('../widgets/fontscale').FontScale;
-var Components = require('../components');
-var monaco = require('monaco-editor');
-var options = require('../options').options;
-var Alert = require('../alert').Alert;
-var ga = require('../analytics').ga;
-var monacoVim = require('monaco-vim');
-var monacoConfig = require('../monaco-config');
-var quickFixesHandler = require('../quick-fixes-handler');
-var TomSelect = require('tom-select');
-var Settings = require('../settings').Settings;
-var utils = require('../utils');
-require('../formatter-registry');
-require('../modes/_all');
-
-var loadSave = new loadSaveLib.LoadSave();
-var languages = options.languages;
-
-// eslint-disable-next-line max-statements
-function Editor(hub, state, container) {
- this.id = state.id || hub.nextEditorId();
- this.container = container;
- this.domRoot = container.getElement();
- this.domRoot.html($('#codeEditor').html());
- this.hub = hub;
- this.eventHub = hub.createEventHub();
- // Should probably be its own function somewhere
- this.settings = Settings.getStoredSettings();
- this.ourCompilers = {};
- this.ourExecutors = {};
- this.httpRoot = window.httpRoot;
- this.asmByCompiler = {};
- this.defaultFileByCompiler = {};
- this.busyCompilers = {};
- this.colours = [];
- this.treeCompilers = {};
-
- this.decorations = {};
- this.prevDecorations = [];
- this.extraDecorations = [];
-
- this.fadeTimeoutId = -1;
-
- this.editorSourceByLang = {};
- this.alertSystem = new Alert();
- this.alertSystem.prefixMessage = 'Editor #' + this.id;
-
- this.filename = state.filename || false;
-
- this.awaitingInitialResults = false;
- this.selection = state.selection;
-
- this.revealJumpStack = [];
-
- this.langKeys = _.keys(languages);
- this.initLanguage(state);
-
- var root = this.domRoot.find('.monaco-placeholder');
- var legacyReadOnly = state.options && !!state.options.readOnly;
- this.editor = monaco.editor.create(
- root[0],
- monacoConfig.extendConfig(
- {
- language: this.currentLanguage.monaco,
- readOnly:
- !!options.readOnly ||
- legacyReadOnly ||
- (window.compilerExplorerOptions && window.compilerExplorerOptions.mobileViewer),
- glyphMargin: !options.embedded,
- },
- this.settings
- )
- );
- this.editor.getModel().setEOL(monaco.editor.EndOfLineSequence.LF);
-
- if (state.source !== undefined) {
- this.setSource(state.source);
- } else {
- this.updateEditorCode();
- }
-
- var startFolded = /^[/*#;]+\s*setup.*/;
- if (state.source && state.source.match(startFolded)) {
- // With reference to https://github.com/Microsoft/monaco-editor/issues/115
- // I tried that and it didn't work, but a delay of 500 seems to "be enough".
- // FIXME: Currently not working - No folding is performed
- setTimeout(
- _.bind(function () {
- this.editor.setSelection(new monaco.Selection(1, 1, 1, 1));
- this.editor.focus();
- this.editor.getAction('editor.fold').run();
- //this.editor.clearSelection();
- }, this),
- 500
- );
- }
-
- this.initEditorActions();
- this.initButtons(state);
- this.initCallbacks();
-
- if (this.settings.useVim) {
- this.enableVim();
- }
-
- var usableLanguages = _.filter(languages, function (language) {
- return hub.compilerService.compilersByLang[language.id];
- });
-
- this.selectize = new TomSelect(this.languageBtn, {
- sortField: 'name',
- valueField: 'id',
- labelField: 'name',
- searchField: ['name'],
- placeholder: '🔍 Select a language...',
- options: _.map(usableLanguages, _.identity),
- items: [this.currentLanguage.id],
- dropdownParent: 'body',
- plugins: ['dropdown_input'],
- onChange: _.bind(this.onLanguageChange, this),
- closeAfterSelect: true,
- render: {
- option: renderSelectizeOption,
- item: renderSelectizeItem,
- },
- });
-
- // We suppress posting changes until the user has stopped typing by:
- // * Using _.debounce() to run emitChange on any key event or change
- // only after a delay.
- // * Only actually triggering a change if the document text has changed from
- // the previous emitted.
- this.lastChangeEmitted = null;
- this.onSettingsChange(this.settings);
- // this.editor.on("keydown", _.bind(function () {
- // // Not strictly a change; but this suppresses changes until some time
- // // after the last key down (be it an actual change or a just a cursor
- // // movement etc).
- // this.debouncedEmitChange();
- // }, this));
-
- this.updateTitle();
- this.updateState();
- ga.proxy('send', {
- hitType: 'event',
- eventCategory: 'OpenViewPane',
- eventAction: 'Editor',
- });
- ga.proxy('send', {
- hitType: 'event',
- eventCategory: 'LanguageChange',
- eventAction: this.currentLanguage.id,
- });
-}
-
-Editor.prototype.onMotd = function (motd) {
- this.extraDecorations = motd.decorations;
- this.updateExtraDecorations();
-};
-
-Editor.prototype.updateExtraDecorations = function () {
- var decorationsDirty = false;
- _.each(
- this.extraDecorations,
- _.bind(function (decoration) {
- if (decoration.filter && decoration.filter.indexOf(this.currentLanguage.name.toLowerCase()) < 0) return;
- var match = this.editor.getModel().findNextMatch(
- decoration.regex,
- {
- column: 1,
- lineNumber: 1,
- },
- true,
- true,
- null,
- false
- );
- if (match !== this.decorations[decoration.name]) {
- decorationsDirty = true;
- this.decorations[decoration.name] = match
- ? [{range: match.range, options: decoration.decoration}]
- : null;
- }
- }, this)
- );
- if (decorationsDirty) this.updateDecorations();
-};
-
-// If compilerId is undefined, every compiler will be pinged
-Editor.prototype.maybeEmitChange = function (force, compilerId) {
- var source = this.getSource();
- if (!force && source === this.lastChangeEmitted) return;
-
- this.updateExtraDecorations();
-
- this.lastChangeEmitted = source;
- this.eventHub.emit('editorChange', this.id, this.lastChangeEmitted, this.currentLanguage.id, compilerId);
-};
-
-Editor.prototype.updateState = function () {
- var state = {
- id: this.id,
- source: this.getSource(),
- lang: this.currentLanguage.id,
- selection: this.selection,
- filename: this.filename,
- };
- this.fontScale.addState(state);
- this.container.setState(state);
-
- this.updateButtons();
-};
-
-Editor.prototype.setSource = function (newSource) {
- this.updateSource(newSource);
-
- if (window.compilerExplorerOptions.mobileViewer) {
- $(this.domRoot.find('.monaco-placeholder textarea')).hide();
- }
-};
-
-Editor.prototype.onNewSource = function (editorId, newSource) {
- if (this.id === editorId) {
- this.setSource(newSource);
- }
-};
-
-Editor.prototype.getSource = function () {
- return this.editor.getModel().getValue();
-};
-
-Editor.prototype.initLanguage = function (state) {
- this.currentLanguage = languages[this.langKeys[0]];
- this.waitingForLanguage = state.source && !state.lang;
- if (languages[this.settings.defaultLanguage]) {
- this.currentLanguage = languages[this.settings.defaultLanguage];
- }
- if (languages[state.lang]) {
- this.currentLanguage = languages[state.lang];
- } else if (this.settings.newEditorLastLang && languages[this.hub.lastOpenedLangId]) {
- this.currentLanguage = languages[this.hub.lastOpenedLangId];
- }
-};
-
-Editor.prototype.initCallbacks = function () {
- this.fontScale.on('change', _.bind(this.updateState, this));
- this.eventHub.on(
- 'broadcastFontScale',
- _.bind(function (scale) {
- this.fontScale.setScale(scale);
- this.updateState();
- }, this)
- );
-
- this.container.on('resize', this.resize, this);
- this.container.on('shown', this.resize, this);
- this.container.on(
- 'open',
- _.bind(function () {
- this.eventHub.emit('editorOpen', this.id, this);
- }, this)
- );
- this.container.on('destroy', this.close, this);
- this.container.layoutManager.on(
- 'initialised',
- function () {
- // Once initialized, let everyone know what text we have.
- this.maybeEmitChange();
- // And maybe ask for a compilation (Will hit the cache most of the time)
- this.requestCompilation();
- },
- this
- );
-
- this.eventHub.on('treeCompilerEditorIncludeChange', this.onTreeCompilerEditorIncludeChange, this);
- this.eventHub.on('treeCompilerEditorExcludeChange', this.onTreeCompilerEditorExcludeChange, this);
- this.eventHub.on('coloursForEditor', this.onColoursForEditor, this);
- this.eventHub.on('compilerOpen', this.onCompilerOpen, this);
- this.eventHub.on('executorOpen', this.onExecutorOpen, this);
- this.eventHub.on('executorClose', this.onExecutorClose, this);
- this.eventHub.on('compilerClose', this.onCompilerClose, this);
- this.eventHub.on('compiling', this.onCompiling, this);
- this.eventHub.on('compileResult', this.onCompileResponse, this);
- this.eventHub.on('executeResult', this.onExecuteResponse, this);
- this.eventHub.on('selectLine', this.onSelectLine, this);
- this.eventHub.on('editorSetDecoration', this.onEditorSetDecoration, this);
- this.eventHub.on('editorDisplayFlow', this.onEditorDisplayFlow, this);
- this.eventHub.on('editorLinkLine', this.onEditorLinkLine, this);
- this.eventHub.on('settingsChange', this.onSettingsChange, this);
- this.eventHub.on('conformanceViewOpen', this.onConformanceViewOpen, this);
- this.eventHub.on('conformanceViewClose', this.onConformanceViewClose, this);
- this.eventHub.on('resize', this.resize, this);
- this.eventHub.on('newSource', this.onNewSource, this);
- this.eventHub.on('motd', this.onMotd, this);
- this.eventHub.on('findEditors', this.sendEditor, this);
- this.eventHub.emit('requestMotd');
-
- this.editor.getModel().onDidChangeContent(
- _.bind(function () {
- this.debouncedEmitChange();
- this.updateState();
- }, this)
- );
-
- this.mouseMoveThrottledFunction = _.throttle(_.bind(this.onMouseMove, this), 50);
-
- this.editor.onMouseMove(
- _.bind(function (e) {
- this.mouseMoveThrottledFunction(e);
- }, this)
- );
-
- if (window.compilerExplorerOptions.mobileViewer) {
- // workaround for issue with contextmenu not going away when tapping somewhere else on the screen
- this.editor.onDidChangeCursorSelection(
- _.bind(function () {
- var contextmenu = $('div.context-view.monaco-menu-container');
- if (contextmenu.css('display') !== 'none') {
- contextmenu.hide();
- }
- }, this)
- );
- }
-
- this.cursorSelectionThrottledFunction = _.throttle(_.bind(this.onDidChangeCursorSelection, this), 500);
- this.editor.onDidChangeCursorSelection(
- _.bind(function (e) {
- this.cursorSelectionThrottledFunction(e);
- }, this)
- );
-
- this.editor.onDidFocusEditorText(_.bind(this.onDidFocusEditorText, this));
- this.editor.onDidBlurEditorText(_.bind(this.onDidBlurEditorText, this));
- this.editor.onDidChangeCursorPosition(_.bind(this.onDidChangeCursorPosition, this));
-
- this.eventHub.on('initialised', this.maybeEmitChange, this);
-
- $(document).on(
- 'keyup.editable',
- _.bind(function (e) {
- if (e.target === this.domRoot.find('.monaco-placeholder .inputarea')[0]) {
- if (e.which === 27) {
- this.onEscapeKey(e);
- } else if (e.which === 45) {
- this.onInsertKey(e);
- }
- }
- }, this)
- );
-};
-
-Editor.prototype.sendEditor = function () {
- this.eventHub.emit('editorOpen', this.id, this);
-};
-
-Editor.prototype.onMouseMove = function (e) {
- if (e !== null && e.target !== null && this.settings.hoverShowSource && e.target.position !== null) {
- this.clearLinkedLine();
- var pos = e.target.position;
- this.tryPanesLinkLine(pos.lineNumber, pos.column, false);
- }
-};
-
-Editor.prototype.onDidChangeCursorSelection = function (e) {
- if (this.awaitingInitialResults) {
- this.selection = e.selection;
- this.updateState();
- }
-};
-
-Editor.prototype.onDidChangeCursorPosition = function (e) {
- if (e.position) {
- this.currentCursorPosition.text('(' + e.position.lineNumber + ', ' + e.position.column + ')');
- }
-};
-
-Editor.prototype.onDidFocusEditorText = function () {
- var position = this.editor.getPosition();
- if (position) {
- this.currentCursorPosition.text('(' + position.lineNumber + ', ' + position.column + ')');
- }
- this.currentCursorPosition.show();
-};
-
-Editor.prototype.onDidBlurEditorText = function () {
- this.currentCursorPosition.text('');
- this.currentCursorPosition.hide();
-};
-
-Editor.prototype.onEscapeKey = function () {
- if (this.editor.vimInUse) {
- var currentState = monacoVim.VimMode.Vim.maybeInitVimState_(this.vimMode);
- if (currentState.insertMode) {
- monacoVim.VimMode.Vim.exitInsertMode(this.vimMode);
- } else if (currentState.visualMode) {
- monacoVim.VimMode.Vim.exitVisualMode(this.vimMode, false);
- }
- }
-};
-
-Editor.prototype.onInsertKey = function (event) {
- if (this.editor.vimInUse) {
- var currentState = monacoVim.VimMode.Vim.maybeInitVimState_(this.vimMode);
- if (!currentState.insertMode) {
- var insertEvent = {
- preventDefault: event.preventDefault,
- stopPropagation: event.stopPropagation,
- browserEvent: {
- key: 'i',
- defaultPrevented: false,
- },
- keyCode: 39,
- };
- this.vimMode.handleKeyDown(insertEvent);
- }
- }
-};
-
-Editor.prototype.enableVim = function () {
- this.vimMode = monacoVim.initVimMode(this.editor, this.domRoot.find('#v-status')[0]);
- this.vimFlag.prop('class', 'btn btn-info');
- this.editor.vimInUse = true;
-};
-
-Editor.prototype.disableVim = function () {
- this.vimMode.dispose();
- this.domRoot.find('#v-status').html('');
- this.vimFlag.prop('class', 'btn btn-light');
- this.editor.vimInUse = false;
-};
-
-Editor.prototype.initButtons = function (state) {
- this.fontScale = new FontScale(this.domRoot, state, this.editor);
- this.languageBtn = this.domRoot.find('.change-language');
- // Ensure that the button is disabled if we don't have anything to select
- // Note that is might be disabled for other reasons beforehand
- if (this.langKeys.length <= 1) {
- this.languageBtn.prop('disabled', true);
- }
- this.topBar = this.domRoot.find('.top-bar');
- this.hideable = this.domRoot.find('.hideable');
-
- this.loadSaveButton = this.domRoot.find('.load-save');
- var paneAdderDropdown = this.domRoot.find('.add-pane');
- var addCompilerButton = this.domRoot.find('.btn.add-compiler');
- this.addExecutorButton = this.domRoot.find('.btn.add-executor');
- this.conformanceViewerButton = this.domRoot.find('.btn.conformance');
- var addEditorButton = this.domRoot.find('.btn.add-editor');
- var toggleVimButton = this.domRoot.find('#vim-flag');
- this.vimFlag = this.domRoot.find('#vim-flag');
- toggleVimButton.on(
- 'click',
- _.bind(function () {
- if (this.editor.vimInUse) {
- this.disableVim();
- } else {
- this.enableVim();
- }
- }, this)
- );
-
- // NB a new compilerConfig needs to be created every time; else the state is shared
- // between all compilers created this way. That leads to some nasty-to-find state
- // bugs e.g. https://github.com/compiler-explorer/compiler-explorer/issues/225
- var getCompilerConfig = _.bind(function () {
- return Components.getCompiler(this.id, this.currentLanguage.id);
- }, this);
-
- var getExecutorConfig = _.bind(function () {
- return Components.getExecutor(this.id, this.currentLanguage.id);
- }, this);
-
- var getConformanceConfig = _.bind(function () {
- // TODO: this doesn't pass any treeid introduced by #3360
- return Components.getConformanceView(this.id, undefined, this.getSource(), this.currentLanguage.id);
- }, this);
-
- var getEditorConfig = _.bind(function () {
- return Components.getEditor();
- }, this);
-
- var addPaneOpener = _.bind(function (dragSource, dragConfig) {
- this.container.layoutManager
- .createDragSource(dragSource, dragConfig)
- ._dragListener.on('dragStart', function () {
- paneAdderDropdown.dropdown('toggle');
- });
-
- dragSource.on(
- 'click',
- _.bind(function () {
- var insertPoint =
- this.hub.findParentRowOrColumn(this.container) || this.container.layoutManager.root.contentItems[0];
- insertPoint.addChild(dragConfig);
- }, this)
- );
- }, this);
-
- addPaneOpener(addCompilerButton, getCompilerConfig);
- addPaneOpener(this.addExecutorButton, getExecutorConfig);
- addPaneOpener(this.conformanceViewerButton, getConformanceConfig);
- addPaneOpener(addEditorButton, getEditorConfig);
-
- this.initLoadSaver();
- $(this.domRoot).on(
- 'keydown',
- _.bind(function (event) {
- if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 's') {
- this.handleCtrlS(event);
- }
- }, this)
- );
-
- if (options.thirdPartyIntegrationEnabled) {
- this.cppInsightsButton = this.domRoot.find('.open-in-cppinsights');
- this.cppInsightsButton.on(
- 'mousedown',
- _.bind(function () {
- this.updateOpenInCppInsights();
- }, this)
- );
-
- this.quickBenchButton = this.domRoot.find('.open-in-quickbench');
- this.quickBenchButton.on(
- 'mousedown',
- _.bind(function () {
- this.updateOpenInQuickBench();
- }, this)
- );
- }
-
- this.currentCursorPosition = this.domRoot.find('.currentCursorPosition');
- this.currentCursorPosition.hide();
-};
-
-Editor.prototype.handleCtrlS = function (event) {
- event.preventDefault();
- if (this.settings.enableCtrlStree && this.hub.hasTree()) {
- var trees = this.hub.trees;
- // todo: change when multiple trees are used
- if (trees && trees.length > 0) {
- trees[0].multifileService.includeByEditorId(this.id).then(
- _.bind(function () {
- trees[0].refresh();
- }, this)
- );
- }
- } else {
- if (this.settings.enableCtrlS === 'true') {
- loadSave.setMinimalOptions(this.getSource(), this.currentLanguage);
- if (!loadSave.onSaveToFile(this.id)) {
- this.showLoadSaver();
- }
- } else if (this.settings.enableCtrlS === 'false') {
- this.emitShortLinkEvent();
- } else if (this.settings.enableCtrlS === '2') {
- this.runFormatDocumentAction();
- } else if (this.settings.enableCtrlS === '3') {
- this.handleCtrlSDoNothing();
- }
- }
-};
-
-Editor.prototype.handleCtrlSDoNothing = function () {
- if (this.nothingCtrlSTimes === undefined) {
- this.nothingCtrlSTimes = 0;
- this.nothingCtrlSSince = Date.now();
- } else {
- if (Date.now() - this.nothingCtrlSSince > 5000) {
- this.nothingCtrlSTimes = undefined;
- } else if (this.nothingCtrlSTimes === 4) {
- var element = this.domRoot.find('.ctrlSNothing');
- element.show(100);
- setTimeout(function () {
- element.hide();
- }, 2000);
- this.nothingCtrlSTimes = undefined;
- } else {
- this.nothingCtrlSTimes++;
- }
- }
-};
-
-Editor.prototype.updateButtons = function () {
- if (options.thirdPartyIntegrationEnabled) {
- if (this.currentLanguage.id === 'c++') {
- this.cppInsightsButton.show();
- this.quickBenchButton.show();
- } else {
- this.cppInsightsButton.hide();
- this.quickBenchButton.hide();
- }
- }
-
- this.addExecutorButton.prop('disabled', !this.currentLanguage.supportsExecute);
-};
-
-Editor.prototype.b64UTFEncode = function (str) {
- return btoa(
- encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, v) {
- return String.fromCharCode(parseInt(v, 16));
- })
- );
-};
-
-Editor.prototype.asciiEncodeJsonText = function (json) {
- return json.replace(/[\u007F-\uFFFF]/g, function (chr) {
- // json unicode escapes must always be 4 characters long, so pad with leading zeros
- return '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substr(-4);
- });
-};
-
-Editor.prototype.getCompilerStates = function () {
- var states = [];
-
- _.each(
- this.ourCompilers,
- _.bind(function (val, compilerIdStr) {
- var compilerId = parseInt(compilerIdStr);
-
- var glCompiler = _.find(this.container.layoutManager.root.getComponentsByName('compiler'), function (c) {
- return c.id === compilerId;
- });
-
- if (glCompiler) {
- var state = glCompiler.currentState();
- states.push(state);
- }
- }, this)
- );
-
- return states;
-};
-
-Editor.prototype.updateOpenInCppInsights = function () {
- if (options.thirdPartyIntegrationEnabled) {
- var cppStd = 'cpp2a';
-
- var compilers = this.getCompilerStates();
- _.each(
- compilers,
- _.bind(function (compiler) {
- if (compiler.options.indexOf('-std=c++11') !== -1 || compiler.options.indexOf('-std=gnu++11') !== -1) {
- cppStd = 'cpp11';
- } else if (
- compiler.options.indexOf('-std=c++14') !== -1 ||
- compiler.options.indexOf('-std=gnu++14') !== -1
- ) {
- cppStd = 'cpp14';
- } else if (
- compiler.options.indexOf('-std=c++17') !== -1 ||
- compiler.options.indexOf('-std=gnu++17') !== -1
- ) {
- cppStd = 'cpp17';
- } else if (
- compiler.options.indexOf('-std=c++2a') !== -1 ||
- compiler.options.indexOf('-std=gnu++2a') !== -1
- ) {
- cppStd = 'cpp2a';
- } else if (compiler.options.indexOf('-std=c++98') !== -1) {
- cppStd = 'cpp98';
- }
- }, this)
- );
-
- var maxURL = 8177; // apache's default maximum url length
- var maxCode = maxURL - ('/lnk?code=&std=' + cppStd + '&rev=1.0').length;
- var codeData = this.b64UTFEncode(this.getSource());
- if (codeData.length > maxCode) {
- codeData = this.b64UTFEncode('/** Source too long to fit in a URL */\n');
- }
-
- var link = 'https://cppinsights.io/lnk?code=' + codeData + '&std=' + cppStd + '&rev=1.0';
-
- this.cppInsightsButton.attr('href', link);
- }
-};
-
-Editor.prototype.cleanupSemVer = function (semver) {
- if (semver) {
- var semverStr = semver.toString();
- if (semverStr !== '' && semverStr.indexOf('(') === -1) {
- var vercomps = semverStr.split('.');
- return vercomps[0] + '.' + (vercomps[1] ? vercomps[1] : '0');
- }
- }
-
- return false;
-};
-
-Editor.prototype.updateOpenInQuickBench = function () {
- if (options.thirdPartyIntegrationEnabled) {
- var quickBenchState = {
- text: this.getSource(),
- };
-
- var compilers = this.getCompilerStates();
-
- _.each(
- compilers,
- _.bind(function (compiler) {
- var knownCompiler = false;
-
- var compilerExtInfo = this.hub.compilerService.findCompiler(this.currentLanguage.id, compiler.compiler);
- var semver = this.cleanupSemVer(compilerExtInfo.semver);
- var groupOrName = compilerExtInfo.baseName || compilerExtInfo.groupName || compilerExtInfo.name;
- if (semver && groupOrName) {
- groupOrName = groupOrName.toLowerCase();
- if (groupOrName.indexOf('gcc') !== -1) {
- quickBenchState.compiler = 'gcc-' + semver;
- knownCompiler = true;
- } else if (groupOrName.indexOf('clang') !== -1) {
- quickBenchState.compiler = 'clang-' + semver;
- knownCompiler = true;
- }
- }
-
- if (knownCompiler) {
- var match = compiler.options.match(/-(O([0-3sg]|fast))/);
- if (match !== null) {
- if (match[2] === 'fast') {
- quickBenchState.optim = 'F';
- } else {
- quickBenchState.optim = match[2].toUpperCase();
- }
- }
-
- if (
- compiler.options.indexOf('-std=c++11') !== -1 ||
- compiler.options.indexOf('-std=gnu++11') !== -1
- ) {
- quickBenchState.cppVersion = '11';
- } else if (
- compiler.options.indexOf('-std=c++14') !== -1 ||
- compiler.options.indexOf('-std=gnu++14') !== -1
- ) {
- quickBenchState.cppVersion = '14';
- } else if (
- compiler.options.indexOf('-std=c++17') !== -1 ||
- compiler.options.indexOf('-std=gnu++17') !== -1
- ) {
- quickBenchState.cppVersion = '17';
- } else if (
- compiler.options.indexOf('-std=c++2a') !== -1 ||
- compiler.options.indexOf('-std=gnu++2a') !== -1
- ) {
- quickBenchState.cppVersion = '20';
- }
-
- if (compiler.options.indexOf('-stdlib=libc++') !== -1) {
- quickBenchState.lib = 'llvm';
- }
- }
- }, this)
- );
-
- var link = 'https://quick-bench.com/#' + btoa(this.asciiEncodeJsonText(JSON.stringify(quickBenchState)));
- this.quickBenchButton.attr('href', link);
- }
-};
-
-Editor.prototype.changeLanguage = function (newLang) {
- if (newLang === 'cmake') {
- this.selectize.addOption(languages.cmake);
- }
- this.selectize.setValue(newLang);
-};
-
-Editor.prototype.clearLinkedLine = function () {
- this.decorations.linkedCode = [];
- this.updateDecorations();
-};
-
-Editor.prototype.tryPanesLinkLine = function (thisLineNumber, column, reveal) {
- var selectedToken = this.getTokenSpan(thisLineNumber, column);
- _.each(
- this.asmByCompiler,
- _.bind(function (asms, compilerId) {
- this.eventHub.emit(
- 'panesLinkLine',
- compilerId,
- thisLineNumber,
- selectedToken.colBegin,
- selectedToken.colEnd,
- reveal,
- undefined,
- this.id
- );
- }, this)
- );
-};
-
-Editor.prototype.requestCompilation = function () {
- this.eventHub.emit('requestCompilation', this.id);
- if (this.settings.formatOnCompile) {
- this.runFormatDocumentAction();
- }
-
- _.each(
- this.hub.trees,
- _.bind(function (tree) {
- if (tree.multifileService.isEditorPartOfProject(this.id)) {
- this.eventHub.emit('requestCompilation', this.id, tree.id);
- }
- }, this)
- );
-};
-
-Editor.prototype.initEditorActions = function () {
- this.editor.addAction({
- id: 'compile',
- label: 'Compile',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
- keybindingContext: null,
- contextMenuGroupId: 'navigation',
- contextMenuOrder: 1.5,
- run: _.bind(function () {
- // This change request is mostly superfluous
- this.maybeEmitChange();
- this.requestCompilation();
- }, this),
- });
-
- this.revealJumpStackHasElementsCtxKey = this.editor.createContextKey('hasRevealJumpStackElements', false);
-
- this.editor.addAction({
- id: 'returnfromreveal',
- label: 'Return from reveal jump',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
- contextMenuGroupId: 'navigation',
- contextMenuOrder: 1.4,
- precondition: 'hasRevealJumpStackElements',
- run: _.bind(function () {
- this.popAndRevealJump();
- }, this),
- });
-
- this.editor.addAction({
- id: 'toggleCompileOnChange',
- label: 'Toggle compile on change',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
- keybindingContext: null,
- run: _.bind(function () {
- this.eventHub.emit('modifySettings', {
- compileOnChange: !this.settings.compileOnChange,
- });
- this.alertSystem.notify(
- 'Compile on change has been toggled ' + (this.settings.compileOnChange ? 'ON' : 'OFF'),
- {
- group: 'togglecompile',
- alertClass: this.settings.compileOnChange ? 'notification-on' : 'notification-off',
- dismissTime: 3000,
- }
- );
- }, this),
- });
-
- this.editor.addAction({
- id: 'toggleColourisation',
- label: 'Toggle colourisation',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.F1],
- keybindingContext: null,
- run: _.bind(function () {
- this.eventHub.emit('modifySettings', {
- colouriseAsm: !this.settings.colouriseAsm,
- });
- }, this),
- });
-
- this.editor.addAction({
- id: 'viewasm',
- label: 'Reveal linked code',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10],
- keybindingContext: null,
- contextMenuGroupId: 'navigation',
- contextMenuOrder: 1.5,
- run: _.bind(function (ed) {
- var pos = ed.getPosition();
- if (pos != null) {
- this.tryPanesLinkLine(pos.lineNumber, pos.column, true);
- }
- }, this),
- });
-
- this.isCpp = this.editor.createContextKey('isCpp', true);
- this.isCpp.set(this.currentLanguage.id === 'c++');
-
- this.isClean = this.editor.createContextKey('isClean', true);
- this.isClean.set(this.currentLanguage.id === 'clean');
-
- this.editor.addAction({
- id: 'cpprefsearch',
- label: 'Search on Cppreference',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8],
- keybindingContext: null,
- contextMenuGroupId: 'help',
- contextMenuOrder: 1.5,
- precondition: 'isCpp',
- run: _.bind(this.searchOnCppreference, this),
- });
-
- this.editor.addAction({
- id: 'clooglesearch',
- label: 'Search on Cloogle',
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8],
- keybindingContext: null,
- contextMenuGroupId: 'help',
- contextMenuOrder: 1.5,
- precondition: 'isClean',
- run: _.bind(this.searchOnCloogle, this),
- });
-
- this.editor.addCommand(
- monaco.KeyMod.CtrlCmd | monaco.KeyCode.F9,
- _.bind(function () {
- this.runFormatDocumentAction();
- }, this)
- );
-
- this.editor.addCommand(
- monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyD,
- _.bind(function () {
- this.editor.getAction('editor.action.duplicateSelection').run();
- }, this)
- );
-};
-
-Editor.prototype.emitShortLinkEvent = function () {
- if (this.settings.enableSharingPopover) {
- this.eventHub.emit('displaySharingPopover');
- } else {
- this.eventHub.emit('copyShortLinkToClip');
- }
-};
-
-Editor.prototype.runFormatDocumentAction = function () {
- this.editor.getAction('editor.action.formatDocument').run();
-};
-
-Editor.prototype.searchOnCppreference = function (ed) {
- var pos = ed.getPosition();
- if (!pos || !ed.getModel()) return;
- var word = ed.getModel().getWordAtPosition(pos);
- if (!word || !word.word) return;
- var preferredLanguage = this.getPreferredLanguageTag();
- // This list comes from the footer of the page
- var cpprefLangs = ['ar', 'cs', 'de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'pl', 'pt', 'ru', 'tr', 'zh'];
- // If navigator.languages is supported, we could be a bit more clever and look for a match there too
- var langTag = 'en';
- if (cpprefLangs.indexOf(preferredLanguage) !== -1) {
- langTag = preferredLanguage;
- }
- var url = 'https://' + langTag + '.cppreference.com/mwiki/index.php?search=' + encodeURIComponent(word.word);
- window.open(url, '_blank', 'noopener');
-};
-
-Editor.prototype.searchOnCloogle = function (ed) {
- var pos = ed.getPosition();
- if (!pos || !ed.getModel()) return;
- var word = ed.getModel().getWordAtPosition(pos);
- if (!word || !word.word) return;
- var url = 'https://cloogle.org/#' + encodeURIComponent(word.word);
- window.open(url, '_blank', 'noopener');
-};
-
-Editor.prototype.getPreferredLanguageTag = function () {
- var result = 'en';
- var lang = 'en';
- if (navigator) {
- if (navigator.languages && navigator.languages.length) {
- lang = navigator.languages[0];
- } else if (navigator.language) {
- lang = navigator.language;
- }
- }
- // navigator.language[s] is supposed to return strings, but hey, you never know
- if (lang !== result && _.isString(lang)) {
- var primaryLanguageSubtagIdx = lang.indexOf('-');
- result = lang.substr(0, primaryLanguageSubtagIdx).toLowerCase();
- }
- return result;
-};
-
-Editor.prototype.doesMatchEditor = function (otherSource) {
- return otherSource === this.getSource();
-};
-
-Editor.prototype.confirmOverwrite = function (yes) {
- this.alertSystem.ask(
- 'Changes were made to the code',
- 'Changes were made to the code while it was being processed. Overwrite changes?',
- {yes: yes, no: null}
- );
-};
-
-Editor.prototype.updateSource = function (newSource) {
- // Create something that looks like an edit operation for the whole text
- var operation = {
- range: this.editor.getModel().getFullModelRange(),
- forceMoveMarkers: true,
- text: newSource,
- };
- var nullFn = function () {
- return null;
- };
- var viewState = this.editor.saveViewState();
- // Add an undo stop so we don't go back further than expected
- this.editor.pushUndoStop();
- // Apply de edit. Note that we lose cursor position, but I've not found a better alternative yet
- this.editor.getModel().pushEditOperations(viewState.cursorState, [operation], nullFn);
- this.numberUsedLines();
-
- if (!this.awaitingInitialResults) {
- if (this.selection) {
- /*
- * this setTimeout is a really crap workaround to fix #2150
- * the TL;DR; is that we reach this point *before* GL has laid
- * out the window, so we have no height
- *
- * If we revealLinesInCenter at this point the editor "does the right thing"
- * and scrolls itself all the way to the line we requested.
- *
- * Unfortunately the editor thinks it is very small, so the "center"
- * is the first line, and when the editor does resize eventually things are off.
- *
- * The workaround is to just delay things "long enough"
- *
- * This is bad and I feel bad.
- */
- setTimeout(
- _.bind(function () {
- this.editor.setSelection(this.selection);
- this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber);
- }, this),
- 500
- );
- }
- this.awaitingInitialResults = true;
- }
-};
-
-Editor.prototype.formatCurrentText = function () {
- var previousSource = this.getSource();
- var lang = this.currentLanguage;
-
- if (!Object.prototype.hasOwnProperty.call(lang, 'formatter')) {
- return this.alertSystem.notify('This language does not support in-editor formatting', {
- group: 'formatting',
- alertClass: 'notification-error',
- });
- }
-
- $.ajax({
- type: 'POST',
- url: window.location.origin + this.httpRoot + 'api/format/' + lang.formatter,
- dataType: 'json', // Expected
- contentType: 'application/json', // Sent
- data: JSON.stringify({
- source: previousSource,
- base: this.settings.formatBase,
- }),
- success: _.bind(function (result) {
- if (result.exit === 0) {
- if (this.doesMatchEditor(previousSource)) {
- this.updateSource(result.answer);
- } else {
- this.confirmOverwrite(
- _.bind(function () {
- this.updateSource(result.answer);
- }, this),
- null
- );
- }
- } else {
- // Ops, the formatter itself failed!
- this.alertSystem.notify('We encountered an error formatting your code: ' + result.answer, {
- group: 'formatting',
- alertClass: 'notification-error',
- });
- }
- }, this),
- error: _.bind(function (xhr, e_status, error) {
- // Hopefully we have not exploded!
- if (xhr.responseText) {
- try {
- var res = JSON.parse(xhr.responseText);
- error = res.answer || error;
- } catch (e) {
- // continue regardless of error
- }
- }
- error = error || 'Unknown error';
- this.alertSystem.notify('We ran into some issues while formatting your code: ' + error, {
- group: 'formatting',
- alertClass: 'notification-error',
- });
- }, this),
- cache: true,
- });
-};
-
-Editor.prototype.resize = function () {
- var topBarHeight = utils.updateAndCalcTopBarHeight(this.domRoot, this.topBar, this.hideable);
-
- this.editor.layout({
- width: this.domRoot.width(),
- height: this.domRoot.height() - topBarHeight,
- });
-
- // Only update the options if needed
- if (this.settings.wordWrap) {
- this.editor.updateOptions({
- wordWrapColumn: this.editor.getLayoutInfo().viewportColumn,
- });
- }
-};
-
-Editor.prototype.onSettingsChange = function (newSettings) {
- var before = this.settings;
- var after = newSettings;
- this.settings = _.clone(newSettings);
-
- this.editor.updateOptions({
- autoIndent: this.settings.autoIndent ? 'advanced' : 'none',
- autoClosingBrackets: this.settings.autoCloseBrackets,
- useVim: this.settings.useVim,
- quickSuggestions: this.settings.showQuickSuggestions,
- contextmenu: this.settings.useCustomContextMenu,
- minimap: {
- enabled: this.settings.showMinimap && !options.embedded,
- },
- fontFamily: this.settings.editorsFFont,
- fontLigatures: this.settings.editorsFLigatures,
- wordWrap: this.settings.wordWrap ? 'bounded' : 'off',
- wordWrapColumn: this.editor.getLayoutInfo().viewportColumn, // Ensure the column count is up to date
- });
-
- // Unconditionally send editor changes. The compiler only compiles when needed
- this.debouncedEmitChange = _.debounce(
- _.bind(function () {
- this.maybeEmitChange();
- }, this),
- after.delayAfterChange
- );
-
- if (before.hoverShowSource && !after.hoverShowSource) {
- this.onEditorSetDecoration(this.id, -1, false);
- }
-
- if (after.useVim && !before.useVim) {
- this.enableVim();
- } else if (!after.useVim && before.useVim) {
- this.disableVim();
- }
-
- if (this.editor.getModel()) {
- this.editor.getModel().updateOptions({
- tabSize: this.settings.tabWidth,
- insertSpaces: this.settings.useSpaces,
- });
- }
-
- this.numberUsedLines();
-};
-
-Editor.prototype.numberUsedLines = function () {
- if (_.any(this.busyCompilers)) return;
-
- if (!this.settings.colouriseAsm) {
- this.updateColours([]);
- return;
- }
-
- if (this.hub.hasTree()) {
- return;
- }
-
- var result = {};
- // First, note all lines used.
- _.each(
- this.asmByCompiler,
- _.bind(function (asm, compilerId) {
- _.each(
- asm,
- _.bind(function (asmLine) {
- var foundInTrees = false;
-
- _.each(
- this.treeCompilers,
- _.bind(function (compilerIds, treeId) {
- if (compilerIds[compilerId]) {
- var tree = this.hub.getTreeById(Number(treeId));
- if (tree) {
- var defaultFile = this.defaultFileByCompiler[compilerId];
- foundInTrees = true;
-
- if (asmLine.source && asmLine.source.line > 0) {
- var sourcefilename = asmLine.source.file ? asmLine.source.file : defaultFile;
- if (this.id === tree.multifileService.getEditorIdByFilename(sourcefilename)) {
- result[asmLine.source.line - 1] = true;
- }
- }
- }
- }
- }, this)
- );
-
- if (!foundInTrees) {
- if (
- asmLine.source &&
- (asmLine.source.file === null || asmLine.source.mainsource) &&
- asmLine.source.line > 0
- ) {
- result[asmLine.source.line - 1] = true;
- }
- }
- }, this)
- );
- }, this)
- );
- // Now assign an ordinal to each used line.
- var ordinal = 0;
- _.each(result, function (v, k) {
- result[k] = ordinal++;
- });
-
- this.updateColours(result);
-};
-
-Editor.prototype.updateColours = function (colours) {
- this.colours = colour.applyColours(this.editor, colours, this.settings.colourScheme, this.colours);
- this.eventHub.emit('colours', this.id, colours, this.settings.colourScheme);
-};
-
-Editor.prototype.onCompilerOpen = function (compilerId, editorId, treeId) {
- if (editorId === this.id) {
- // On any compiler open, rebroadcast our state in case they need to know it.
- if (this.waitingForLanguage) {
- var glCompiler = _.find(this.container.layoutManager.root.getComponentsByName('compiler'), function (c) {
- return c.id === compilerId;
- });
- if (glCompiler) {
- var selected = _.find(options.compilers, function (compiler) {
- return compiler.id === glCompiler.originalCompilerId;
- });
- if (selected) {
- this.changeLanguage(selected.lang);
- }
- }
- }
-
- if (treeId > 0) {
- if (!this.treeCompilers[treeId]) {
- this.treeCompilers[treeId] = {};
- }
- this.treeCompilers[treeId][compilerId] = true;
- }
- this.ourCompilers[compilerId] = true;
-
- if (!treeId) {
- this.maybeEmitChange(true, compilerId);
- }
- }
-};
-
-Editor.prototype.onTreeCompilerEditorIncludeChange = function (treeId, editorId, compilerId) {
- if (this.id === editorId) {
- this.onCompilerOpen(compilerId, editorId, treeId);
- }
-};
-
-Editor.prototype.onTreeCompilerEditorExcludeChange = function (treeId, editorId, compilerId) {
- if (this.id === editorId) {
- this.onCompilerClose(compilerId);
- }
-};
-
-Editor.prototype.onColoursForEditor = function (editorId, colours, scheme) {
- if (this.id === editorId) {
- this.colours = colour.applyColours(this.editor, colours, scheme, this.colours);
- }
-};
-
-Editor.prototype.onExecutorOpen = function (executorId, editorId) {
- if (editorId === this.id) {
- this.maybeEmitChange(true);
- this.ourExecutors[executorId] = true;
- }
-};
-
-Editor.prototype.onCompilerClose = function (compilerId, unused, treeId) {
- if (this.treeCompilers[treeId]) {
- delete this.treeCompilers[treeId][compilerId];
- }
-
- if (this.ourCompilers[compilerId]) {
- monaco.editor.setModelMarkers(this.editor.getModel(), compilerId, []);
- delete this.asmByCompiler[compilerId];
- delete this.busyCompilers[compilerId];
- delete this.ourCompilers[compilerId];
- delete this.defaultFileByCompiler[compilerId];
- this.numberUsedLines();
- }
-};
-
-Editor.prototype.onExecutorClose = function (id) {
- if (this.ourExecutors[id]) {
- delete this.ourExecutors[id];
- monaco.editor.setModelMarkers(this.editor.getModel(), 'Executor ' + id, []);
- }
-};
-
-Editor.prototype.onCompiling = function (compilerId) {
- if (!this.ourCompilers[compilerId]) return;
- this.busyCompilers[compilerId] = true;
-};
-
-Editor.prototype.addSource = function (arr, source) {
- arr.forEach(function (element) {
- element.source = source;
- });
- return arr;
-};
-
-Editor.prototype.getAllOutputAndErrors = function (result, compilerName, compilerId) {
- var compilerTitle = compilerName + ' #' + compilerId;
- var all = this.addSource(result.stdout || [], compilerTitle);
-
- if (result.buildsteps) {
- _.each(result.buildsteps, step => {
- all = all.concat(this.addSource(step.stdout, compilerTitle));
- all = all.concat(this.addSource(step.stderr, compilerTitle));
- });
- }
- if (result.tools) {
- _.each(result.tools, tool => {
- all = all.concat(this.addSource(tool.stdout, tool.name + ' #' + compilerId));
- all = all.concat(this.addSource(tool.stderr, tool.name + ' #' + compilerId));
- });
- }
- all = all.concat(this.addSource(result.stderr || [], compilerTitle));
-
- return all;
-};
-
-Editor.prototype.collectOutputWidgets = function (output) {
- var fixes = [];
- var editorModel = this.editor.getModel();
- var widgets = _.compact(
- _.map(
- output,
- function (obj) {
- if (!obj.tag) return;
-
- var trees = this.hub.trees;
- if (trees && trees.length > 0) {
- if (obj.tag.file) {
- if (this.id !== trees[0].multifileService.getEditorIdByFilename(obj.tag.file)) {
- return;
- }
- } else {
- if (this.id !== trees[0].multifileService.getMainSourceEditorId()) {
- return;
- }
- }
- }
-
- var colBegin = 0;
- var colEnd = Infinity;
- var lineBegin = obj.tag.line;
- var lineEnd = obj.tag.line;
- if (obj.tag.column) {
- if (obj.tag.endcolumn) {
- colBegin = obj.tag.column;
- colEnd = obj.tag.endcolumn;
- lineBegin = obj.tag.line;
- lineEnd = obj.tag.endline;
- } else {
- var span = this.getTokenSpan(obj.tag.line, obj.tag.column);
- colBegin = obj.tag.column;
- colEnd = span.colEnd;
- if (colEnd === obj.tag.column) colEnd = -1;
- }
- }
- var link;
- if (obj.tag.link) {
- link = {
- value: obj.tag.link.text,
- target: obj.tag.link.url,
- };
- }
- var diag = {
- severity: obj.tag.severity,
- message: obj.tag.text,
- source: obj.source,
- startLineNumber: lineBegin,
- startColumn: colBegin,
- endLineNumber: lineEnd,
- endColumn: colEnd,
- code: link,
- };
- if (obj.tag.fixes) {
- fixes = fixes.concat(
- obj.tag.fixes.map(function (fs, ind) {
- return {
- title: fs.title,
- diagnostics: [diag],
- kind: 'quickfix',
- edit: {
- edits: fs.edits.map(function (f) {
- return {
- resource: editorModel.uri,
- edit: {
- range: new monaco.Range(f.line, f.column, f.endline, f.endcolumn),
- text: f.text,
- },
- };
- }),
- },
- isPreferred: ind === 0,
- };
- })
- );
- }
- return diag;
- },
- this
- )
- );
- return {
- fixes: fixes,
- widgets: widgets,
- };
-};
-
-Editor.prototype.setDecorationTags = function (widgets, ownerId) {
- monaco.editor.setModelMarkers(this.editor.getModel(), ownerId, widgets);
-
- this.decorations.tags = _.map(
- widgets,
- function (tag) {
- return {
- range: new monaco.Range(tag.startLineNumber, tag.startColumn, tag.startLineNumber + 1, 1),
- options: {
- isWholeLine: false,
- inlineClassName: 'error-code',
- },
- };
- },
- this
- );
-
- this.updateDecorations();
-};
-
-Editor.prototype.setQuickFixes = function (fixes) {
- if (fixes.length) {
- var editorModel = this.editor.getModel();
- quickFixesHandler.registerQuickFixesForCompiler(this.id, editorModel, fixes);
- quickFixesHandler.registerProviderForLanguage(editorModel.getLanguageId());
- } else {
- quickFixesHandler.unregister(this.id);
- }
-};
-
-Editor.prototype.onCompileResponse = function (compilerId, compiler, result) {
- if (!compiler || !this.ourCompilers[compilerId]) return;
-
- this.busyCompilers[compilerId] = false;
-
- var collectedOutput = this.collectOutputWidgets(this.getAllOutputAndErrors(result, compiler.name, compilerId));
-
- this.setDecorationTags(collectedOutput.widgets, compilerId);
- this.setQuickFixes(collectedOutput.fixes);
-
- if (result.result && result.result.asm) {
- this.asmByCompiler[compilerId] = result.result.asm;
- } else {
- this.asmByCompiler[compilerId] = result.asm;
- }
-
- if (result.inputFilename) {
- this.defaultFileByCompiler[compilerId] = result.inputFilename;
- } else {
- this.defaultFileByCompiler[compilerId] = 'example' + this.currentLanguage.extensions[0];
- }
-
- this.numberUsedLines();
-};
-
-Editor.prototype.onExecuteResponse = function (executorId, compiler, result) {
- if (this.ourExecutors[executorId]) {
- var output = this.getAllOutputAndErrors(result, compiler.name, 'Execution ' + executorId);
- if (result.buildResult) {
- output = output.concat(
- this.getAllOutputAndErrors(result.buildResult, compiler.name, 'Executor ' + executorId)
- );
- }
- this.setDecorationTags(this.collectOutputWidgets(output).widgets, 'Executor ' + executorId);
-
- this.numberUsedLines();
- }
-};
-
-Editor.prototype.onSelectLine = function (id, lineNum) {
- if (Number(id) === this.id) {
- this.editor.setSelection(new monaco.Selection(lineNum - 1, 0, lineNum, 0));
- }
-};
-
-// Returns a half-segment [a, b) for the token on the line lineNum
-// that spans across the column.
-// a - colStart points to the first character of the token
-// b - colEnd points to the character immediately following the token
-// e.g.: "this->callableMethod ( x, y );"
-// ^a ^column ^b
-Editor.prototype.getTokenSpan = function (lineNum, column) {
- var model = this.editor.getModel();
- if (lineNum < 1 || lineNum > model.getLineCount()) {
- // #3592 Be forgiving towards parsing errors
- return {colBegin: 0, colEnd: 0};
- }
- if (lineNum <= model.getLineCount()) {
- var line = model.getLineContent(lineNum);
- if (0 < column && column <= line.length) {
- var tokens = monaco.editor.tokenize(line, model.getLanguageId());
- if (tokens.length > 0) {
- var lastOffset = 0;
- var lastWasString = false;
- for (var i = 0; i < tokens[0].length; ++i) {
- // Treat all the contiguous string tokens as one,
- // For example "hello \" world" is treated as one token
- // instead of 3 "string.cpp", "string.escape.cpp", "string.cpp"
- if (tokens[0][i].type.startsWith('string')) {
- if (lastWasString) {
- continue;
- }
- lastWasString = true;
- } else {
- lastWasString = false;
- }
- var currentOffset = tokens[0][i].offset;
- if (column <= currentOffset) {
- return {colBegin: lastOffset + 1, colEnd: currentOffset + 1};
- } else {
- lastOffset = currentOffset;
- }
- }
- return {colBegin: lastOffset + 1, colEnd: line.length + 1};
- }
- }
- }
- return {colBegin: column, colEnd: column + 1};
-};
-
-Editor.prototype.pushRevealJump = function () {
- this.revealJumpStack.push(this.editor.saveViewState());
- this.revealJumpStackHasElementsCtxKey.set(true);
-};
-
-Editor.prototype.popAndRevealJump = function () {
- if (this.revealJumpStack.length > 0) {
- this.editor.restoreViewState(this.revealJumpStack.pop());
- this.revealJumpStackHasElementsCtxKey.set(this.revealJumpStack.length > 0);
- }
-};
-
-Editor.prototype.onEditorLinkLine = function (editorId, lineNum, columnBegin, columnEnd, reveal) {
- if (Number(editorId) === this.id) {
- if (reveal && lineNum) {
- this.pushRevealJump();
- this.hub.activateTabForContainer(this.container);
- this.editor.revealLineInCenter(lineNum);
- }
- this.decorations.linkedCode = [];
- if (lineNum && lineNum !== -1) {
- this.decorations.linkedCode.push({
- range: new monaco.Range(lineNum, 1, lineNum, 1),
- options: {
- isWholeLine: true,
- linesDecorationsClassName: 'linked-code-decoration-margin',
- className: 'linked-code-decoration-line',
- },
- });
- }
-
- if (lineNum > 0 && columnBegin !== -1) {
- var lastTokenSpan = this.getTokenSpan(lineNum, columnEnd);
- this.decorations.linkedCode.push({
- range: new monaco.Range(lineNum, columnBegin, lineNum, lastTokenSpan.colEnd),
- options: {
- isWholeLine: false,
- inlineClassName: 'linked-code-decoration-column',
- },
- });
- }
-
- if (this.fadeTimeoutId !== -1) {
- clearTimeout(this.fadeTimeoutId);
- }
- this.fadeTimeoutId = setTimeout(
- _.bind(function () {
- this.clearLinkedLine();
- this.fadeTimeoutId = -1;
- }, this),
- 5000
- );
-
- this.updateDecorations();
- }
-};
-
-Editor.prototype.onEditorSetDecoration = function (id, lineNum, reveal, column) {
- if (Number(id) === this.id) {
- if (reveal && lineNum) {
- this.pushRevealJump();
- this.editor.revealLineInCenter(lineNum);
- this.editor.focus();
- this.editor.setPosition({column: column || 0, lineNumber: lineNum});
- }
- this.decorations.linkedCode = [];
- if (lineNum && lineNum !== -1) {
- this.decorations.linkedCode.push({
- range: new monaco.Range(lineNum, 1, lineNum, 1),
- options: {
- isWholeLine: true,
- linesDecorationsClassName: 'linked-code-decoration-margin',
- inlineClassName: 'linked-code-decoration-inline',
- },
- });
- }
- this.updateDecorations();
- }
-};
-
-Editor.prototype.onEditorDisplayFlow = function (id, flow) {
- if (Number(id) === this.id) {
- if (this.decorations.flows && this.decorations.flows.length) {
- this.decorations.flows = [];
- } else {
- this.decorations.flows = flow.map((ri, ind) => {
- return {
- range: new monaco.Range(ri.line, ri.column, ri.endline || ri.line, ri.endcolumn || ri.column),
- options: {
- before: {
- content: ' ' + (ind + 1).toString() + ' ',
- inlineClassName: 'flow-decoration',
- cursorStops: monaco.editor.InjectedTextCursorStops.None,
- },
- inlineClassName: 'flow-highlight',
- isWholeLine: false,
- hoverMessage: {value: ri.text},
- },
- };
- });
- }
- this.updateDecorations();
- }
-};
-
-Editor.prototype.updateDecorations = function () {
- this.prevDecorations = this.editor.deltaDecorations(
- this.prevDecorations,
- _.compact(_.flatten(_.values(this.decorations)))
- );
-};
-
-Editor.prototype.onConformanceViewOpen = function (editorId) {
- if (editorId === this.id) {
- this.conformanceViewerButton.attr('disabled', true);
- }
-};
-
-Editor.prototype.onConformanceViewClose = function (editorId) {
- if (editorId === this.id) {
- this.conformanceViewerButton.attr('disabled', false);
- }
-};
-
-Editor.prototype.showLoadSaver = function () {
- this.loadSaveButton.click();
-};
-
-Editor.prototype.initLoadSaver = function () {
- this.loadSaveButton.off('click').click(
- _.bind(function () {
- loadSave.run(
- _.bind(function (text, filename) {
- this.setSource(text);
- this.setFilename(filename);
- this.updateState();
- this.maybeEmitChange(true);
- this.requestCompilation();
- }, this),
- this.getSource(),
- this.currentLanguage
- );
- }, this)
- );
-};
-
-Editor.prototype.onLanguageChange = function (newLangId) {
- if (languages[newLangId]) {
- if (newLangId !== this.currentLanguage.id) {
- var oldLangId = this.currentLanguage.id;
- this.currentLanguage = languages[newLangId];
- if (!this.waitingForLanguage && !this.settings.keepSourcesOnLangChange && newLangId !== 'cmake') {
- this.editorSourceByLang[oldLangId] = this.getSource();
- this.updateEditorCode();
- }
- this.initLoadSaver();
- monaco.editor.setModelLanguage(this.editor.getModel(), this.currentLanguage.monaco);
- this.isCpp.set(this.currentLanguage.id === 'c++');
- this.isClean.set(this.currentLanguage.id === 'clean');
- this.updateTitle();
- this.updateState();
- // Broadcast the change to other panels
- this.eventHub.emit('languageChange', this.id, newLangId);
- this.decorations = {};
- this.maybeEmitChange(true);
- this.requestCompilation();
- ga.proxy('send', {
- hitType: 'event',
- eventCategory: 'LanguageChange',
- eventAction: newLangId,
- });
- }
- this.waitingForLanguage = false;
- }
-};
-
-Editor.prototype.getPaneName = function () {
- if (this.filename) {
- return this.filename;
- } else {
- return this.currentLanguage.name + ' source #' + this.id;
- }
-};
-
-Editor.prototype.setFilename = function (name) {
- this.filename = name;
- this.updateTitle();
- this.updateState();
-};
-
-Editor.prototype.updateTitle = function () {
- var name = this.getPaneName();
- var customName = this.paneName ? this.paneName : name;
- if (name.endsWith('CMakeLists.txt')) {
- this.changeLanguage('cmake');
- }
- this.container.setTitle(_.escape(customName));
-};
-
-// Called every time we change language, so we get the relevant code
-Editor.prototype.updateEditorCode = function () {
- this.setSource(this.editorSourceByLang[this.currentLanguage.id] || languages[this.currentLanguage.id].example);
-};
-
-Editor.prototype.close = function () {
- this.eventHub.unsubscribe();
- this.eventHub.emit('editorClose', this.id);
- this.editor.dispose();
- this.hub.removeEditor(this.id);
-};
-
-function getSelectizeRenderHtml(data, escape, width, height) {
- var result =
- '
' +
- '
' +
- '
 +
- ')
';
- if (data.logoDataDark) {
- result +=
- '

';
- }
-
- result += '
' + escape(data.name) + '
';
- return result;
-}
-
-function renderSelectizeOption(data, escape) {
- return getSelectizeRenderHtml(data, escape, 23, 23);
-}
-
-function renderSelectizeItem(data, escape) {
- return getSelectizeRenderHtml(data, escape, 20, 20);
-}
-
-module.exports = {
- Editor: Editor,
-};
diff --git a/static/panes/editor.ts b/static/panes/editor.ts
new file mode 100644
index 000000000..111e125bc
--- /dev/null
+++ b/static/panes/editor.ts
@@ -0,0 +1,1914 @@
+// Copyright (c) 2016, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+import _ from 'underscore';
+import $ from 'jquery';
+import * as colour from '../colour';
+import * as loadSaveLib from '../widgets/load-save';
+import {FontScale} from '../widgets/fontscale';
+import * as Components from '../components';
+import * as monaco from 'monaco-editor';
+import {options} from '../options';
+import {Alert} from '../alert';
+import {ga} from '../analytics';
+import monacoVim from 'monaco-vim';
+import * as monacoConfig from '../monaco-config';
+import * as quickFixesHandler from '../quick-fixes-handler';
+import TomSelect from 'tom-select';
+import {Settings, SiteSettings} from '../settings';
+import '../formatter-registry';
+import '../modes/_all';
+import {MonacoPane} from './pane';
+import {Hub} from '../hub';
+import {MonacoPaneState} from './pane.interfaces';
+import {Container} from 'golden-layout';
+import {EditorState} from './editor.interfaces';
+import {Language, LanguageKey} from '../../types/languages.interfaces';
+import {editor} from 'monaco-editor';
+import IModelDeltaDecoration = editor.IModelDeltaDecoration;
+import {MessageWithLocation, ResultLine} from '../../types/resultline/resultline.interfaces';
+import {CompilerInfo} from '../../types/compiler.interfaces';
+import {CompilationResult} from '../../types/compilation/compilation.interfaces';
+import {Decoration, Motd} from '../motd.interfaces';
+import type {escape_html} from 'tom-select/dist/types/utils';
+import ICursorSelectionChangedEvent = editor.ICursorSelectionChangedEvent;
+
+const loadSave = new loadSaveLib.LoadSave();
+const languages = options.languages as Record;
+
+// eslint-disable-next-line max-statements
+export class Editor extends MonacoPane {
+ private readonly id: number;
+ private hub: Hub;
+ private readonly ourCompilers: Record;
+ private readonly ourExecutors: Record;
+ private readonly httpRoot: string;
+ private readonly asmByCompiler: Record;
+ private readonly defaultFileByCompiler: Record;
+ private readonly busyCompilers: Record;
+ private colours: string[];
+ private readonly treeCompilers: Record | undefined>;
+ private decorations: Record;
+ private prevDecorations: string[];
+ private extraDecorations?: Decoration[];
+ private fadeTimeoutId: NodeJS.Timeout | null;
+ private readonly editorSourceByLang: Record;
+ private alertSystem: Alert;
+ private filename: string | null;
+ private awaitingInitialResults: boolean;
+ private revealJumpStack: editor.ICodeEditorViewState[];
+ private readonly langKeys: string[];
+ private readonly legacyReadOnly?: boolean;
+ private selectize: TomSelect;
+ private lastChangeEmitted: string | null;
+ private readonly languageBtn: JQuery;
+ public currentLanguage?: Language;
+ private waitingForLanguage: boolean;
+ private currentCursorPosition: JQuery;
+ private mouseMoveThrottledFunction?: ((e: monaco.editor.IEditorMouseEvent) => void) & _.Cancelable;
+ private cursorSelectionThrottledFunction?: (e: monaco.editor.ICursorSelectionChangedEvent) => void & _.Cancelable;
+ private vimMode: any;
+ private vimFlag: JQuery;
+ private loadSaveButton: JQuery;
+ private addExecutorButton: JQuery;
+ private conformanceViewerButton: JQuery;
+ private cppInsightsButton: JQuery;
+ private quickBenchButton: JQuery;
+ private nothingCtrlSSince?: number;
+ private nothingCtrlSTimes?: number;
+ private isCpp: editor.IContextKey;
+ private isClean: editor.IContextKey;
+ private debouncedEmitChange: (() => void) & _.Cancelable;
+ private revealJumpStackHasElementsCtxKey: editor.IContextKey;
+
+ constructor(hub: Hub, state: MonacoPaneState & EditorState, container: Container) {
+ super(hub, container, state);
+ this.id = state.id || hub.nextEditorId();
+ this.hub = hub;
+ // Should probably be its own function somewhere
+ this.settings = Settings.getStoredSettings();
+ this.ourCompilers = {};
+ this.ourExecutors = {};
+ this.httpRoot = window.httpRoot;
+ this.asmByCompiler = {};
+ this.defaultFileByCompiler = {};
+ this.busyCompilers = {};
+ this.colours = [];
+ this.treeCompilers = {};
+
+ this.decorations = {};
+ this.prevDecorations = [];
+ this.extraDecorations = [];
+
+ this.fadeTimeoutId = null;
+
+ this.editorSourceByLang = {} as Record;
+ this.alertSystem = new Alert();
+ this.alertSystem.prefixMessage = 'Editor #' + this.id;
+
+ this.filename = state.filename || null;
+
+ this.awaitingInitialResults = false;
+ this.selection = state.selection;
+
+ this.revealJumpStack = [];
+
+ this.langKeys = Object.keys(languages);
+ this.initLanguage(state);
+
+ this.legacyReadOnly = state.options && !!state.options.readOnly;
+
+ if (state.source !== undefined) {
+ this.setSource(state.source);
+ } else {
+ this.updateEditorCode();
+ }
+
+ const startFolded = /^[/*#;]+\s*setup.*/;
+ if (state.source && state.source.match(startFolded)) {
+ // With reference to https://github.com/Microsoft/monaco-editor/issues/115
+ // I tried that and it didn't work, but a delay of 500 seems to "be enough".
+ // FIXME: Currently not working - No folding is performed
+ setTimeout(() => {
+ this.editor.setSelection(new monaco.Selection(1, 1, 1, 1));
+ this.editor.focus();
+ this.editor.getAction('editor.fold').run();
+ //this.editor.clearSelection();
+ }, 500);
+ }
+
+ this.initEditorActions();
+ this.initButtons(state);
+ this.initCallbacks();
+
+ if (this.settings.useVim) {
+ this.enableVim();
+ }
+
+ const usableLanguages = Object.values(languages).filter(language => {
+ return hub.compilerService.compilersByLang[language?.id ?? ''];
+ });
+
+ this.languageBtn = this.domRoot.find('.change-language');
+ this.selectize = new TomSelect(this.languageBtn as any, {
+ sortField: 'name',
+ valueField: 'id',
+ labelField: 'name',
+ searchField: ['name'],
+ placeholder: '🔍 Select a language...',
+ options: _.map(usableLanguages, _.identity),
+ items: this.currentLanguage?.id ? [this.currentLanguage.id] : [],
+ dropdownParent: 'body',
+ plugins: ['dropdown_input'],
+ onChange: _.bind(this.onLanguageChange, this),
+ closeAfterSelect: true,
+ render: {
+ option: this.renderSelectizeOption.bind(this),
+ item: this.renderSelectizeItem.bind(this),
+ },
+ });
+
+ // We suppress posting changes until the user has stopped typing by:
+ // * Using _.debounce() to run emitChange on any key event or change
+ // only after a delay.
+ // * Only actually triggering a change if the document text has changed from
+ // the previous emitted.
+ this.lastChangeEmitted = null;
+ this.onSettingsChange(this.settings);
+ // this.editor.on("keydown", _.bind(function () {
+ // // Not strictly a change; but this suppresses changes until some time
+ // // after the last key down (be it an actual change or a just a cursor
+ // // movement etc).
+ // this.debouncedEmitChange();
+ // }, this));
+
+ this.updateTitle();
+ this.updateState();
+ }
+
+ override registerOpeningAnalyticsEvent(): void {
+ ga.proxy('send', {
+ hitType: 'event',
+ eventCategory: 'OpenViewPane',
+ eventAction: 'Editor',
+ });
+ ga.proxy('send', {
+ hitType: 'event',
+ eventCategory: 'LanguageChange',
+ eventAction: this.currentLanguage?.id,
+ });
+ }
+
+ override getInitialHTML(): string {
+ return $('#codeEditor').html();
+ }
+
+ override createEditor(editorRoot: HTMLElement): editor.IStandaloneCodeEditor {
+ const editor = monaco.editor.create(
+ editorRoot,
+ // @ts-expect-error: options.readOnly and anything inside window.compilerExplorerOptions is unknown
+ monacoConfig.extendConfig(
+ {
+ language: this.currentLanguage?.monaco,
+ readOnly:
+ !!options.readOnly ||
+ this.legacyReadOnly ||
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ (window.compilerExplorerOptions && window.compilerExplorerOptions.mobileViewer),
+ glyphMargin: !options.embedded,
+ },
+ this.settings as SiteSettings
+ )
+ );
+
+ editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF);
+ return editor;
+ }
+
+ onMotd(motd: Motd): void {
+ this.extraDecorations = motd.decorations;
+ this.updateExtraDecorations();
+ }
+
+ updateExtraDecorations(): void {
+ let decorationsDirty = false;
+ this.extraDecorations?.forEach(decoration => {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ decoration.filter &&
+ this.currentLanguage?.name &&
+ decoration.filter.indexOf(this.currentLanguage.name.toLowerCase()) < 0
+ )
+ return;
+ const match = this.editor.getModel()?.findNextMatch(
+ decoration.regex,
+ {
+ column: 1,
+ lineNumber: 1,
+ },
+ true,
+ true,
+ null,
+ false
+ );
+
+ if (match !== this.decorations[decoration.name]) {
+ decorationsDirty = true;
+ this.decorations[decoration.name] = match
+ ? [{range: match.range, options: decoration.decoration}]
+ : undefined;
+ }
+ });
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (decorationsDirty) this.updateDecorations();
+ }
+
+ // If compilerId is undefined, every compiler will be pinged
+ maybeEmitChange(force?: boolean, compilerId?: number): void {
+ const source = this.getSource();
+ if (!force && source === this.lastChangeEmitted) return;
+
+ this.updateExtraDecorations();
+
+ this.lastChangeEmitted = source ?? null;
+ this.eventHub.emit(
+ 'editorChange',
+ this.id,
+ this.lastChangeEmitted ?? '',
+ this.currentLanguage?.id ?? '',
+ compilerId
+ );
+ }
+
+ override updateState(): void {
+ const state = {
+ id: this.id,
+ source: this.getSource(),
+ lang: this.currentLanguage?.id,
+ selection: this.selection,
+ filename: this.filename,
+ };
+ this.fontScale.addState(state);
+ this.container.setState(state);
+
+ this.updateButtons();
+ }
+
+ setSource(newSource: string): void {
+ this.updateSource(newSource);
+
+ if (window.compilerExplorerOptions.mobileViewer) {
+ $(this.domRoot.find('.monaco-placeholder textarea')).hide();
+ }
+ }
+
+ onNewSource(editorId: number, newSource: string): void {
+ if (this.id === editorId) {
+ this.setSource(newSource);
+ }
+ }
+
+ getSource(): string | undefined {
+ return this.editor.getModel()?.getValue();
+ }
+
+ initLanguage(state: MonacoPaneState & EditorState): void {
+ this.currentLanguage = languages[this.langKeys[0]];
+ this.waitingForLanguage = Boolean(state.source && !state.lang);
+ if (languages[this.settings.defaultLanguage ?? '']) {
+ this.currentLanguage = languages[this.settings.defaultLanguage ?? ''];
+ }
+
+ if (languages[state.lang ?? '']) {
+ this.currentLanguage = languages[state.lang ?? ''];
+ } else if (this.settings.newEditorLastLang && languages[this.hub.lastOpenedLangId ?? '']) {
+ this.currentLanguage = languages[this.hub.lastOpenedLangId ?? ''];
+ }
+ }
+
+ initCallbacks(): void {
+ this.fontScale.on('change', _.bind(this.updateState, this));
+ this.eventHub.on('broadcastFontScale', scale => {
+ this.fontScale.setScale(scale);
+ this.updateState();
+ });
+
+ this.container.on('resize', this.resize, this);
+ this.container.on('shown', this.resize, this);
+ this.container.on('open', () => {
+ this.eventHub.emit('editorOpen', this.id);
+ });
+ this.container.on('destroy', this.close, this);
+ this.container.layoutManager.on('initialised', () => {
+ // Once initialized, let everyone know what text we have.
+ this.maybeEmitChange();
+ // And maybe ask for a compilation (Will hit the cache most of the time)
+ this.requestCompilation();
+ });
+
+ this.eventHub.on('treeCompilerEditorIncludeChange', this.onTreeCompilerEditorIncludeChange, this);
+ this.eventHub.on('treeCompilerEditorExcludeChange', this.onTreeCompilerEditorExcludeChange, this);
+ this.eventHub.on('coloursForEditor', this.onColoursForEditor, this);
+ this.eventHub.on('compilerOpen', this.onCompilerOpen, this);
+ this.eventHub.on('executorOpen', this.onExecutorOpen, this);
+ this.eventHub.on('executorClose', this.onExecutorClose, this);
+ this.eventHub.on('compilerClose', this.onCompilerClose, this);
+ this.eventHub.on('compiling', this.onCompiling, this);
+ this.eventHub.on('compileResult', this.onCompileResult, this);
+ this.eventHub.on('executeResult', this.onExecuteResponse, this);
+ this.eventHub.on('selectLine', this.onSelectLine, this);
+ this.eventHub.on('editorSetDecoration', this.onEditorSetDecoration, this);
+ this.eventHub.on('editorDisplayFlow', this.onEditorDisplayFlow, this);
+ this.eventHub.on('editorLinkLine', this.onEditorLinkLine, this);
+ this.eventHub.on('settingsChange', this.onSettingsChange, this);
+ this.eventHub.on('conformanceViewOpen', this.onConformanceViewOpen, this);
+ this.eventHub.on('conformanceViewClose', this.onConformanceViewClose, this);
+ this.eventHub.on('resize', this.resize, this);
+ this.eventHub.on('newSource', this.onNewSource, this);
+ this.eventHub.on('motd', this.onMotd, this);
+ this.eventHub.on('findEditors', this.sendEditor, this);
+ this.eventHub.emit('requestMotd');
+
+ this.editor.getModel()?.onDidChangeContent(() => {
+ this.debouncedEmitChange();
+ this.updateState();
+ });
+
+ this.mouseMoveThrottledFunction = _.throttle(this.onMouseMove.bind(this), 50);
+
+ this.editor.onMouseMove(e => {
+ if (this.mouseMoveThrottledFunction) this.mouseMoveThrottledFunction(e);
+ });
+
+ if (window.compilerExplorerOptions.mobileViewer) {
+ // workaround for issue with contextmenu not going away when tapping somewhere else on the screen
+ this.editor.onDidChangeCursorSelection(() => {
+ const contextmenu = $('div.context-view.monaco-menu-container');
+ if (contextmenu.css('display') !== 'none') {
+ contextmenu.hide();
+ }
+ });
+ }
+
+ this.cursorSelectionThrottledFunction = _.throttle(
+ this.onDidChangeCursorSelection.bind(this) as (
+ e: editor.ICursorSelectionChangedEvent
+ ) => void & _.Cancelable,
+ 500
+ );
+ this.editor.onDidChangeCursorSelection(e => {
+ if (this.cursorSelectionThrottledFunction) this.cursorSelectionThrottledFunction(e);
+ });
+
+ this.editor.onDidFocusEditorText(_.bind(this.onDidFocusEditorText, this));
+ this.editor.onDidBlurEditorText(_.bind(this.onDidBlurEditorText, this));
+ this.editor.onDidChangeCursorPosition(_.bind(this.onDidChangeCursorPosition, this));
+
+ this.eventHub.on('initialised', this.maybeEmitChange, this);
+
+ $(document).on('keyup.editable', e => {
+ // @ts-expect-error: Document and JQuery have no overlap
+ if (e.target === this.domRoot.find('.monaco-placeholder .inputarea')[0]) {
+ if (e.which === 27) {
+ this.onEscapeKey();
+ } else if (e.which === 45) {
+ this.onInsertKey(e);
+ }
+ }
+ });
+ }
+
+ sendEditor(): void {
+ this.eventHub.emit('editorOpen', this.id);
+ }
+
+ onMouseMove(e: editor.IEditorMouseEvent): void {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (e !== null && e.target !== null && this.settings.hoverShowSource && e.target.position !== null) {
+ this.clearLinkedLine();
+ const pos = e.target.position;
+ this.tryPanesLinkLine(pos.lineNumber, pos.column, false);
+ }
+ }
+
+ override onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void {
+ if (this.awaitingInitialResults) {
+ this.selection = e.selection;
+ this.updateState();
+ }
+ }
+
+ onDidChangeCursorPosition(e: ICursorSelectionChangedEvent): void {
+ // @ts-expect-error: 'position' is not a property of 'e'
+ if (e.position) {
+ // @ts-expect-error: 'position' is not a property of 'e'
+ this.currentCursorPosition.text('(' + e.position.lineNumber + ', ' + e.position.column + ')');
+ }
+ }
+
+ onDidFocusEditorText(): void {
+ const position = this.editor.getPosition();
+ if (position) {
+ this.currentCursorPosition.text('(' + position.lineNumber + ', ' + position.column + ')');
+ }
+ this.currentCursorPosition.show();
+ }
+
+ onDidBlurEditorText(): void {
+ this.currentCursorPosition.text('');
+ this.currentCursorPosition.hide();
+ }
+
+ onEscapeKey(): void {
+ // @ts-expect-error: IStandaloneCodeEditor is missing this property
+ if (this.editor.vimInUse) {
+ const currentState = monacoVim.VimMode.Vim.maybeInitVimState_(this.vimMode);
+ if (currentState.insertMode) {
+ monacoVim.VimMode.Vim.exitInsertMode(this.vimMode);
+ } else if (currentState.visualMode) {
+ monacoVim.VimMode.Vim.exitVisualMode(this.vimMode, false);
+ }
+ }
+ }
+
+ onInsertKey(event: JQuery.TriggeredEvent): void {
+ // @ts-expect-error: IStandaloneCodeEditor is missing this property
+ if (this.editor.vimInUse) {
+ const currentState = monacoVim.VimMode.Vim.maybeInitVimState_(this.vimMode);
+ if (!currentState.insertMode) {
+ const insertEvent = {
+ preventDefault: event.preventDefault,
+ stopPropagation: event.stopPropagation,
+ browserEvent: {
+ key: 'i',
+ defaultPrevented: false,
+ },
+ keyCode: 39,
+ };
+ this.vimMode.handleKeyDown(insertEvent);
+ }
+ }
+ }
+
+ enableVim(): void {
+ this.vimMode = monacoVim.initVimMode(this.editor, this.domRoot.find('#v-status')[0]);
+ this.vimFlag.prop('class', 'btn btn-info');
+ // @ts-expect-error: IStandaloneCodeEditor is missing this property
+ this.editor.vimInUse = true;
+ }
+
+ disableVim(): void {
+ this.vimMode.dispose();
+ this.domRoot.find('#v-status').html('');
+ this.vimFlag.prop('class', 'btn btn-light');
+ // @ts-expect-error: IStandaloneCodeEditor is missing this property
+ this.editor.vimInUse = false;
+ }
+
+ initButtons(state: MonacoPaneState & EditorState): void {
+ this.fontScale = new FontScale(this.domRoot, state, this.editor);
+ // Ensure that the button is disabled if we don't have anything to select
+ // Note that is might be disabled for other reasons beforehand
+ if (this.langKeys.length <= 1) {
+ this.languageBtn.prop('disabled', true);
+ }
+ this.topBar = this.domRoot.find('.top-bar');
+ this.hideable = this.domRoot.find('.hideable');
+
+ this.loadSaveButton = this.domRoot.find('.load-save');
+ const paneAdderDropdown = this.domRoot.find('.add-pane');
+ const addCompilerButton = this.domRoot.find('.btn.add-compiler');
+ this.addExecutorButton = this.domRoot.find('.btn.add-executor');
+ this.conformanceViewerButton = this.domRoot.find('.btn.conformance');
+ const addEditorButton = this.domRoot.find('.btn.add-editor');
+ const toggleVimButton = this.domRoot.find('#vim-flag');
+ this.vimFlag = this.domRoot.find('#vim-flag');
+ toggleVimButton.on('click', () => {
+ // @ts-expect-error: IStandaloneCodeEditor is missing this property
+ if (this.editor.vimInUse) {
+ this.disableVim();
+ } else {
+ this.enableVim();
+ }
+ });
+
+ // NB a new compilerConfig needs to be created every time; else the state is shared
+ // between all compilers created this way. That leads to some nasty-to-find state
+ // bugs e.g. https://github.com/compiler-explorer/compiler-explorer/issues/225
+ const getCompilerConfig = () => {
+ return Components.getCompiler(this.id, this.currentLanguage?.id ?? '');
+ };
+
+ const getExecutorConfig = () => {
+ return Components.getExecutor(this.id, this.currentLanguage?.id ?? '');
+ };
+
+ const getConformanceConfig = () => {
+ // TODO: this doesn't pass any treeid introduced by #3360
+ return Components.getConformanceView(this.id, 0, this.getSource() ?? '', this.currentLanguage?.id ?? '');
+ };
+
+ const getEditorConfig = () => {
+ return Components.getEditor();
+ };
+
+ const addPaneOpener = (dragSource, dragConfig) => {
+ this.container.layoutManager
+ .createDragSource(dragSource, dragConfig)
+ // @ts-expect-error: createDragSource returns not void
+ ._dragListener.on('dragStart', () => {
+ paneAdderDropdown.dropdown('toggle');
+ });
+
+ dragSource.on('click', () => {
+ const insertPoint =
+ this.hub.findParentRowOrColumn(this.container.parent) ||
+ this.container.layoutManager.root.contentItems[0];
+ insertPoint.addChild(dragConfig);
+ });
+ };
+
+ addPaneOpener(addCompilerButton, getCompilerConfig);
+ addPaneOpener(this.addExecutorButton, getExecutorConfig);
+ addPaneOpener(this.conformanceViewerButton, getConformanceConfig);
+ addPaneOpener(addEditorButton, getEditorConfig);
+
+ this.initLoadSaver();
+ $(this.domRoot).on('keydown', event => {
+ if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 's') {
+ this.handleCtrlS(event);
+ }
+ });
+
+ if (options.thirdPartyIntegrationEnabled) {
+ this.cppInsightsButton = this.domRoot.find('.open-in-cppinsights');
+ this.cppInsightsButton.on('mousedown', () => {
+ this.updateOpenInCppInsights();
+ });
+
+ this.quickBenchButton = this.domRoot.find('.open-in-quickbench');
+ this.quickBenchButton.on('mousedown', () => {
+ this.updateOpenInQuickBench();
+ });
+ }
+
+ this.currentCursorPosition = this.domRoot.find('.currentCursorPosition');
+ this.currentCursorPosition.hide();
+ }
+
+ handleCtrlS(event: JQuery.KeyDownEvent): void {
+ event.preventDefault();
+ if (this.settings.enableCtrlStree && this.hub.hasTree()) {
+ const trees = this.hub.trees;
+ // todo: change when multiple trees are used
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (trees && trees.length > 0) {
+ trees[0].multifileService.includeByEditorId(this.id).then(() => {
+ trees[0].refresh();
+ });
+ }
+ } else {
+ if (this.settings.enableCtrlS === 'true') {
+ if (this.currentLanguage) loadSave.setMinimalOptions(this.getSource() ?? '', this.currentLanguage);
+ // @ts-expect-error: this.id is not a string
+ if (!loadSave.onSaveToFile(this.id)) {
+ this.showLoadSaver();
+ }
+ } else if (this.settings.enableCtrlS === 'false') {
+ this.emitShortLinkEvent();
+ } else if (this.settings.enableCtrlS === '2') {
+ this.runFormatDocumentAction();
+ } else if (this.settings.enableCtrlS === '3') {
+ this.handleCtrlSDoNothing();
+ }
+ }
+ }
+
+ handleCtrlSDoNothing(): void {
+ if (this.nothingCtrlSTimes === undefined) {
+ this.nothingCtrlSTimes = 0;
+ this.nothingCtrlSSince = Date.now();
+ } else {
+ if (Date.now() - (this.nothingCtrlSSince ?? 0) > 5000) {
+ this.nothingCtrlSTimes = undefined;
+ } else if (this.nothingCtrlSTimes === 4) {
+ const element = this.domRoot.find('.ctrlSNothing');
+ element.show(100);
+ setTimeout(function () {
+ element.hide();
+ }, 2000);
+ this.nothingCtrlSTimes = undefined;
+ } else {
+ this.nothingCtrlSTimes++;
+ }
+ }
+ }
+
+ updateButtons(): void {
+ if (options.thirdPartyIntegrationEnabled) {
+ if (this.currentLanguage?.id === 'c++') {
+ this.cppInsightsButton.show();
+ this.quickBenchButton.show();
+ } else {
+ this.cppInsightsButton.hide();
+ this.quickBenchButton.hide();
+ }
+ }
+
+ this.addExecutorButton.prop('disabled', !this.currentLanguage?.supportsExecute);
+ }
+
+ b64UTFEncode(str: string): string {
+ return Buffer.from(
+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, v) => {
+ return String.fromCharCode(parseInt(v, 16));
+ })
+ ).toString('base64');
+ }
+
+ asciiEncodeJsonText(json: string): string {
+ return json.replace(/[\u007F-\uFFFF]/g, chr => {
+ // json unicode escapes must always be 4 characters long, so pad with leading zeros
+ return '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substring(-4);
+ });
+ }
+
+ getCompilerStates(): any[] {
+ const states: any[] = [];
+
+ for (const compilerIdStr of Object.keys(this.ourCompilers)) {
+ const compilerId = parseInt(compilerIdStr);
+
+ const glCompiler = _.find(this.container.layoutManager.root.getComponentsByName('compiler'), function (c) {
+ return c.id === compilerId;
+ });
+
+ if (glCompiler) {
+ const state = glCompiler.currentState();
+ states.push(state);
+ }
+ }
+
+ return states;
+ }
+
+ updateOpenInCppInsights(): void {
+ if (options.thirdPartyIntegrationEnabled) {
+ let cppStd = 'cpp2a';
+
+ const compilers = this.getCompilerStates();
+ compilers.forEach(compiler => {
+ if (compiler.options.indexOf('-std=c++11') !== -1 || compiler.options.indexOf('-std=gnu++11') !== -1) {
+ cppStd = 'cpp11';
+ } else if (
+ compiler.options.indexOf('-std=c++14') !== -1 ||
+ compiler.options.indexOf('-std=gnu++14') !== -1
+ ) {
+ cppStd = 'cpp14';
+ } else if (
+ compiler.options.indexOf('-std=c++17') !== -1 ||
+ compiler.options.indexOf('-std=gnu++17') !== -1
+ ) {
+ cppStd = 'cpp17';
+ } else if (
+ compiler.options.indexOf('-std=c++2a') !== -1 ||
+ compiler.options.indexOf('-std=gnu++2a') !== -1
+ ) {
+ cppStd = 'cpp2a';
+ } else if (compiler.options.indexOf('-std=c++98') !== -1) {
+ cppStd = 'cpp98';
+ }
+ });
+
+ const maxURL = 8177; // apache's default maximum url length
+ const maxCode = maxURL - ('/lnk?code=&std=' + cppStd + '&rev=1.0').length;
+ let codeData = this.b64UTFEncode(this.getSource() ?? '');
+ if (codeData.length > maxCode) {
+ codeData = this.b64UTFEncode('/** Source too long to fit in a URL */\n');
+ }
+
+ const link = 'https://cppinsights.io/lnk?code=' + codeData + '&std=' + cppStd + '&rev=1.0';
+
+ this.cppInsightsButton.attr('href', link);
+ }
+ }
+
+ cleanupSemVer(semver: string): string | null {
+ if (semver) {
+ const semverStr = semver.toString();
+ if (semverStr !== '' && semverStr.indexOf('(') === -1) {
+ const vercomps = semverStr.split('.');
+ return vercomps[0] + '.' + (vercomps[1] ? vercomps[1] : '0');
+ }
+ }
+
+ return null;
+ }
+
+ updateOpenInQuickBench(): void {
+ if (options.thirdPartyIntegrationEnabled) {
+ type QuickBenchState = {
+ text?: string;
+ compiler?: string;
+ optim?: string;
+ cppVersion?: string;
+ lib?: string;
+ };
+
+ const quickBenchState: QuickBenchState = {
+ text: this.getSource(),
+ };
+
+ const compilers = this.getCompilerStates();
+
+ compilers.forEach(compiler => {
+ let knownCompiler = false;
+
+ const compilerExtInfo = this.hub.compilerService.findCompiler(
+ this.currentLanguage?.id ?? '',
+ compiler.compiler
+ );
+ const semver = this.cleanupSemVer(compilerExtInfo.semver);
+ let groupOrName = compilerExtInfo.baseName || compilerExtInfo.groupName || compilerExtInfo.name;
+ if (semver && groupOrName) {
+ groupOrName = groupOrName.toLowerCase();
+ if (groupOrName.indexOf('gcc') !== -1) {
+ quickBenchState.compiler = 'gcc-' + semver;
+ knownCompiler = true;
+ } else if (groupOrName.indexOf('clang') !== -1) {
+ quickBenchState.compiler = 'clang-' + semver;
+ knownCompiler = true;
+ }
+ }
+
+ if (knownCompiler) {
+ const match = compiler.options.match(/-(O([0-3sg]|fast))/);
+ if (match !== null) {
+ if (match[2] === 'fast') {
+ quickBenchState.optim = 'F';
+ } else {
+ quickBenchState.optim = match[2].toUpperCase();
+ }
+ }
+
+ if (
+ compiler.options.indexOf('-std=c++11') !== -1 ||
+ compiler.options.indexOf('-std=gnu++11') !== -1
+ ) {
+ quickBenchState.cppVersion = '11';
+ } else if (
+ compiler.options.indexOf('-std=c++14') !== -1 ||
+ compiler.options.indexOf('-std=gnu++14') !== -1
+ ) {
+ quickBenchState.cppVersion = '14';
+ } else if (
+ compiler.options.indexOf('-std=c++17') !== -1 ||
+ compiler.options.indexOf('-std=gnu++17') !== -1
+ ) {
+ quickBenchState.cppVersion = '17';
+ } else if (
+ compiler.options.indexOf('-std=c++2a') !== -1 ||
+ compiler.options.indexOf('-std=gnu++2a') !== -1
+ ) {
+ quickBenchState.cppVersion = '20';
+ }
+
+ if (compiler.options.indexOf('-stdlib=libc++') !== -1) {
+ quickBenchState.lib = 'llvm';
+ }
+ }
+ });
+
+ const link =
+ 'https://quick-bench.com/#' +
+ Buffer.from(this.asciiEncodeJsonText(JSON.stringify(quickBenchState))).toString('base64');
+ this.quickBenchButton.attr('href', link);
+ }
+ }
+
+ changeLanguage(newLang: string): void {
+ if (newLang === 'cmake' && languages.cmake) {
+ this.selectize.addOption(languages.cmake);
+ }
+ this.selectize.setValue(newLang);
+ }
+
+ clearLinkedLine() {
+ this.decorations.linkedCode = [];
+ this.updateDecorations();
+ }
+
+ tryPanesLinkLine(thisLineNumber: number, column: number, reveal: boolean): void {
+ const selectedToken = this.getTokenSpan(thisLineNumber, column);
+ for (const compilerId of Object.keys(this.asmByCompiler)) {
+ this.eventHub.emit(
+ 'panesLinkLine',
+ Number(compilerId),
+ thisLineNumber,
+ selectedToken.colBegin,
+ selectedToken.colEnd,
+ reveal,
+ this.id + ''
+ );
+ }
+ }
+
+ requestCompilation(): void {
+ this.eventHub.emit('requestCompilation', this.id, false);
+ if (this.settings.formatOnCompile) {
+ this.runFormatDocumentAction();
+ }
+
+ this.hub.trees.forEach(tree => {
+ if (tree.multifileService.isEditorPartOfProject(this.id)) {
+ this.eventHub.emit('requestCompilation', this.id, tree.id);
+ }
+ });
+ }
+
+ initEditorActions(): void {
+ this.editor.addAction({
+ id: 'compile',
+ label: 'Compile',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
+ keybindingContext: undefined,
+ contextMenuGroupId: 'navigation',
+ contextMenuOrder: 1.5,
+ run: () => {
+ // This change request is mostly superfluous
+ this.maybeEmitChange();
+ this.requestCompilation();
+ },
+ });
+
+ this.revealJumpStackHasElementsCtxKey = this.editor.createContextKey('hasRevealJumpStackElements', false);
+
+ this.editor.addAction({
+ id: 'returnfromreveal',
+ label: 'Return from reveal jump',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
+ contextMenuGroupId: 'navigation',
+ contextMenuOrder: 1.4,
+ precondition: 'hasRevealJumpStackElements',
+ run: () => {
+ this.popAndRevealJump();
+ },
+ });
+
+ this.editor.addAction({
+ id: 'toggleCompileOnChange',
+ label: 'Toggle compile on change',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
+ keybindingContext: undefined,
+ run: () => {
+ this.eventHub.emit('modifySettings', {
+ compileOnChange: !this.settings.compileOnChange,
+ });
+ this.alertSystem.notify(
+ 'Compile on change has been toggled ' + (this.settings.compileOnChange ? 'ON' : 'OFF'),
+ {
+ group: 'togglecompile',
+ alertClass: this.settings.compileOnChange ? 'notification-on' : 'notification-off',
+ dismissTime: 3000,
+ }
+ );
+ },
+ });
+
+ this.editor.addAction({
+ id: 'toggleColourisation',
+ label: 'Toggle colourisation',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.F1],
+ keybindingContext: undefined,
+ run: () => {
+ this.eventHub.emit('modifySettings', {
+ colouriseAsm: !this.settings.colouriseAsm,
+ });
+ },
+ });
+
+ this.editor.addAction({
+ id: 'viewasm',
+ label: 'Reveal linked code',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10],
+ keybindingContext: undefined,
+ contextMenuGroupId: 'navigation',
+ contextMenuOrder: 1.5,
+ run: ed => {
+ const pos = ed.getPosition();
+ if (pos != null) {
+ this.tryPanesLinkLine(pos.lineNumber, pos.column, true);
+ }
+ },
+ });
+
+ this.isCpp = this.editor.createContextKey('isCpp', true);
+ this.isCpp.set(this.currentLanguage?.id === 'c++');
+
+ this.isClean = this.editor.createContextKey('isClean', true);
+ this.isClean.set(this.currentLanguage?.id === 'clean');
+
+ this.editor.addAction({
+ id: 'cpprefsearch',
+ label: 'Search on Cppreference',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8],
+ keybindingContext: undefined,
+ contextMenuGroupId: 'help',
+ contextMenuOrder: 1.5,
+ precondition: 'isCpp',
+ run: this.searchOnCppreference.bind(this),
+ });
+
+ this.editor.addAction({
+ id: 'clooglesearch',
+ label: 'Search on Cloogle',
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8],
+ keybindingContext: undefined,
+ contextMenuGroupId: 'help',
+ contextMenuOrder: 1.5,
+ precondition: 'isClean',
+ run: this.searchOnCloogle.bind(this),
+ });
+
+ this.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.F9, () => {
+ this.runFormatDocumentAction();
+ });
+
+ this.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyD, () => {
+ this.editor.getAction('editor.action.duplicateSelection').run();
+ });
+ }
+
+ emitShortLinkEvent(): void {
+ if (this.settings.enableSharingPopover) {
+ this.eventHub.emit('displaySharingPopover');
+ } else {
+ this.eventHub.emit('copyShortLinkToClip');
+ }
+ }
+
+ runFormatDocumentAction(): void {
+ this.editor.getAction('editor.action.formatDocument').run();
+ }
+
+ searchOnCppreference(ed: monaco.editor.ICodeEditor): void {
+ const pos = ed.getPosition();
+ if (!pos || !ed.getModel()) return;
+ const word = ed.getModel()?.getWordAtPosition(pos);
+ if (!word || !word.word) return;
+ const preferredLanguage = this.getPreferredLanguageTag();
+ // This list comes from the footer of the page
+ const cpprefLangs = ['ar', 'cs', 'de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'pl', 'pt', 'ru', 'tr', 'zh'];
+ // If navigator.languages is supported, we could be a bit more clever and look for a match there too
+ let langTag = 'en';
+ if (cpprefLangs.indexOf(preferredLanguage) !== -1) {
+ langTag = preferredLanguage;
+ }
+ const url = 'https://' + langTag + '.cppreference.com/mwiki/index.php?search=' + encodeURIComponent(word.word);
+ window.open(url, '_blank', 'noopener');
+ }
+
+ searchOnCloogle(ed: monaco.editor.ICodeEditor): void {
+ const pos = ed.getPosition();
+ if (!pos || !ed.getModel()) return;
+ const word = ed.getModel()?.getWordAtPosition(pos);
+ if (!word || !word.word) return;
+ const url = 'https://cloogle.org/#' + encodeURIComponent(word.word);
+ window.open(url, '_blank', 'noopener');
+ }
+
+ getPreferredLanguageTag(): string {
+ let result = 'en';
+ let lang = 'en';
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (navigator) {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (navigator.languages && navigator.languages.length) {
+ lang = navigator.languages[0];
+ } else if (navigator.language) {
+ lang = navigator.language;
+ }
+ }
+ // navigator.language[s] is supposed to return strings, but hey, you never know
+ if (lang !== result && _.isString(lang)) {
+ const primaryLanguageSubtagIdx = lang.indexOf('-');
+ result = lang.substring(0, primaryLanguageSubtagIdx).toLowerCase();
+ }
+ return result;
+ }
+
+ doesMatchEditor(otherSource?: string): boolean {
+ return otherSource === this.getSource();
+ }
+
+ confirmOverwrite(yes: () => void): void {
+ this.alertSystem.ask(
+ 'Changes were made to the code',
+ 'Changes were made to the code while it was being processed. Overwrite changes?',
+ {yes: yes, no: undefined}
+ );
+ }
+
+ updateSource(newSource: string): void {
+ // Create something that looks like an edit operation for the whole text
+ const operation = {
+ range: this.editor.getModel()?.getFullModelRange(),
+ forceMoveMarkers: true,
+ text: newSource,
+ };
+ const nullFn = () => {
+ return null;
+ };
+
+ const viewState = this.editor.saveViewState();
+ // Add an undo stop so we don't go back further than expected
+ this.editor.pushUndoStop();
+ // Apply de edit. Note that we lose cursor position, but I've not found a better alternative yet
+ // @ts-expect-error: See above comment maybe
+ this.editor.getModel()?.pushEditOperations(viewState?.cursorState ?? null, [operation], nullFn);
+ this.numberUsedLines();
+
+ if (!this.awaitingInitialResults) {
+ if (this.selection) {
+ /*
+ * this setTimeout is a really crap workaround to fix #2150
+ * the TL;DR; is that we reach this point *before* GL has laid
+ * out the window, so we have no height
+ *
+ * If we revealLinesInCenter at this point the editor "does the right thing"
+ * and scrolls itself all the way to the line we requested.
+ *
+ * Unfortunately the editor thinks it is very small, so the "center"
+ * is the first line, and when the editor does resize eventually things are off.
+ *
+ * The workaround is to just delay things "long enough"
+ *
+ * This is bad and I feel bad.
+ */
+ setTimeout(() => {
+ if (this.selection) {
+ this.editor.setSelection(this.selection);
+ this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber);
+ }
+ }, 500);
+ }
+ this.awaitingInitialResults = true;
+ }
+ }
+
+ formatCurrentText(): void {
+ const previousSource = this.getSource();
+ const lang = this.currentLanguage;
+
+ if (!Object.prototype.hasOwnProperty.call(lang, 'formatter')) {
+ return this.alertSystem.notify('This language does not support in-editor formatting', {
+ group: 'formatting',
+ alertClass: 'notification-error',
+ });
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: window.location.origin + this.httpRoot + 'api/format/' + lang?.formatter,
+ dataType: 'json', // Expected
+ contentType: 'application/json', // Sent
+ data: JSON.stringify({
+ source: previousSource,
+ base: this.settings.formatBase,
+ }),
+ success: result => {
+ if (result.exit === 0) {
+ if (this.doesMatchEditor(previousSource)) {
+ this.updateSource(result.answer);
+ } else {
+ this.confirmOverwrite(this.updateSource.bind(this, result.answer));
+ }
+ } else {
+ // Ops, the formatter itself failed!
+ this.alertSystem.notify('We encountered an error formatting your code: ' + result.answer, {
+ group: 'formatting',
+ alertClass: 'notification-error',
+ });
+ }
+ },
+ error: (xhr, e_status, error) => {
+ // Hopefully we have not exploded!
+ if (xhr.responseText) {
+ try {
+ const res = JSON.parse(xhr.responseText);
+ error = res.answer || error;
+ } catch (e) {
+ // continue regardless of error
+ }
+ }
+ error = error || 'Unknown error';
+ this.alertSystem.notify('We ran into some issues while formatting your code: ' + error, {
+ group: 'formatting',
+ alertClass: 'notification-error',
+ });
+ },
+ cache: true,
+ });
+ }
+
+ override resize(): void {
+ super.resize();
+
+ // Only update the options if needed
+ if (this.settings.wordWrap) {
+ this.editor.updateOptions({
+ wordWrapColumn: this.editor.getLayoutInfo().viewportColumn,
+ });
+ }
+ }
+
+ override onSettingsChange(newSettings: SiteSettings): void {
+ const before = this.settings;
+ const after = newSettings;
+ this.settings = _.clone(newSettings);
+
+ this.editor.updateOptions({
+ autoIndent: this.settings.autoIndent ? 'advanced' : 'none',
+ // @ts-expect-error: boolean is not assignable to editor.EditorAutoClosingStrategy
+ autoClosingBrackets: this.settings.autoCloseBrackets,
+ useVim: this.settings.useVim,
+ quickSuggestions: this.settings.showQuickSuggestions,
+ contextmenu: this.settings.useCustomContextMenu,
+ minimap: {
+ enabled: this.settings.showMinimap && !options.embedded,
+ },
+ fontFamily: this.settings.editorsFFont,
+ fontLigatures: this.settings.editorsFLigatures,
+ wordWrap: this.settings.wordWrap ? 'bounded' : 'off',
+ wordWrapColumn: this.editor.getLayoutInfo().viewportColumn, // Ensure the column count is up to date
+ });
+
+ // Unconditionally send editor changes. The compiler only compiles when needed
+ this.debouncedEmitChange = _.debounce(() => {
+ this.maybeEmitChange();
+ }, after.delayAfterChange);
+
+ if (before.hoverShowSource && !after.hoverShowSource) {
+ this.onEditorSetDecoration(this.id, -1, false);
+ }
+
+ if (after.useVim && !before.useVim) {
+ this.enableVim();
+ } else if (!after.useVim && before.useVim) {
+ this.disableVim();
+ }
+
+ this.editor.getModel()?.updateOptions({
+ tabSize: this.settings.tabWidth,
+ insertSpaces: this.settings.useSpaces,
+ });
+
+ this.numberUsedLines();
+ }
+
+ numberUsedLines(): void {
+ if (_.any(this.busyCompilers)) return;
+
+ if (!this.settings.colouriseAsm) {
+ this.updateColours([]);
+ return;
+ }
+
+ if (this.hub.hasTree()) {
+ return;
+ }
+
+ const result: Record = {};
+ // First, note all lines used.
+ for (const [compilerId, asm] of Object.entries(this.asmByCompiler)) {
+ asm?.forEach(asmLine => {
+ let foundInTrees = false;
+
+ for (const [treeId, compilerIds] of Object.entries(this.treeCompilers)) {
+ if (compilerIds && compilerIds[compilerId]) {
+ const tree = this.hub.getTreeById(Number(treeId));
+ if (tree) {
+ const defaultFile = this.defaultFileByCompiler[compilerId];
+ foundInTrees = true;
+
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ if (asmLine.source && asmLine.source.line > 0) {
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ const sourcefilename = asmLine.source.file ? asmLine.source.file : defaultFile;
+ if (this.id === tree.multifileService.getEditorIdByFilename(sourcefilename)) {
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ result[asmLine.source.line - 1] = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!foundInTrees) {
+ if (
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ asmLine.source &&
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ (asmLine.source.file === null || asmLine.source.mainsource) &&
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ asmLine.source.line > 0
+ ) {
+ // @ts-expect-error: Property 'source' does not exist on type 'ResultLine'
+ result[asmLine.source.line - 1] = true;
+ }
+ }
+ });
+ }
+ // Now assign an ordinal to each used line.
+ let ordinal = 0;
+ Object.keys(result).forEach(k => {
+ result[k] = ordinal++;
+ });
+
+ this.updateColours(result);
+ }
+
+ updateColours(colours) {
+ this.colours = colour.applyColours(this.editor, colours, this.settings.colourScheme, this.colours);
+ this.eventHub.emit('colours', this.id, colours, this.settings.colourScheme);
+ }
+
+ onCompilerOpen(compilerId: number, editorId: number, treeId: number | boolean): void {
+ if (editorId === this.id) {
+ // On any compiler open, rebroadcast our state in case they need to know it.
+ if (this.waitingForLanguage) {
+ const glCompiler = _.find(
+ this.container.layoutManager.root.getComponentsByName('compiler'),
+ function (c) {
+ return c.id === compilerId;
+ }
+ );
+ if (glCompiler) {
+ const selected = options.compilers.find(compiler => {
+ return compiler.id === glCompiler.originalCompilerId;
+ });
+ if (selected) {
+ this.changeLanguage(selected.lang);
+ }
+ }
+ }
+
+ if (typeof treeId === 'number' && treeId > 0) {
+ if (!this.treeCompilers[treeId]) {
+ this.treeCompilers[treeId] = {};
+ }
+
+ // @ts-expect-error: this.treeCompilers[treeId] is never undefined at this point
+ this.treeCompilers[treeId][compilerId] = true;
+ }
+ this.ourCompilers[compilerId] = true;
+
+ if (!treeId) {
+ this.maybeEmitChange(true, compilerId);
+ }
+ }
+ }
+
+ onTreeCompilerEditorIncludeChange(treeId: number, editorId: number, compilerId: number): void {
+ if (this.id === editorId) {
+ this.onCompilerOpen(compilerId, editorId, treeId);
+ }
+ }
+
+ onTreeCompilerEditorExcludeChange(treeId: number, editorId: number, compilerId: number): void {
+ if (this.id === editorId) {
+ this.onCompilerClose(compilerId);
+ }
+ }
+
+ onColoursForEditor(editorId: number, colours: Record, scheme: string): void {
+ if (this.id === editorId) {
+ this.colours = colour.applyColours(this.editor, colours, scheme, this.colours);
+ }
+ }
+
+ onExecutorOpen(executorId: number, editorId: boolean | number): void {
+ if (editorId === this.id) {
+ this.maybeEmitChange(true);
+ this.ourExecutors[executorId] = true;
+ }
+ }
+
+ override onCompilerClose(compilerId: number): void {
+ /*if (this.treeCompilers[treeId]) {
+ delete this.treeCompilers[treeId][compilerId];
+ }*/
+
+ if (this.ourCompilers[compilerId]) {
+ const model = this.editor.getModel();
+ if (model) monaco.editor.setModelMarkers(model, String(compilerId), []);
+ delete this.asmByCompiler[compilerId];
+ delete this.busyCompilers[compilerId];
+ delete this.ourCompilers[compilerId];
+ delete this.defaultFileByCompiler[compilerId];
+ this.numberUsedLines();
+ }
+ }
+
+ onExecutorClose(id: number): void {
+ if (this.ourExecutors[id]) {
+ delete this.ourExecutors[id];
+ const model = this.editor.getModel();
+ if (model) monaco.editor.setModelMarkers(model, 'Executor ' + id, []);
+ }
+ }
+
+ onCompiling(compilerId: number): void {
+ if (!this.ourCompilers[compilerId]) return;
+ this.busyCompilers[compilerId] = true;
+ }
+
+ addSource(arr: (ResultLine & {source?: string})[] | undefined, source: string): (ResultLine & {source: string})[] {
+ arr?.forEach(element => {
+ element.source = source;
+ });
+
+ return (arr as (ResultLine & {source: string})[] | undefined) ?? [];
+ }
+
+ getAllOutputAndErrors(
+ result: CompilationResult,
+ compilerName: string,
+ compilerId: number | string
+ ): (ResultLine & {source: string})[] {
+ const compilerTitle = compilerName + ' #' + compilerId;
+ let all = this.addSource(result.stdout, compilerTitle);
+
+ // @ts-expect-error: Property 'buildsteps' does not exist on type 'CompilationResult'
+ if (result.buildsteps) {
+ // @ts-expect-error: Property 'buildsteps' does not exist on type 'CompilationResult'
+ _.each(result.buildsteps, step => {
+ all = all.concat(this.addSource(step.stdout, compilerTitle));
+ all = all.concat(this.addSource(step.stderr, compilerTitle));
+ });
+ }
+ if (result.tools) {
+ _.each(result.tools, tool => {
+ all = all.concat(this.addSource(tool.stdout, tool.name + ' #' + compilerId));
+ all = all.concat(this.addSource(tool.stderr, tool.name + ' #' + compilerId));
+ });
+ }
+ all = all.concat(this.addSource(result.stderr, compilerTitle));
+
+ return all;
+ }
+
+ collectOutputWidgets(output: (ResultLine & {source: string})[]): {
+ fixes: monaco.languages.CodeAction[];
+ widgets: editor.IMarkerData[];
+ } {
+ let fixes: monaco.languages.CodeAction[] = [];
+ const editorModel = this.editor.getModel();
+ const widgets = _.compact(
+ output.map(obj => {
+ if (!obj.tag) return;
+
+ const trees = this.hub.trees;
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (trees && trees.length > 0) {
+ if (obj.tag.file) {
+ if (this.id !== trees[0].multifileService.getEditorIdByFilename(obj.tag.file)) {
+ return;
+ }
+ } else {
+ if (this.id !== trees[0].multifileService.getMainSourceEditorId()) {
+ return;
+ }
+ }
+ }
+
+ let colBegin = 0;
+ let colEnd = Infinity;
+ let lineBegin = obj.tag.line;
+ let lineEnd = obj.tag.line;
+ if (obj.tag.column) {
+ if (obj.tag.endcolumn) {
+ colBegin = obj.tag.column;
+ colEnd = obj.tag.endcolumn;
+ lineBegin = obj.tag.line;
+ lineEnd = obj.tag.endline;
+ } else {
+ const span = this.getTokenSpan(obj.tag.line ?? 0, obj.tag.column);
+ colBegin = obj.tag.column;
+ colEnd = span.colEnd;
+ if (colEnd === obj.tag.column) colEnd = -1;
+ }
+ }
+ let link;
+ if (obj.tag.link) {
+ link = {
+ value: obj.tag.link.text,
+ target: obj.tag.link.url,
+ };
+ }
+
+ const diag: monaco.editor.IMarkerData = {
+ severity: obj.tag.severity,
+ message: obj.tag.text,
+ source: obj.source,
+ startLineNumber: lineBegin ?? 0,
+ startColumn: colBegin,
+ endLineNumber: lineEnd ?? 0,
+ endColumn: colEnd,
+ code: link,
+ };
+
+ if (obj.tag.fixes && editorModel) {
+ fixes = fixes.concat(
+ obj.tag.fixes.map((fs, ind) => {
+ return {
+ title: fs.title,
+ diagnostics: [diag],
+ kind: 'quickfix',
+ edit: {
+ edits: fs.edits.map(f => {
+ return {
+ resource: editorModel.uri,
+ edit: {
+ range: new monaco.Range(
+ f.line ?? 0,
+ f.column ?? 0,
+ f.endline ?? 0,
+ f.endcolumn ?? 0
+ ),
+ text: f.text,
+ },
+ };
+ }),
+ },
+ isPreferred: ind === 0,
+ };
+ })
+ );
+ }
+ return diag;
+ })
+ );
+
+ return {
+ fixes: fixes,
+ widgets: widgets,
+ };
+ }
+
+ setDecorationTags(widgets: editor.IMarkerData[], ownerId: string): void {
+ const editorModel = this.editor.getModel();
+ if (editorModel) monaco.editor.setModelMarkers(editorModel, ownerId, widgets);
+
+ this.decorations.tags = _.map(
+ widgets,
+ function (tag) {
+ return {
+ range: new monaco.Range(tag.startLineNumber, tag.startColumn, tag.startLineNumber + 1, 1),
+ options: {
+ isWholeLine: false,
+ inlineClassName: 'error-code',
+ },
+ };
+ },
+ this
+ );
+
+ this.updateDecorations();
+ }
+
+ setQuickFixes(fixes: monaco.languages.CodeAction[]): void {
+ if (fixes.length) {
+ const editorModel = this.editor.getModel();
+ if (editorModel) {
+ quickFixesHandler.registerQuickFixesForCompiler(this.id, editorModel, fixes);
+ quickFixesHandler.registerProviderForLanguage(editorModel.getLanguageId());
+ }
+ } else {
+ quickFixesHandler.unregister(this.id);
+ }
+ }
+
+ override onCompileResult(compilerId: number, compiler: CompilerInfo, result: CompilationResult): void {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (!compiler || !this.ourCompilers[compilerId]) return;
+
+ this.busyCompilers[compilerId] = false;
+
+ const collectedOutput = this.collectOutputWidgets(
+ this.getAllOutputAndErrors(result, compiler.name, compilerId)
+ );
+
+ this.setDecorationTags(collectedOutput.widgets, String(compilerId));
+ this.setQuickFixes(collectedOutput.fixes);
+
+ // @ts-expect-error: result has no property 'result'
+ if (result.result && result.result.asm) {
+ // @ts-expect-error: result has no property 'result'
+ this.asmByCompiler[compilerId] = result.result.asm;
+ } else {
+ this.asmByCompiler[compilerId] = result.asm;
+ }
+
+ if (result.inputFilename) {
+ this.defaultFileByCompiler[compilerId] = result.inputFilename;
+ } else {
+ this.defaultFileByCompiler[compilerId] = 'example' + this.currentLanguage?.extensions[0];
+ }
+
+ this.numberUsedLines();
+ }
+
+ onExecuteResponse(executorId: number, compiler: CompilerInfo, result: CompilationResult): void {
+ if (this.ourExecutors[executorId]) {
+ let output = this.getAllOutputAndErrors(result, compiler.name, 'Execution ' + executorId);
+ if (result.buildResult) {
+ output = output.concat(
+ // @ts-expect-error: buildResult is 'unknown'
+ this.getAllOutputAndErrors(result.buildResult, compiler.name, 'Executor ' + executorId)
+ );
+ }
+ this.setDecorationTags(this.collectOutputWidgets(output).widgets, 'Executor ' + executorId);
+
+ this.numberUsedLines();
+ }
+ }
+
+ onSelectLine(id: number, lineNum: number): void {
+ if (Number(id) === this.id) {
+ this.editor.setSelection(new monaco.Selection(lineNum - 1, 0, lineNum, 0));
+ }
+ }
+
+ // Returns a half-segment [a, b) for the token on the line lineNum
+ // that spans across the column.
+ // a - colStart points to the first character of the token
+ // b - colEnd points to the character immediately following the token
+ // e.g.: "this->callableMethod ( x, y );"
+ // ^a ^column ^b
+ getTokenSpan(lineNum: number, column: number): {colBegin: number; colEnd: number} {
+ const model = this.editor.getModel();
+ if (model && (lineNum < 1 || lineNum > model.getLineCount())) {
+ // #3592 Be forgiving towards parsing errors
+ return {colBegin: 0, colEnd: 0};
+ }
+
+ if (model && lineNum <= model.getLineCount()) {
+ const line = model.getLineContent(lineNum);
+ if (0 < column && column <= line.length) {
+ const tokens = monaco.editor.tokenize(line, model.getLanguageId());
+ if (tokens.length > 0) {
+ let lastOffset = 0;
+ let lastWasString = false;
+ for (let i = 0; i < tokens[0].length; ++i) {
+ // Treat all the contiguous string tokens as one,
+ // For example "hello \" world" is treated as one token
+ // instead of 3 "string.cpp", "string.escape.cpp", "string.cpp"
+ if (tokens[0][i].type.startsWith('string')) {
+ if (lastWasString) {
+ continue;
+ }
+ lastWasString = true;
+ } else {
+ lastWasString = false;
+ }
+ const currentOffset = tokens[0][i].offset;
+ if (column <= currentOffset) {
+ return {colBegin: lastOffset + 1, colEnd: currentOffset + 1};
+ } else {
+ lastOffset = currentOffset;
+ }
+ }
+ return {colBegin: lastOffset + 1, colEnd: line.length + 1};
+ }
+ }
+ }
+ return {colBegin: column, colEnd: column + 1};
+ }
+
+ pushRevealJump(): void {
+ const state = this.editor.saveViewState();
+ if (state) this.revealJumpStack.push(state);
+ this.revealJumpStackHasElementsCtxKey.set(true);
+ }
+
+ popAndRevealJump(): void {
+ if (this.revealJumpStack.length > 0) {
+ const state = this.revealJumpStack.pop();
+ if (state) this.editor.restoreViewState(state);
+ this.revealJumpStackHasElementsCtxKey.set(this.revealJumpStack.length > 0);
+ }
+ }
+
+ onEditorLinkLine(editorId: number, lineNum: number, columnBegin: number, columnEnd: number, reveal: boolean): void {
+ if (Number(editorId) === this.id) {
+ if (reveal && lineNum) {
+ this.pushRevealJump();
+ this.hub.activateTabForContainer(this.container);
+ this.editor.revealLineInCenter(lineNum);
+ }
+ this.decorations.linkedCode = [];
+ if (lineNum && lineNum !== -1) {
+ this.decorations.linkedCode.push({
+ range: new monaco.Range(lineNum, 1, lineNum, 1),
+ options: {
+ isWholeLine: true,
+ linesDecorationsClassName: 'linked-code-decoration-margin',
+ className: 'linked-code-decoration-line',
+ },
+ });
+ }
+
+ if (lineNum > 0 && columnBegin !== -1) {
+ const lastTokenSpan = this.getTokenSpan(lineNum, columnEnd);
+ this.decorations.linkedCode.push({
+ range: new monaco.Range(lineNum, columnBegin, lineNum, lastTokenSpan.colEnd),
+ options: {
+ isWholeLine: false,
+ inlineClassName: 'linked-code-decoration-column',
+ },
+ });
+ }
+
+ if (this.fadeTimeoutId !== null) {
+ clearTimeout(this.fadeTimeoutId);
+ }
+ this.fadeTimeoutId = setTimeout(() => {
+ this.clearLinkedLine();
+ this.fadeTimeoutId = null;
+ }, 5000);
+
+ this.updateDecorations();
+ }
+ }
+
+ onEditorSetDecoration(id: number, lineNum: number, reveal: boolean, column?: number): void {
+ if (Number(id) === this.id) {
+ if (reveal && lineNum) {
+ this.pushRevealJump();
+ this.editor.revealLineInCenter(lineNum);
+ this.editor.focus();
+ this.editor.setPosition({column: column || 0, lineNumber: lineNum});
+ }
+ this.decorations.linkedCode = [];
+ if (lineNum && lineNum !== -1) {
+ this.decorations.linkedCode.push({
+ range: new monaco.Range(lineNum, 1, lineNum, 1),
+ options: {
+ isWholeLine: true,
+ linesDecorationsClassName: 'linked-code-decoration-margin',
+ inlineClassName: 'linked-code-decoration-inline',
+ },
+ });
+ }
+ this.updateDecorations();
+ }
+ }
+
+ onEditorDisplayFlow(id: number, flow: MessageWithLocation[]): void {
+ if (Number(id) === this.id) {
+ if (this.decorations.flows && this.decorations.flows.length) {
+ this.decorations.flows = [];
+ } else {
+ this.decorations.flows = flow.map((ri, ind) => {
+ return {
+ range: new monaco.Range(
+ ri.line ?? 0,
+ ri.column ?? 0,
+ (ri.endline || ri.line) ?? 0,
+ (ri.endcolumn || ri.column) ?? 0
+ ),
+ options: {
+ before: {
+ content: ' ' + (ind + 1).toString() + ' ',
+ inlineClassName: 'flow-decoration',
+ cursorStops: monaco.editor.InjectedTextCursorStops.None,
+ },
+ inlineClassName: 'flow-highlight',
+ isWholeLine: false,
+ hoverMessage: {value: ri.text},
+ },
+ };
+ });
+ }
+ this.updateDecorations();
+ }
+ }
+
+ updateDecorations(): void {
+ this.prevDecorations = this.editor.deltaDecorations(
+ this.prevDecorations,
+ _.compact(_.flatten(_.values(this.decorations)))
+ );
+ }
+
+ onConformanceViewOpen(editorId: number): void {
+ if (editorId === this.id) {
+ this.conformanceViewerButton.attr('disabled', 1);
+ }
+ }
+
+ onConformanceViewClose(editorId: number): void {
+ if (editorId === this.id) {
+ this.conformanceViewerButton.attr('disabled', null);
+ }
+ }
+
+ showLoadSaver(): void {
+ this.loadSaveButton.trigger('click');
+ }
+
+ initLoadSaver(): void {
+ this.loadSaveButton.off('click').on('click', () => {
+ if (this.currentLanguage) {
+ loadSave.run(
+ (text, filename) => {
+ this.setSource(text);
+ this.setFilename(filename);
+ this.updateState();
+ this.maybeEmitChange(true);
+ this.requestCompilation();
+ },
+ this.getSource(),
+ this.currentLanguage
+ );
+ }
+ });
+ }
+
+ onLanguageChange(newLangId: string): void {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (languages[newLangId]) {
+ if (newLangId !== this.currentLanguage?.id) {
+ const oldLangId = this.currentLanguage?.id;
+ this.currentLanguage = languages[newLangId];
+ if (!this.waitingForLanguage && !this.settings.keepSourcesOnLangChange && newLangId !== 'cmake') {
+ this.editorSourceByLang[oldLangId ?? ''] = this.getSource();
+ this.updateEditorCode();
+ }
+ this.initLoadSaver();
+ const editorModel = this.editor.getModel();
+ if (editorModel && this.currentLanguage)
+ monaco.editor.setModelLanguage(editorModel, this.currentLanguage.monaco);
+ this.isCpp.set(this.currentLanguage?.id === 'c++');
+ this.isClean.set(this.currentLanguage?.id === 'clean');
+ this.updateTitle();
+ this.updateState();
+ // Broadcast the change to other panels
+ this.eventHub.emit('languageChange', this.id, newLangId);
+ this.decorations = {};
+ this.maybeEmitChange(true);
+ this.requestCompilation();
+ ga.proxy('send', {
+ hitType: 'event',
+ eventCategory: 'LanguageChange',
+ eventAction: newLangId,
+ });
+ }
+ this.waitingForLanguage = false;
+ }
+ }
+
+ override getDefaultPaneName(): string {
+ return '';
+ }
+
+ override getPaneName(): string {
+ if (this.filename) {
+ return this.filename;
+ } else {
+ return this.currentLanguage?.name + ' source #' + this.id;
+ }
+ }
+
+ setFilename(name: string): void {
+ this.filename = name;
+ this.updateTitle();
+ this.updateState();
+ }
+
+ override updateTitle(): void {
+ const name = this.getPaneName();
+ const customName = this.paneName ? this.paneName : name;
+ if (name.endsWith('CMakeLists.txt')) {
+ this.changeLanguage('cmake');
+ }
+ this.container.setTitle(_.escape(customName));
+ }
+
+ // Called every time we change language, so we get the relevant code
+ updateEditorCode(): void {
+ this.setSource(
+ this.editorSourceByLang[this.currentLanguage?.id ?? ''] ||
+ languages[this.currentLanguage?.id ?? '']?.example
+ );
+ }
+
+ override close(): void {
+ this.eventHub.unsubscribe();
+ this.eventHub.emit('editorClose', this.id);
+ this.editor.dispose();
+ this.hub.removeEditor(this.id);
+ }
+
+ getSelectizeRenderHtml(data: any, escape: typeof escape_html, width: number, height: number): string {
+ let result =
+ '' +
+ '
' +
+ '
 +
+ ')
';
+ if (data.logoDataDark) {
+ result +=
+ '

';
+ }
+
+ result += '
' + escape(data.name) + '
';
+ return result;
+ }
+
+ renderSelectizeOption(data: any, escape: typeof escape_html) {
+ return this.getSelectizeRenderHtml(data, escape, 23, 23);
+ }
+
+ renderSelectizeItem(data: any, escape: typeof escape_html) {
+ return this.getSelectizeRenderHtml(data, escape, 20, 20);
+ }
+
+ onCompiler(compilerId: number, compiler: unknown, options: string, editorId: number, treeId: number): void {}
+}
diff --git a/static/panes/tree.ts b/static/panes/tree.ts
index 9f37eee80..6505bd44f 100644
--- a/static/panes/tree.ts
+++ b/static/panes/tree.ts
@@ -291,8 +291,8 @@ export class Tree {
if (file) {
file.isOpen = false;
const editor = this.hub.getEditorById(editorId);
- file.langId = editor.currentLanguage.id;
- file.content = editor.getSource();
+ file.langId = editor?.currentLanguage?.id ?? '';
+ file.content = editor?.getSource() ?? '';
file.editorId = -1;
}
@@ -380,7 +380,7 @@ export class Tree {
(file.isIncluded ? this.namedItems : this.unnamedItems).append(item);
}
- private refresh() {
+ refresh() {
this.updateState();
this.namedItems.html('');
@@ -399,7 +399,7 @@ export class Tree {
this.hub.addInEditorStackIfPossible(dragConfig);
} else {
const editor = this.hub.getEditorById(file.editorId);
- this.hub.activateTabForContainer(editor.container);
+ this.hub.activateTabForContainer(editor?.container);
}
this.sendChangesToAllEditors();
diff --git a/static/widgets/load-save.ts b/static/widgets/load-save.ts
index e1f59d700..7b27e0119 100644
--- a/static/widgets/load-save.ts
+++ b/static/widgets/load-save.ts
@@ -257,13 +257,13 @@ export class LoadSave {
}
}
- private setMinimalOptions(editorText: string, currentLanguage: Language) {
+ setMinimalOptions(editorText: string, currentLanguage: Language) {
this.editorText = editorText;
this.currentLanguage = currentLanguage;
this.extension = currentLanguage.extensions[0] || '.txt';
}
- private onSaveToFile(fileEditor?: string) {
+ onSaveToFile(fileEditor?: string) {
try {
const fileLang = this.currentLanguage?.name ?? '';
const name = fileLang && fileEditor !== undefined ? fileLang + ' Editor #' + fileEditor + ' ' : '';