diff --git a/amd/build/ui_ace_gapfiller.min.js b/amd/build/ui_ace_gapfiller.min.js
index 85b70a1c8..3ee068865 100644
--- a/amd/build/ui_ace_gapfiller.min.js
+++ b/amd/build/ui_ace_gapfiller.min.js
@@ -38,6 +38,6 @@
* @copyright Matthew Toohey, 2021, The University of Canterbury
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define("qtype_coderunner/ui_ace_gapfiller",["jquery"],(function($){var Range;const validChars=/[ !"#$%&'()*+,`\-./0-9:;<=>?@A-Z\[\]\\^_a-z{}|~]/;function AceGapfillerUi(textareaId,w,h,uiParams){this.textArea=$(document.getElementById(textareaId));var wrapper=$(document.getElementById(textareaId+"_wrapper")),focused=this.textArea[0]===document.activeElement,lang=uiParams.lang,t=this;let code="";this.uiParams=uiParams,this.gaps=[],this.source=uiParams.ui_source||"globalextra",this.nextGapIndex=0,"globalextra"!==this.source&&"test0"!==this.source&&(alert("Invalid source for code in ui_ace_gapfiller"),this.source="globalextra"),code="globalextra"==this.source?this.textArea.attr("data-globalextra"):this.textArea.attr("data-test0");try{window.ace.require("ace/ext/language_tools"),Range=window.ace.require("ace/range").Range,this.modelist=window.ace.require("ace/ext/modelist"),this.enabled=!1,this.contents_changed=!1,this.capturingTab=!1,this.clickInProgress=!1,this.editNode=$("
"),this.editNode.css({resize:"none",height:h,width:"100%"}),this.editor=window.ace.edit(this.editNode.get(0)),this.textArea.prop("readonly")&&this.editor.setReadOnly(!0),this.editor.setOptions({displayIndentGuides:!1,dragEnabled:!1,enableBasicAutocompletion:!0,newLineMode:"unix"}),this.editor.$blockScrolling=1/0,uiParams.theme?this.editor.setTheme("ace/theme/"+uiParams.theme):this.editor.setTheme("ace/theme/textmate"),this.setLanguage(lang),this.setEventHandlers(this.textArea),this.captureTab(),this.editor.renderer.on("afterRender",(function(){var gutter=wrapper.find(".ace_gutter");gutter.hasClass("moodle-has-zindex")||(gutter.addClass("moodle-has-zindex"),focused&&(t.editor.focus(),t.editor.navigateFileEnd()),t.aceLabel=wrapper.find(".answerprompt"),t.aceLabel.attr("for","ace_"+textareaId),t.aceTextarea=wrapper.find(".ace_text-input"),t.aceTextarea.attr("id","ace_"+textareaId))})),this.createGaps(code),this.editor.commands.on("exec",(function(e){let cursor=t.editor.selection.getCursor(),commandName=e.command.name,selectionRange=t.editor.getSelectionRange(),gap=t.findCursorGap(cursor);if(commandName.startsWith("go")){if(null===gap||"gotoright"!==commandName||cursor.column!==gap.range.start.column+gap.textSize)return;t.editor.moveCursorTo(cursor.row,gap.range.end.column+1)}if(null===gap)"selectall"===commandName&&t.editor.selection.selectAll();else if("indent"===commandName){let nextGap=t.gaps[(gap.index+1)%t.gaps.length];t.editor.moveCursorTo(nextGap.range.start.row,nextGap.range.start.column+nextGap.textSize),t.editor.selection.clearSelection()}else if("selectall"===commandName)t.editor.selection.setSelectionRange(new Range(gap.range.start.row,gap.range.start.column,gap.range.start.row,gap.range.end.column),!1);else if(t.editor.selection.isEmpty()){if("insertstring"===commandName){let char=e.args;validChars.test(char)&&gap.insertChar(t.gaps,cursor,char)}else"backspace"===commandName?cursor.column>gap.range.start.column&&gap.textSize>0&&gap.deleteChar(t.gaps,{row:cursor.row,column:cursor.column-1}):"del"===commandName&&cursor.column0&&gap.deleteChar(t.gaps,cursor);t.editor.selection.clearSelection()}else if(!t.editor.selection.isEmpty()&&gap.cursorInGap(selectionRange.start)&&gap.cursorInGap(selectionRange.end)&&("insertstring"!==commandName&&"backspace"!==commandName&&"del"!==commandName&&"paste"!==commandName&&"cut"!==commandName||(gap.deleteRange(t.gaps,selectionRange.start.column,selectionRange.end.column),t.editor.selection.clearSelection()),"insertstring"===commandName)){let char=e.args;validChars.test(char)&&gap.insertChar(t.gaps,selectionRange.start,char)}null!==gap&&"paste"===commandName&&gap.insertText(t.gaps,selectionRange.start.column,e.args.text),e.preventDefault(),e.stopPropagation()})),t.editor.selection.on("changeCursor",(function(){let cursor=t.editor.selection.getCursor(),gap=t.findCursorGap(cursor);null!==gap&&cursor.column>gap.range.start.column+gap.textSize&&t.editor.moveCursorTo(gap.range.start.row,gap.range.start.column+gap.textSize)})),this.gapToSelect=null,this.editor.on("tripleclick",(function(e){let cursor=t.editor.selection.getCursor(),gap=t.findCursorGap(cursor);null!==gap&&(t.editor.selection.setSelectionRange(new Range(gap.range.start.row,gap.range.start.column,gap.range.start.row,gap.range.end.column),!1),t.gapToSelect=gap,e.preventDefault(),e.stopPropagation())})),this.editor.on("click",(function(e){t.gapToSelect&&(t.editor.moveCursorTo(t.gapToSelect.range.start.row,t.gapToSelect.range.start.column+t.gapToSelect.textSize),t.gapToSelect=null,e.preventDefault(),e.stopPropagation())})),this.fail=!1,this.reload()}catch(err){this.fail=!0}}function Gap(editor,row,column,minWidth){let maxWidth=arguments.length>4&&void 0!==arguments[4]?arguments[4]:1/0;this.editor=editor,this.minWidth=minWidth,this.maxWidth=maxWidth,this.range=new Range(row,column,row,column+minWidth),this.textSize=0,this.editor.session.addMarker(this.range,"ace-gap-outline","text",!0),this.editor.session.addMarker(this.range,"ace-gap-background","text",!1)}return AceGapfillerUi.prototype.createGaps=function(code){function reEscape(s){for(var c,result="",i=0;i1?parseInt(values[1]):1/0,gap=new Gap(this.editor,i,columnPos,minWidth,maxWidth);gap.index=this.nextGapIndex,this.nextGapIndex+=1,this.gaps.push(gap),columnPos+=minWidth,editorContent+=" ".repeat(minWidth),j+12,AceGapfillerUi.prototype.reload=function(){let content=this.textArea.val();if(content)try{let values=JSON.parse(content);for(let i=0;i=this.range.start.row&&cursor.column>=this.range.start.column&&cursor.row<=this.range.end.row&&cursor.column<=this.range.end.column},Gap.prototype.getWidth=function(){return this.range.end.column-this.range.start.column},Gap.prototype.changeWidth=function(gaps,delta){this.range.end.column+=delta;for(let i=0;ithis.range.end.column&&(other.range.start.column+=delta,other.range.end.column+=delta)}this.editor.$onChangeBackMarker(),this.editor.$onChangeFrontMarker()},Gap.prototype.insertChar=function(gaps,pos,char){this.textSize===this.getWidth()&&this.getWidth()=this.minWidth?this.changeWidth(gaps,-1):this.editor.session.insert({row:pos.row,column:this.range.end.column-1}," ")},Gap.prototype.deleteRange=function(gaps,start,end){for(let i=start;i?@\[\]\\^_{}|~]/u;function AceGapfillerUi(textareaId,w,h,uiParams){this.textArea=$(document.getElementById(textareaId));var wrapper=$(document.getElementById(textareaId+"_wrapper")),focused=this.textArea[0]===document.activeElement,lang=uiParams.lang,t=this;let code="";this.uiParams=uiParams,this.gaps=[],this.source=uiParams.ui_source||"globalextra",this.nextGapIndex=0,"globalextra"!==this.source&&"test0"!==this.source&&(alert("Invalid source for code in ui_ace_gapfiller"),this.source="globalextra"),code="globalextra"==this.source?this.textArea.attr("data-globalextra"):this.textArea.attr("data-test0");try{window.ace.require("ace/ext/language_tools"),Range=window.ace.require("ace/range").Range,this.modelist=window.ace.require("ace/ext/modelist"),this.enabled=!1,this.contents_changed=!1,this.capturingTab=!1,this.clickInProgress=!1,this.editNode=$(""),this.editNode.css({resize:"none",height:h,width:"100%"}),this.editor=window.ace.edit(this.editNode.get(0)),this.textArea.prop("readonly")&&this.editor.setReadOnly(!0),this.editor.setOptions({displayIndentGuides:!1,dragEnabled:!1,enableBasicAutocompletion:!0,newLineMode:"unix"}),this.editor.$blockScrolling=1/0,uiParams.theme?this.editor.setTheme("ace/theme/"+uiParams.theme):this.editor.setTheme("ace/theme/textmate"),this.setLanguage(lang),this.setEventHandlers(this.textArea),this.captureTab(),this.editor.renderer.on("afterRender",(function(){var gutter=wrapper.find(".ace_gutter");gutter.hasClass("moodle-has-zindex")||(gutter.addClass("moodle-has-zindex"),focused&&(t.editor.focus(),t.editor.navigateFileEnd()),t.aceLabel=wrapper.find(".answerprompt"),t.aceLabel.attr("for","ace_"+textareaId),t.aceTextarea=wrapper.find(".ace_text-input"),t.aceTextarea.attr("id","ace_"+textareaId))})),this.createGaps(code),this.editor.commands.on("exec",(function(e){let cursor=t.editor.selection.getCursor(),commandName=e.command.name,selectionRange=t.editor.getSelectionRange(),gap=t.findCursorGap(cursor);if(commandName.startsWith("go")){if(null===gap||"gotoright"!==commandName||cursor.column!==gap.range.start.column+gap.textSize)return;t.editor.moveCursorTo(cursor.row,gap.range.end.column+1)}if(null===gap)"selectall"===commandName&&t.editor.selection.selectAll();else if("indent"===commandName){let nextGap=t.gaps[(gap.index+1)%t.gaps.length];t.editor.moveCursorTo(nextGap.range.start.row,nextGap.range.start.column+nextGap.textSize),t.editor.selection.clearSelection()}else if("selectall"===commandName)t.editor.selection.setSelectionRange(new Range(gap.range.start.row,gap.range.start.column,gap.range.start.row,gap.range.end.column),!1);else if(t.editor.selection.isEmpty()){if("insertstring"===commandName){let char=e.args;validChars.test(char)&&gap.insertChar(t.gaps,cursor,char)}else"backspace"===commandName?cursor.column>gap.range.start.column&&gap.textSize>0&&gap.deleteChar(t.gaps,{row:cursor.row,column:cursor.column-1}):"del"===commandName&&cursor.column0&&gap.deleteChar(t.gaps,cursor);t.editor.selection.clearSelection()}else if(!t.editor.selection.isEmpty()&&gap.cursorInGap(selectionRange.start)&&gap.cursorInGap(selectionRange.end)&&("insertstring"!==commandName&&"backspace"!==commandName&&"del"!==commandName&&"paste"!==commandName&&"cut"!==commandName||(gap.deleteRange(t.gaps,selectionRange.start.column,selectionRange.end.column),t.editor.selection.clearSelection()),"insertstring"===commandName)){let char=e.args;validChars.test(char)&&gap.insertChar(t.gaps,selectionRange.start,char)}null!==gap&&"paste"===commandName&&gap.insertText(t.gaps,selectionRange.start.column,e.args.text),e.preventDefault(),e.stopPropagation()})),t.editor.selection.on("changeCursor",(function(){let cursor=t.editor.selection.getCursor(),gap=t.findCursorGap(cursor);null!==gap&&cursor.column>gap.range.start.column+gap.textSize&&t.editor.moveCursorTo(gap.range.start.row,gap.range.start.column+gap.textSize)})),this.gapToSelect=null,this.editor.on("tripleclick",(function(e){let cursor=t.editor.selection.getCursor(),gap=t.findCursorGap(cursor);null!==gap&&(t.editor.selection.setSelectionRange(new Range(gap.range.start.row,gap.range.start.column,gap.range.start.row,gap.range.end.column),!1),t.gapToSelect=gap,e.preventDefault(),e.stopPropagation())})),this.editor.on("click",(function(e){t.gapToSelect&&(t.editor.moveCursorTo(t.gapToSelect.range.start.row,t.gapToSelect.range.start.column+t.gapToSelect.textSize),t.gapToSelect=null,e.preventDefault(),e.stopPropagation())})),this.fail=!1,this.reload()}catch(err){this.fail=!0}}function Gap(editor,row,column,minWidth){let maxWidth=arguments.length>4&&void 0!==arguments[4]?arguments[4]:1/0;this.editor=editor,this.minWidth=minWidth,this.maxWidth=maxWidth,this.range=new Range(row,column,row,column+minWidth),this.textSize=0,this.editor.session.addMarker(this.range,"ace-gap-outline","text",!0),this.editor.session.addMarker(this.range,"ace-gap-background","text",!1)}return AceGapfillerUi.prototype.createGaps=function(code){function reEscape(s){for(var c,result="",i=0;i1?parseInt(values[1]):1/0,gap=new Gap(this.editor,i,columnPos,minWidth,maxWidth);gap.index=this.nextGapIndex,this.nextGapIndex+=1,this.gaps.push(gap),columnPos+=minWidth,editorContent+=" ".repeat(minWidth),j+12,AceGapfillerUi.prototype.reload=function(){let content=this.textArea.val();if(content)try{let values=JSON.parse(content);for(let i=0;i=this.range.start.row&&cursor.column>=this.range.start.column&&cursor.row<=this.range.end.row&&cursor.column<=this.range.end.column},Gap.prototype.getWidth=function(){return this.range.end.column-this.range.start.column},Gap.prototype.changeWidth=function(gaps,delta){this.range.end.column+=delta;for(let i=0;ithis.range.end.column&&(other.range.start.column+=delta,other.range.end.column+=delta)}this.editor.$onChangeBackMarker(),this.editor.$onChangeFrontMarker()},Gap.prototype.insertChar=function(gaps,pos,char){this.textSize===this.getWidth()&&this.getWidth()=this.minWidth?this.changeWidth(gaps,-1):this.editor.session.insert({row:pos.row,column:this.range.end.column-1}," ")},Gap.prototype.deleteRange=function(gaps,start,end){for(let i=start;i.\n\n/**\n * Implementation of the ace_gapfiller_ui user interface plugin. For overall details\n * of the UI plugin architecture, see userinterfacewrapper.js.\n *\n * This plugin uses the usual ace editor but only makes some portions of the text editable.\n * The pre-formatted text is supplied by the question author in either the\n * \"globalextra\" field or the testcode field of the first test case, according\n * to the ui parameter ui_source (default: globalextra).\n * Editable \"gaps\" are inserted into the ace editor at specified points.\n * It is intended primarily for use with coding questions where the answerbox presents\n * the students with code that has smallish bits missing.\n *\n * The locations within the globalextra text at which the gaps are\n * to be inserted are denoted by \"tags\" of the form\n *\n * {[ size ]}\n *\n * or\n *\n * {[ size-maxSize ]}\n *\n * where size and maxSize are integer literals. These respectively inject a \"gap\" into\n * the editor of the specified size and maxSize. If maxSize is not specified then the\n * \"gap\" has no maximum size and can grow without bound.\n *\n * The serialisation of the answer box contents, i.e. the text that\n * copied back into the textarea for submissions\n * as the answer, is simply a list of all the field values (strings), in order.\n *\n * As a special case of the serialisation, if the value list is empty, the\n * serialisation itself is the empty string.\n *\n * The delimiters for the gap tags are by default '{[' and\n * ']}'.\n *\n * @module qtype_coderunner/ui_ace_gapfiller\n * @copyright Richard Lobb, 2019, The University of Canterbury\n * @copyright Matthew Toohey, 2021, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n var Range; // Can't load this until ace has loaded.\n const fillChar = \" \";\n const validChars = /[ !\"#$%&'()*+,`\\-./0-9:;<=>?@A-Z\\[\\]\\\\^_a-z{}|~]/;\n const ACE_LIGHT_THEME = 'ace/theme/textmate';\n\n /**\n * Constructor for the Ace interface object\n * @param {string} textareaId The ID of the textarea html element.\n * @param {int} w The width of the text area in pixels.\n * @param {int} h The height of the text area in pixels.\n * @param {object} uiParams The UI parameter specifier object.\n */\n function AceGapfillerUi(textareaId, w, h, uiParams) {\n this.textArea = $(document.getElementById(textareaId));\n var wrapper = $(document.getElementById(textareaId + '_wrapper')),\n focused = this.textArea[0] === document.activeElement,\n lang = uiParams.lang,\n t = this; // For embedded callbacks.\n\n let code = \"\";\n this.uiParams = uiParams;\n this.gaps = [];\n this.source = uiParams.ui_source || 'globalextra';\n this.nextGapIndex = 0;\n if (this.source !== 'globalextra' && this.source !== 'test0') {\n alert('Invalid source for code in ui_ace_gapfiller');\n this.source = 'globalextra';\n }\n if (this.source == 'globalextra') {\n code = this.textArea.attr('data-globalextra');\n } else {\n code = this.textArea.attr('data-test0');\n }\n\n try {\n window.ace.require(\"ace/ext/language_tools\");\n Range = window.ace.require(\"ace/range\").Range;\n this.modelist = window.ace.require('ace/ext/modelist');\n\n this.enabled = false;\n this.contents_changed = false;\n this.capturingTab = false;\n this.clickInProgress = false;\n\n this.editNode = $(\"\"); // Ace editor manages this\n this.editNode.css({\n resize: 'none',\n height: h,\n width: \"100%\"\n });\n\n this.editor = window.ace.edit(this.editNode.get(0));\n if (this.textArea.prop('readonly')) {\n this.editor.setReadOnly(true);\n }\n\n this.editor.setOptions({\n displayIndentGuides: false,\n dragEnabled: false,\n enableBasicAutocompletion: true,\n newLineMode: \"unix\",\n });\n this.editor.$blockScrolling = Infinity;\n\n // Use the uiParams theme if provided else use light.\n if (uiParams.theme) {\n this.editor.setTheme(\"ace/theme/\" + uiParams.theme);\n } else {\n this.editor.setTheme(ACE_LIGHT_THEME);\n }\n\n this.setLanguage(lang);\n\n this.setEventHandlers(this.textArea);\n this.captureTab();\n\n // Try to tell Moodle about parts of the editor with z-index.\n // It is hard to be sure if this is complete. ACE adds all its CSS using JavaScript.\n // Here, we just deal with things that are known to cause a problem.\n // Can't do these operations until editor has rendered. So ...\n this.editor.renderer.on('afterRender', function() {\n var gutter = wrapper.find('.ace_gutter');\n if (gutter.hasClass('moodle-has-zindex')) {\n return; // So we only do what follows once.\n }\n gutter.addClass('moodle-has-zindex');\n\n if (focused) {\n t.editor.focus();\n t.editor.navigateFileEnd();\n }\n t.aceLabel = wrapper.find('.answerprompt');\n t.aceLabel.attr('for', 'ace_' + textareaId);\n\n t.aceTextarea = wrapper.find('.ace_text-input');\n t.aceTextarea.attr('id', 'ace_' + textareaId);\n });\n\n this.createGaps(code);\n\n // Intercept commands sent to ace.\n this.editor.commands.on(\"exec\", function(e) {\n let cursor = t.editor.selection.getCursor();\n let commandName = e.command.name;\n let selectionRange = t.editor.getSelectionRange();\n\n let gap = t.findCursorGap(cursor);\n\n if (commandName.startsWith(\"go\")) { // If command just moves the cursor then do nothing.\n if (gap !== null && commandName === \"gotoright\" && cursor.column === gap.range.start.column+gap.textSize) {\n // In this case we jump out of gap over the empty space that contains nothing that the user has entered.\n t.editor.moveCursorTo(cursor.row, gap.range.end.column+1);\n } else {\n return;\n }\n }\n\n if (gap === null) {\n // Not in a gap\n if (commandName === \"selectall\") {\n t.editor.selection.selectAll();\n }\n\n } else if (commandName === \"indent\") {\n // Instead of indenting, move to next gap.\n let nextGap = t.gaps[(gap.index+1) % t.gaps.length];\n t.editor.moveCursorTo(nextGap.range.start.row, nextGap.range.start.column+nextGap.textSize);\n t.editor.selection.clearSelection(); // Clear selection.\n\n } else if (commandName === \"selectall\") {\n // Select all text in a gap if we are in a gap.\n t.editor.selection.setSelectionRange(new Range(gap.range.start.row,\n gap.range.start.column,\n gap.range.start.row,\n gap.range.end.column), false);\n\n } else if (t.editor.selection.isEmpty()) {\n // User is not selecting multiple characters.\n if (commandName === \"insertstring\") {\n let char = e.args;\n // Only allow user to insert 'valid' chars.\n if (validChars.test(char)) {\n gap.insertChar(t.gaps, cursor, char);\n }\n } else if (commandName === \"backspace\") {\n // Only delete chars that are actually in the gap.\n if (cursor.column > gap.range.start.column && gap.textSize > 0) {\n gap.deleteChar(t.gaps, {row: cursor.row, column: cursor.column-1});\n }\n } else if (commandName === \"del\") {\n // Only delete chars that are actually in the gap.\n if (cursor.column < gap.range.start.column + gap.textSize && gap.textSize > 0) {\n gap.deleteChar(t.gaps, cursor);\n }\n }\n t.editor.selection.clearSelection(); // Keep selection clear.\n\n } else if (!t.editor.selection.isEmpty() && gap.cursorInGap(selectionRange.start)\n && gap.cursorInGap(selectionRange.end)) {\n // User is selecting multiple characters and is in a gap.\n\n // These are the commands that remove the selected text.\n if (commandName === \"insertstring\" || commandName === \"backspace\"\n || commandName === \"del\" || commandName === \"paste\"\n || commandName === \"cut\") {\n\n gap.deleteRange(t.gaps, selectionRange.start.column, selectionRange.end.column);\n t.editor.selection.clearSelection(); // Clear selection.\n }\n\n if (commandName === \"insertstring\") {\n let char = e.args;\n if (validChars.test(char)) {\n gap.insertChar(t.gaps, selectionRange.start, char);\n }\n }\n }\n\n // Paste text into gap.\n if (gap !== null && commandName === \"paste\") {\n gap.insertText(t.gaps, selectionRange.start.column, e.args.text);\n }\n\n e.preventDefault();\n e.stopPropagation();\n });\n\n // Move cursor to where it should be if we click on a gap.\n t.editor.selection.on('changeCursor', function() {\n let cursor = t.editor.selection.getCursor();\n let gap = t.findCursorGap(cursor);\n if (gap !== null) {\n if (cursor.column > gap.range.start.column+gap.textSize) {\n t.editor.moveCursorTo(gap.range.start.row, gap.range.start.column+gap.textSize);\n }\n }\n });\n\n this.gapToSelect = null; // Stores gap that has been selected with triple click.\n\n // Select all text in gap on triple click within gap.\n this.editor.on(\"tripleclick\", function(e) {\n let cursor = t.editor.selection.getCursor();\n let gap = t.findCursorGap(cursor);\n if (gap !== null) {\n t.editor.selection.setSelectionRange(new Range(gap.range.start.row,\n gap.range.start.column,\n gap.range.start.row,\n gap.range.end.column), false);\n t.gapToSelect = gap;\n e.preventDefault();\n e.stopPropagation();\n }\n });\n\n // Annoying hack to ensure the tripple click thing works.\n this.editor.on(\"click\", function(e) {\n if (t.gapToSelect) {\n t.editor.moveCursorTo(t.gapToSelect.range.start.row, t.gapToSelect.range.start.column+t.gapToSelect.textSize);\n t.gapToSelect = null;\n e.preventDefault();\n e.stopPropagation();\n }\n });\n\n this.fail = false;\n this.reload();\n }\n catch(err) {\n // Something ugly happened. Probably ace editor hasn't been loaded\n this.fail = true;\n }\n }\n\n /**\n * The method that creates the gaps at all places containing the appropriate\n * marker (default {[ ... ]}).\n * Do not call until after this.editor has been instantiated.\n * @param {string} code The initial raw text code\n */\n AceGapfillerUi.prototype.createGaps = function(code) {\n this.gaps = [];\n /**\n * Escape special characters in a given string.\n * @param {string} s The input string.\n * @returns {string} The updated string, with escaped specials.\n */\n function reEscape(s) {\n var c, specials = '{[(*+\\\\', result='';\n for (var i = 0; i < s.length; i++) {\n c = s[i];\n for (var j = 0; j < specials.length; j++) {\n if (c === specials[j]) {\n c = '\\\\' + c;\n }\n }\n result += c;\n }\n return result;\n }\n\n let lines = code.split(/\\r?\\n/);\n\n let sepLeft = reEscape('{[');\n let sepRight = reEscape(']}');\n let splitter = new RegExp(sepLeft + ' *((?:\\\\d+)|(?:\\\\d+- *\\\\d+)) *' + sepRight);\n\n let editorContent = \"\";\n for (let i = 0; i < lines.length; i++) {\n let bits = lines[i].split(splitter);\n editorContent += bits[0];\n\n let columnPos = bits[0].length;\n for (let j = 1; j < bits.length; j += 2) {\n let values = bits[j].split('-');\n let minWidth = parseInt(values[0]);\n let maxWidth = (values.length > 1 ? parseInt(values[1]) : Infinity);\n\n // Create new gap.\n let gap = new Gap(this.editor, i, columnPos, minWidth, maxWidth);\n gap.index = this.nextGapIndex;\n this.nextGapIndex += 1;\n this.gaps.push(gap);\n\n columnPos += minWidth;\n editorContent += ' '.repeat(minWidth);\n if (j + 1 < bits.length) {\n editorContent += bits[j+1];\n columnPos += bits[j+1].length;\n }\n\n }\n\n if (i < lines.length-1) {\n editorContent += '\\n';\n }\n }\n this.editor.session.setValue(editorContent);\n };\n\n /**\n * Return the gap that the cursor is in. This will actually return a gap if\n * the cursor is 1 outside the gap as this will be needed for\n * backspace/insertion to work. Rigth now this is done as a simple\n * linear search but could be improved later.\n * @param {object} cursor The ace editor cursor position.\n * @returns {object} The gap that the cursor is current in, or null otherwise.\n */\n AceGapfillerUi.prototype.findCursorGap = function(cursor) {\n for (let i=0; i < this.gaps.length; i++) {\n let gap = this.gaps[i];\n if (gap.cursorInGap(cursor)) {\n return gap;\n }\n }\n return null;\n };\n\n AceGapfillerUi.prototype.failed = function() {\n return this.fail;\n };\n\n AceGapfillerUi.prototype.failMessage = function() {\n return 'ace_ui_notready';\n };\n\n\n // Sync to TextArea\n AceGapfillerUi.prototype.sync = function() {\n if (this.fail) {\n return; // Leave the text area alone if Ace load failed.\n }\n let serialisation = []; // A list of field values.\n let empty = true;\n\n for (let i=0; i < this.gaps.length; i++) {\n let gap = this.gaps[i];\n let value = gap.getText();\n serialisation.push(value);\n if (value !== \"\") {\n empty = false;\n }\n }\n if (empty) {\n this.textArea.val('');\n } else {\n this.textArea.val(JSON.stringify(serialisation));\n }\n };\n\n // Sync every 2 seconds in case quiz closes automatically without user\n // action.\n AceGapfillerUi.prototype.syncIntervalSecs = (() => 2);\n\n // Reload the HTML fields from the given serialisation.\n AceGapfillerUi.prototype.reload = function() {\n let content = this.textArea.val();\n if (content) {\n try {\n let values = JSON.parse(content);\n for (let i = 0; i < this.gaps.length; i++) {\n let value = i < values.length ? values[i]: '???';\n this.gaps[i].insertText(this.gaps, this.gaps[i].range.start.column, value);\n }\n } catch(e) {\n // Just ignore errors\n }\n }\n };\n\n AceGapfillerUi.prototype.setLanguage = function(language) {\n var session = this.editor.getSession(),\n mode = this.findMode(language);\n if (mode) {\n session.setMode(mode.mode);\n }\n };\n\n AceGapfillerUi.prototype.getElement = function() {\n return this.editNode;\n };\n\n AceGapfillerUi.prototype.captureTab = function () {\n this.capturingTab = true;\n this.editor.commands.bindKeys({'Tab': 'indent', 'Shift-Tab': 'outdent'});\n };\n\n AceGapfillerUi.prototype.releaseTab = function () {\n this.capturingTab = false;\n this.editor.commands.bindKeys({'Tab': null, 'Shift-Tab': null});\n };\n\n AceGapfillerUi.prototype.setEventHandlers = function () {\n var TAB = 9,\n ESC = 27,\n KEY_M = 77,\n t = this;\n\n this.editor.getSession().on('change', function() {\n t.contents_changed = true;\n });\n\n this.editor.on('blur', function() {\n if (t.contents_changed) {\n t.textArea.trigger('change');\n }\n });\n\n this.editor.on('mousedown', function() {\n // Event order seems to be (\\ is where the mouse button is pressed, / released):\n // Chrome: \\ mousedown, mouseup, focusin / click.\n // Firefox/IE: \\ mousedown, focusin / mouseup, click.\n t.clickInProgress = true;\n });\n\n this.editor.on('focus', function() {\n if (t.clickInProgress) {\n t.captureTab();\n } else {\n t.releaseTab();\n }\n });\n\n this.editor.on('click', function() {\n t.clickInProgress = false;\n });\n\n this.editor.container.addEventListener('keydown', function(e) {\n if (e.which === undefined || e.which !== 0) { // Normal keypress?\n if (e.keyCode === KEY_M && e.ctrlKey && !e.altKey) {\n if (t.capturingTab) {\n t.releaseTab();\n } else {\n t.captureTab();\n }\n e.preventDefault(); // Firefox uses this for mute audio in current browser tab.\n }\n else if (e.keyCode === ESC) {\n t.releaseTab();\n }\n else if (!(e.shiftKey || e.ctrlKey || e.altKey || e.keyCode == TAB)) {\n t.captureTab();\n }\n }\n }, true);\n };\n\n AceGapfillerUi.prototype.destroy = function () {\n this.sync();\n var focused;\n if (!this.fail) {\n // Proceed only if this wrapper was correctly constructed\n focused = this.editor.isFocused();\n this.editor.destroy();\n $(this.editNode).remove();\n if (focused) {\n this.textArea.focus();\n this.textArea[0].selectionStart = this.textArea[0].value.length;\n }\n }\n };\n\n AceGapfillerUi.prototype.hasFocus = function() {\n return this.editor.isFocused();\n };\n\n AceGapfillerUi.prototype.findMode = function (language) {\n var candidate,\n filename,\n result,\n candidates = [], // List of candidate modes.\n nameMap = {\n 'octave': 'matlab',\n 'nodejs': 'javascript',\n 'c#': 'cs'\n };\n\n if (typeof language !== 'string') {\n return undefined;\n }\n if (language.toLowerCase() in nameMap) {\n language = nameMap[language.toLowerCase()];\n }\n\n candidates = [language, language.replace(/\\d+$/, \"\")];\n for (var i = 0; i < candidates.length; i++) {\n candidate = candidates[i];\n filename = \"input.\" + candidate;\n result = this.modelist.modesByName[candidate] ||\n this.modelist.modesByName[candidate.toLowerCase()] ||\n this.modelist.getModeForPath(filename) ||\n this.modelist.getModeForPath(filename.toLowerCase());\n\n if (result && result.name !== 'text') {\n return result;\n }\n }\n return undefined;\n };\n\n AceGapfillerUi.prototype.resize = function(w, h) {\n this.editNode.outerHeight(h);\n this.editNode.outerWidth(w);\n this.editor.resize();\n };\n\n /**\n * Constructor for the Gap object that represents a gap in the source code\n * that the user is expected to fill.\n * @param {object} editor The Ace Editor object.\n * @param {int} row The row within the text of the gap.\n * @param {int} column The column within the text of the gap.\n * @param {int} minWidth The minimum width (in characters) of the gap.\n * @param {int} maxWidth The maximum width (in characters) of the gap.\n */\n function Gap(editor, row, column, minWidth, maxWidth=Infinity) {\n this.editor = editor;\n\n this.minWidth = minWidth;\n this.maxWidth = maxWidth;\n\n this.range = new Range(row, column, row, column+minWidth);\n this.textSize = 0;\n\n // Create markers\n this.editor.session.addMarker(this.range, \"ace-gap-outline\", \"text\", true);\n this.editor.session.addMarker(this.range, \"ace-gap-background\", \"text\", false);\n }\n\n Gap.prototype.cursorInGap = function(cursor) {\n return (cursor.row >= this.range.start.row && cursor.column >= this.range.start.column &&\n cursor.row <= this.range.end.row && cursor.column <= this.range.end.column);\n };\n\n Gap.prototype.getWidth = function() {\n return (this.range.end.column-this.range.start.column);\n };\n\n Gap.prototype.changeWidth = function(gaps, delta) {\n this.range.end.column += delta;\n\n // Update any gaps that come after this one on the same line\n for (let i=0; i < gaps.length; i++) {\n let other = gaps[i];\n if (other.range.start.row === this.range.start.row && other.range.start.column > this.range.end.column) {\n other.range.start.column += delta;\n other.range.end.column += delta;\n }\n }\n\n this.editor.$onChangeBackMarker();\n this.editor.$onChangeFrontMarker();\n };\n\n Gap.prototype.insertChar = function(gaps, pos, char) {\n if (this.textSize === this.getWidth() && this.getWidth() < this.maxWidth) { // Grow the size of gap and insert char.\n this.changeWidth(gaps, 1);\n this.textSize += 1; // Important to record that texSize has increased before insertion.\n this.editor.session.insert(pos, char);\n } else if (this.textSize < this.maxWidth) { // Insert char.\n this.editor.session.remove(new Range(pos.row, this.range.end.column-1, pos.row, this.range.end.column));\n this.textSize += 1; // Important to record that texSize has increased before insertion.\n this.editor.session.insert(pos, char);\n }\n };\n\n Gap.prototype.deleteChar = function(gaps, pos) {\n this.textSize -= 1;\n this.editor.session.remove(new Range(pos.row, pos.column, pos.row, pos.column+1));\n\n if (this.textSize >= this.minWidth) {\n this.changeWidth(gaps, -1); // Shrink the size of the gap.\n } else {\n // Put new space at end so everything is shifted across.\n this.editor.session.insert({row: pos.row, column: this.range.end.column-1}, fillChar);\n }\n };\n\n Gap.prototype.deleteRange = function(gaps, start, end) {\n for (let i = start; i < end; i++) {\n if (start < this.range.start.column+this.textSize) {\n this.deleteChar(gaps, {row: this.range.start.row, column: start});\n }\n }\n };\n\n Gap.prototype.insertText = function(gaps, start, text) {\n for (let i = 0; i < text.length; i++) {\n if (start+i < this.range.start.column+this.maxWidth) {\n this.insertChar(gaps, {row: this.range.start.row, column: start+i}, text[i]);\n }\n }\n };\n\n Gap.prototype.getText = function() {\n return this.editor.session.getTextRange(new Range(this.range.start.row, this.range.start.column,\n this.range.end.row, this.range.start.column+this.textSize));\n\n };\n\n return {\n Constructor: AceGapfillerUi\n };\n});\n"],"names":["define","$","Range","validChars","AceGapfillerUi","textareaId","w","h","uiParams","textArea","document","getElementById","wrapper","focused","this","activeElement","lang","t","code","gaps","source","ui_source","nextGapIndex","alert","attr","window","ace","require","modelist","enabled","contents_changed","capturingTab","clickInProgress","editNode","css","resize","height","width","editor","edit","get","prop","setReadOnly","setOptions","displayIndentGuides","dragEnabled","enableBasicAutocompletion","newLineMode","$blockScrolling","Infinity","theme","setTheme","setLanguage","setEventHandlers","captureTab","renderer","on","gutter","find","hasClass","addClass","focus","navigateFileEnd","aceLabel","aceTextarea","createGaps","commands","e","cursor","selection","getCursor","commandName","command","name","selectionRange","getSelectionRange","gap","findCursorGap","startsWith","column","range","start","textSize","moveCursorTo","row","end","selectAll","nextGap","index","length","clearSelection","setSelectionRange","isEmpty","char","args","test","insertChar","deleteChar","cursorInGap","deleteRange","insertText","text","preventDefault","stopPropagation","gapToSelect","fail","reload","err","Gap","minWidth","maxWidth","session","addMarker","prototype","reEscape","s","c","result","i","j","lines","split","sepLeft","sepRight","splitter","RegExp","editorContent","bits","columnPos","values","parseInt","push","repeat","setValue","failed","failMessage","sync","serialisation","empty","value","getText","val","JSON","stringify","syncIntervalSecs","content","parse","language","getSession","mode","findMode","setMode","getElement","bindKeys","releaseTab","trigger","container","addEventListener","undefined","which","keyCode","ctrlKey","altKey","shiftKey","destroy","isFocused","remove","selectionStart","hasFocus","candidate","filename","candidates","nameMap","toLowerCase","replace","modesByName","getModeForPath","outerHeight","outerWidth","getWidth","changeWidth","delta","other","$onChangeBackMarker","$onChangeFrontMarker","pos","insert","getTextRange","Constructor"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ui_ace_gapfiller.min.js","sources":["../src/ui_ace_gapfiller.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more util.details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implementation of the ace_gapfiller_ui user interface plugin. For overall details\n * of the UI plugin architecture, see userinterfacewrapper.js.\n *\n * This plugin uses the usual ace editor but only makes some portions of the text editable.\n * The pre-formatted text is supplied by the question author in either the\n * \"globalextra\" field or the testcode field of the first test case, according\n * to the ui parameter ui_source (default: globalextra).\n * Editable \"gaps\" are inserted into the ace editor at specified points.\n * It is intended primarily for use with coding questions where the answerbox presents\n * the students with code that has smallish bits missing.\n *\n * The locations within the globalextra text at which the gaps are\n * to be inserted are denoted by \"tags\" of the form\n *\n * {[ size ]}\n *\n * or\n *\n * {[ size-maxSize ]}\n *\n * where size and maxSize are integer literals. These respectively inject a \"gap\" into\n * the editor of the specified size and maxSize. If maxSize is not specified then the\n * \"gap\" has no maximum size and can grow without bound.\n *\n * The serialisation of the answer box contents, i.e. the text that\n * copied back into the textarea for submissions\n * as the answer, is simply a list of all the field values (strings), in order.\n *\n * As a special case of the serialisation, if the value list is empty, the\n * serialisation itself is the empty string.\n *\n * The delimiters for the gap tags are by default '{[' and\n * ']}'.\n *\n * @module qtype_coderunner/ui_ace_gapfiller\n * @copyright Richard Lobb, 2019, The University of Canterbury\n * @copyright Matthew Toohey, 2021, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n var Range; // Can't load this until ace has loaded.\n const fillChar = \" \";\n const validChars = /[ !\"#$%&'()*+,`\\-./0-9\\p{L}:;<=>?@\\[\\]\\\\^_{}|~]/u;\n const ACE_LIGHT_THEME = 'ace/theme/textmate';\n\n /**\n * Constructor for the Ace interface object\n * @param {string} textareaId The ID of the textarea html element.\n * @param {int} w The width of the text area in pixels.\n * @param {int} h The height of the text area in pixels.\n * @param {object} uiParams The UI parameter specifier object.\n */\n function AceGapfillerUi(textareaId, w, h, uiParams) {\n this.textArea = $(document.getElementById(textareaId));\n var wrapper = $(document.getElementById(textareaId + '_wrapper')),\n focused = this.textArea[0] === document.activeElement,\n lang = uiParams.lang,\n t = this; // For embedded callbacks.\n\n let code = \"\";\n this.uiParams = uiParams;\n this.gaps = [];\n this.source = uiParams.ui_source || 'globalextra';\n this.nextGapIndex = 0;\n if (this.source !== 'globalextra' && this.source !== 'test0') {\n alert('Invalid source for code in ui_ace_gapfiller');\n this.source = 'globalextra';\n }\n if (this.source == 'globalextra') {\n code = this.textArea.attr('data-globalextra');\n } else {\n code = this.textArea.attr('data-test0');\n }\n\n try {\n window.ace.require(\"ace/ext/language_tools\");\n Range = window.ace.require(\"ace/range\").Range;\n this.modelist = window.ace.require('ace/ext/modelist');\n\n this.enabled = false;\n this.contents_changed = false;\n this.capturingTab = false;\n this.clickInProgress = false;\n\n this.editNode = $(\"\"); // Ace editor manages this\n this.editNode.css({\n resize: 'none',\n height: h,\n width: \"100%\"\n });\n\n this.editor = window.ace.edit(this.editNode.get(0));\n if (this.textArea.prop('readonly')) {\n this.editor.setReadOnly(true);\n }\n\n this.editor.setOptions({\n displayIndentGuides: false,\n dragEnabled: false,\n enableBasicAutocompletion: true,\n newLineMode: \"unix\",\n });\n this.editor.$blockScrolling = Infinity;\n\n // Use the uiParams theme if provided else use light.\n if (uiParams.theme) {\n this.editor.setTheme(\"ace/theme/\" + uiParams.theme);\n } else {\n this.editor.setTheme(ACE_LIGHT_THEME);\n }\n\n this.setLanguage(lang);\n\n this.setEventHandlers(this.textArea);\n this.captureTab();\n\n // Try to tell Moodle about parts of the editor with z-index.\n // It is hard to be sure if this is complete. ACE adds all its CSS using JavaScript.\n // Here, we just deal with things that are known to cause a problem.\n // Can't do these operations until editor has rendered. So ...\n this.editor.renderer.on('afterRender', function() {\n var gutter = wrapper.find('.ace_gutter');\n if (gutter.hasClass('moodle-has-zindex')) {\n return; // So we only do what follows once.\n }\n gutter.addClass('moodle-has-zindex');\n\n if (focused) {\n t.editor.focus();\n t.editor.navigateFileEnd();\n }\n t.aceLabel = wrapper.find('.answerprompt');\n t.aceLabel.attr('for', 'ace_' + textareaId);\n\n t.aceTextarea = wrapper.find('.ace_text-input');\n t.aceTextarea.attr('id', 'ace_' + textareaId);\n });\n\n this.createGaps(code);\n\n // Intercept commands sent to ace.\n this.editor.commands.on(\"exec\", function(e) {\n let cursor = t.editor.selection.getCursor();\n let commandName = e.command.name;\n let selectionRange = t.editor.getSelectionRange();\n\n let gap = t.findCursorGap(cursor);\n\n if (commandName.startsWith(\"go\")) { // If command just moves the cursor then do nothing.\n if (gap !== null && commandName === \"gotoright\" && cursor.column === gap.range.start.column+gap.textSize) {\n // In this case we jump out of gap over the empty space that contains nothing that the user has entered.\n t.editor.moveCursorTo(cursor.row, gap.range.end.column+1);\n } else {\n return;\n }\n }\n\n if (gap === null) {\n // Not in a gap\n if (commandName === \"selectall\") {\n t.editor.selection.selectAll();\n }\n\n } else if (commandName === \"indent\") {\n // Instead of indenting, move to next gap.\n let nextGap = t.gaps[(gap.index+1) % t.gaps.length];\n t.editor.moveCursorTo(nextGap.range.start.row, nextGap.range.start.column+nextGap.textSize);\n t.editor.selection.clearSelection(); // Clear selection.\n\n } else if (commandName === \"selectall\") {\n // Select all text in a gap if we are in a gap.\n t.editor.selection.setSelectionRange(new Range(gap.range.start.row,\n gap.range.start.column,\n gap.range.start.row,\n gap.range.end.column), false);\n\n } else if (t.editor.selection.isEmpty()) {\n // User is not selecting multiple characters.\n if (commandName === \"insertstring\") {\n let char = e.args;\n // Only allow user to insert 'valid' chars.\n if (validChars.test(char)) {\n gap.insertChar(t.gaps, cursor, char);\n }\n } else if (commandName === \"backspace\") {\n // Only delete chars that are actually in the gap.\n if (cursor.column > gap.range.start.column && gap.textSize > 0) {\n gap.deleteChar(t.gaps, {row: cursor.row, column: cursor.column-1});\n }\n } else if (commandName === \"del\") {\n // Only delete chars that are actually in the gap.\n if (cursor.column < gap.range.start.column + gap.textSize && gap.textSize > 0) {\n gap.deleteChar(t.gaps, cursor);\n }\n }\n t.editor.selection.clearSelection(); // Keep selection clear.\n\n } else if (!t.editor.selection.isEmpty() && gap.cursorInGap(selectionRange.start)\n && gap.cursorInGap(selectionRange.end)) {\n // User is selecting multiple characters and is in a gap.\n\n // These are the commands that remove the selected text.\n if (commandName === \"insertstring\" || commandName === \"backspace\"\n || commandName === \"del\" || commandName === \"paste\"\n || commandName === \"cut\") {\n\n gap.deleteRange(t.gaps, selectionRange.start.column, selectionRange.end.column);\n t.editor.selection.clearSelection(); // Clear selection.\n }\n\n if (commandName === \"insertstring\") {\n let char = e.args;\n if (validChars.test(char)) {\n gap.insertChar(t.gaps, selectionRange.start, char);\n }\n }\n }\n\n // Paste text into gap.\n if (gap !== null && commandName === \"paste\") {\n gap.insertText(t.gaps, selectionRange.start.column, e.args.text);\n }\n\n e.preventDefault();\n e.stopPropagation();\n });\n\n // Move cursor to where it should be if we click on a gap.\n t.editor.selection.on('changeCursor', function() {\n let cursor = t.editor.selection.getCursor();\n let gap = t.findCursorGap(cursor);\n if (gap !== null) {\n if (cursor.column > gap.range.start.column+gap.textSize) {\n t.editor.moveCursorTo(gap.range.start.row, gap.range.start.column+gap.textSize);\n }\n }\n });\n\n this.gapToSelect = null; // Stores gap that has been selected with triple click.\n\n // Select all text in gap on triple click within gap.\n this.editor.on(\"tripleclick\", function(e) {\n let cursor = t.editor.selection.getCursor();\n let gap = t.findCursorGap(cursor);\n if (gap !== null) {\n t.editor.selection.setSelectionRange(new Range(gap.range.start.row,\n gap.range.start.column,\n gap.range.start.row,\n gap.range.end.column), false);\n t.gapToSelect = gap;\n e.preventDefault();\n e.stopPropagation();\n }\n });\n\n // Annoying hack to ensure the tripple click thing works.\n this.editor.on(\"click\", function(e) {\n if (t.gapToSelect) {\n t.editor.moveCursorTo(t.gapToSelect.range.start.row, t.gapToSelect.range.start.column+t.gapToSelect.textSize);\n t.gapToSelect = null;\n e.preventDefault();\n e.stopPropagation();\n }\n });\n\n this.fail = false;\n this.reload();\n }\n catch(err) {\n // Something ugly happened. Probably ace editor hasn't been loaded\n this.fail = true;\n }\n }\n\n /**\n * The method that creates the gaps at all places containing the appropriate\n * marker (default {[ ... ]}).\n * Do not call until after this.editor has been instantiated.\n * @param {string} code The initial raw text code\n */\n AceGapfillerUi.prototype.createGaps = function(code) {\n this.gaps = [];\n /**\n * Escape special characters in a given string.\n * @param {string} s The input string.\n * @returns {string} The updated string, with escaped specials.\n */\n function reEscape(s) {\n var c, specials = '{[(*+\\\\', result='';\n for (var i = 0; i < s.length; i++) {\n c = s[i];\n for (var j = 0; j < specials.length; j++) {\n if (c === specials[j]) {\n c = '\\\\' + c;\n }\n }\n result += c;\n }\n return result;\n }\n\n let lines = code.split(/\\r?\\n/);\n\n let sepLeft = reEscape('{[');\n let sepRight = reEscape(']}');\n let splitter = new RegExp(sepLeft + ' *((?:\\\\d+)|(?:\\\\d+- *\\\\d+)) *' + sepRight);\n\n let editorContent = \"\";\n for (let i = 0; i < lines.length; i++) {\n let bits = lines[i].split(splitter);\n editorContent += bits[0];\n\n let columnPos = bits[0].length;\n for (let j = 1; j < bits.length; j += 2) {\n let values = bits[j].split('-');\n let minWidth = parseInt(values[0]);\n let maxWidth = (values.length > 1 ? parseInt(values[1]) : Infinity);\n\n // Create new gap.\n let gap = new Gap(this.editor, i, columnPos, minWidth, maxWidth);\n gap.index = this.nextGapIndex;\n this.nextGapIndex += 1;\n this.gaps.push(gap);\n\n columnPos += minWidth;\n editorContent += ' '.repeat(minWidth);\n if (j + 1 < bits.length) {\n editorContent += bits[j+1];\n columnPos += bits[j+1].length;\n }\n\n }\n\n if (i < lines.length-1) {\n editorContent += '\\n';\n }\n }\n this.editor.session.setValue(editorContent);\n };\n\n /**\n * Return the gap that the cursor is in. This will actually return a gap if\n * the cursor is 1 outside the gap as this will be needed for\n * backspace/insertion to work. Rigth now this is done as a simple\n * linear search but could be improved later.\n * @param {object} cursor The ace editor cursor position.\n * @returns {object} The gap that the cursor is current in, or null otherwise.\n */\n AceGapfillerUi.prototype.findCursorGap = function(cursor) {\n for (let i=0; i < this.gaps.length; i++) {\n let gap = this.gaps[i];\n if (gap.cursorInGap(cursor)) {\n return gap;\n }\n }\n return null;\n };\n\n AceGapfillerUi.prototype.failed = function() {\n return this.fail;\n };\n\n AceGapfillerUi.prototype.failMessage = function() {\n return 'ace_ui_notready';\n };\n\n\n // Sync to TextArea\n AceGapfillerUi.prototype.sync = function() {\n if (this.fail) {\n return; // Leave the text area alone if Ace load failed.\n }\n let serialisation = []; // A list of field values.\n let empty = true;\n\n for (let i=0; i < this.gaps.length; i++) {\n let gap = this.gaps[i];\n let value = gap.getText();\n serialisation.push(value);\n if (value !== \"\") {\n empty = false;\n }\n }\n if (empty) {\n this.textArea.val('');\n } else {\n this.textArea.val(JSON.stringify(serialisation));\n }\n };\n\n // Sync every 2 seconds in case quiz closes automatically without user\n // action.\n AceGapfillerUi.prototype.syncIntervalSecs = (() => 2);\n\n // Reload the HTML fields from the given serialisation.\n AceGapfillerUi.prototype.reload = function() {\n let content = this.textArea.val();\n if (content) {\n try {\n let values = JSON.parse(content);\n for (let i = 0; i < this.gaps.length; i++) {\n let value = i < values.length ? values[i]: '???';\n this.gaps[i].insertText(this.gaps, this.gaps[i].range.start.column, value);\n }\n } catch(e) {\n // Just ignore errors\n }\n }\n };\n\n AceGapfillerUi.prototype.setLanguage = function(language) {\n var session = this.editor.getSession(),\n mode = this.findMode(language);\n if (mode) {\n session.setMode(mode.mode);\n }\n };\n\n AceGapfillerUi.prototype.getElement = function() {\n return this.editNode;\n };\n\n AceGapfillerUi.prototype.captureTab = function () {\n this.capturingTab = true;\n this.editor.commands.bindKeys({'Tab': 'indent', 'Shift-Tab': 'outdent'});\n };\n\n AceGapfillerUi.prototype.releaseTab = function () {\n this.capturingTab = false;\n this.editor.commands.bindKeys({'Tab': null, 'Shift-Tab': null});\n };\n\n AceGapfillerUi.prototype.setEventHandlers = function () {\n var TAB = 9,\n ESC = 27,\n KEY_M = 77,\n t = this;\n\n this.editor.getSession().on('change', function() {\n t.contents_changed = true;\n });\n\n this.editor.on('blur', function() {\n if (t.contents_changed) {\n t.textArea.trigger('change');\n }\n });\n\n this.editor.on('mousedown', function() {\n // Event order seems to be (\\ is where the mouse button is pressed, / released):\n // Chrome: \\ mousedown, mouseup, focusin / click.\n // Firefox/IE: \\ mousedown, focusin / mouseup, click.\n t.clickInProgress = true;\n });\n\n this.editor.on('focus', function() {\n if (t.clickInProgress) {\n t.captureTab();\n } else {\n t.releaseTab();\n }\n });\n\n this.editor.on('click', function() {\n t.clickInProgress = false;\n });\n\n this.editor.container.addEventListener('keydown', function(e) {\n if (e.which === undefined || e.which !== 0) { // Normal keypress?\n if (e.keyCode === KEY_M && e.ctrlKey && !e.altKey) {\n if (t.capturingTab) {\n t.releaseTab();\n } else {\n t.captureTab();\n }\n e.preventDefault(); // Firefox uses this for mute audio in current browser tab.\n }\n else if (e.keyCode === ESC) {\n t.releaseTab();\n }\n else if (!(e.shiftKey || e.ctrlKey || e.altKey || e.keyCode == TAB)) {\n t.captureTab();\n }\n }\n }, true);\n };\n\n AceGapfillerUi.prototype.destroy = function () {\n this.sync();\n var focused;\n if (!this.fail) {\n // Proceed only if this wrapper was correctly constructed\n focused = this.editor.isFocused();\n this.editor.destroy();\n $(this.editNode).remove();\n if (focused) {\n this.textArea.focus();\n this.textArea[0].selectionStart = this.textArea[0].value.length;\n }\n }\n };\n\n AceGapfillerUi.prototype.hasFocus = function() {\n return this.editor.isFocused();\n };\n\n AceGapfillerUi.prototype.findMode = function (language) {\n var candidate,\n filename,\n result,\n candidates = [], // List of candidate modes.\n nameMap = {\n 'octave': 'matlab',\n 'nodejs': 'javascript',\n 'c#': 'cs'\n };\n\n if (typeof language !== 'string') {\n return undefined;\n }\n if (language.toLowerCase() in nameMap) {\n language = nameMap[language.toLowerCase()];\n }\n\n candidates = [language, language.replace(/\\d+$/, \"\")];\n for (var i = 0; i < candidates.length; i++) {\n candidate = candidates[i];\n filename = \"input.\" + candidate;\n result = this.modelist.modesByName[candidate] ||\n this.modelist.modesByName[candidate.toLowerCase()] ||\n this.modelist.getModeForPath(filename) ||\n this.modelist.getModeForPath(filename.toLowerCase());\n\n if (result && result.name !== 'text') {\n return result;\n }\n }\n return undefined;\n };\n\n AceGapfillerUi.prototype.resize = function(w, h) {\n this.editNode.outerHeight(h);\n this.editNode.outerWidth(w);\n this.editor.resize();\n };\n\n /**\n * Constructor for the Gap object that represents a gap in the source code\n * that the user is expected to fill.\n * @param {object} editor The Ace Editor object.\n * @param {int} row The row within the text of the gap.\n * @param {int} column The column within the text of the gap.\n * @param {int} minWidth The minimum width (in characters) of the gap.\n * @param {int} maxWidth The maximum width (in characters) of the gap.\n */\n function Gap(editor, row, column, minWidth, maxWidth=Infinity) {\n this.editor = editor;\n\n this.minWidth = minWidth;\n this.maxWidth = maxWidth;\n\n this.range = new Range(row, column, row, column+minWidth);\n this.textSize = 0;\n\n // Create markers\n this.editor.session.addMarker(this.range, \"ace-gap-outline\", \"text\", true);\n this.editor.session.addMarker(this.range, \"ace-gap-background\", \"text\", false);\n }\n\n Gap.prototype.cursorInGap = function(cursor) {\n return (cursor.row >= this.range.start.row && cursor.column >= this.range.start.column &&\n cursor.row <= this.range.end.row && cursor.column <= this.range.end.column);\n };\n\n Gap.prototype.getWidth = function() {\n return (this.range.end.column-this.range.start.column);\n };\n\n Gap.prototype.changeWidth = function(gaps, delta) {\n this.range.end.column += delta;\n\n // Update any gaps that come after this one on the same line\n for (let i=0; i < gaps.length; i++) {\n let other = gaps[i];\n if (other.range.start.row === this.range.start.row && other.range.start.column > this.range.end.column) {\n other.range.start.column += delta;\n other.range.end.column += delta;\n }\n }\n\n this.editor.$onChangeBackMarker();\n this.editor.$onChangeFrontMarker();\n };\n\n Gap.prototype.insertChar = function(gaps, pos, char) {\n if (this.textSize === this.getWidth() && this.getWidth() < this.maxWidth) { // Grow the size of gap and insert char.\n this.changeWidth(gaps, 1);\n this.textSize += 1; // Important to record that texSize has increased before insertion.\n this.editor.session.insert(pos, char);\n } else if (this.textSize < this.maxWidth) { // Insert char.\n this.editor.session.remove(new Range(pos.row, this.range.end.column-1, pos.row, this.range.end.column));\n this.textSize += 1; // Important to record that texSize has increased before insertion.\n this.editor.session.insert(pos, char);\n }\n };\n\n Gap.prototype.deleteChar = function(gaps, pos) {\n this.textSize -= 1;\n this.editor.session.remove(new Range(pos.row, pos.column, pos.row, pos.column+1));\n\n if (this.textSize >= this.minWidth) {\n this.changeWidth(gaps, -1); // Shrink the size of the gap.\n } else {\n // Put new space at end so everything is shifted across.\n this.editor.session.insert({row: pos.row, column: this.range.end.column-1}, fillChar);\n }\n };\n\n Gap.prototype.deleteRange = function(gaps, start, end) {\n for (let i = start; i < end; i++) {\n if (start < this.range.start.column+this.textSize) {\n this.deleteChar(gaps, {row: this.range.start.row, column: start});\n }\n }\n };\n\n Gap.prototype.insertText = function(gaps, start, text) {\n for (let i = 0; i < text.length; i++) {\n if (start+i < this.range.start.column+this.maxWidth) {\n this.insertChar(gaps, {row: this.range.start.row, column: start+i}, text[i]);\n }\n }\n };\n\n Gap.prototype.getText = function() {\n return this.editor.session.getTextRange(new Range(this.range.start.row, this.range.start.column,\n this.range.end.row, this.range.start.column+this.textSize));\n\n };\n\n return {\n Constructor: AceGapfillerUi\n };\n});\n"],"names":["define","$","Range","validChars","AceGapfillerUi","textareaId","w","h","uiParams","textArea","document","getElementById","wrapper","focused","this","activeElement","lang","t","code","gaps","source","ui_source","nextGapIndex","alert","attr","window","ace","require","modelist","enabled","contents_changed","capturingTab","clickInProgress","editNode","css","resize","height","width","editor","edit","get","prop","setReadOnly","setOptions","displayIndentGuides","dragEnabled","enableBasicAutocompletion","newLineMode","$blockScrolling","Infinity","theme","setTheme","setLanguage","setEventHandlers","captureTab","renderer","on","gutter","find","hasClass","addClass","focus","navigateFileEnd","aceLabel","aceTextarea","createGaps","commands","e","cursor","selection","getCursor","commandName","command","name","selectionRange","getSelectionRange","gap","findCursorGap","startsWith","column","range","start","textSize","moveCursorTo","row","end","selectAll","nextGap","index","length","clearSelection","setSelectionRange","isEmpty","char","args","test","insertChar","deleteChar","cursorInGap","deleteRange","insertText","text","preventDefault","stopPropagation","gapToSelect","fail","reload","err","Gap","minWidth","maxWidth","session","addMarker","prototype","reEscape","s","c","result","i","j","lines","split","sepLeft","sepRight","splitter","RegExp","editorContent","bits","columnPos","values","parseInt","push","repeat","setValue","failed","failMessage","sync","serialisation","empty","value","getText","val","JSON","stringify","syncIntervalSecs","content","parse","language","getSession","mode","findMode","setMode","getElement","bindKeys","releaseTab","trigger","container","addEventListener","undefined","which","keyCode","ctrlKey","altKey","shiftKey","destroy","isFocused","remove","selectionStart","hasFocus","candidate","filename","candidates","nameMap","toLowerCase","replace","modesByName","getModeForPath","outerHeight","outerWidth","getWidth","changeWidth","delta","other","$onChangeBackMarker","$onChangeFrontMarker","pos","insert","getTextRange","Constructor"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDAA,2CAAO,CAAC,WAAW,SAASC,OAEpBC,YAEEC,WAAa,4DAUVC,eAAeC,WAAYC,EAAGC,EAAGC,eACjCC,SAAWR,EAAES,SAASC,eAAeN,iBACtCO,QAAUX,EAAES,SAASC,eAAeN,WAAa,aACjDQ,QAAUC,KAAKL,SAAS,KAAOC,SAASK,cACxCC,KAAOR,SAASQ,KAChBC,EAAIH,SAEJI,KAAO,QACNV,SAAWA,cACXW,KAAO,QACPC,OAASZ,SAASa,WAAa,mBAC/BC,aAAe,EACA,gBAAhBR,KAAKM,QAA4C,UAAhBN,KAAKM,SACtCG,MAAM,oDACDH,OAAS,eAGdF,KADe,eAAfJ,KAAKM,OACEN,KAAKL,SAASe,KAAK,oBAEnBV,KAAKL,SAASe,KAAK,kBAI1BC,OAAOC,IAAIC,QAAQ,0BACnBzB,MAAQuB,OAAOC,IAAIC,QAAQ,aAAazB,WACnC0B,SAAWH,OAAOC,IAAIC,QAAQ,yBAE9BE,SAAU,OACVC,kBAAmB,OACnBC,cAAe,OACfC,iBAAkB,OAElBC,SAAWhC,EAAE,oBACbgC,SAASC,IAAI,CACdC,OAAQ,OACRC,OAAQ7B,EACR8B,MAAO,cAGNC,OAASb,OAAOC,IAAIa,KAAKzB,KAAKmB,SAASO,IAAI,IAC5C1B,KAAKL,SAASgC,KAAK,kBACdH,OAAOI,aAAY,QAGvBJ,OAAOK,WAAW,CACnBC,qBAAqB,EACrBC,aAAa,EACbC,2BAA2B,EAC3BC,YAAa,cAEZT,OAAOU,gBAAkBC,EAAAA,EAG1BzC,SAAS0C,WACJZ,OAAOa,SAAS,aAAe3C,SAAS0C,YAExCZ,OAAOa,SAjEA,2BAoEXC,YAAYpC,WAEZqC,iBAAiBvC,KAAKL,eACtB6C,kBAMAhB,OAAOiB,SAASC,GAAG,eAAe,eAC/BC,OAAU7C,QAAQ8C,KAAK,eACvBD,OAAOE,SAAS,uBAGpBF,OAAOG,SAAS,qBAEZ/C,UACAI,EAAEqB,OAAOuB,QACT5C,EAAEqB,OAAOwB,mBAEb7C,EAAE8C,SAAWnD,QAAQ8C,KAAK,iBAC1BzC,EAAE8C,SAASvC,KAAK,MAAO,OAASnB,YAEhCY,EAAE+C,YAAcpD,QAAQ8C,KAAK,mBAC7BzC,EAAE+C,YAAYxC,KAAK,KAAM,OAASnB,qBAGjC4D,WAAW/C,WAGXoB,OAAO4B,SAASV,GAAG,QAAQ,SAASW,OACjCC,OAASnD,EAAEqB,OAAO+B,UAAUC,YAC5BC,YAAcJ,EAAEK,QAAQC,KACxBC,eAAiBzD,EAAEqB,OAAOqC,oBAE1BC,IAAM3D,EAAE4D,cAAcT,WAEtBG,YAAYO,WAAW,MAAO,IAClB,OAARF,KAAgC,cAAhBL,aAA+BH,OAAOW,SAAWH,IAAII,MAAMC,MAAMF,OAAOH,IAAIM,gBAE5FjE,EAAEqB,OAAO6C,aAAaf,OAAOgB,IAAKR,IAAII,MAAMK,IAAIN,OAAO,MAMnD,OAARH,IAEoB,cAAhBL,aACAtD,EAAEqB,OAAO+B,UAAUiB,iBAGpB,GAAoB,WAAhBf,YAA0B,KAE7BgB,QAAUtE,EAAEE,MAAMyD,IAAIY,MAAM,GAAKvE,EAAEE,KAAKsE,QAC5CxE,EAAEqB,OAAO6C,aAAaI,QAAQP,MAAMC,MAAMG,IAAKG,QAAQP,MAAMC,MAAMF,OAAOQ,QAAQL,UAClFjE,EAAEqB,OAAO+B,UAAUqB,sBAEhB,GAAoB,cAAhBnB,YAEPtD,EAAEqB,OAAO+B,UAAUsB,kBAAkB,IAAIzF,MAAM0E,IAAII,MAAMC,MAAMG,IAC1BR,IAAII,MAAMC,MAAMF,OAChBH,IAAII,MAAMC,MAAMG,IAChBR,IAAII,MAAMK,IAAIN,SAAS,QAEzD,GAAI9D,EAAEqB,OAAO+B,UAAUuB,UAAW,IAEjB,iBAAhBrB,YAAgC,KAC5BsB,KAAO1B,EAAE2B,KAET3F,WAAW4F,KAAKF,OAChBjB,IAAIoB,WAAW/E,EAAEE,KAAMiD,OAAQyB,UAEZ,cAAhBtB,YAEHH,OAAOW,OAASH,IAAII,MAAMC,MAAMF,QAAUH,IAAIM,SAAW,GACzDN,IAAIqB,WAAWhF,EAAEE,KAAM,CAACiE,IAAKhB,OAAOgB,IAAKL,OAAQX,OAAOW,OAAO,IAE5C,QAAhBR,aAEHH,OAAOW,OAASH,IAAII,MAAMC,MAAMF,OAASH,IAAIM,UAAYN,IAAIM,SAAW,GACxEN,IAAIqB,WAAWhF,EAAEE,KAAMiD,QAG/BnD,EAAEqB,OAAO+B,UAAUqB,sBAEhB,IAAKzE,EAAEqB,OAAO+B,UAAUuB,WAAahB,IAAIsB,YAAYxB,eAAeO,QAC7DL,IAAIsB,YAAYxB,eAAeW,OAIrB,iBAAhBd,aAAkD,cAAhBA,aACf,QAAhBA,aAAyC,UAAhBA,aACT,QAAhBA,cAEHK,IAAIuB,YAAYlF,EAAEE,KAAMuD,eAAeO,MAAMF,OAAQL,eAAeW,IAAIN,QACxE9D,EAAEqB,OAAO+B,UAAUqB,kBAGH,iBAAhBnB,aAAgC,KAC5BsB,KAAO1B,EAAE2B,KACT3F,WAAW4F,KAAKF,OAChBjB,IAAIoB,WAAW/E,EAAEE,KAAMuD,eAAeO,MAAOY,MAM7C,OAARjB,KAAgC,UAAhBL,aAChBK,IAAIwB,WAAWnF,EAAEE,KAAMuD,eAAeO,MAAMF,OAAQZ,EAAE2B,KAAKO,MAG/DlC,EAAEmC,iBACFnC,EAAEoC,qBAINtF,EAAEqB,OAAO+B,UAAUb,GAAG,gBAAgB,eAC9BY,OAASnD,EAAEqB,OAAO+B,UAAUC,YAC5BM,IAAM3D,EAAE4D,cAAcT,QACd,OAARQ,KACIR,OAAOW,OAASH,IAAII,MAAMC,MAAMF,OAAOH,IAAIM,UAC3CjE,EAAEqB,OAAO6C,aAAaP,IAAII,MAAMC,MAAMG,IAAKR,IAAII,MAAMC,MAAMF,OAAOH,IAAIM,kBAK7EsB,YAAc,UAGdlE,OAAOkB,GAAG,eAAe,SAASW,OAC/BC,OAASnD,EAAEqB,OAAO+B,UAAUC,YAC5BM,IAAM3D,EAAE4D,cAAcT,QACd,OAARQ,MACA3D,EAAEqB,OAAO+B,UAAUsB,kBAAkB,IAAIzF,MAAM0E,IAAII,MAAMC,MAAMG,IAChBR,IAAII,MAAMC,MAAMF,OAChBH,IAAII,MAAMC,MAAMG,IAChBR,IAAII,MAAMK,IAAIN,SAAS,GACtE9D,EAAEuF,YAAc5B,IAChBT,EAAEmC,iBACFnC,EAAEoC,2BAKLjE,OAAOkB,GAAG,SAAS,SAASW,GACzBlD,EAAEuF,cACFvF,EAAEqB,OAAO6C,aAAalE,EAAEuF,YAAYxB,MAAMC,MAAMG,IAAKnE,EAAEuF,YAAYxB,MAAMC,MAAMF,OAAO9D,EAAEuF,YAAYtB,UACpGjE,EAAEuF,YAAc,KAChBrC,EAAEmC,iBACFnC,EAAEoC,2BAILE,MAAO,OACPC,SAET,MAAMC,UAEGF,MAAO,YA6RXG,IAAItE,OAAQ8C,IAAKL,OAAQ8B,cAAUC,gEAAS7D,EAAAA,OAC5CX,OAASA,YAETuE,SAAWA,cACXC,SAAWA,cAEX9B,MAAQ,IAAI9E,MAAMkF,IAAKL,OAAQK,IAAKL,OAAO8B,eAC3C3B,SAAW,OAGX5C,OAAOyE,QAAQC,UAAUlG,KAAKkE,MAAO,kBAAmB,QAAQ,QAChE1C,OAAOyE,QAAQC,UAAUlG,KAAKkE,MAAO,qBAAsB,QAAQ,UA9R5E5E,eAAe6G,UAAUhD,WAAa,SAAS/C,eAOlCgG,SAASC,WACVC,EAAyBC,OAAO,GAC3BC,EAAI,EAAGA,EAAIH,EAAE1B,OAAQ6B,IAAK,CAC/BF,EAAID,EAAEG,OACD,IAAIC,EAAI,EAAGA,EAHF,UAGe9B,OAAQ8B,IAC7BH,IAJM,UAISG,KACfH,EAAI,KAAOA,GAGnBC,QAAUD,SAEPC,YAjBNlG,KAAO,OAoBRqG,MAAQtG,KAAKuG,MAAM,SAEnBC,QAAUR,SAAS,MACnBS,SAAWT,SAAS,MACpBU,SAAW,IAAIC,OAAOH,QAAU,iCAAmCC,UAEnEG,cAAgB,OACf,IAAIR,EAAI,EAAGA,EAAIE,MAAM/B,OAAQ6B,IAAK,KAC/BS,KAAOP,MAAMF,GAAGG,MAAMG,UAC1BE,eAAiBC,KAAK,OAElBC,UAAYD,KAAK,GAAGtC,WACnB,IAAI8B,EAAI,EAAGA,EAAIQ,KAAKtC,OAAQ8B,GAAK,EAAG,KACjCU,OAASF,KAAKR,GAAGE,MAAM,KACvBZ,SAAWqB,SAASD,OAAO,IAC3BnB,SAAYmB,OAAOxC,OAAS,EAAIyC,SAASD,OAAO,IAAMhF,EAAAA,EAGtD2B,IAAM,IAAIgC,IAAI9F,KAAKwB,OAAQgF,EAAGU,UAAWnB,SAAUC,UACvDlC,IAAIY,MAAQ1E,KAAKQ,kBACZA,cAAgB,OAChBH,KAAKgH,KAAKvD,KAEfoD,WAAanB,SACbiB,eAAiB,IAAIM,OAAOvB,UACxBU,EAAI,EAAIQ,KAAKtC,SACbqC,eAAiBC,KAAKR,EAAE,GACxBS,WAAaD,KAAKR,EAAE,GAAG9B,QAK3B6B,EAAIE,MAAM/B,OAAO,IACjBqC,eAAiB,WAGpBxF,OAAOyE,QAAQsB,SAASP,gBAWjC1H,eAAe6G,UAAUpC,cAAgB,SAAST,YACzC,IAAIkD,EAAE,EAAGA,EAAIxG,KAAKK,KAAKsE,OAAQ6B,IAAK,KACjC1C,IAAM9D,KAAKK,KAAKmG,MAChB1C,IAAIsB,YAAY9B,eACTQ,WAGR,MAGXxE,eAAe6G,UAAUqB,OAAS,kBACvBxH,KAAK2F,MAGhBrG,eAAe6G,UAAUsB,YAAc,iBAC5B,mBAKXnI,eAAe6G,UAAUuB,KAAO,cACxB1H,KAAK2F,gBAGLgC,cAAgB,GAChBC,OAAQ,MAEP,IAAIpB,EAAE,EAAGA,EAAIxG,KAAKK,KAAKsE,OAAQ6B,IAAK,KAEjCqB,MADM7H,KAAKK,KAAKmG,GACJsB,UAChBH,cAAcN,KAAKQ,OACL,KAAVA,QACAD,OAAQ,GAGZA,WACKjI,SAASoI,IAAI,SAEbpI,SAASoI,IAAIC,KAAKC,UAAUN,iBAMzCrI,eAAe6G,UAAU+B,iBAAoB,IAAM,EAGnD5I,eAAe6G,UAAUP,OAAS,eAC1BuC,QAAUnI,KAAKL,SAASoI,SACxBI,gBAEQhB,OAASa,KAAKI,MAAMD,aACnB,IAAI3B,EAAI,EAAGA,EAAIxG,KAAKK,KAAKsE,OAAQ6B,IAAK,KACnCqB,MAAQrB,EAAIW,OAAOxC,OAASwC,OAAOX,GAAI,WACtCnG,KAAKmG,GAAGlB,WAAWtF,KAAKK,KAAML,KAAKK,KAAKmG,GAAGtC,MAAMC,MAAMF,OAAQ4D,QAE1E,MAAMxE,MAMhB/D,eAAe6G,UAAU7D,YAAc,SAAS+F,cACxCpC,QAAUjG,KAAKwB,OAAO8G,aACtBC,KAAOvI,KAAKwI,SAASH,UACrBE,MACAtC,QAAQwC,QAAQF,KAAKA,OAI7BjJ,eAAe6G,UAAUuC,WAAa,kBAC3B1I,KAAKmB,UAGhB7B,eAAe6G,UAAU3D,WAAa,gBAC7BvB,cAAe,OACfO,OAAO4B,SAASuF,SAAS,KAAQ,qBAAuB,aAGjErJ,eAAe6G,UAAUyC,WAAa,gBAC7B3H,cAAe,OACfO,OAAO4B,SAASuF,SAAS,KAAQ,iBAAmB,QAG7DrJ,eAAe6G,UAAU5D,iBAAmB,eAIpCpC,EAAIH,UAEHwB,OAAO8G,aAAa5F,GAAG,UAAU,WAClCvC,EAAEa,kBAAmB,UAGpBQ,OAAOkB,GAAG,QAAQ,WACfvC,EAAEa,kBACFb,EAAER,SAASkJ,QAAQ,kBAItBrH,OAAOkB,GAAG,aAAa,WAIxBvC,EAAEe,iBAAkB,UAGnBM,OAAOkB,GAAG,SAAS,WAChBvC,EAAEe,gBACFf,EAAEqC,aAEFrC,EAAEyI,qBAILpH,OAAOkB,GAAG,SAAS,WACpBvC,EAAEe,iBAAkB,UAGnBM,OAAOsH,UAAUC,iBAAiB,WAAW,SAAS1F,QACvC2F,IAAZ3F,EAAE4F,OAAmC,IAAZ5F,EAAE4F,QAjCvB,KAkCA5F,EAAE6F,SAAqB7F,EAAE8F,UAAY9F,EAAE+F,QACnCjJ,EAAEc,aACFd,EAAEyI,aAEFzI,EAAEqC,aAENa,EAAEmC,kBAzCJ,KA2COnC,EAAE6F,QACP/I,EAAEyI,aAEKvF,EAAEgG,UAAYhG,EAAE8F,SAAW9F,EAAE+F,QA/CtC,GA+CgD/F,EAAE6F,SAChD/I,EAAEqC,iBAGX,IAGPlD,eAAe6G,UAAUmD,QAAU,eAE3BvJ,aADC2H,OAEA1H,KAAK2F,OAEN5F,QAAUC,KAAKwB,OAAO+H,iBACjB/H,OAAO8H,UACZnK,EAAEa,KAAKmB,UAAUqI,SACbzJ,eACKJ,SAASoD,aACTpD,SAAS,GAAG8J,eAAiBzJ,KAAKL,SAAS,GAAGkI,MAAMlD,UAKrErF,eAAe6G,UAAUuD,SAAW,kBACzB1J,KAAKwB,OAAO+H,aAGvBjK,eAAe6G,UAAUqC,SAAW,SAAUH,cACtCsB,UACAC,SACArD,OACAsD,WACAC,QAAU,QACI,gBACA,kBACJ,SAGU,iBAAbzB,UAGPA,SAAS0B,gBAAiBD,UAC1BzB,SAAWyB,QAAQzB,SAAS0B,gBAGhCF,WAAa,CAACxB,SAAUA,SAAS2B,QAAQ,OAAQ,SAC5C,IAAIxD,EAAI,EAAGA,EAAIqD,WAAWlF,OAAQ6B,OAEnCoD,SAAW,UADXD,UAAYE,WAAWrD,KAEvBD,OAASvG,KAAKc,SAASmJ,YAAYN,YAC/B3J,KAAKc,SAASmJ,YAAYN,UAAUI,gBACpC/J,KAAKc,SAASoJ,eAAeN,WAC7B5J,KAAKc,SAASoJ,eAAeN,SAASG,iBAEZ,SAAhBxD,OAAO5C,YACV4C,SAMnBjH,eAAe6G,UAAU9E,OAAS,SAAS7B,EAAGC,QACrC0B,SAASgJ,YAAY1K,QACrB0B,SAASiJ,WAAW5K,QACpBgC,OAAOH,UA0BhByE,IAAIK,UAAUf,YAAc,SAAS9B,eACzBA,OAAOgB,KAAOtE,KAAKkE,MAAMC,MAAMG,KAAOhB,OAAOW,QAAUjE,KAAKkE,MAAMC,MAAMF,QACxEX,OAAOgB,KAAOtE,KAAKkE,MAAMK,IAAID,KAAOhB,OAAOW,QAAUjE,KAAKkE,MAAMK,IAAIN,QAGhF6B,IAAIK,UAAUkE,SAAW,kBACbrK,KAAKkE,MAAMK,IAAIN,OAAOjE,KAAKkE,MAAMC,MAAMF,QAGnD6B,IAAIK,UAAUmE,YAAc,SAASjK,KAAMkK,YAClCrG,MAAMK,IAAIN,QAAUsG,UAGpB,IAAI/D,EAAE,EAAGA,EAAInG,KAAKsE,OAAQ6B,IAAK,KAC5BgE,MAAQnK,KAAKmG,GACbgE,MAAMtG,MAAMC,MAAMG,MAAQtE,KAAKkE,MAAMC,MAAMG,KAAOkG,MAAMtG,MAAMC,MAAMF,OAASjE,KAAKkE,MAAMK,IAAIN,SAC5FuG,MAAMtG,MAAMC,MAAMF,QAAUsG,MAC5BC,MAAMtG,MAAMK,IAAIN,QAAUsG,YAI7B/I,OAAOiJ,2BACPjJ,OAAOkJ,wBAGhB5E,IAAIK,UAAUjB,WAAa,SAAS7E,KAAMsK,IAAK5F,MACvC/E,KAAKoE,WAAapE,KAAKqK,YAAcrK,KAAKqK,WAAarK,KAAKgG,eACvDsE,YAAYjK,KAAM,QAClB+D,UAAY,OACZ5C,OAAOyE,QAAQ2E,OAAOD,IAAK5F,OACzB/E,KAAKoE,SAAWpE,KAAKgG,gBACvBxE,OAAOyE,QAAQuD,OAAO,IAAIpK,MAAMuL,IAAIrG,IAAKtE,KAAKkE,MAAMK,IAAIN,OAAO,EAAG0G,IAAIrG,IAAKtE,KAAKkE,MAAMK,IAAIN,cAC1FG,UAAY,OACZ5C,OAAOyE,QAAQ2E,OAAOD,IAAK5F,QAIxCe,IAAIK,UAAUhB,WAAa,SAAS9E,KAAMsK,UACjCvG,UAAY,OACZ5C,OAAOyE,QAAQuD,OAAO,IAAIpK,MAAMuL,IAAIrG,IAAKqG,IAAI1G,OAAQ0G,IAAIrG,IAAKqG,IAAI1G,OAAO,IAE1EjE,KAAKoE,UAAYpE,KAAK+F,cACjBuE,YAAYjK,MAAO,QAGnBmB,OAAOyE,QAAQ2E,OAAO,CAACtG,IAAKqG,IAAIrG,IAAKL,OAAQjE,KAAKkE,MAAMK,IAAIN,OAAO,GA7jB/D,MAikBjB6B,IAAIK,UAAUd,YAAc,SAAShF,KAAM8D,MAAOI,SACzC,IAAIiC,EAAIrC,MAAOqC,EAAIjC,IAAKiC,IACrBrC,MAAQnE,KAAKkE,MAAMC,MAAMF,OAAOjE,KAAKoE,eAChCe,WAAW9E,KAAM,CAACiE,IAAKtE,KAAKkE,MAAMC,MAAMG,IAAKL,OAAQE,SAKtE2B,IAAIK,UAAUb,WAAa,SAASjF,KAAM8D,MAAOoB,UACxC,IAAIiB,EAAI,EAAGA,EAAIjB,KAAKZ,OAAQ6B,IACzBrC,MAAMqC,EAAIxG,KAAKkE,MAAMC,MAAMF,OAAOjE,KAAKgG,eAClCd,WAAW7E,KAAM,CAACiE,IAAKtE,KAAKkE,MAAMC,MAAMG,IAAKL,OAAQE,MAAMqC,GAAIjB,KAAKiB,KAKrFV,IAAIK,UAAU2B,QAAU,kBACb9H,KAAKwB,OAAOyE,QAAQ4E,aAAa,IAAIzL,MAAMY,KAAKkE,MAAMC,MAAMG,IAAKtE,KAAKkE,MAAMC,MAAMF,OACjDjE,KAAKkE,MAAMK,IAAID,IAAKtE,KAAKkE,MAAMC,MAAMF,OAAOjE,KAAKoE,YAItF,CACH0G,YAAaxL"}
\ No newline at end of file
diff --git a/amd/src/ui_ace_gapfiller.js b/amd/src/ui_ace_gapfiller.js
index c1923358b..101c1c69c 100644
--- a/amd/src/ui_ace_gapfiller.js
+++ b/amd/src/ui_ace_gapfiller.js
@@ -58,7 +58,7 @@ define(['jquery'], function($) {
var Range; // Can't load this until ace has loaded.
const fillChar = " ";
- const validChars = /[ !"#$%&'()*+,`\-./0-9:;<=>?@A-Z\[\]\\^_a-z{}|~]/;
+ const validChars = /[ !"#$%&'()*+,`\-./0-9\p{L}:;<=>?@\[\]\\^_{}|~]/u;
const ACE_LIGHT_THEME = 'ace/theme/textmate';
/**