From 4ef919360c927e64d2296ad3b95a15a946a40101 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sat, 5 Oct 2024 22:12:10 -0500 Subject: [PATCH] Upgrade to CodeMirror 6. Some features of this change are improved syntax highlighting and the addition of autocompletion. To see available autocompletions at the current cursor location, you can type Ctrl-Space. In many cases, autocompletions are offered automatically when certain things have been typed. CodeMirror 6 can not be used via script tags. So a PGProblemEditor package has been created for this. The repository for that is https://github.com/openwebwork/pg-problem-editor. An initial package has been published in npm at https://www.npmjs.com/package/@openwebwork/pg-codemirror-editor. The real work is done in the codemirror-lang-pg package that adds support for the PG language in CodeMirror 6. The repository for that package is https://github.com/openwebwork/codemirror-lang-pg, and the npm package at https://www.npmjs.com/package/@openwebwork/codemirror-lang-pg. The pg-problem-editor package is designed so that it can be used by webwork2, the standalone renderer, and possible others that want a PG problem editor. It may need a few tweaks to make it work for webwork3 in the future, but should also work for that. The codemirror-lang-pg package consists of a base PG parser that is largely derived from the codemirror-lang-perl package (at https://github.com/drgrice1/codemirror-lang-perl and https://www.npmjs.com/package/codemirror-lang-perl) with a modifications specific to the perl available in a PG problem. The main differences are that its escape sequence is ~~, much of what can not be used in the safe compartment has been stripped out, and there are special blocks for PGML, PG text, and LaTeX image code. Then there are two other parsers. Those are the PGML and PG text parsers. Those parsers run on the contents of the PGML and PG text blocks found by the base PG parser. Note that the PGML parser is actually a javascript implementation of the parser in PGML.pl with some modifications to track position and things needed to associate the parsed tree with the original code. Note that the configuration of theme, keymaps, and such are now part of the pg-codemirror-editor, and are shown in a panel of the editor instead of below as before. Emacs and Vim keymaps are available, but at this point there is not a Sublime keymap for CodeMirror 6. The themes that are available include a basic default theme (that is part of the pg-problem-editor package and just adds some syntax highlighting that is missing from the CodeMirror 6 default), the OndDark theme that is the only other theme provided by the CodeMirror 6 developers directly, the themes in the thememirror package, and the themes from https://github.com/craftzdog/cm6-themes which are in several separate npm packages. Note that both thememirror and craftzdog/cm6-themes provide a solarized light theme, and that is why there are two of those. They are slightly different though. I am sure that there is a lot more work to do on this, and there are other things available with CodeMirror 6 that could probably be implemented. In any case, this is a good start. --- htdocs/js/PGCodeMirror/PG.js | 1567 ----------------- htdocs/js/PGCodeMirror/comment.js | 287 --- htdocs/js/PGCodeMirror/pgeditor.js | 141 +- htdocs/js/PGCodeMirror/pgeditor.scss | 60 +- htdocs/js/PGProblemEditor/pgproblemeditor.js | 31 +- htdocs/js/System/system.scss | 2 +- htdocs/package-lock.json | 818 ++++++++- htdocs/package.json | 2 +- lib/WeBWorK/HTML/CodeMirrorEditor.pm | 95 +- .../Instructor/AchievementEditor.html.ep | 6 +- .../AchievementNotificationEditor.html.ep | 7 +- .../Instructor/PGProblemEditor.html.ep | 18 +- .../HTML/CodeMirrorEditor/controls.html.ep | 33 - templates/HTML/CodeMirrorEditor/js.html.ep | 37 +- .../InstructorPGProblemEditor.html.ep | 63 +- 15 files changed, 960 insertions(+), 2207 deletions(-) delete mode 100644 htdocs/js/PGCodeMirror/PG.js delete mode 100644 htdocs/js/PGCodeMirror/comment.js delete mode 100644 templates/HTML/CodeMirrorEditor/controls.html.ep diff --git a/htdocs/js/PGCodeMirror/PG.js b/htdocs/js/PGCodeMirror/PG.js deleted file mode 100644 index e003158379..0000000000 --- a/htdocs/js/PGCodeMirror/PG.js +++ /dev/null @@ -1,1567 +0,0 @@ -// Bassed off of CodeMirror mode perl file: -// https://github.com/codemirror/CodeMirror/blob/master/mode/perl/perl.js - -'use strict'; - -(() => { - CodeMirror.defineMode('PG', function () { - // http://perldoc.perl.org - const PERL = { - // null - magic touch - // 1 - keyword - // 2 - def - // 3 - atom - // 4 - operator - // 5 - variable-2 (predefined) - // [x,y] - x=1,2,3; y=must be defined if x{...} - // PERL operators - '->': 4, - '++': 4, - '--': 4, - '**': 4, - // ! ~ \ and unary + and - - '=~': 4, - '!~': 4, - '*': 4, - '/': 4, - '%': 4, - x: 4, - '+': 4, - '-': 4, - '.': 4, - '<<': 4, - '>>': 4, - // named unary operators - '<': 4, - '>': 4, - '<=': 4, - '>=': 4, - lt: 4, - gt: 4, - le: 4, - ge: 4, - '==': 4, - '!=': 4, - '<=>': 4, - eq: 4, - ne: 4, - cmp: 4, - '~~': 4, - '&': 4, - '|': 4, - '^': 4, - '&&': 4, - '||': 4, - '//': 4, - '..': 4, - '...': 4, - '?': 4, - ':': 4, - '=': 4, - '+=': 4, - '-=': 4, - '*=': 4, // etc. ??? - ',': 4, - '=>': 4, - '::': 4, - // list operators (rightward) - not: 4, - and: 4, - or: 4, - xor: 4, - // PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;) - BEGIN: [5, 1], - END: [5, 1], - PRINT: [5, 1], - PRINTF: [5, 1], - GETC: [5, 1], - READ: [5, 1], - READLINE: [5, 1], - DESTROY: [5, 1], - TIE: [5, 1], - TIEHANDLE: [5, 1], - UNTIE: [5, 1], - STDIN: 5, - STDIN_TOP: 5, - STDOUT: 5, - STDOUT_TOP: 5, - STDERR: 5, - STDERR_TOP: 5, - $ARG: 5, - $_: 5, - '@ARG': 5, - '@_': 5, - $LIST_SEPARATOR: 5, - '$"': 5, - $PROCESS_ID: 5, - $PID: 5, - $$: 5, - $REAL_GROUP_ID: 5, - $GID: 5, - '$(': 5, - $EFFECTIVE_GROUP_ID: 5, - $EGID: 5, - '$)': 5, - $PROGRAM_NAME: 5, - $0: 5, - $SUBSCRIPT_SEPARATOR: 5, - $SUBSEP: 5, - '$;': 5, - $REAL_USER_ID: 5, - $UID: 5, - '$<': 5, - $EFFECTIVE_USER_ID: 5, - $EUID: 5, - '$>': 5, - // '$a' : 5, - // '$b' : 5, - $COMPILING: 5, - '$^C': 5, - $DEBUGGING: 5, - '$^D': 5, - '${^ENCODING}': 5, - $ENV: 5, - '%ENV': 5, - $SYSTEM_FD_MAX: 5, - '$^F': 5, - '@F': 5, - '${^GLOBAL_PHASE}': 5, - '$^H': 5, - '%^H': 5, - '@INC': 5, - '%INC': 5, - $INPLACE_EDIT: 5, - '$^I': 5, - '$^M': 5, - $OSNAME: 5, - '$^O': 5, - '${^OPEN}': 5, - $PERLDB: 5, - '$^P': 5, - $SIG: 5, - '%SIG': 5, - $BASETIME: 5, - '$^T': 5, - '${^TAINT}': 5, - '${^UNICODE}': 5, - '${^UTF8CACHE}': 5, - '${^UTF8LOCALE}': 5, - $PERL_VERSION: 5, - '$^V': 5, - '${^WIN32_SLOPPY_STAT}': 5, - $EXECUTABLE_NAME: 5, - '$^X': 5, - $1: 5, // - regexp $1, $2... - $MATCH: 5, - '$&': 5, - '${^MATCH}': 5, - $PREMATCH: 5, - '$`': 5, - '${^PREMATCH}': 5, - $POSTMATCH: 5, - "$'": 5, - '${^POSTMATCH}': 5, - $LAST_PAREN_MATCH: 5, - '$+': 5, - $LAST_SUBMATCH_RESULT: 5, - '$^N': 5, - '@LAST_MATCH_END': 5, - '@+': 5, - '%LAST_PAREN_MATCH': 5, - '%+': 5, - '@LAST_MATCH_START': 5, - '@-': 5, - '%LAST_MATCH_START': 5, - '%-': 5, - $LAST_REGEXP_CODE_RESULT: 5, - '$^R': 5, - '${^RE_DEBUG_FLAGS}': 5, - '${^RE_TRIE_MAXBUF}': 5, - $ARGV: 5, - '@ARGV': 5, - ARGV: 5, - ARGVOUT: 5, - $OUTPUT_FIELD_SEPARATOR: 5, - $OFS: 5, - '$,': 5, - $INPUT_LINE_NUMBER: 5, - $NR: 5, - '$.': 5, - $INPUT_RECORD_SEPARATOR: 5, - $RS: 5, - '$/': 5, - $OUTPUT_RECORD_SEPARATOR: 5, - $ORS: 5, - '$\\': 5, - $OUTPUT_AUTOFLUSH: 5, - '$|': 5, - $ACCUMULATOR: 5, - '$^A': 5, - $FORMAT_FORMFEED: 5, - '$^L': 5, - $FORMAT_PAGE_NUMBER: 5, - '$%': 5, - $FORMAT_LINES_LEFT: 5, - '$-': 5, - $FORMAT_LINE_BREAK_CHARACTERS: 5, - '$:': 5, - $FORMAT_LINES_PER_PAGE: 5, - '$=': 5, - $FORMAT_TOP_NAME: 5, - '$^': 5, - $FORMAT_NAME: 5, - '$~': 5, - '${^CHILD_ERROR_NATIVE}': 5, - $EXTENDED_OS_ERROR: 5, - '$^E': 5, - $EXCEPTIONS_BEING_CAUGHT: 5, - '$^S': 5, - $WARNING: 5, - '$^W': 5, - '${^WARNING_BITS}': 5, - $OS_ERROR: 5, - $ERRNO: 5, - '$!': 5, - '%OS_ERROR': 5, - '%ERRNO': 5, - '%!': 5, - $CHILD_ERROR: 5, - '$?': 5, - $EVAL_ERROR: 5, - '$@': 5, - $OFMT: 5, - '$#': 5, - '$*': 5, - $ARRAY_BASE: 5, - '$[': 5, - $OLD_PERL_VERSION: 5, - '$]': 5, - // PERL blocks - if: [1, 1], - elsif: [1, 1], - else: [1, 1], - while: [1, 1], - unless: [1, 1], - until: [1, 1], - for: [1, 1], - foreach: [1, 1], - // PERL functions - abs: 1, // - absolute value function - accept: 1, // - accept an incoming socket connect - alarm: 1, // - schedule a SIGALRM - atan2: 1, // - arctangent of Y/X in the range -PI to PI - bind: 1, // - binds an address to a socket - binmode: 1, // - prepare binary files for I/O - bless: 1, // - create an object - bootstrap: 1, // - break: 1, // - break out of a "given" block - caller: 1, // - get context of the current subroutine call - chdir: 1, // - change your current working directory - chmod: 1, // - changes the permissions on a list of files - chomp: 1, // - remove a trailing record separator from a string - chop: 1, // - remove the last character from a string - chown: 1, // - change the ownership on a list of files - chr: 1, // - get character this number represents - chroot: 1, // - make directory new root for path lookups - close: 1, // - close file (or pipe or socket) handle - closedir: 1, // - close directory handle - connect: 1, // - connect to a remote socket - continue: [1, 1], // - optional trailing block in a while or foreach - cos: 1, // - cosine function - crypt: 1, // - one-way passwd-style encryption - dbmclose: 1, // - breaks binding on a tied dbm file - dbmopen: 1, // - create binding on a tied dbm file - default: 1, // - defined: 1, // - test whether a value, variable, or function is defined - delete: 1, // - deletes a value from a hash - die: 1, // - raise an exception or bail out - do: 1, // - turn a BLOCK into a TERM - dump: 1, // - create an immediate core dump - each: 1, // - retrieve the next key/value pair from a hash - endgrent: 1, // - be done using group file - endhostent: 1, // - be done using hosts file - endnetent: 1, // - be done using networks file - endprotoent: 1, // - be done using protocols file - endpwent: 1, // - be done using passwd file - endservent: 1, // - be done using services file - eof: 1, // - test a filehandle for its end - eval: 1, // - catch exceptions or compile and run code - exec: 1, // - abandon this program to run another - exists: 1, // - test whether a hash key is present - exit: 1, // - terminate this program - exp: 1, // - raise I to a power - fcntl: 1, // - file control system call - fileno: 1, // - return file descriptor from filehandle - flock: 1, // - lock an entire file with an advisory lock - fork: 1, // - create a new process just like this one - format: 1, // - declare a picture format with use by the write() function - formline: 1, // - internal function used for formats - getc: 1, // - get the next character from the filehandle - getgrent: 1, // - get next group record - getgrgid: 1, // - get group record given group user ID - getgrnam: 1, // - get group record given group name - gethostbyaddr: 1, // - get host record given its address - gethostbyname: 1, // - get host record given name - gethostent: 1, // - get next hosts record - getlogin: 1, // - return who logged in at this tty - getnetbyaddr: 1, // - get network record given its address - getnetbyname: 1, // - get networks record given name - getnetent: 1, // - get next networks record - getpeername: 1, // - find the other end of a socket connection - getpgrp: 1, // - get process group - getppid: 1, // - get parent process ID - getpriority: 1, // - get current nice value - getprotobyname: 1, // - get protocol record given name - getprotobynumber: 1, // - get protocol record numeric protocol - getprotoent: 1, // - get next protocols record - getpwent: 1, // - get next passwd record - getpwnam: 1, // - get passwd record given user login name - getpwuid: 1, // - get passwd record given user ID - getservbyname: 1, // - get services record given its name - getservbyport: 1, // - get services record given numeric port - getservent: 1, // - get next services record - getsockname: 1, // - retrieve the sockaddr for a given socket - getsockopt: 1, // - get socket options on a given socket - given: 1, // - glob: 1, // - expand filenames using wildcards - gmtime: 1, // - convert UNIX time into record or string using Greenwich time - goto: 1, // - create spaghetti code - grep: 1, // - locate elements in a list test true against a given criterion - hex: 1, // - convert a string to a hexadecimal number - import: 1, // - patch a module's namespace into your own - index: 1, // - find a substring within a string - int: 1, // - get the integer portion of a number - ioctl: 1, // - system-dependent device control system call - join: 1, // - join a list into a string using a separator - keys: 1, // - retrieve list of indices from a hash - kill: 1, // - send a signal to a process or process group - last: 1, // - exit a block prematurely - lc: 1, // - return lower-case version of a string - lcfirst: 1, // - return a string with just the next letter in lower case - length: 1, // - return the number of bytes in a string - link: 1, // - create a hard link in the filesystem - listen: 1, // - register your socket as a server - local: 2, // - create a temporary value for a global variable (dynamic scoping) - localtime: 1, // - convert UNIX time into record or string using local time - lock: 1, // - get a thread lock on a variable, subroutine, or method - log: 1, // - retrieve the natural logarithm for a number - lstat: 1, // - stat a symbolic link - m: null, // - match a string with a regular expression pattern - map: 1, // - apply a change to a list to get back a new list with the changes - mkdir: 1, // - create a directory - msgctl: 1, // - SysV IPC message control operations - msgget: 1, // - get SysV IPC message queue - msgrcv: 1, // - receive a SysV IPC message from a message queue - msgsnd: 1, // - send a SysV IPC message to a message queue - my: 2, // - declare and assign a local variable (lexical scoping) - new: 1, // - next: 1, // - iterate a block prematurely - no: 1, // - unimport some module symbols or semantics at compile time - oct: 1, // - convert a string to an octal number - open: 1, // - open a file, pipe, or descriptor - opendir: 1, // - open a directory - ord: 1, // - find a character's numeric representation - our: 2, // - declare and assign a package variable (lexical scoping) - pack: 1, // - convert a list into a binary representation - package: 1, // - declare a separate global namespace - pipe: 1, // - open a pair of connected filehandles - pop: 1, // - remove the last element from an array and return it - pos: 1, // - find or set the offset for the last/next m//g search - print: 1, // - output a list to a filehandle - printf: 1, // - output a formatted list to a filehandle - prototype: 1, // - get the prototype (if any) of a subroutine - push: 1, // - append one or more elements to an array - q: null, // - singly quote a string - qq: null, // - doubly quote a string - qr: null, // - Compile pattern - quotemeta: null, // - quote regular expression magic characters - qw: null, // - quote a list of words - qx: null, // - backquote quote a string - rand: 1, // - retrieve the next pseudorandom number - read: 1, // - fixed-length buffered input from a filehandle - readdir: 1, // - get a directory from a directory handle - readline: 1, // - fetch a record from a file - readlink: 1, // - determine where a symbolic link is pointing - readpipe: 1, // - execute a system command and collect standard output - recv: 1, // - receive a message over a Socket - redo: 1, // - start this loop iteration over again - ref: 1, // - find out the type of thing being referenced - rename: 1, // - change a filename - require: 1, // - load in external functions from a library at runtime - reset: 1, // - clear all variables of a given name - return: 1, // - get out of a function early - reverse: 1, // - flip a string or a list - rewinddir: 1, // - reset directory handle - rindex: 1, // - right-to-left substring search - rmdir: 1, // - remove a directory - s: null, // - replace a pattern with a string - say: 1, // - print with newline - scalar: 1, // - force a scalar context - seek: 1, // - reposition file pointer for random-access I/O - seekdir: 1, // - reposition directory pointer - select: 1, // - reset default output or do I/O multiplexing - semctl: 1, // - SysV semaphore control operations - semget: 1, // - get set of SysV semaphores - semop: 1, // - SysV semaphore operations - send: 1, // - send a message over a socket - setgrent: 1, // - prepare group file for use - sethostent: 1, // - prepare hosts file for use - setnetent: 1, // - prepare networks file for use - setpgrp: 1, // - set the process group of a process - setpriority: 1, // - set a process's nice value - setprotoent: 1, // - prepare protocols file for use - setpwent: 1, // - prepare passwd file for use - setservent: 1, // - prepare services file for use - setsockopt: 1, // - set some socket options - shift: 1, // - remove the first element of an array, and return it - shmctl: 1, // - SysV shared memory operations - shmget: 1, // - get SysV shared memory segment identifier - shmread: 1, // - read SysV shared memory - shmwrite: 1, // - write SysV shared memory - shutdown: 1, // - close down just half of a socket connection - sin: 1, // - return the sine of a number - sleep: 1, // - block for some number of seconds - socket: 1, // - create a socket - socketpair: 1, // - create a pair of sockets - sort: 1, // - sort a list of values - splice: 1, // - add or remove elements anywhere in an array - split: 1, // - split up a string using a regexp delimiter - sprintf: 1, // - formatted print into a string - sqrt: 1, // - square root function - srand: 1, // - seed the random number generator - stat: 1, // - get a file's status information - state: 1, // - declare and assign a state variable (persistent lexical scoping) - study: 1, // - optimize input data for repeated searches - sub: 1, // - declare a subroutine, possibly anonymously - substr: 1, // - get or alter a portion of a string - symlink: 1, // - create a symbolic link to a file - syscall: 1, // - execute an arbitrary system call - sysopen: 1, // - open a file, pipe, or descriptor - sysread: 1, // - fixed-length unbuffered input from a filehandle - sysseek: 1, // - position I/O pointer on handle used with sysread and syswrite - system: 1, // - run a separate program - syswrite: 1, // - fixed-length unbuffered output to a filehandle - tell: 1, // - get current seekpointer on a filehandle - telldir: 1, // - get current seekpointer on a directory handle - tie: 1, // - bind a variable to an object class - tied: 1, // - get a reference to the object underlying a tied variable - time: 1, // - return number of seconds since 1970 - times: 1, // - return elapsed time for self and child processes - tr: null, // - transliterate a string - truncate: 1, // - shorten a file - uc: 1, // - return upper-case version of a string - ucfirst: 1, // - return a string with just the next letter in upper case - umask: 1, // - set file creation mode mask - undef: 1, // - remove a variable or function definition - unlink: 1, // - remove one link to a file - unpack: 1, // - convert binary structure into normal perl variables - unshift: 1, // - prepend more elements to the beginning of a list - untie: 1, // - break a tie binding to a variable - use: 1, // - load in a module at compile time - utime: 1, // - set a file's last access and modify times - values: 1, // - return a list of the values in a hash - vec: 1, // - test or set particular bits in a string - wait: 1, // - wait for any child process to die - waitpid: 1, // - wait for a particular child process to die - wantarray: 1, // - get void vs scalar vs list context of current subroutine call - warn: 1, // - print debugging info - when: 1, // - write: 1, // - print a picture record - y: null // - transliterate a string - }; - // PG Keywords and Variables - const PGstyle = 'atom'; - const PGkeyword = 'keyword'; - const PGcmds = new Set([ - 'DOCUMENT', - 'ENDDOCUMENT', - 'loadMacros', - 'TEXT', - 'SOLUTION', - 'HINT', - 'STATEMENT', - 'COMMENT', - 'MODES', - 'htmlLink', - 'helpLink', - 'knowlLink', - 'image', - 'Context', - 'Compute', - 'Real', - 'Formula', - 'String', - 'List', - 'Complex', - 'Point', - 'Vector', - 'Matrix', - 'Interval', - 'Set', - 'Fraction', - 'ANS', - 'NAMED_ANS', - 'WEIGHTED_ANS', - 'MultiAnswer', - 'Value', - 'random', - 'list_random', - 'non_zero_random', - 'NchooseK' - ]); - const PGvars = new Set([ - 'BR', - 'RBR', - 'PAR', - 'LQ', - 'RQ', - 'BM', - 'EM', - 'BDM', - 'EDM', - 'LTS', - 'GTS', - 'LTE', - 'GTE', - 'BEGIN_ONE_COLUMN', - 'END_ONE_COLUMN', - 'SOL', - 'SOLUTION', - 'HINT', - 'COMMENT', - 'US', - 'SPACE', - 'NBSP', - 'NDASH', - 'MDASH', - 'BLABEL', - 'ELABEL', - 'BBOLD', - 'EBOLD', - 'BITALIC', - 'EITALIC', - 'BUL', - 'EUL', - 'BCENTER', - 'ECENTER', - 'BLTR', - 'ELTR', - 'BKBD', - 'EKBD', - 'HR', - 'LBRACE', - 'RBRACE', - 'LB', - 'RB', - 'DOLLAR', - 'PERCENT', - 'CARET', - 'PI', - 'E', - 'LATEX', - 'TEX', - 'APOS', - 'showPartialCorrectAnswers', - 'refreshCachedImages', - 'ITEM', - 'ITEMSEP' - ]); - - const RXstyle = 'string-2'; - const RXmodifiers = /[goseximacplud]/; // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type - - function tokenChain(stream, state, chain, style, tail, tokener) { - // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;) - state.chain = null; // 12 3tail - state.style = null; - state.tail = null; - state.tokenize = function (stream, state) { - var e = false, - c, - i = 0; - while ((c = stream.next())) { - if (c === chain[i] && !e) { - if (chain[++i] !== undefined) { - state.chain = chain[i]; - state.style = style; - state.tail = tail; - } else if (tail) stream.eatWhile(tail); - state.tokenize = tokener || tokenPerl; - return style; - } - e = !e && c == '\\'; - } - return style; - }; - return state.tokenize(stream, state); - } - - function tokenSOMETHING(stream, state, string) { - state.tokenize = function (stream, state) { - if (stream.string == string) state.tokenize = tokenPerl; - stream.skipToEnd(); - return 'string'; - }; - return state.tokenize(stream, state); - } - - // EV3 block formatting - function tokenEV3(stream, state, string, style, prevState) { - state.tokenize = function (stream, state) { - if (prevState && prevState.mode == 'math') { - var reg = new RegExp('^\\\\' + string); - } else { - var reg = new RegExp('^' + string); - } - if (stream.match(reg)) { - if (!prevState) { - state.tokenize = tokenPerl; - return PGstyle; - } else { - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, prevState.string, prevState.style, prevState.prevState); - }; - } - if (string.includes('BOLD')) return PGstyle + ' strong'; - if (string.includes('ITALIC')) return PGstyle + ' em'; - if (prevState.endstyle) return prevState.endstyle; - return style; - } else { - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, string, style, prevState); - }; - } - - const newPrevState = {}; - if (prevState) { - newPrevState.prevState = JSON.parse(JSON.stringify(prevState)); - } else { - newPrevState.prevState = null; - } - newPrevState.style = style; - newPrevState.string = string; - - if (prevState && prevState.mode == 'cmd') { - // Some additional formatting for perl code blocks - newPrevState.mode = 'cmd'; - if (stream.match(/^[$@%]{/)) { - // ${, @{, %{ nested blocks - style = 'variable'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '\\}', style, newPrevState); - }; - return style; - } - if (stream.match(/^\(/)) { - // Nested ( ) blocks - newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '\\)', style, newPrevState); - }; - return 'variable'; - } - if (stream.match(/^\[/)) { - // Nested [ ] blocks - newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '\\]', style, newPrevState); - }; - return 'variable'; - } - if (stream.match(/^\{/)) { - // Nested { } blocks - newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '\\}', style, newPrevState); - }; - return 'variable'; - } - if (stream.match(/^\w+/)) { - // Check for PG keywords - if (PGcmds.has(stream.current())) return PGkeyword; - else return style; - } - if (stream.match(/^['"]/)) { - // Quotes - return tokenChain(stream, state, [stream.current()], 'string', null, function (stream, state) { - tokenEV3(stream, state, string, style, prevState); - }); - } - if (stream.match(/^[=,;/\*><%&|.~?:+/-]/)) { - // Catch some perl operators - return 'variable'; - } - } - - if (stream.match(/^\\\(/)) { - // \(...\) TeX block - if (prevState) { - style = 'error'; - } else { - style = 'comment'; - } - state.tokenize = function (stream, state) { - newPrevState.mode = 'math'; - return tokenEV3(stream, state, '\\)', style, newPrevState); - }; - } else if (stream.match(/^\\\[/)) { - // \[...\] TeX block - if (prevState) { - style = 'error'; - } else { - style = 'comment'; - } - state.tokenize = function (stream, state) { - newPrevState.mode = 'math'; - return tokenEV3(stream, state, '\\]', style, newPrevState); - }; - } else if (stream.match(/^\\{/)) { - // \{...\} Perl code block - if (prevState) { - style = 'error'; - } else { - style = 'variable-2'; - } - state.tokenize = function (stream, state) { - newPrevState.mode = 'cmd'; - return tokenEV3(stream, state, '\\\\}', style, newPrevState); - }; - } else if (stream.match(/^``/)) { - // ``...`` math object math - if (prevState && prevState.mode != 'math') { - style = 'error'; - } else { - style = 'variable-3'; - } - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '``', style, newPrevState); - }; - } else if (stream.match(/^`/)) { - // `...` math object math - if (prevState && prevState.mode != 'math') { - style = 'error'; - } else { - style = 'variable-3'; - } - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '`([^`]|$)', style, newPrevState, 'tick'); - }; - } else if (stream.match(/^(\$BBOLD|\${BBOLD})/)) { - // Bold - style = style = ' strong'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '(\\$EBOLD|\\${EBOLD})', style, newPrevState); - }; - return PGstyle + ' strong'; - } else if (stream.match(/^(\$BITALIC|\${BITALIC})/)) { - // Italic - style = style + ' em'; - state.tokenize = function (stream, state) { - return tokenEV3(stream, state, '(\\$EITALIC|\\${EITALIC})', style, newPrevState); - }; - return PGstyle + ' em'; - } else if (stream.match(/^[$@%]\w+/)) { - // PG Variables - if (PGvars.has(stream.current().substring(1))) return PGstyle; - return 'variable'; - } else if (stream.match(/^[$@%]{\w+}/)) { - // ${foo} PG variables - if (PGvars.has(stream.current().slice(2, -1))) return PGstyle; - return 'variable'; - } else if (stream.match(/^ +$/)) { - // Trailing white space - return 'trailingspace'; - } else if (stream.match(/^[\[\]\\ (){}$@%`]/)) { - // Advance a single character if special - return style; - } else { - // Otherwise advance through all non special characters - if (prevState && prevState.mode == 'cmd') { - // Only eat through words in perl code mode - if (stream.match(/\w+/)) return style; - else stream.next(); - } else { - stream.eatWhile(/[^\[\]\\ (){}$@%`]/); - } - } - return style; - }; - return state.tokenize(stream, state); - } - - // No additional formatting inside comment block, only looks for end string. - // Currently only used for comments and ``` code blocks. - // The final stream.match and stream.eatWhile may need updated if used for other blocks. - function tokenPGMLComment(stream, state, string, style, prevState) { - state.tokenize = function (stream, state) { - var reg = new RegExp('^' + string); - if (stream.match(reg)) { - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, prevState.string, prevState.style, prevState.prevState); - }; - return style; - } else { - state.tokenize = function (stream, state) { - return tokenPGMLComment(stream, state, string, style, prevState); - }; - } - if (stream.match(/^[\]%`]/)) return style; - stream.eatWhile(/[^\]%`]/); - return style; - }; - return state.tokenize(stream, state); - } - - // PGML subblock which has limited formatting options compared to main block. - // This block nests {} and [] blocks, for correct pairing in variables and commands. - function tokenPGMLSubBlock(stream, state, string, style, prevState) { - state.tokenize = function (stream, state) { - var reg = new RegExp('^' + string); - if (stream.match(reg)) { - // Needed to ensure ': ' verbatim lines exit out if ended with a secondary subblock. - if (stream.eol() && prevState.subblock && prevState.prevState && prevState.prevState.stopeol) { - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock( - stream, - state, - prevState.prevState.string, - prevState.prevState.style, - prevState.prevState.prevState - ); - }; - } else if (stream.eol() && prevState.prevState && prevState.prevState.stopeol) { - state.tokenize = function (stream, state) { - return tokenPGML( - stream, - state, - prevState.prevState.string, - prevState.prevState.style, - prevState.prevState.prevState - ); - }; - } else if (prevState.subblock) { - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock( - stream, - state, - prevState.string, - prevState.style, - prevState.prevState - ); - }; - } else { - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, prevState.string, prevState.style, prevState.prevState); - }; - } - if (prevState.mode == 'var' || prevState.mode == 'cmd') stream.match(/^\*{1,3}([^\*]|$)/); - if (prevState.mode == 'calc') { - if (!stream.match(/^\*(\s|$)/)) stream.match(/^\{.+\}/); - } - if (prevState.endstyle) return prevState.endstyle; - return style; - } else { - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, string, style, prevState); - }; - } - - var newPrevState = {}; - if (prevState) { - newPrevState.prevState = JSON.parse(JSON.stringify(prevState)); - } else { - newPrevState.prevState = null; - } - newPrevState.style = style; - newPrevState.string = string; - newPrevState.subblock = true; - if (prevState.mode) newPrevState.mode = prevState.mode; - - if (prevState.mode == 'cmd') { - // Some formatting for [@ ... @] blocks - if (stream.match(/^[$@%]\w+/)) { - // $, @, % variables - if (PGvars.has(stream.current().substring(1))) return PGstyle; - else return 'variable'; - } - if (stream.match(/^[$@%]{\w+}/)) { - // ${foo}, @{foo}, %{foo} variables - if (PGvars.has(stream.current().slice(2, -1))) return PGstyle; - else return 'variable'; - } - if (stream.match(/^[$@%]{/)) { - // ${, @{, %{ nested blocks - style = 'variable'; - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, '\\}', style, newPrevState); - }; - return style; - } - if (stream.match(/^\(/)) { - // Nested ( ) blocks - newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, '\\)', style, newPrevState); - }; - return 'variable'; - } - if (stream.match(/^\w+/)) { - // Check for PG keywords - if (PGcmds.has(stream.current())) return PGkeyword; - else return style; - } - if (stream.match(/^['"]/)) { - // Quotes - return tokenChain(stream, state, [stream.current()], 'string', null, function (stream, state) { - tokenPGMLSubBlock(stream, state, string, style, prevState); - }); - } - if (stream.match(/^[=,;/\*><%$&|.~?:]/)) - // Catch some perl operators - return 'variable'; - } - - if (stream.match(/^\[\$/)) { - // Variable - const p = stream.pos; - if (stream.match(/^\w+/) && PGvars.has(stream.current().substring(2)) && stream.eat(']')) { - stream.match(/^\*{1,3}/); - return PGstyle; - } else { - stream.pos = p; - } - style = 'variable'; - state.tokenize = function (stream, state) { - newPrevState.mode = 'var'; - return tokenPGMLSubBlock(stream, state, '\\]', style, newPrevState); - }; - } else if (stream.match(/^\[/)) { - // Nested [ ] blocks - if (prevState.mode == 'cmd') newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, '\\]', style, newPrevState); - }; - if (prevState.mode == 'cmd') return 'variable'; - } else if (stream.match(/^\{/)) { - // Nested { } blocks - if (prevState.mode == 'cmd') newPrevState.endstyle = 'variable'; - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, '\\}', style, newPrevState); - }; - if (prevState.mode == 'cmd') return 'variable'; - } else if (stream.match(/^\w+\s*/)) { - // Grab next word before going forward - return style; - } else { - // Catchall to advanced one character if no match was found. - stream.eat(/./); - } - return style; - }; - return state.tokenize(stream, state); - } - - // Main PGML block. Can nest to allow subblocks with PGML formatting in them. - function tokenPGML(stream, state, string, style, prevState) { - state.tokenize = function (stream, state) { - var reg = new RegExp('^' + string); - if (stream.match(reg)) { - if (!prevState) { - state.tokenize = tokenPerl; - return PGkeyword; - // Needed to ensure ': ' verbatim lines exit out if ended with a secondary block. - } else if (stream.eol() && prevState.prevState && prevState.prevState.stopeol) { - state.tokenize = function (stream, state) { - return tokenPGML( - stream, - state, - prevState.prevState.string, - prevState.prevState.style, - prevState.prevState.prevState - ); - }; - } else { - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, prevState.string, prevState.style, prevState.prevState); - }; - } - return style; - } else { - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, string, style, prevState); - }; - } - - var newPrevState = {}; - if (prevState) { - newPrevState.prevState = JSON.parse(JSON.stringify(prevState)); - } else { - newPrevState.prevState = null; - } - newPrevState.style = style; - newPrevState.string = string; - - if (stream.sol()) { - if ( - stream.match(/^ *(>> +)?[ivxlIVXL]+[.)] /) || - stream.match(/^ *(>> +)?\d+[.)] /) || - stream.match(/^ *(>> +)?\w[.)] /) || - stream.match(/^ *(>> +)?[*\-+o] /) - ) { - // Lists - return 'atom strong'; - } - if (stream.match(/^ *[\-=]{3,}/)) { - // Rules - stream.match(/^\{[^}]*\}/); - stream.match(/^\{[^}]*\}/); - return 'hr'; - } - if (stream.match(/^ *(>> +)?#{1,}.*$/)) - // Headers - return 'header'; - if (stream.match(/^ *>> /)) - // Justification - return 'atom strong'; - if (stream.match(/^ *: /)) { - // Single line verbatim - style = 'tag'; - state.tokenize = function (stream, state) { - newPrevState.stopeol = true; - return tokenPGML(stream, state, '.$', style, newPrevState); - }; - return style; - } - } - - if (stream.match(/^\[:{1,3}/)) { - // Algebra notation math - style = 'variable-3'; - const endstring = stream.current().substring(1) + '\\]'; - state.tokenize = function (stream, state) { - newPrevState.mode = 'calc'; - return tokenPGMLSubBlock(stream, state, endstring, style, newPrevState); - }; - } else if (stream.match(/^\[`{1,3}/)) { - // TeX notation math - style = 'comment'; - const endstring = stream.current().substring(1) + '\\]'; - state.tokenize = function (stream, state) { - newPrevState.mode = 'tex'; - return tokenPGMLSubBlock(stream, state, endstring, style, newPrevState); - }; - } else if (stream.match(/^\[\|+/)) { - // Verbatim - style = 'tag'; - const endstring = stream.current().substring(1) + '\\]'; - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, endstring, style, newPrevState); - }; - } else if (!prevState && stream.match(/^```/)) { - // Multiline verbatim / code - style = 'tag'; - state.tokenize = function (stream, state) { - return tokenPGMLComment(stream, state, '```', style, newPrevState); - }; - } else if (stream.match(/^\[%/)) { - // Comment - style = 'comment'; - state.tokenize = function (stream, state) { - return tokenPGMLComment(stream, state, '%\\]', style, newPrevState); - }; - } else if (stream.match(/^\[@/)) { - // Perl code - style = 'variable-2'; - state.tokenize = function (stream, state) { - newPrevState.mode = 'cmd'; - return tokenPGMLSubBlock(stream, state, '@\\]', style, newPrevState); - }; - } else if (stream.match(/^\[\$/)) { - // Variable - const p = stream.pos; - if (stream.match(/^[\w\d_]+/) && PGvars.has(stream.current().substring(2)) && stream.eat(']')) { - stream.match(/^\*{1,3}/); - return PGstyle; - } else { - stream.pos = p; - } - style = 'variable'; - state.tokenize = function (stream, state) { - newPrevState.mode = 'var'; - return tokenPGMLSubBlock(stream, state, '\\]', style, newPrevState); - }; - } else if (stream.match(/^\[_+\]/)) { - // Answer blank - if (stream.match(/^\*?\{/)) { - state.tokenize = function (stream, state) { - return tokenPGMLSubBlock(stream, state, '\\}', 'builtin', newPrevState); - }; - } - return 'builtin'; - } else if (stream.match(/<< *$/)) { - // Justification - return 'atom strong'; - } else if (stream.match(/^(\*_|_\*)\w/)) { - // Bold and italic - style = style + ' strong em'; - const endstring = (stream.current().charAt(1) + stream.current().charAt(0)).replace(/\*/, '\\*'); - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, endstring, style, newPrevState); - }; - } else if (stream.match(/^\*{1,3}\w/)) { - // Bold - style = style + ' strong'; - const endstring = stream.current().slice(0, -1).replace(/\*/g, '\\*'); - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, endstring, style, newPrevState); - }; - } else if (stream.match(/^_{1,3}\w/)) { - // Italic - style = style + ' em'; - const endstring = stream.current().slice(0, -1); - state.tokenize = function (stream, state) { - return tokenPGML(stream, state, endstring, style, newPrevState); - }; - } else if (stream.match(/^ +$/)) { - // Trailing whitespace - return 'trailingspace'; - } else if (stream.match(/[A-Za-z0-9]+\s*/)) { - // Grab next word before going forward - return style; - } else { - // Catchall to advanced one character if no match was found. - stream.eat(/./); - } - return style; - }; - - return state.tokenize(stream, state); - } - - function tokenPerl(stream, state) { - if (stream.eatSpace()) return null; - if (state.chain) return tokenChain(stream, state, state.chain, state.style, state.tail); - if ( - stream.match( - /^(\-?((\d[\d_]*)?\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F_]+|0b[01_]+|\d[\d_]*(e[+-]?\d+)?)/ - ) - ) - return 'number'; - if (stream.match(/^<<(?=[_a-zA-Z])/)) { - // NOTE: <'], RXstyle, RXmodifiers); - } - if (/[\^'"!~\/]/.test(c)) { - eatSuffix(stream, 1); - return tokenChain(stream, state, [stream.eat(c)], RXstyle, RXmodifiers); - } - } else if (c == 'q') { - c = look(stream, 1); - if (c == '(') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [')'], 'string'); - } - if (c == '[') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [']'], 'string'); - } - if (c == '{') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['}'], 'string'); - } - if (c == '<') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['>'], 'string'); - } - if (/[\^'"!~\/]/.test(c)) { - eatSuffix(stream, 1); - return tokenChain(stream, state, [stream.eat(c)], 'string'); - } - } else if (c == 'w') { - c = look(stream, 1); - if (c == '(') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [')'], 'bracket'); - } - if (c == '[') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [']'], 'bracket'); - } - if (c == '{') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['}'], 'bracket'); - } - if (c == '<') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['>'], 'bracket'); - } - if (/[\^'"!~\/]/.test(c)) { - eatSuffix(stream, 1); - return tokenChain(stream, state, [stream.eat(c)], 'bracket'); - } - } else if (c == 'r') { - c = look(stream, 1); - if (c == '(') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [')'], RXstyle, RXmodifiers); - } - if (c == '[') { - eatSuffix(stream, 2); - return tokenChain(stream, state, [']'], RXstyle, RXmodifiers); - } - if (c == '{') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['}'], RXstyle, RXmodifiers); - } - if (c == '<') { - eatSuffix(stream, 2); - return tokenChain(stream, state, ['>'], RXstyle, RXmodifiers); - } - if (/[\^'"!~\/]/.test(c)) { - eatSuffix(stream, 1); - return tokenChain(stream, state, [stream.eat(c)], RXstyle, RXmodifiers); - } - } else if (/[\^'"!~\/(\[{<]/.test(c)) { - if (c == '(') { - eatSuffix(stream, 1); - return tokenChain(stream, state, [')'], 'string'); - } - if (c == '[') { - eatSuffix(stream, 1); - return tokenChain(stream, state, [']'], 'string'); - } - if (c == '{') { - eatSuffix(stream, 1); - return tokenChain(stream, state, ['}'], 'string'); - } - if (c == '<') { - eatSuffix(stream, 1); - return tokenChain(stream, state, ['>'], 'string'); - } - if (/[\^'"!~\/]/.test(c)) { - return tokenChain(stream, state, [stream.eat(c)], 'string'); - } - } - } - } - if (ch == 'm') { - var c = look(stream, -2); - if (!(c && /\w/.test(c))) { - c = stream.eat(/[(\[{<\^'"!~\/]/); - if (c) { - if (/[\^'"!~\/]/.test(c)) { - return tokenChain(stream, state, [c], RXstyle, RXmodifiers); - } - if (c == '(') { - return tokenChain(stream, state, [')'], RXstyle, RXmodifiers); - } - if (c == '[') { - return tokenChain(stream, state, [']'], RXstyle, RXmodifiers); - } - if (c == '{') { - return tokenChain(stream, state, ['}'], RXstyle, RXmodifiers); - } - if (c == '<') { - return tokenChain(stream, state, ['>'], RXstyle, RXmodifiers); - } - } - } - } - if (ch == 's') { - var c = /[\/>\]})\w]/.test(look(stream, -2)); - if (!c) { - c = stream.eat(/[(\[{<\^'"!~\/]/); - if (c) { - if (c == '[') return tokenChain(stream, state, [']', ']'], RXstyle, RXmodifiers); - if (c == '{') return tokenChain(stream, state, ['}', '}'], RXstyle, RXmodifiers); - if (c == '<') return tokenChain(stream, state, ['>', '>'], RXstyle, RXmodifiers); - if (c == '(') return tokenChain(stream, state, [')', ')'], RXstyle, RXmodifiers); - return tokenChain(stream, state, [c, c], RXstyle, RXmodifiers); - } - } - } - if (ch == 'y') { - var c = /[\/>\]})\w]/.test(look(stream, -2)); - if (!c) { - c = stream.eat(/[(\[{<\^'"!~\/]/); - if (c) { - if (c == '[') return tokenChain(stream, state, [']', ']'], RXstyle, RXmodifiers); - if (c == '{') return tokenChain(stream, state, ['}', '}'], RXstyle, RXmodifiers); - if (c == '<') return tokenChain(stream, state, ['>', '>'], RXstyle, RXmodifiers); - if (c == '(') return tokenChain(stream, state, [')', ')'], RXstyle, RXmodifiers); - return tokenChain(stream, state, [c, c], RXstyle, RXmodifiers); - } - } - } - if (ch == 't') { - var c = /[\/>\]})\w]/.test(look(stream, -2)); - if (!c) { - c = stream.eat('r'); - if (c) { - c = stream.eat(/[(\[{<\^'"!~\/]/); - if (c) { - if (c == '[') return tokenChain(stream, state, [']', ']'], RXstyle, RXmodifiers); - if (c == '{') return tokenChain(stream, state, ['}', '}'], RXstyle, RXmodifiers); - if (c == '<') return tokenChain(stream, state, ['>', '>'], RXstyle, RXmodifiers); - if (c == '(') return tokenChain(stream, state, [')', ')'], RXstyle, RXmodifiers); - return tokenChain(stream, state, [c, c], RXstyle, RXmodifiers); - } - } - } - } - if (ch == '`') { - return tokenChain(stream, state, [ch], 'variable-2'); - } - if (ch == '/') { - if (!/~\s*$/.test(prefix(stream))) return 'operator'; - else return tokenChain(stream, state, [ch], RXstyle, RXmodifiers); - } - if (ch == '$') { - var p = stream.pos; - if (stream.eatWhile(/\w/) && PGvars.has(stream.current().substring(1))) return PGstyle; - else stream.pos = p; - if (stream.eatWhile(/\d/) || (stream.eat('{') && stream.eatWhile(/\d/) && stream.eat('}'))) - return 'variable-2'; - else stream.pos = p; - } - if (/[$@%]/.test(ch)) { - var p = stream.pos; - if ( - (stream.eat('^') && stream.eat(/[A-Z]/)) || - (!/[@$%&]/.test(look(stream, -2)) && stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)) - ) { - var c = stream.current(); - if (PERL[c]) return 'variable-2'; - } - stream.pos = p; - } - if (/[$@%&]/.test(ch)) { - if (stream.eatWhile(/[\w$]/) || (stream.eat('{') && stream.eatWhile(/[\w$]/) && stream.eat('}'))) { - var c = stream.current(); - if (PERL[c]) return 'variable-2'; - else return 'variable'; - } - } - if (ch == '#') { - if (look(stream, -2) != '$') { - stream.skipToEnd(); - return 'comment'; - } - } - if (ch == '-' && look(stream, -2) != ' ' && stream.match(/>\w+/)) return 'variable'; - if (/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)) { - var p = stream.pos; - stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/); - if (PERL[stream.current()]) return 'operator'; - else stream.pos = p; - } - if (ch == '_') { - if (stream.pos == 1) { - if (suffix(stream, 6) == '_END__') { - return tokenChain(stream, state, ['\0'], 'comment'); - } else if (suffix(stream, 7) == '_DATA__') { - return tokenChain(stream, state, ['\0'], 'variable-2'); - } else if (suffix(stream, 7) == '_C__') { - return tokenChain(stream, state, ['\0'], 'string'); - } - } - } - if (/\w/.test(ch)) { - var p = stream.pos; - if ( - look(stream, -2) == '{' && - (look(stream, 0) == '}' || (stream.eatWhile(/\w/) && look(stream, 0) == '}')) - ) - return 'string'; - else stream.pos = p; - if (stream.match(/\w* *=>/)) return 'string'; - } - if (/[A-Z]/.test(ch)) { - var l = look(stream, -2); - var p = stream.pos; - stream.eatWhile(/[A-Z_]/); - if (/[\da-z]/.test(look(stream, 0))) { - stream.pos = p; - } else { - var c = PERL[stream.current()]; - var isPG = PGcmds.has(stream.current()); - if (!c && !isPG) return 'meta'; - if (isPG) return PGkeyword; - if (c[1]) c = c[0]; - if (l != ':') { - if (c == 1) return 'keyword'; - else if (c == 2) return 'def'; - else if (c == 3) return 'atom'; - else if (c == 4) return 'operator'; - else if (c == 5) return 'variable-2'; - else return 'meta'; - } else return 'meta'; - } - } - if (/[a-zA-Z_]/.test(ch)) { - var l = look(stream, -2); - stream.eatWhile(/\w/); - var c = PERL[stream.current()]; - var isPG = PGcmds.has(stream.current()); - if (!c && !isPG) return 'meta'; - if (isPG) return PGkeyword; - if (c[1]) c = c[0]; - if (l != ':') { - if (c == 1) return 'keyword'; - else if (c == 2) return 'def'; - else if (c == 3) return 'atom'; - else if (c == 4) return 'operator'; - else if (c == 5) return 'variable-2'; - else return 'meta'; - } else return 'meta'; - } - return null; - } - - return { - startState: function () { - return { - tokenize: tokenPerl, - chain: null, - style: null, - tail: null - }; - }, - token: function (stream, state) { - return (state.tokenize || tokenPerl)(stream, state); - }, - lineComment: '#' - }; - }); - - CodeMirror.registerHelper('wordChars', 'perl', /[\w$]/); - - CodeMirror.registerHelper('fold', 'PG', (cm, start) => { - const m1 = - /^\s*BEGIN_(PGML|PGML_SOLUTION|PGML_HINT|TEXT)\s*$/.exec(cm.getLine(start.line)) || - /^\s*[$\w]*\s*->\s*BEGIN_(TIKZ|LATEX_IMAGE)\s*$/.exec(cm.getLine(start.line)); - const m2 = /^\s*(Section|Scaffold)::Begin/.exec(cm.getLine(start.line)); - if (m1 || m2) { - for (let current_line = start.line + 1; current_line <= cm.lineCount(); ++current_line) { - const end_re = m1 ? RegExp(`END_${m1[1]}`) : RegExp(`${m2[1]}::End`); - if (end_re.test(cm.getLine(current_line))) { - return { - from: CodeMirror.Pos(start.line, cm.getLine(start.line).length), - to: CodeMirror.Pos(current_line, cm.getLine(current_line).length) - }; - } - } - } - return; - }); - - CodeMirror.defineMIME('text/x-perl', 'perl'); - - // it's like "peek", but need for look-ahead or look-behind if index < 0 - function look(stream, c) { - return stream.string.charAt(stream.pos + (c || 0)); - } - - // return a part of prefix of current stream from current position - function prefix(stream, c) { - if (c) { - var x = stream.pos - c; - return stream.string.substr(x >= 0 ? x : 0, c); - } else { - return stream.string.substr(0, stream.pos - 1); - } - } - - // return a part of suffix of current stream from current position - function suffix(stream, c) { - var y = stream.string.length; - var x = y - stream.pos + 1; - return stream.string.substr(stream.pos, c && c < y ? c : x); - } - - // eating and vomiting a part of stream from current position - function eatSuffix(stream, c) { - var x = stream.pos + c; - var y; - if (x <= 0) stream.pos = 0; - else if (x >= (y = stream.string.length - 1)) stream.pos = y; - else stream.pos = x; - } -})(); diff --git a/htdocs/js/PGCodeMirror/comment.js b/htdocs/js/PGCodeMirror/comment.js deleted file mode 100644 index 356b9ce146..0000000000 --- a/htdocs/js/PGCodeMirror/comment.js +++ /dev/null @@ -1,287 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/LICENSE - -(function (mod) { - if (typeof exports == 'object' && typeof module == 'object') - // CommonJS - mod(require('../../lib/codemirror')); - else if (typeof define == 'function' && define.amd) - // AMD - define(['../../lib/codemirror'], mod); - // Plain browser env - else mod(CodeMirror); -})(function (CodeMirror) { - 'use strict'; - - var noOptions = {}; - var nonWS = /[^\s\u00a0]/; - var Pos = CodeMirror.Pos, - cmp = CodeMirror.cmpPos; - - function firstNonWS(str) { - var found = str.search(nonWS); - return found == -1 ? 0 : found; - } - - CodeMirror.commands.toggleComment = function (cm) { - cm.toggleComment(); - }; - - CodeMirror.defineExtension('toggleComment', function (options) { - if (!options) options = noOptions; - var cm = this; - var minLine = Infinity, - ranges = this.listSelections(), - mode = null; - for (var i = ranges.length - 1; i >= 0; i--) { - var from = ranges[i].from(), - to = ranges[i].to(); - if (from.line >= minLine) continue; - if (to.line >= minLine) to = Pos(minLine, 0); - minLine = from.line; - if (mode == null) { - if (cm.uncomment(from, to, options)) mode = 'un'; - else { - cm.lineComment(from, to, options); - mode = 'line'; - } - } else if (mode == 'un') { - cm.uncomment(from, to, options); - } else { - cm.lineComment(from, to, options); - } - } - }); - - // Rough heuristic to try and detect lines that are part of multi-line string - function probablyInsideString(cm, pos, line) { - return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line); - } - - const md_section = RegExp( - 'DESCRIPTION|KEYWORDS|DBsubject|DBchapter|DBsection|Date|Author|Institution ' + - '|MO|Static|TitleText|EditionText|AuthorText|Section|Problem|Language|Level' - ); - - // Custom version of getMode for PG files. - function getMode(cm, pos) { - const mode = cm.getModeAt(pos); - // Clear any comment fields of mode. - delete mode.lineComment; - delete mode.blockCommentStart; - delete mode.blockCommentEnd; - - if (md_section.test(cm.getLine(pos.line)) || insideDescriptionBlock(cm, pos)) { - mode.lineComment = '##'; - mode.name = 'PG_meta'; - } else if (inPGMLBlock(cm, pos)) { - mode.name = 'PGML'; - mode.blockCommentStart = '[%'; - mode.blockCommentEnd = '%]'; - } else if (inTikzBlock(cm, pos)) { - mode.lineComment = '%'; - mode.name = 'tikz'; - } else { - mode.name = 'perl'; - mode.lineComment = '#'; - } - return mode; - } - - function insideDescriptionBlock(cm, pos) { - for (let line = pos.line; line >= 0; --line) { - if (/ENDDESCRIPTION/.test(cm.getLine(line))) return false; - if (/DESCRIPTION/.test(cm.getLine(line))) return true; - } - return false; - } - - function inTikzBlock(cm, pos) { - for (let line = pos.line; line >= 0; --line) { - if (/BEGIN_TIKZ|BEGIN_LATEX_IMAGE/.test(cm.getLine(line))) return true; - if (/END_PGML|END_TIKZ|END_LATEX_IMAGE/.test(cm.getLine(line))) return false; - } - return false; - } - - function inPGMLBlock(cm, pos) { - for (let line = pos.line; line >= 0; --line) { - if (/BEGIN_PGML/.test(cm.getLine(line))) return true; - if (/END_PGML/.test(cm.getLine(line))) return false; - } - return false; - } - - CodeMirror.defineExtension('lineComment', function (from, to, options) { - if (!options) options = noOptions; - var self = this, - mode = getMode(self, from); - var firstLine = self.getLine(from.line); - if (firstLine == null || probablyInsideString(self, from, firstLine)) return; - - var commentString = options.lineComment || mode.lineComment; - if (!commentString) { - if (options.blockCommentStart || mode.blockCommentStart) { - options.fullLines = true; - self.blockComment(from, to, options); - } - return; - } - - var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1); - var pad = options.padding == null ? ' ' : options.padding; - var blankLines = options.commentBlankLines || from.line == to.line; - - self.operation(function () { - if (options.indent) { - var baseString = null; - for (var i = from.line; i < end; ++i) { - var line = self.getLine(i); - var whitespace = line.search(nonWS) === -1 ? line : line.slice(0, firstNonWS(line)); - if (baseString == null || baseString.length > whitespace.length) { - baseString = whitespace; - } - } - for (var i = from.line; i < end; ++i) { - var line = self.getLine(i), - cut = baseString.length; - if (!blankLines && !nonWS.test(line)) continue; - if (line.slice(0, cut) != baseString) cut = firstNonWS(line); - self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut)); - } - } else { - for (var i = from.line; i < end; ++i) { - if (blankLines || nonWS.test(self.getLine(i))) self.replaceRange(commentString + pad, Pos(i, 0)); - } - } - }); - }); - - CodeMirror.defineExtension('blockComment', function (from, to, options) { - if (!options) options = noOptions; - var self = this, - mode = getMode(self, from); - var startString = options.blockCommentStart || mode.blockCommentStart; - var endString = options.blockCommentEnd || mode.blockCommentEnd; - if (!startString || !endString) { - if ((options.lineComment || mode.lineComment) && options.fullLines != false) - self.lineComment(from, to, options); - return; - } - if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return; - - var end = Math.min(to.line, self.lastLine()); - if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; - - var pad = options.padding == null ? ' ' : options.padding; - if (from.line > end) return; - - self.operation(function () { - if (options.fullLines != false) { - var lastLineHasText = nonWS.test(self.getLine(end)); - self.replaceRange(pad + endString, Pos(end)); - self.replaceRange(startString + pad, Pos(from.line, 0)); - var lead = options.blockCommentLead || mode.blockCommentLead; - if (lead != null) - for (var i = from.line + 1; i <= end; ++i) - if (i != end || lastLineHasText) self.replaceRange(lead + pad, Pos(i, 0)); - } else { - var atCursor = cmp(self.getCursor('to'), to) == 0, - empty = !self.somethingSelected(); - self.replaceRange(endString, to); - if (atCursor) self.setSelection(empty ? to : self.getCursor('from'), to); - self.replaceRange(startString, from); - } - }); - }); - - CodeMirror.defineExtension('uncomment', function (from, to, options) { - if (!options) options = noOptions; - var self = this, - mode = getMode(self, from); - var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), - start = Math.min(from.line, end); - - // Try finding line comments - var lineString = options.lineComment || mode.lineComment, - lines = []; - var pad = options.padding == null ? ' ' : options.padding, - didSomething; - lineComment: { - if (!lineString) break lineComment; - for (var i = start; i <= end; ++i) { - var line = self.getLine(i); - var found = line.indexOf(lineString); - if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; - if (found == -1 && nonWS.test(line)) break lineComment; - if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; - lines.push(line); - } - self.operation(function () { - for (var i = start; i <= end; ++i) { - var line = lines[i - start]; - var pos = line.indexOf(lineString), - endPos = pos + lineString.length; - if (pos < 0) continue; - if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length; - didSomething = true; - self.replaceRange('', Pos(i, pos), Pos(i, endPos)); - } - }); - if (didSomething) return true; - } - - // Try block comments - var startString = options.blockCommentStart || mode.blockCommentStart; - var endString = options.blockCommentEnd || mode.blockCommentEnd; - if (!startString || !endString) return false; - var lead = options.blockCommentLead || mode.blockCommentLead; - var startLine = self.getLine(start), - open = startLine.indexOf(startString); - if (open == -1) return false; - var endLine = end == start ? startLine : self.getLine(end); - var close = endLine.indexOf(endString, end == start ? open + startString.length : 0); - var insideStart = Pos(start, open + 1), - insideEnd = Pos(end, close + 1); - if ( - close == -1 || - !/comment/.test(self.getTokenTypeAt(insideStart)) || - !/comment/.test(self.getTokenTypeAt(insideEnd)) || - self.getRange(insideStart, insideEnd, '\n').indexOf(endString) > -1 - ) - return false; - - // Avoid killing block comments completely outside the selection. - // Positions of the last startString before the start of the selection, and the first endString after it. - var lastStart = startLine.lastIndexOf(startString, from.ch); - var firstEnd = - lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); - if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; - // Positions of the first endString after the end of the selection, and the last startString before it. - firstEnd = endLine.indexOf(endString, to.ch); - var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); - lastStart = firstEnd == -1 || almostLastStart == -1 ? -1 : to.ch + almostLastStart; - if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; - - self.operation(function () { - self.replaceRange( - '', - Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), - Pos(end, close + endString.length) - ); - var openEnd = open + startString.length; - if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length; - self.replaceRange('', Pos(start, open), Pos(start, openEnd)); - if (lead) - for (var i = start + 1; i <= end; ++i) { - var line = self.getLine(i), - found = line.indexOf(lead); - if (found == -1 || nonWS.test(line.slice(0, found))) continue; - var foundEnd = found + lead.length; - if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length; - self.replaceRange('', Pos(i, found), Pos(i, foundEnd)); - } - }); - return true; - }); -}); diff --git a/htdocs/js/PGCodeMirror/pgeditor.js b/htdocs/js/PGCodeMirror/pgeditor.js index 286cf45247..85209e4eba 100644 --- a/htdocs/js/PGCodeMirror/pgeditor.js +++ b/htdocs/js/PGCodeMirror/pgeditor.js @@ -12,139 +12,18 @@ * Artistic License for more details. */ -(async function () { - if (!CodeMirror) return; +(async () => { + const editorContainer = document.querySelector('.code-mirror-editor'); + if (!PGCodeMirrorEditor || !editorContainer) return; - const loadResource = async (src) => { - return new Promise((resolve, reject) => { - let shouldAppend = false; - let el; - if (/\.js(?:\?[0-9a-zA-Z=^.]*)?$/.exec(src)) { - el = document.querySelector(`script[src="${src}"]`); - if (!el) { - el = document.createElement('script'); - el.async = false; - el.src = src; - shouldAppend = true; - } - } else if (/\.css(?:\?[0-9a-zA-Z=^.]*)?$/.exec(src)) { - el = document.querySelector(`link[href="${src}"]`); - if (!el) { - el = document.createElement('link'); - el.rel = 'stylesheet'; - el.href = src; - shouldAppend = true; - } - } else { - reject(); - return; - } + const editorInput = document.getElementsByName(editorContainer.id)[0]; - if (el.dataset.loaded) { - resolve(); - return; - } + const cm = (webworkConfig.pgCodeMirror = new PGCodeMirrorEditor.View(editorContainer, { + source: editorInput?.value ?? '', + language: editorContainer.dataset.language ?? 'pg' + })); - el.addEventListener('error', reject); - el.addEventListener('abort', reject); - el.addEventListener('load', () => { - if (el) el.dataset.loaded = 'true'; - resolve(); - }); + new ResizeObserver(() => cm.refresh('window-resize')).observe(editorContainer); - if (shouldAppend) document.head.appendChild(el); - }); - }; - - const loadConfig = async (file) => { - const configName = - [...file.matchAll(/.*\/([^.]*?)(?:\.min)?\.(?:js|css)(?:\?[0-9a-zA-Z=^.]*)?$/g)][0]?.[1] ?? 'default'; - if (configName !== 'default') { - try { - await loadResource(file); - } catch { - return 'default'; - } - } - return configName; - }; - - const mode = document.querySelector('.codeMirrorEditor')?.dataset.mode ?? 'PG'; - const options = { - mode, - indentUnit: 4, - tabMode: 'spaces', - lineNumbers: true, - lineWrapping: true, - extraKeys: { - Tab: (cm) => cm.execCommand('insertSoftTab'), - 'Shift-Ctrl-F': (cm) => cm.foldCode(cm.getCursor(), { scanUp: true }), - 'Shift-Cmd-F': (cm) => cm.foldCode(cm.getCursor(), { scanUp: true }), - 'Shift-Ctrl-A': (cm) => CodeMirror.commands.foldAll(cm), - 'Shift-Cmd-A': (cm) => CodeMirror.commands.foldAll(cm), - 'Shift-Ctrl-G': (cm) => CodeMirror.commands.unfoldAll(cm), - 'Shift-Cmd-G': (cm) => CodeMirror.commands.unfoldAll(cm) - }, - highlightSelectionMatches: { annotateScrollbar: true }, - matchBrackets: true, - inputStyle: 'contenteditable', - spellcheck: localStorage.getItem('WW_PGEditor_spellcheck') === 'true', - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] - }; - - if (mode === 'PG') { - options.extraKeys['Ctrl-/'] = (cm) => cm.execCommand('toggleComment'); - options.extraKeys['Cmd-/'] = (cm) => cm.execCommand('toggleComment'); - options.foldGutter = { rangeFinder: new CodeMirror.fold.combine(CodeMirror.fold.PG) }; - options.fold = 'PG'; - } else { - options.foldGutter = true; - } - - const cm = (webworkConfig.pgCodeMirror = CodeMirror.fromTextArea( - document.querySelector('.codeMirrorEditor'), - options - )); - cm.setSize('100%', '550px'); - - // Refresh the CodeMirror instance anytime the containing div resizes so that if line wrapping changes, - // the mouse cursor will still go to the correct place when the user clicks on the CodeMirror window. - new ResizeObserver(() => cm.refresh()).observe(document.querySelector('.CodeMirror')); - - const currentThemeFile = localStorage.getItem('WW_PGEditor_selected_theme') ?? 'default'; - const currentThemeName = await loadConfig(currentThemeFile); - cm.setOption('theme', currentThemeName); - - const currentKeymapFile = localStorage.getItem('WW_PGEditor_selected_keymap') ?? 'default'; - const currentKeymapName = await loadConfig(currentKeymapFile); - cm.setOption('keyMap', currentKeymapName); - - const selectTheme = document.getElementById('selectTheme'); - selectTheme.value = currentThemeName === 'default' ? 'default' : currentThemeFile; - selectTheme.addEventListener('change', async () => { - const themeName = await loadConfig(selectTheme.value); - cm.setOption('theme', themeName); - localStorage.setItem('WW_PGEditor_selected_theme', themeName === 'default' ? 'default' : selectTheme.value); - }); - - const selectKeymap = document.getElementById('selectKeymap'); - selectKeymap.value = currentKeymapName === 'default' ? 'default' : currentKeymapFile; - selectKeymap.addEventListener('change', async () => { - const keymapName = await loadConfig(selectKeymap.value); - cm.setOption('keyMap', keymapName); - localStorage.setItem('WW_PGEditor_selected_keymap', keymapName === 'default' ? 'default' : selectKeymap.value); - }); - - const enableSpell = document.getElementById('enableSpell'); - enableSpell.checked = localStorage.getItem('WW_PGEditor_spellcheck') === 'true'; - enableSpell.addEventListener('change', () => { - cm.setOption('spellcheck', enableSpell.checked); - localStorage.setItem('WW_PGEditor_spellcheck', enableSpell.checked); - cm.focus(); - }); - - const forceRTL = document.getElementById('forceRTL'); - forceRTL.addEventListener('change', () => { - cm.setOption('direction', forceRTL.checked ? 'rtl' : 'ltr'); - }); + editorInput?.form.addEventListener('submit', () => (editorInput.value = cm.source)); })(); diff --git a/htdocs/js/PGCodeMirror/pgeditor.scss b/htdocs/js/PGCodeMirror/pgeditor.scss index 1fd132c9fc..f37a1aabef 100644 --- a/htdocs/js/PGCodeMirror/pgeditor.scss +++ b/htdocs/js/PGCodeMirror/pgeditor.scss @@ -12,14 +12,33 @@ * Artistic License for more details. */ -.CodeMirror { +.code-mirror-editor { border: 1px solid #ddd; min-height: 400px; + overflow: auto; resize: vertical; + height: 600px; + + .cm-editor { + height: 100%; + + .cm-scroller { + height: 100%; + + .cm-content { + height: 100%; + min-height: 400px; + } + } + + .cm-panels { + z-index: 18; + } + } } -// This style is only used if the CodeMirror editor is disabled in localOverrides.conf. -.codeMirrorEditor { +// This style is used if the CodeMirror editor is disabled in localOverrides.conf. +.text-area-editor { border: 1px solid #ddd; padding: 2px; height: 550px; @@ -27,38 +46,3 @@ width: 100%; resize: vertical; } - -// Additional CSS for codemirror addons and overrides - -// CodeMirror overrides -.CodeMirror-code { - outline: none; -} - -pre.CodeMirror-line { - unicode-bidi: embed; -} - -// Match Highligher CSS -.CodeMirror-focused { - .cm-matchhighlight { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); - background-position: bottom; - background-repeat: repeat-x; - } -} - -.cm-matchhighlight { - background-color: lightgreen; -} - -.CodeMirror-selection-highlight-scrollbar { - background-color: green; -} - -// CSS to highlight trailing whitespace in PGML blocks -.cm-trailingspace { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); - background-position: bottom left; - background-repeat: repeat-x; -} diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index f7e419d47b..a5ce979a5c 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -48,7 +48,7 @@ request_object.rpc_command = 'saveFile'; request_object.outputFilePath = document.getElementsByName('temp_file_path')[0]?.value ?? ''; request_object.fileContents = - webworkConfig?.pgCodeMirror?.getValue() ?? document.getElementById('problemContents')?.value ?? ''; + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? ''; if (!request_object.outputFilePath) return; @@ -119,7 +119,7 @@ request_object.rpc_command = 'tidyPGCode'; request_object.pgCode = - webworkConfig?.pgCodeMirror?.getValue() ?? document.getElementById('problemContents')?.value ?? ''; + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? ''; fetch(webserviceURL, { method: 'post', mode: 'same-origin', body: new URLSearchParams(request_object) }) .then((response) => response.json()) @@ -141,7 +141,7 @@ if (request_object.pgCode === data.result_data.tidiedPGCode) { showMessage('There were no changes to the code.', true); } else { - if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.setValue(data.result_data.tidiedPGCode); + if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.source = data.result_data.tidiedPGCode; else document.getElementById('problemContents').value = data.result_data.tidiedPGCode; saveTempFile(); showMessage('Successfuly perltidied code.', true); @@ -161,7 +161,7 @@ request_object.rpc_command = 'convertCodeToPGML'; request_object.pgCode = - webworkConfig?.pgCodeMirror?.getValue() ?? document.getElementById('problemContents')?.value ?? ''; + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? ''; fetch(webserviceURL, { method: 'post', mode: 'same-origin', body: new URLSearchParams(request_object) }) .then((response) => response.json()) @@ -169,7 +169,7 @@ if (request_object.pgCode === data.result_data.pgmlCode) { showMessage('There were no changes to the code.', true); } else { - if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.setValue(data.result_data.pgmlCode); + if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.source = data.result_data.pgmlCode; else document.getElementById('problemContents').value = data.result_data.pgmlCode; saveTempFile(); showMessage('Successfully converted code to PGML', true); @@ -252,9 +252,9 @@ const renderArea = document.getElementById('pgedit-render-area'); const fileType = document.getElementsByName('file_type')[0]?.value; - // This is either the div created by the CodeMirror editor or the problemContents textarea in the case that + // This is either the div containing the CodeMirror editor or the problemContents textarea in the case that // CodeMirror is disabled in localOverrides.conf. - const editorArea = document.querySelector('.CodeMirror') ?? document.getElementById('problemContents'); + const editorArea = document.querySelector('.code-mirror-editor') ?? document.getElementById('problemContents'); // Add hot key, ctrl-enter, to render the problem editorArea.addEventListener('keydown', async (e) => { @@ -279,8 +279,7 @@ if (window.getComputedStyle(renderArea).getPropertyValue('height') !== `${height}px`) renderArea.style.height = `${height}px`; if (window.getComputedStyle(editorArea).getPropertyValue('height') !== `${height}px`) { - if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.setSize('100%', `${height}px`); - else editorArea.style.height = `${height}px`; + editorArea.style.height = `${height}px`; } } } @@ -321,7 +320,7 @@ const requestData = new URLSearchParams(new FormData(problemForm)); requestData.set( 'rawProblemSource', - webworkConfig?.pgCodeMirror?.getValue() ?? document.getElementById('problemContents')?.value ?? '' + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? '' ); requestData.set('send_pg_flags', 1); requestData.set(button.name, button.value); @@ -352,7 +351,7 @@ } if (fileType === 'course_info') { - const contents = webworkConfig?.pgCodeMirror?.getValue(); + const contents = webworkConfig?.pgCodeMirror?.source; if (contents) renderArea.innerHTML = `
${contents}
`; else renderArea.innerHTML = @@ -370,7 +369,7 @@ } if (fileType === 'hardcopy_theme') { - const contents = webworkConfig?.pgCodeMirror?.getValue(); + const contents = webworkConfig?.pgCodeMirror?.source; if (contents) { renderArea.innerHTML = '
' + contents.replace(/&/g, '&').replace(/';
 				} else
@@ -396,9 +395,7 @@
 					problemSeed: document.getElementById('action_view_seed_id')?.value ?? 1,
 					sourceFilePath: document.getElementsByName('edit_file_path')[0]?.value,
 					rawProblemSource:
-						webworkConfig?.pgCodeMirror?.getValue() ??
-						document.getElementById('problemContents')?.value ??
-						'',
+						webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? '',
 					outputformat: 'simple',
 					showAnswerNumbers: 0,
 					// The set id is really only needed by set headers to get the correct dates for the set.
@@ -508,9 +505,7 @@
 					problemSeed: document.getElementById('action_hardcopy_seed_id')?.value ?? 1,
 					sourceFilePath: document.getElementsByName('edit_file_path')[0]?.value,
 					rawProblemSource:
-						webworkConfig?.pgCodeMirror?.getValue() ??
-						document.getElementById('problemContents')?.value ??
-						'',
+						webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? '',
 					outputformat: document.getElementById('action_hardcopy_format_id')?.value ?? 'pdf',
 					hardcopy_theme: document.getElementById('action_hardcopy_theme_id')?.value ?? 'oneColumn',
 					// The set id is really only needed by set headers to get the correct dates for the set.
diff --git a/htdocs/js/System/system.scss b/htdocs/js/System/system.scss
index 6f60b1b4ab..18164250ce 100644
--- a/htdocs/js/System/system.scss
+++ b/htdocs/js/System/system.scss
@@ -809,7 +809,7 @@ input.changed[type='text'] {
 #pgedit-render-area {
 	border: 1px solid #ddd;
 	min-height: 400px;
-	height: 550px;
+	height: 600px;
 	resize: vertical;
 	display: flex;
 	flex-direction: column;
diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json
index 3b186ab181..b439141302 100644
--- a/htdocs/package-lock.json
+++ b/htdocs/package-lock.json
@@ -8,8 +8,8 @@
             "license": "GPL-2.0+",
             "dependencies": {
                 "@fortawesome/fontawesome-free": "^6.5.2",
+                "@openwebwork/pg-codemirror-editor": "^0.0.1-beta.6",
                 "bootstrap": "~5.3.3",
-                "codemirror": "^5.65.15",
                 "flatpickr": "^4.6.13",
                 "iframe-resizer": "^4.3.11",
                 "jquery": "^3.7.1",
@@ -31,6 +31,203 @@
                 "yargs": "^17.7.2"
             }
         },
+        "../../../../home/rice/Projects/Javascript/CodeMirror/pg-codemirror-editor": {
+            "name": "@openwebwork/pg-codemirror-editor",
+            "version": "0.0.1-beta.1",
+            "extraneous": true,
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/lang-html": "^6.4.9",
+                "@codemirror/lang-xml": "^6.1.0",
+                "@codemirror/theme-one-dark": "^6.1.2",
+                "@openwebwork/codemirror-lang-pg": "^0.0.1-beta.2",
+                "@replit/codemirror-emacs": "^6.1.0",
+                "@replit/codemirror-vim": "^6.2.1",
+                "cm6-theme-basic-dark": "^0.2.0",
+                "cm6-theme-basic-light": "^0.2.0",
+                "cm6-theme-gruvbox-dark": "^0.2.0",
+                "cm6-theme-gruvbox-light": "^0.2.0",
+                "cm6-theme-material-dark": "^0.2.0",
+                "cm6-theme-nord": "^0.2.0",
+                "cm6-theme-solarized-dark": "^0.2.0",
+                "cm6-theme-solarized-light": "^0.2.0",
+                "codemirror": "^6.0.1",
+                "codemirror-lang-perl": "^0.1.3",
+                "thememirror": "^2.0.1"
+            },
+            "devDependencies": {
+                "@awmottaz/prettier-plugin-void-html": "^1.6.1",
+                "@stylistic/eslint-plugin": "^2.7.2",
+                "css-loader": "^7.1.2",
+                "eslint": "^9.9.1",
+                "eslint-config-prettier": "^9.1.0",
+                "eslint-webpack-plugin": "^4.2.0",
+                "prettier": "^3.3.3",
+                "sass": "^1.78.0",
+                "sass-loader": "^16.0.1",
+                "style-loader": "^4.0.0",
+                "ts-loader": "^9.5.1",
+                "typescript": "^5.5.4",
+                "typescript-eslint": "^8.4.0",
+                "webpack": "^5.94.0",
+                "webpack-cli": "^5.1.4",
+                "webpack-dev-server": "^5.1.0"
+            }
+        },
+        "node_modules/@codemirror/autocomplete": {
+            "version": "6.18.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz",
+            "integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0"
+            },
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "node_modules/@codemirror/commands": {
+            "version": "6.7.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.0.tgz",
+            "integrity": "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.4.0",
+                "@codemirror/view": "^6.27.0",
+                "@lezer/common": "^1.1.0"
+            }
+        },
+        "node_modules/@codemirror/lang-css": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz",
+            "integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@lezer/common": "^1.0.2",
+                "@lezer/css": "^1.1.7"
+            }
+        },
+        "node_modules/@codemirror/lang-html": {
+            "version": "6.4.9",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+            "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/lang-css": "^6.0.0",
+                "@codemirror/lang-javascript": "^6.0.0",
+                "@codemirror/language": "^6.4.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/css": "^1.1.0",
+                "@lezer/html": "^1.3.0"
+            }
+        },
+        "node_modules/@codemirror/lang-javascript": {
+            "version": "6.2.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+            "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.6.0",
+                "@codemirror/lint": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/javascript": "^1.0.0"
+            }
+        },
+        "node_modules/@codemirror/lang-xml": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+            "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.4.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/xml": "^1.0.0"
+            }
+        },
+        "node_modules/@codemirror/language": {
+            "version": "6.10.3",
+            "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz",
+            "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.23.0",
+                "@lezer/common": "^1.1.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0",
+                "style-mod": "^4.0.0"
+            }
+        },
+        "node_modules/@codemirror/lint": {
+            "version": "6.8.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz",
+            "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "crelt": "^1.0.5"
+            }
+        },
+        "node_modules/@codemirror/search": {
+            "version": "6.5.6",
+            "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
+            "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "crelt": "^1.0.5"
+            }
+        },
+        "node_modules/@codemirror/state": {
+            "version": "6.4.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+            "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
+            "license": "MIT"
+        },
+        "node_modules/@codemirror/theme-one-dark": {
+            "version": "6.1.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+            "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/@codemirror/view": {
+            "version": "6.34.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz",
+            "integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/state": "^6.4.0",
+                "style-mod": "^4.1.0",
+                "w3c-keyname": "^2.2.4"
+            }
+        },
         "node_modules/@fortawesome/fontawesome-free": {
             "version": "6.5.2",
             "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
@@ -98,6 +295,110 @@
                 "@jridgewell/sourcemap-codec": "^1.4.14"
             }
         },
+        "node_modules/@lezer/common": {
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.2.tgz",
+            "integrity": "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw==",
+            "license": "MIT"
+        },
+        "node_modules/@lezer/css": {
+            "version": "1.1.9",
+            "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
+            "integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "node_modules/@lezer/highlight": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+            "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "node_modules/@lezer/html": {
+            "version": "1.3.10",
+            "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
+            "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "node_modules/@lezer/javascript": {
+            "version": "1.4.19",
+            "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.19.tgz",
+            "integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.1.3",
+                "@lezer/lr": "^1.3.0"
+            }
+        },
+        "node_modules/@lezer/lr": {
+            "version": "1.4.2",
+            "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+            "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "node_modules/@lezer/xml": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.5.tgz",
+            "integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==",
+            "license": "MIT",
+            "dependencies": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "node_modules/@openwebwork/codemirror-lang-pg": {
+            "version": "0.0.1-beta.5",
+            "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.1-beta.5.tgz",
+            "integrity": "sha512-7TAI1h/V9+2pwhM/AJtj5HZyDHDqN4ly1dEiW1klaXbv4AX0aYQr8aCU9PYrUOEUJnWH7JKWgh26ft/Ti+r71g==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/language": "^6.10.2",
+                "@lezer/highlight": "^1.2.1",
+                "@lezer/lr": "^1.4.2"
+            }
+        },
+        "node_modules/@openwebwork/pg-codemirror-editor": {
+            "version": "0.0.1-beta.7",
+            "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.1-beta.7.tgz",
+            "integrity": "sha512-ILrr+/8hX0HYLvfg6LD1sOJt44073Wofus187K39p6RWBArXGvZ2GQvHCwReVxj+z0CzHnbJ4lKFyVF+UEY+SA==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/lang-html": "^6.4.9",
+                "@codemirror/lang-xml": "^6.1.0",
+                "@codemirror/theme-one-dark": "^6.1.2",
+                "@openwebwork/codemirror-lang-pg": "^0.0.1-beta.5",
+                "@replit/codemirror-emacs": "^6.1.0",
+                "@replit/codemirror-vim": "^6.2.1",
+                "cm6-theme-basic-dark": "^0.2.0",
+                "cm6-theme-basic-light": "^0.2.0",
+                "cm6-theme-gruvbox-dark": "^0.2.0",
+                "cm6-theme-gruvbox-light": "^0.2.0",
+                "cm6-theme-material-dark": "^0.2.0",
+                "cm6-theme-nord": "^0.2.0",
+                "cm6-theme-solarized-dark": "^0.2.0",
+                "cm6-theme-solarized-light": "^0.2.0",
+                "codemirror": "^6.0.1",
+                "codemirror-lang-perl": "^0.1.5-beta.1",
+                "thememirror": "^2.0.1"
+            }
+        },
         "node_modules/@popperjs/core": {
             "version": "2.11.8",
             "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -108,6 +409,32 @@
                 "url": "https://opencollective.com/popperjs"
             }
         },
+        "node_modules/@replit/codemirror-emacs": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@replit/codemirror-emacs/-/codemirror-emacs-6.1.0.tgz",
+            "integrity": "sha512-74DITnht6Cs6sHg02PQ169IKb1XgtyhI9sLD0JeOFco6Ds18PT+dkD8+DgXBDokne9UIFKsBbKPnpFRAz60/Lw==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/autocomplete": "^6.0.2",
+                "@codemirror/commands": "^6.0.0",
+                "@codemirror/search": "^6.0.0",
+                "@codemirror/state": "^6.0.1",
+                "@codemirror/view": "^6.3.0"
+            }
+        },
+        "node_modules/@replit/codemirror-vim": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/@replit/codemirror-vim/-/codemirror-vim-6.2.1.tgz",
+            "integrity": "sha512-qDAcGSHBYU5RrdO//qCmD8K9t6vbP327iCj/iqrkVnjbrpFhrjOt92weGXGHmTNRh16cUtkUZ7Xq7rZf+8HVow==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/commands": "^6.0.0",
+                "@codemirror/language": "^6.1.0",
+                "@codemirror/search": "^6.2.0",
+                "@codemirror/state": "^6.0.1",
+                "@codemirror/view": "^6.0.3"
+            }
+        },
         "node_modules/@trysound/sax": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -356,10 +683,127 @@
                 "node": ">=12"
             }
         },
+        "node_modules/cm6-theme-basic-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-basic-dark/-/cm6-theme-basic-dark-0.2.0.tgz",
+            "integrity": "sha512-+mNNJecRtxS/KkloMDCQF0oTrT6aFGRZTjnBcdT5UG1pcDO4Brq8l1+0KR/8dZ7hub2gOGOzoi3rGFD8GzlH7Q==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-basic-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz",
+            "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-gruvbox-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-gruvbox-dark/-/cm6-theme-gruvbox-dark-0.2.0.tgz",
+            "integrity": "sha512-xyqsG19qV+nb7ZHTMocSNWwZHMExfQxDm0FlbNMqEGKeQR96WryssXJH/IZZQudwrPpWU2dCoyOgMFhti2UTYA==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-gruvbox-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-gruvbox-light/-/cm6-theme-gruvbox-light-0.2.0.tgz",
+            "integrity": "sha512-sc4dEMLU5y4F3QGLjwMQs1H3Q0a0ooXA1EvyWnknxLEGQVXwJrxkkV67gs1TqWASl2i63iomt4zyz5pkbfO1yg==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-material-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-material-dark/-/cm6-theme-material-dark-0.2.0.tgz",
+            "integrity": "sha512-H09JZihzg4w0mTtOqo5bQdxItkQWw+ergKlk7BSfwYjaR2nOi+wIN0R+ByAo7bON8GbFODvjTxH3EIqdhovFeA==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-nord": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-nord/-/cm6-theme-nord-0.2.0.tgz",
+            "integrity": "sha512-jTh+5nvl+N/5CtTK7UVcrxDCj2AOStvbNM8uP6tx6amq4QaaLDlapjMw+MNzEkvxcPnHY+YM91tbklS2KNlR2w==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-solarized-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-solarized-dark/-/cm6-theme-solarized-dark-0.2.0.tgz",
+            "integrity": "sha512-FWtYHcX8NLzNSs21yGbkLF+q/5m2u80ug0JytKoI9nMZWPP5dcnsFYp1iZBEegLehiZnpv1qcmTsLTUG2KD39w==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "node_modules/cm6-theme-solarized-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-solarized-light/-/cm6-theme-solarized-light-0.2.0.tgz",
+            "integrity": "sha512-Iw7Xv+9A6NlT7sRGlM2pOwD3ZBETkAqpb7c6O0LPj5kjwcK6C3k+mvjzaQt1gzfBErMmhL1HHuK07zICeXkE+w==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
         "node_modules/codemirror": {
-            "version": "5.65.16",
-            "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz",
-            "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg=="
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+            "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/commands": "^6.0.0",
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/lint": "^6.0.0",
+                "@codemirror/search": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0"
+            }
+        },
+        "node_modules/codemirror-lang-perl": {
+            "version": "0.1.5-beta.1",
+            "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.5-beta.1.tgz",
+            "integrity": "sha512-iAp+G5UHpoQwrtCs1kD5yKuZ/tYYh812aI66FDoJYnOESysr/BlTaoTsgH9u3fskEcA6SRYmB3UqpkxiFH5fvw==",
+            "license": "MIT",
+            "dependencies": {
+                "@codemirror/language": "^6.10.2",
+                "@lezer/highlight": "^1.2.1",
+                "@lezer/lr": "^1.4.2"
+            }
         },
         "node_modules/color-convert": {
             "version": "2.0.1",
@@ -394,6 +838,12 @@
                 "node": ">= 10"
             }
         },
+        "node_modules/crelt": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+            "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+            "license": "MIT"
+        },
         "node_modules/css-declaration-sorter": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
@@ -1519,6 +1969,12 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/style-mod": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+            "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+            "license": "MIT"
+        },
         "node_modules/stylehacks": {
             "version": "6.1.1",
             "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz",
@@ -1584,6 +2040,17 @@
             "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
             "dev": true
         },
+        "node_modules/thememirror": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/thememirror/-/thememirror-2.0.1.tgz",
+            "integrity": "sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w==",
+            "license": "MIT",
+            "peerDependencies": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0"
+            }
+        },
         "node_modules/to-regex-range": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1632,6 +2099,12 @@
             "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
             "dev": true
         },
+        "node_modules/w3c-keyname": {
+            "version": "2.2.8",
+            "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+            "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+            "license": "MIT"
+        },
         "node_modules/wrap-ansi": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -1687,6 +2160,142 @@
         }
     },
     "dependencies": {
+        "@codemirror/autocomplete": {
+            "version": "6.18.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz",
+            "integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==",
+            "requires": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "@codemirror/commands": {
+            "version": "6.7.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.0.tgz",
+            "integrity": "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==",
+            "requires": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.4.0",
+                "@codemirror/view": "^6.27.0",
+                "@lezer/common": "^1.1.0"
+            }
+        },
+        "@codemirror/lang-css": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz",
+            "integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==",
+            "requires": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@lezer/common": "^1.0.2",
+                "@lezer/css": "^1.1.7"
+            }
+        },
+        "@codemirror/lang-html": {
+            "version": "6.4.9",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+            "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+            "requires": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/lang-css": "^6.0.0",
+                "@codemirror/lang-javascript": "^6.0.0",
+                "@codemirror/language": "^6.4.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/css": "^1.1.0",
+                "@lezer/html": "^1.3.0"
+            }
+        },
+        "@codemirror/lang-javascript": {
+            "version": "6.2.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+            "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+            "requires": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.6.0",
+                "@codemirror/lint": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.17.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/javascript": "^1.0.0"
+            }
+        },
+        "@codemirror/lang-xml": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+            "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+            "requires": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/language": "^6.4.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/common": "^1.0.0",
+                "@lezer/xml": "^1.0.0"
+            }
+        },
+        "@codemirror/language": {
+            "version": "6.10.3",
+            "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz",
+            "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==",
+            "requires": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.23.0",
+                "@lezer/common": "^1.1.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0",
+                "style-mod": "^4.0.0"
+            }
+        },
+        "@codemirror/lint": {
+            "version": "6.8.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz",
+            "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==",
+            "requires": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "crelt": "^1.0.5"
+            }
+        },
+        "@codemirror/search": {
+            "version": "6.5.6",
+            "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
+            "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
+            "requires": {
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "crelt": "^1.0.5"
+            }
+        },
+        "@codemirror/state": {
+            "version": "6.4.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+            "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+        },
+        "@codemirror/theme-one-dark": {
+            "version": "6.1.2",
+            "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+            "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+            "requires": {
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0",
+                "@lezer/highlight": "^1.0.0"
+            }
+        },
+        "@codemirror/view": {
+            "version": "6.34.1",
+            "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz",
+            "integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==",
+            "requires": {
+                "@codemirror/state": "^6.4.0",
+                "style-mod": "^4.1.0",
+                "w3c-keyname": "^2.2.4"
+            }
+        },
         "@fortawesome/fontawesome-free": {
             "version": "6.5.2",
             "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
@@ -1741,12 +2350,119 @@
                 "@jridgewell/sourcemap-codec": "^1.4.14"
             }
         },
+        "@lezer/common": {
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.2.tgz",
+            "integrity": "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="
+        },
+        "@lezer/css": {
+            "version": "1.1.9",
+            "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
+            "integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
+            "requires": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "@lezer/highlight": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+            "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+            "requires": {
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "@lezer/html": {
+            "version": "1.3.10",
+            "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
+            "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+            "requires": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "@lezer/javascript": {
+            "version": "1.4.19",
+            "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.19.tgz",
+            "integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==",
+            "requires": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.1.3",
+                "@lezer/lr": "^1.3.0"
+            }
+        },
+        "@lezer/lr": {
+            "version": "1.4.2",
+            "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+            "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+            "requires": {
+                "@lezer/common": "^1.0.0"
+            }
+        },
+        "@lezer/xml": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.5.tgz",
+            "integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==",
+            "requires": {
+                "@lezer/common": "^1.2.0",
+                "@lezer/highlight": "^1.0.0",
+                "@lezer/lr": "^1.0.0"
+            }
+        },
+        "@openwebwork/codemirror-lang-pg": {
+            "version": "0.0.1-beta.5",
+            "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.1-beta.5.tgz",
+            "integrity": "sha512-7TAI1h/V9+2pwhM/AJtj5HZyDHDqN4ly1dEiW1klaXbv4AX0aYQr8aCU9PYrUOEUJnWH7JKWgh26ft/Ti+r71g==",
+            "requires": {
+                "@codemirror/language": "^6.10.2",
+                "@lezer/highlight": "^1.2.1",
+                "@lezer/lr": "^1.4.2"
+            }
+        },
+        "@openwebwork/pg-codemirror-editor": {
+            "version": "0.0.1-beta.7",
+            "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.1-beta.7.tgz",
+            "integrity": "sha512-ILrr+/8hX0HYLvfg6LD1sOJt44073Wofus187K39p6RWBArXGvZ2GQvHCwReVxj+z0CzHnbJ4lKFyVF+UEY+SA==",
+            "requires": {
+                "@codemirror/lang-html": "^6.4.9",
+                "@codemirror/lang-xml": "^6.1.0",
+                "@codemirror/theme-one-dark": "^6.1.2",
+                "@openwebwork/codemirror-lang-pg": "^0.0.1-beta.5",
+                "@replit/codemirror-emacs": "^6.1.0",
+                "@replit/codemirror-vim": "^6.2.1",
+                "cm6-theme-basic-dark": "^0.2.0",
+                "cm6-theme-basic-light": "^0.2.0",
+                "cm6-theme-gruvbox-dark": "^0.2.0",
+                "cm6-theme-gruvbox-light": "^0.2.0",
+                "cm6-theme-material-dark": "^0.2.0",
+                "cm6-theme-nord": "^0.2.0",
+                "cm6-theme-solarized-dark": "^0.2.0",
+                "cm6-theme-solarized-light": "^0.2.0",
+                "codemirror": "^6.0.1",
+                "codemirror-lang-perl": "^0.1.5-beta.1",
+                "thememirror": "^2.0.1"
+            }
+        },
         "@popperjs/core": {
             "version": "2.11.8",
             "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
             "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
             "peer": true
         },
+        "@replit/codemirror-emacs": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@replit/codemirror-emacs/-/codemirror-emacs-6.1.0.tgz",
+            "integrity": "sha512-74DITnht6Cs6sHg02PQ169IKb1XgtyhI9sLD0JeOFco6Ds18PT+dkD8+DgXBDokne9UIFKsBbKPnpFRAz60/Lw==",
+            "requires": {}
+        },
+        "@replit/codemirror-vim": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/@replit/codemirror-vim/-/codemirror-vim-6.2.1.tgz",
+            "integrity": "sha512-qDAcGSHBYU5RrdO//qCmD8K9t6vbP327iCj/iqrkVnjbrpFhrjOt92weGXGHmTNRh16cUtkUZ7Xq7rZf+8HVow==",
+            "requires": {}
+        },
         "@trysound/sax": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -1888,10 +2604,77 @@
                 "wrap-ansi": "^7.0.0"
             }
         },
+        "cm6-theme-basic-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-basic-dark/-/cm6-theme-basic-dark-0.2.0.tgz",
+            "integrity": "sha512-+mNNJecRtxS/KkloMDCQF0oTrT6aFGRZTjnBcdT5UG1pcDO4Brq8l1+0KR/8dZ7hub2gOGOzoi3rGFD8GzlH7Q==",
+            "requires": {}
+        },
+        "cm6-theme-basic-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz",
+            "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==",
+            "requires": {}
+        },
+        "cm6-theme-gruvbox-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-gruvbox-dark/-/cm6-theme-gruvbox-dark-0.2.0.tgz",
+            "integrity": "sha512-xyqsG19qV+nb7ZHTMocSNWwZHMExfQxDm0FlbNMqEGKeQR96WryssXJH/IZZQudwrPpWU2dCoyOgMFhti2UTYA==",
+            "requires": {}
+        },
+        "cm6-theme-gruvbox-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-gruvbox-light/-/cm6-theme-gruvbox-light-0.2.0.tgz",
+            "integrity": "sha512-sc4dEMLU5y4F3QGLjwMQs1H3Q0a0ooXA1EvyWnknxLEGQVXwJrxkkV67gs1TqWASl2i63iomt4zyz5pkbfO1yg==",
+            "requires": {}
+        },
+        "cm6-theme-material-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-material-dark/-/cm6-theme-material-dark-0.2.0.tgz",
+            "integrity": "sha512-H09JZihzg4w0mTtOqo5bQdxItkQWw+ergKlk7BSfwYjaR2nOi+wIN0R+ByAo7bON8GbFODvjTxH3EIqdhovFeA==",
+            "requires": {}
+        },
+        "cm6-theme-nord": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-nord/-/cm6-theme-nord-0.2.0.tgz",
+            "integrity": "sha512-jTh+5nvl+N/5CtTK7UVcrxDCj2AOStvbNM8uP6tx6amq4QaaLDlapjMw+MNzEkvxcPnHY+YM91tbklS2KNlR2w==",
+            "requires": {}
+        },
+        "cm6-theme-solarized-dark": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-solarized-dark/-/cm6-theme-solarized-dark-0.2.0.tgz",
+            "integrity": "sha512-FWtYHcX8NLzNSs21yGbkLF+q/5m2u80ug0JytKoI9nMZWPP5dcnsFYp1iZBEegLehiZnpv1qcmTsLTUG2KD39w==",
+            "requires": {}
+        },
+        "cm6-theme-solarized-light": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/cm6-theme-solarized-light/-/cm6-theme-solarized-light-0.2.0.tgz",
+            "integrity": "sha512-Iw7Xv+9A6NlT7sRGlM2pOwD3ZBETkAqpb7c6O0LPj5kjwcK6C3k+mvjzaQt1gzfBErMmhL1HHuK07zICeXkE+w==",
+            "requires": {}
+        },
         "codemirror": {
-            "version": "5.65.16",
-            "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz",
-            "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg=="
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+            "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+            "requires": {
+                "@codemirror/autocomplete": "^6.0.0",
+                "@codemirror/commands": "^6.0.0",
+                "@codemirror/language": "^6.0.0",
+                "@codemirror/lint": "^6.0.0",
+                "@codemirror/search": "^6.0.0",
+                "@codemirror/state": "^6.0.0",
+                "@codemirror/view": "^6.0.0"
+            }
+        },
+        "codemirror-lang-perl": {
+            "version": "0.1.5-beta.1",
+            "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.5-beta.1.tgz",
+            "integrity": "sha512-iAp+G5UHpoQwrtCs1kD5yKuZ/tYYh812aI66FDoJYnOESysr/BlTaoTsgH9u3fskEcA6SRYmB3UqpkxiFH5fvw==",
+            "requires": {
+                "@codemirror/language": "^6.10.2",
+                "@lezer/highlight": "^1.2.1",
+                "@lezer/lr": "^1.4.2"
+            }
         },
         "color-convert": {
             "version": "2.0.1",
@@ -1920,6 +2703,11 @@
             "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
             "dev": true
         },
+        "crelt": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+            "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+        },
         "css-declaration-sorter": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
@@ -2665,6 +3453,11 @@
             "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
             "dev": true
         },
+        "style-mod": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+            "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+        },
         "stylehacks": {
             "version": "6.1.1",
             "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz",
@@ -2710,6 +3503,12 @@
                 }
             }
         },
+        "thememirror": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/thememirror/-/thememirror-2.0.1.tgz",
+            "integrity": "sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w==",
+            "requires": {}
+        },
         "to-regex-range": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2735,6 +3534,11 @@
             "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
             "dev": true
         },
+        "w3c-keyname": {
+            "version": "2.2.8",
+            "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+            "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+        },
         "wrap-ansi": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/htdocs/package.json b/htdocs/package.json
index ddbb13e751..bdbc7bfb86 100644
--- a/htdocs/package.json
+++ b/htdocs/package.json
@@ -14,8 +14,8 @@
     },
     "dependencies": {
         "@fortawesome/fontawesome-free": "^6.5.2",
+        "@openwebwork/pg-codemirror-editor": "^0.0.1-beta.7",
         "bootstrap": "~5.3.3",
-        "codemirror": "^5.65.15",
         "flatpickr": "^4.6.13",
         "iframe-resizer": "^4.3.11",
         "jquery": "^3.7.1",
diff --git a/lib/WeBWorK/HTML/CodeMirrorEditor.pm b/lib/WeBWorK/HTML/CodeMirrorEditor.pm
index 21bb0f60b9..d38800970d 100644
--- a/lib/WeBWorK/HTML/CodeMirrorEditor.pm
+++ b/lib/WeBWorK/HTML/CodeMirrorEditor.pm
@@ -24,80 +24,33 @@ PGProblemEditor.pm modules.
 
 =cut
 
-use WeBWorK::Utils qw(getAssetURL);
-
-our @EXPORT_OK = qw(generate_codemirror_html generate_codemirror_controls_html output_codemirror_static_files);
-
-# Available CodeMirror themes
-use constant CODEMIRROR_THEMES => [
-	'3024-day',                '3024-night',         'abbott',        'abcdef',
-	'ambiance',                'ambiance-mobile',    'ayu-dark',      'ayu-mirage',
-	'base16-dark',             'base16-light',       'bespin',        'blackboard',
-	'cobalt',                  'colorforth',         'darcula',       'dracula',
-	'duotone-dark',            'duotone-light',      'eclipse',       'elegant',
-	'erlang-dark',             'gruvbox-dark',       'hopscotch',     'icecoder',
-	'idea',                    'isotope',            'juejin',        'lesser-dark',
-	'liquibyte',               'lucario',            'material',      'material-darker',
-	'material-ocean',          'material-palenight', 'mbo',           'mdn-like',
-	'midnight',                'monokai',            'moxer',         'neat',
-	'neo',                     'night',              'nord',          'oceanic-next',
-	'panda-syntax',            'paraiso-dark',       'paraiso-light', 'pastel-on-dark',
-	'railscasts',              'rubyblue',           'seti',          'shadowfox',
-	'solarized',               'ssms',               'the-matrix',    'tomorrow-night-bright',
-	'tomorrow-night-eighties', 'ttcn',               'twilight',      'vibrant-ink',
-	'xq-dark',                 'xq-light',           'yeti',          'yonce',
-	'zenburn'
-];
-
-# Available CodeMirror keymaps
-use constant CODEMIRROR_KEYMAPS => [ 'emacs', 'sublime', 'vim' ];
-
-# Javascript for addons used by the PG editor (relative to the node_modules/codemirror/addon directory).
-use constant CODEMIRROR_ADDONS_CSS => [ 'dialog/dialog.css', 'search/matchesonscrollbar.css', 'fold/foldgutter.css' ];
-
-# Javascript for addons used by the PG editor (relative to the node_modules/codemirror/addon directory).
-use constant CODEMIRROR_ADDONS_JS => [
-	'dialog/dialog.js',            'search/search.js',
-	'search/searchcursor.js',      'search/matchesonscrollbar.js',
-	'search/match-highlighter.js', 'search/match-highlighter.js',
-	'scroll/annotatescrollbar.js', 'edit/matchbrackets.js',
-	'fold/foldcode.js',            'fold/foldgutter.js',
-	'fold/xml-fold.js'
-];
-
-sub generate_codemirror_html ($c, $name, $contents = '', $mode = 'PG') {
-	# Output the textarea that will be used by CodeMirror.
-	# If CodeMirror is disabled, then this is directly the editing area.
-	return $c->text_area($name => $contents, id => $name, class => 'codeMirrorEditor', data => { mode => $mode });
-}
-
-sub generate_codemirror_controls_html ($c) {
-	my $ce = $c->ce;
-
-	return '' unless $ce->{options}{PGCodeMirror};
-
-	# Construct the labels and values for the theme menu.
-	my $themeValues = [ [ default => 'default', selected => 'selected' ] ];
-	for (@{ CODEMIRROR_THEMES() }) {
-		push @$themeValues, [ $_ => getAssetURL($ce, "node_modules/codemirror/theme/$_.css") ];
+our @EXPORT_OK = qw(generate_codemirror_html output_codemirror_static_files);
+
+sub generate_codemirror_html ($c, $name, $contents = '', $language = 'pg') {
+	if ($c->ce->{options}{PGCodeMirror}) {
+		# Output the div that will be used by CodeMirror and a hidden input containing the contents.
+		return $c->c(
+			$c->hidden_field($name => $contents),
+			$c->tag(
+				'div',
+				id    => $name,
+				class => 'code-mirror-editor tex2jax_ignore',
+				data  => { language => $language }
+			)
+		)->join('');
+	} else {
+		# If CodeMirror is disabled, then a text area is used instead.
+		return $c->text_area(
+			$name => $contents,
+			id    => $name,
+			class => 'text-area-editor',
+			data  => { language => $language }
+		);
 	}
-
-	# Construct the labels and values for the keymap menu.
-	my $keymapValues = [ [ default => 'default', selected => 'selected' ] ];
-	for (@{ CODEMIRROR_KEYMAPS() }) {
-		push @$keymapValues, [ $_ => getAssetURL($ce, "node_modules/codemirror/keymap/$_.js") ];
-	}
-
-	return $c->include('HTML/CodeMirrorEditor/controls', themeValues => $themeValues, keymapValues => $keymapValues);
 }
 
-sub output_codemirror_static_files ($c, $mode = 'PG') {
-	return $c->include(
-		'HTML/CodeMirrorEditor/js',
-		codemirrorAddonsCSS => CODEMIRROR_ADDONS_CSS(),
-		codemirrorAddonsJS  => CODEMIRROR_ADDONS_JS(),
-		codemirrorModesJS   => $mode eq 'htmlmixed' ? [ 'xml', 'css', 'javascript', 'htmlmixed' ] : [$mode]
-	);
+sub output_codemirror_static_files ($c) {
+	return $c->include('HTML/CodeMirrorEditor/js');
 }
 
 1;
diff --git a/templates/ContentGenerator/Instructor/AchievementEditor.html.ep b/templates/ContentGenerator/Instructor/AchievementEditor.html.ep
index 28c4097172..b593704ec0 100644
--- a/templates/ContentGenerator/Instructor/AchievementEditor.html.ep
+++ b/templates/ContentGenerator/Instructor/AchievementEditor.html.ep
@@ -1,9 +1,8 @@
 % use WeBWorK::Utils qw(not_blank getAssetURL);
-% use WeBWorK::HTML::CodeMirrorEditor
-	% qw(generate_codemirror_html generate_codemirror_controls_html output_codemirror_static_files);
+% use WeBWorK::HTML::CodeMirrorEditor qw(generate_codemirror_html output_codemirror_static_files);
 %
 % content_for js => begin
-	<%= output_codemirror_static_files($c, 'perl') =%>
+	<%= output_codemirror_static_files($c) =%>
 	<%= javascript getAssetURL($ce, 'js/ActionTabs/actiontabs.js'), defer => undef =%>
 % end
 %
@@ -24,7 +23,6 @@
 	% }
 	%
 	
<%= generate_codemirror_html($c, 'achievementContents', $achievementContents, 'perl') =%>
- <%= generate_codemirror_controls_html($c) =%> % % # Output action forms % my $default_choice; diff --git a/templates/ContentGenerator/Instructor/AchievementNotificationEditor.html.ep b/templates/ContentGenerator/Instructor/AchievementNotificationEditor.html.ep index 01e5697a11..4a26f76c8f 100644 --- a/templates/ContentGenerator/Instructor/AchievementNotificationEditor.html.ep +++ b/templates/ContentGenerator/Instructor/AchievementNotificationEditor.html.ep @@ -1,9 +1,8 @@ % use WeBWorK::Utils qw(not_blank getAssetURL); -% use WeBWorK::HTML::CodeMirrorEditor - % qw(generate_codemirror_html generate_codemirror_controls_html output_codemirror_static_files); +% use WeBWorK::HTML::CodeMirrorEditor qw(generate_codemirror_html output_codemirror_static_files); % % content_for js => begin - <%= output_codemirror_static_files($c, 'perl') =%> + <%= output_codemirror_static_files($c) =%> <%= javascript getAssetURL($ce, 'js/ActionTabs/actiontabs.js'), defer => undef =%> % end % @@ -24,8 +23,8 @@ % } %
+ % # FIXME: This should not be using perl. Mojolicious templates have embedded perl, but are not perl. <%= generate_codemirror_html($c, 'achievementNotification', $achievementNotification, 'perl') =%>
- <%= generate_codemirror_controls_html($c) =%> % % # Output action forms % my $default_choice; diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep index 6c11e49345..bc85653afa 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep @@ -4,16 +4,10 @@ % use WeBWorK::Utils qw(not_blank x getAssetURL); % use WeBWorK::Utils::Files qw(readFile); % use WeBWorK::Utils::Sets qw(format_set_name_display); -% use WeBWorK::HTML::CodeMirrorEditor - % qw(generate_codemirror_html generate_codemirror_controls_html output_codemirror_static_files); +% use WeBWorK::HTML::CodeMirrorEditor qw(generate_codemirror_html output_codemirror_static_files); % -% my $codemirrorMode = 'PG'; -% if ($c->{file_type}) { -% $codemirrorMode = 'htmlmixed' if ($c->{file_type} eq 'course_info'); -% $codemirrorMode = 'xml' if ($c->{file_type} eq 'hardcopy_theme'); -% } % content_for js => begin - <%= output_codemirror_static_files($c, $codemirrorMode) =%> + <%= output_codemirror_static_files($c) =%> <%= javascript getAssetURL($ce, 'js/ActionTabs/actiontabs.js'), defer => undef =%> <%= javascript getAssetURL($ce, 'js/PGProblemEditor/pgproblemeditor.js'), defer => undef =%> % end @@ -156,7 +150,12 @@ % }
- <%= generate_codemirror_html($c, 'problemContents', $problemContents, $codemirrorMode) =%> + <%= generate_codemirror_html( + $c, + 'problemContents', + $problemContents, + { course_info => 'html', hardcopy_theme => 'xml' }->{ $c->{file_type} } // 'pg' + ) =%>
@@ -168,7 +167,6 @@
- <%= generate_codemirror_controls_html($c) =%> <%= $fileInfo->() %> % % # Output action forms diff --git a/templates/HTML/CodeMirrorEditor/controls.html.ep b/templates/HTML/CodeMirrorEditor/controls.html.ep deleted file mode 100644 index c91a9996fc..0000000000 --- a/templates/HTML/CodeMirrorEditor/controls.html.ep +++ /dev/null @@ -1,33 +0,0 @@ -% # Output the html elements for setting the CodeMirror options. -
-
-
- <%= label_for selectTheme => maketext('Theme:'), class => 'col-form-label col-auto' =%> -
- <%= select_field selectTheme => $themeValues, - id => 'selectTheme', class => 'form-select form-select-sm d-inline w-auto' =%> -
-
-
-
-
- <%= label_for selectKeymap => maketext('Key Map:'), class => 'col-form-label col-auto' =%> -
- <%= select_field selectKeymap => $keymapValues, - id => 'selectKeymap', class => 'form-select form-select-sm d-inline w-auto' =%> -
-
-
-
-
- <%= check_box 'enableSpell', id => 'enableSpell', class => 'form-check-input' =%> - <%= label_for enableSpell => maketext('Enable Spell Checking'), class => 'form-check-label' =%> -
-
-
-
- <%= check_box 'forceRTL', id => 'forceRTL', class => 'form-check-input' =%> - <%= label_for forceRTL => maketext('Force editor to RTL'), class => 'form-check-label' =%> -
-
-
diff --git a/templates/HTML/CodeMirrorEditor/js.html.ep b/templates/HTML/CodeMirrorEditor/js.html.ep index 33a4be141f..3d6edad16b 100644 --- a/templates/HTML/CodeMirrorEditor/js.html.ep +++ b/templates/HTML/CodeMirrorEditor/js.html.ep @@ -1,34 +1,17 @@ % use WeBWorK::Utils qw(getAssetURL); % +% # The textarea styles in this file are still needed if CodeMirror is disabled. +% content_for css => begin + <%= stylesheet getAssetURL($ce, 'js/PGCodeMirror/pgeditor.css') =%> +% end + % if ($ce->{options}{PGCodeMirror}) { - % content_for css => begin - <%= stylesheet getAssetURL($ce, 'node_modules/codemirror/lib/codemirror.css') =%> - % - % for my $addon (@$codemirrorAddonsCSS) { - <%= stylesheet getAssetURL($ce, "node_modules/codemirror/addon/$addon") =%> - % } - <%= stylesheet getAssetURL($ce, 'js/PGCodeMirror/pgeditor.css') =%> - % end - % % content_for js => begin - <%= javascript getAssetURL($ce, 'node_modules/codemirror/lib/codemirror.js'), defer => undef =%> - % - % for my $addon (@$codemirrorAddonsJS) { - <%= javascript getAssetURL($ce, "node_modules/codemirror/addon/$addon"), defer => undef =%> - % } - % for my $mode (@$codemirrorModesJS) { - <%= javascript getAssetURL( - $ce, - $mode eq 'PG' ? 'js/PGCodeMirror/PG.js' : "node_modules/codemirror/mode/$mode/$mode.js" - ), defer => undef =%> - % } - % + <%= javascript getAssetURL( + $ce, + 'node_modules/@openwebwork/pg-codemirror-editor/dist/pg-codemirror-editor.js' + ), + defer => undef =%> <%= javascript getAssetURL($ce, 'js/PGCodeMirror/pgeditor.js'), defer => undef =%> - <%= javascript getAssetURL($ce, 'js/PGCodeMirror/comment.js'), defer => undef =%> % end % } -% -% # The textarea styles in this file are still needed if CodeMirror is disabled. -% content_for css => begin - <%= stylesheet getAssetURL($ce, 'js/PGCodeMirror/pgeditor.css') =%> -% end diff --git a/templates/HelpFiles/InstructorPGProblemEditor.html.ep b/templates/HelpFiles/InstructorPGProblemEditor.html.ep index c28751e7ee..a57c23dd2f 100644 --- a/templates/HelpFiles/InstructorPGProblemEditor.html.ep +++ b/templates/HelpFiles/InstructorPGProblemEditor.html.ep @@ -72,13 +72,55 @@
<%= maketext('This links to problem authoring information on the WeBWorK wiki.') %>
-
<%= maketext('The large text window') %>
+
<%= maketext('Text Editor Window') %>
- <%= maketext('This is where you edit the text of the problem template. Type Ctrl-Enter while this ' - . 'window has focus to re-render the problem. Code folding is enabled either by clicking on ' - . 'the triangles in the gutter next to line numbers or using the shortcut Shift-Ctrl-F. Folding ' - . 'all regions can be accomplished with Shift-Ctrl-A and unfold all regions with Shift-Ctrl-G. ' - . 'Comments can be toggled with Ctrl-/.') =%> + <%= maketext('This is where you edit the code of the problem. Type Ctrl-Enter while this window ' + . 'has focus to re-render the problem. In addition, the following keyboard shortcuts are available.') =%> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= maketext('Key Binding') %><%= maketext('Mac alternate') %><%= maketext('Action') %>
Ctrl-mAlt-Shift-m + <%= maketext('Enable or disable tab-focus mode. When tab-focus mode is off, pressing Tab ' + . 'inside the editor window indents the current line. When tab-focus mode is on ' + . 'Tab and Shift-Tab move focus out of the editor window.') %> +
Ctrl-Shift-[Cmd-Shift-[ + <%= maketext('Fold the region that begins on the current line. Triangles in the gutter ' + . 'next to line numbers indicate which regions can be folded.') %> +
Ctrl-Shift-]Cmd-Shift-]<%= maketext('Unfold the region that begins on the current line.') %>
Ctrl-Alt-[Cmd-Alt-[<%= maketext('Fold all regions.') %>
Cmd-Alt-]Cmd-Alt-]<%= maketext('Unfold all regions.') %>
Ctrl-/Cmd-/<%= maketext('Toggle comment.') %>
Shift-Alt-a<%= maketext('Toggle block comment. (Only has effect inside PGML blocks.)') %>
Ctrl-SpaceAlt-`<%= maketext('Show available autocompletions at the current cursor location.') %>
<%= maketext('Text Editor Options') %>
@@ -89,14 +131,19 @@
<%= maketext('The key maps that are available are "default", "emacs", "sublime", and "vim". The ' . '"default" key map has the standard behavior of a browser text area. You can use Ctrl-C to copy, ' - . 'Ctrl-V to paste, Ctrl-F to search, etc. If you are more comfortable with the "emacs", ' - . '"sublime", or "vim" text editors then you may choose to use one of those key maps instead.') =%> + . 'Ctrl-V to paste, Ctrl-F to search, etc. If you are more comfortable with the "emacs" ' + . ' or "vim" text editors then you may choose to use one of those key maps instead.') =%>
<%= maketext('Enable Spell Checking') %>
<%= maketext('Spell check the text of the file that your are editing. Note that there will be many ' . 'spelling errors in the code parts of your file.') =%>
+
<%= maketext('Force RTL') %>
+
+ <%= maketext('Force the editor to display text from right-to-left. ' + . '(Note that this does not persist when reloading the page.)') =%> +