diff --git a/.brackets.json b/.brackets.json index aac17af92f..89ac20eeeb 100644 --- a/.brackets.json +++ b/.brackets.json @@ -18,7 +18,7 @@ } }, "path": { - "src/thirdparty/CodeMirror/**/*.js": { + "src/thirdparty/CodeMirror6/**/*.js": { "spaceUnits": 2, "linting.enabled": false }, @@ -33,4 +33,4 @@ "livePreviewServerURL": "", "livePreviewServerProjectPath": "/", "livePreviewHotReloadSupported": false -} \ No newline at end of file +} diff --git a/.gitignore b/.gitignore index 369ebdba2f..4f11581582 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ Thumbs.db # ignore MCP server runtime files /phoenix-builder-mcp/.mcp-server.pid +/phoenix-builder-mcp/.mcp-server-*.pid # ignore chrome extension build artifacts /phoenix-builder-mcp/chrome_extension/build/ @@ -52,7 +53,7 @@ src/phoenix/virtualfs.js.map !/src/thirdparty/licences /src/thirdparty/less.* /src/thirdparty/emmet.* -/src/thirdparty/CodeMirror +/src/thirdparty/CodeMirror6 /src/thirdparty/acorn /src/thirdparty/tern /src/thirdparty/mustache diff --git a/build/build-codemirror6.mjs b/build/build-codemirror6.mjs new file mode 100644 index 0000000000..cf3f1398b9 --- /dev/null +++ b/build/build-codemirror6.mjs @@ -0,0 +1,278 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; +import { babel } from "@rollup/plugin-babel"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import { rollup } from "rollup"; +import codeMirror5Validation from "./validate-codemirror5.js"; + +const { + assertNoCodeMirror5Dependencies +} = codeMirror5Validation; + +const SCRIPT_DIRECTORY = path.dirname(fileURLToPath(import.meta.url)); +const REPOSITORY_ROOT = path.resolve(SCRIPT_DIRECTORY, ".."); +const ENTRY_FILE = path.join(SCRIPT_DIRECTORY, "codemirror6-entry.js"); +const OUTPUT_DIRECTORY = path.join(REPOSITORY_ROOT, "src/thirdparty/CodeMirror6"); +const OUTPUT_FILE = path.join(OUTPUT_DIRECTORY, "codemirror6.js"); +const LEGACY_OUTPUT_DIRECTORY = path.join(REPOSITORY_ROOT, "src/thirdparty/CodeMirror"); +const LICENSE_FILE = path.join(REPOSITORY_ROOT, "src/thirdparty/licences/codemirror6.markdown"); +const VIM_CORE_FILE = normalizePath(path.join( + REPOSITORY_ROOT, + "node_modules/@replit/codemirror-vim-core/vim.js" +)); +const AMD_MODULE_ID = "thirdparty/CodeMirror6/codemirror6"; +const NODE_MODULES_PATH_SEGMENT = "/node_modules/"; + +const DEDUPED_PACKAGES = [ + "@codemirror/autocomplete", + "@codemirror/commands", + "@codemirror/lang-css", + "@codemirror/lang-html", + "@codemirror/lang-javascript", + "@codemirror/lang-json", + "@codemirror/lang-markdown", + "@codemirror/lang-php", + "@codemirror/lang-xml", + "@codemirror/language", + "@codemirror/legacy-modes", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/css", + "@lezer/highlight", + "@lezer/html", + "@lezer/javascript", + "@lezer/json", + "@lezer/lr", + "@lezer/markdown", + "@lezer/php", + "@lezer/xml", + "@marijn/find-cluster-break", + "crelt", + "style-mod", + "w3c-keyname" +]; + +const SINGLETON_PACKAGES = [ + "@codemirror/language", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr" +]; + +const LICENSE_FILE_NAMES = [ + "LICENSE", + "LICENSE.md", + "LICENSE.txt", + "LICENCE", + "LICENCE.md", + "LICENCE.txt" +]; + +function normalizePath(filePath) { + return filePath.replaceAll("\\", "/"); +} + +function getPackageDetails(moduleId) { + const normalizedId = normalizePath(moduleId.split("?")[0]); + const nodeModulesIndex = normalizedId.lastIndexOf(NODE_MODULES_PATH_SEGMENT); + if (nodeModulesIndex === -1) { + return null; + } + + const packageRelativePath = normalizedId.slice( + nodeModulesIndex + NODE_MODULES_PATH_SEGMENT.length + ); + const pathParts = packageRelativePath.split("/"); + const packageName = pathParts[0].startsWith("@") + ? `${pathParts[0]}/${pathParts[1]}` + : pathParts[0]; + const packagePathPartCount = packageName.startsWith("@") ? 2 : 1; + const packageRoot = normalizedId.slice( + 0, + nodeModulesIndex + NODE_MODULES_PATH_SEGMENT.length + + pathParts.slice(0, packagePathPartCount).join("/").length + ); + + return { + name: packageName, + root: packageRoot + }; +} + +function findLicenseFile(packageRoot) { + for (const fileName of LICENSE_FILE_NAMES) { + const candidate = path.join(packageRoot, fileName); + if (fs.existsSync(candidate)) { + return candidate; + } + } + return null; +} + +function writeAggregateLicenseNotice(packageRoots) { + const sections = [ + "# CodeMirror 6 bundle licenses", + "", + "This file is generated by `build/build-codemirror6.mjs` from the packages included in", + "`src/thirdparty/CodeMirror6/codemirror6.js`. Each package's license text is reproduced", + "below.", + "" + ]; + + for (const packageName of [...packageRoots.keys()].sort()) { + const packageRoot = [...packageRoots.get(packageName)][0]; + const packageJSON = JSON.parse( + fs.readFileSync(path.join(packageRoot, "package.json"), "utf8") + ); + const licensePath = findLicenseFile(packageRoot); + if (!licensePath) { + throw new Error(`No license file found for bundled package ${packageName}`); + } + + sections.push( + `## ${packageName} ${packageJSON.version}`, + "", + fs.readFileSync(licensePath, "utf8").trim(), + "" + ); + } + + fs.writeFileSync(LICENSE_FILE, `${sections.join("\n").trimEnd()}\n`, "utf8"); +} + +const packageRoots = new Map(); + +const validateBundlePlugin = { + name: "validate-codemirror6-bundle", + + moduleParsed(moduleInfo) { + const packageDetails = getPackageDetails(moduleInfo.id); + if (!packageDetails) { + return; + } + if (packageDetails.name === "codemirror") { + throw new Error( + `CodeMirror 5 package "codemirror" is not allowed in the CodeMirror 6 bundle: ` + + moduleInfo.id + ); + } + + if (!packageRoots.has(packageDetails.name)) { + packageRoots.set(packageDetails.name, new Set()); + } + packageRoots.get(packageDetails.name).add(packageDetails.root); + }, + + generateBundle(_outputOptions, outputBundle) { + const chunks = Object.values(outputBundle).filter(item => item.type === "chunk"); + if (chunks.length !== 1) { + throw new Error(`CodeMirror 6 must build as one chunk, but Rollup emitted ${chunks.length}`); + } + + const [chunk] = chunks; + if (chunk.imports.length || chunk.dynamicImports.length) { + throw new Error("CodeMirror 6 bundle contains external or dynamic imports"); + } + + for (const packageName of SINGLETON_PACKAGES) { + const roots = packageRoots.get(packageName); + if (!roots || roots.size !== 1) { + throw new Error( + `Expected exactly one bundled copy of ${packageName}, found ${roots ? roots.size : 0}` + ); + } + } + + for (const [packageName, roots] of packageRoots) { + if (roots.size > 1) { + throw new Error( + `Multiple bundled copies of ${packageName} were detected: ${[...roots].join(", ")}` + ); + } + } + } +}; + +async function buildCodeMirror6() { + assertNoCodeMirror5Dependencies({ + repositoryRoot: REPOSITORY_ROOT + }); + fs.rmSync(LEGACY_OUTPUT_DIRECTORY, { recursive: true, force: true }); + fs.rmSync(OUTPUT_DIRECTORY, { recursive: true, force: true }); + fs.mkdirSync(OUTPUT_DIRECTORY, { recursive: true }); + + const bundle = await rollup({ + input: ENTRY_FILE, + plugins: [ + nodeResolve({ + browser: true, + dedupe: DEDUPED_PACKAGES + }), + babel({ + babelHelpers: "bundled", + babelrc: false, + configFile: false, + extensions: [".js"], + include: VIM_CORE_FILE, + plugins: ["@babel/plugin-transform-optional-chaining"] + }), + validateBundlePlugin + ] + }); + + try { + await bundle.write({ + amd: { + id: AMD_MODULE_ID + }, + banner: "/*! DONT_STRIP_MINIFY: Third-party license notices: " + + "thirdparty/licences/codemirror6.markdown. */", + exports: "named", + file: OUTPUT_FILE, + format: "amd", + inlineDynamicImports: true, + sourcemap: true, + validate: true + }); + } finally { + await bundle.close(); + } + + writeAggregateLicenseNotice(packageRoots); + + const outputSizeKB = Math.round(fs.statSync(OUTPUT_FILE).size / 1024); + console.log( + `Built ${AMD_MODULE_ID} (${outputSizeKB} KB, ${packageRoots.size} runtime packages)` + ); +} + +buildCodeMirror6().catch(error => { + console.error(error); + process.exitCode = 1; +}); diff --git a/build/codemirror6-entry.js b/build/codemirror6-entry.js new file mode 100644 index 0000000000..ad2da53a3e --- /dev/null +++ b/build/codemirror6-entry.js @@ -0,0 +1,235 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +// This file is bundled into one named AMD module for Phoenix's RequireJS +// runtime. Keeping all CodeMirror 6 imports behind this boundary guarantees +// that the browser receives exactly one copy of @codemirror/state. + +export { initVim } from "@replit/codemirror-vim-core"; + +export { + Annotation, + Compartment, + EditorSelection, + EditorState, + RangeSet, + StateEffect, + StateField, + Transaction, + countColumn +} from "@codemirror/state"; + +export { + Decoration, + EditorView, + GutterMarker, + ViewPlugin, + WidgetType, + crosshairCursor, + drawSelection, + dropCursor, + gutter, + gutterLineClass, + gutters, + highlightActiveLine, + highlightActiveLineGutter, + highlightSpecialChars, + keymap, + lineNumberMarkers, + lineNumbers, + placeholder, + rectangularSelection, + scrollPastEnd +} from "@codemirror/view"; + +export { + autocompletion, + closeBrackets, + closeBracketsKeymap, + completionKeymap, + deleteBracketPair, + insertBracket +} from "@codemirror/autocomplete"; + +export { + cursorCharLeft, + cursorCharRight, + cursorDocEnd, + cursorDocStart, + cursorGroupLeft, + cursorGroupRight, + cursorLineDown, + cursorLineEnd, + cursorLineStart, + cursorLineUp, + cursorPageDown, + cursorPageUp, + defaultKeymap, + deleteCharBackward, + deleteCharForward, + deleteGroupBackward, + deleteGroupForward, + deleteLine, + deleteLineBoundaryBackward, + deleteLineBoundaryForward, + deleteToLineEnd, + history, + historyKeymap, + indentLess, + indentMore, + indentSelection, + indentWithTab, + insertNewlineAndIndent, + insertTab, + redo, + redoSelection, + selectAll, + selectCharLeft, + selectCharRight, + selectDocEnd, + selectDocStart, + selectGroupLeft, + selectGroupRight, + selectLineDown, + selectLineEnd, + selectLineStart, + selectLineUp, + selectPageDown, + selectPageUp, + simplifySelection, + splitLine, + toggleComment, + transposeChars, + undo, + undoSelection +} from "@codemirror/commands"; + +export { + HighlightStyle, + StreamLanguage, + StringStream, + bracketMatching, + defaultHighlightStyle, + foldAll, + foldCode, + foldEffect, + foldGutter, + foldKeymap, + foldState, + foldable, + foldedRanges, + indentOnInput, + indentUnit, + syntaxHighlighting, + syntaxTree, + unfoldAll, + unfoldCode, + unfoldEffect +} from "@codemirror/language"; + +export { + lintGutter, + lintKeymap, + linter, + setDiagnostics +} from "@codemirror/lint"; + +export { + highlightSelectionMatches, + searchKeymap +} from "@codemirror/search"; +export { tags } from "@lezer/highlight"; +export { + legacyModeMIMEs, + legacyModeModules, + legacyModeParsers +} from "./codemirror6-legacy-modes.js"; + +export { css } from "@codemirror/lang-css"; +export { html } from "@codemirror/lang-html"; +export { javascript } from "@codemirror/lang-javascript"; +export { json } from "@codemirror/lang-json"; +export { + markdown, + markdownLanguage +} from "@codemirror/lang-markdown"; +export { php } from "@codemirror/lang-php"; +export { xml } from "@codemirror/lang-xml"; + +export { + c, + clike as makeLegacyCLike, + cpp, + csharp, + dart, + java, + kotlin, + objectiveC, + scala +} from "@codemirror/legacy-modes/mode/clike"; +export { clojure } from "@codemirror/legacy-modes/mode/clojure"; +export { coffeeScript } from "@codemirror/legacy-modes/mode/coffeescript"; +export { diff } from "@codemirror/legacy-modes/mode/diff"; +export { go } from "@codemirror/legacy-modes/mode/go"; +export { groovy } from "@codemirror/legacy-modes/mode/groovy"; +export { haskell } from "@codemirror/legacy-modes/mode/haskell"; +export { haxe } from "@codemirror/legacy-modes/mode/haxe"; +export { lua } from "@codemirror/legacy-modes/mode/lua"; +export { perl } from "@codemirror/legacy-modes/mode/perl"; +export { pascal } from "@codemirror/legacy-modes/mode/pascal"; +export { properties } from "@codemirror/legacy-modes/mode/properties"; +export { pug } from "@codemirror/legacy-modes/mode/pug"; +export { python } from "@codemirror/legacy-modes/mode/python"; +export { ruby } from "@codemirror/legacy-modes/mode/ruby"; +export { rust } from "@codemirror/legacy-modes/mode/rust"; +export { sass } from "@codemirror/legacy-modes/mode/sass"; +export { scheme } from "@codemirror/legacy-modes/mode/scheme"; +export { shell } from "@codemirror/legacy-modes/mode/shell"; +export { mySQL, standardSQL } from "@codemirror/legacy-modes/mode/sql"; +export { stex } from "@codemirror/legacy-modes/mode/stex"; +export { stylus } from "@codemirror/legacy-modes/mode/stylus"; +export { swift } from "@codemirror/legacy-modes/mode/swift"; +export { toml } from "@codemirror/legacy-modes/mode/toml"; +export { turtle } from "@codemirror/legacy-modes/mode/turtle"; +export { vb } from "@codemirror/legacy-modes/mode/vb"; +export { vbScript } from "@codemirror/legacy-modes/mode/vbscript"; +export { yaml } from "@codemirror/legacy-modes/mode/yaml"; +export { erlang } from "@codemirror/legacy-modes/mode/erlang"; + +// Stream parsers used by Phoenix's legacy editor compatibility facade. These +// aliases avoid collisions with the native CM6 language-support factories +// exported above. +export { + css as legacyCSS, + less as legacyLess, + mkCSS as makeLegacyCSS, + sCSS as legacySCSS +} from "@codemirror/legacy-modes/mode/css"; +export { + javascript as legacyJavaScript, + json as legacyJSON, + jsonld as legacyJSONLD, + typescript as legacyTypeScript +} from "@codemirror/legacy-modes/mode/javascript"; +export { + html as legacyHTML, + mkXML as makeLegacyXML, + xml as legacyXML +} from "@codemirror/legacy-modes/mode/xml"; diff --git a/build/codemirror6-legacy-modes.js b/build/codemirror6-legacy-modes.js new file mode 100644 index 0000000000..428e095564 --- /dev/null +++ b/build/codemirror6-legacy-modes.js @@ -0,0 +1,558 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +import { apl } from "@codemirror/legacy-modes/mode/apl"; +import { asciiArmor } from "@codemirror/legacy-modes/mode/asciiarmor"; +import { asn1 } from "@codemirror/legacy-modes/mode/asn1"; +import { asterisk } from "@codemirror/legacy-modes/mode/asterisk"; +import { brainfuck } from "@codemirror/legacy-modes/mode/brainfuck"; +import { + c, + ceylon, + cpp, + csharp, + dart, + java, + kotlin, + nesC, + objectiveC, + objectiveCpp, + scala, + shader, + squirrel +} from "@codemirror/legacy-modes/mode/clike"; +import { clojure } from "@codemirror/legacy-modes/mode/clojure"; +import { cmake } from "@codemirror/legacy-modes/mode/cmake"; +import { cobol } from "@codemirror/legacy-modes/mode/cobol"; +import { coffeeScript } from "@codemirror/legacy-modes/mode/coffeescript"; +import { commonLisp } from "@codemirror/legacy-modes/mode/commonlisp"; +import { crystal } from "@codemirror/legacy-modes/mode/crystal"; +import { + css, + gss, + less, + sCSS +} from "@codemirror/legacy-modes/mode/css"; +import { cypher } from "@codemirror/legacy-modes/mode/cypher"; +import { d } from "@codemirror/legacy-modes/mode/d"; +import { diff } from "@codemirror/legacy-modes/mode/diff"; +import { dockerFile } from "@codemirror/legacy-modes/mode/dockerfile"; +import { dtd } from "@codemirror/legacy-modes/mode/dtd"; +import { dylan } from "@codemirror/legacy-modes/mode/dylan"; +import { ebnf } from "@codemirror/legacy-modes/mode/ebnf"; +import { ecl } from "@codemirror/legacy-modes/mode/ecl"; +import { eiffel } from "@codemirror/legacy-modes/mode/eiffel"; +import { elm } from "@codemirror/legacy-modes/mode/elm"; +import { erlang } from "@codemirror/legacy-modes/mode/erlang"; +import { factor } from "@codemirror/legacy-modes/mode/factor"; +import { fcl } from "@codemirror/legacy-modes/mode/fcl"; +import { forth } from "@codemirror/legacy-modes/mode/forth"; +import { fortran } from "@codemirror/legacy-modes/mode/fortran"; +import { + gas, + gasArm +} from "@codemirror/legacy-modes/mode/gas"; +import { gherkin } from "@codemirror/legacy-modes/mode/gherkin"; +import { go } from "@codemirror/legacy-modes/mode/go"; +import { groovy } from "@codemirror/legacy-modes/mode/groovy"; +import { haskell } from "@codemirror/legacy-modes/mode/haskell"; +import { + haxe, + hxml +} from "@codemirror/legacy-modes/mode/haxe"; +import { http } from "@codemirror/legacy-modes/mode/http"; +import { idl } from "@codemirror/legacy-modes/mode/idl"; +import { + javascript, + json, + jsonld, + typescript +} from "@codemirror/legacy-modes/mode/javascript"; +import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2"; +import { julia } from "@codemirror/legacy-modes/mode/julia"; +import { liveScript } from "@codemirror/legacy-modes/mode/livescript"; +import { lua } from "@codemirror/legacy-modes/mode/lua"; +import { mathematica } from "@codemirror/legacy-modes/mode/mathematica"; +import { mbox } from "@codemirror/legacy-modes/mode/mbox"; +import { mirc } from "@codemirror/legacy-modes/mode/mirc"; +import { + fSharp, + oCaml, + sml +} from "@codemirror/legacy-modes/mode/mllike"; +import { modelica } from "@codemirror/legacy-modes/mode/modelica"; +import { + mscgen, + msgenny, + xu +} from "@codemirror/legacy-modes/mode/mscgen"; +import { mumps } from "@codemirror/legacy-modes/mode/mumps"; +import { nginx } from "@codemirror/legacy-modes/mode/nginx"; +import { nsis } from "@codemirror/legacy-modes/mode/nsis"; +import { ntriples } from "@codemirror/legacy-modes/mode/ntriples"; +import { octave } from "@codemirror/legacy-modes/mode/octave"; +import { oz } from "@codemirror/legacy-modes/mode/oz"; +import { pascal } from "@codemirror/legacy-modes/mode/pascal"; +import { pegjs } from "@codemirror/legacy-modes/mode/pegjs"; +import { perl } from "@codemirror/legacy-modes/mode/perl"; +import { pig } from "@codemirror/legacy-modes/mode/pig"; +import { powerShell } from "@codemirror/legacy-modes/mode/powershell"; +import { properties } from "@codemirror/legacy-modes/mode/properties"; +import { protobuf } from "@codemirror/legacy-modes/mode/protobuf"; +import { pug } from "@codemirror/legacy-modes/mode/pug"; +import { puppet } from "@codemirror/legacy-modes/mode/puppet"; +import { + cython, + python +} from "@codemirror/legacy-modes/mode/python"; +import { q } from "@codemirror/legacy-modes/mode/q"; +import { r } from "@codemirror/legacy-modes/mode/r"; +import { + rpmChanges, + rpmSpec +} from "@codemirror/legacy-modes/mode/rpm"; +import { ruby } from "@codemirror/legacy-modes/mode/ruby"; +import { rust } from "@codemirror/legacy-modes/mode/rust"; +import { sas } from "@codemirror/legacy-modes/mode/sas"; +import { sass } from "@codemirror/legacy-modes/mode/sass"; +import { scheme } from "@codemirror/legacy-modes/mode/scheme"; +import { shell } from "@codemirror/legacy-modes/mode/shell"; +import { sieve } from "@codemirror/legacy-modes/mode/sieve"; +import { smalltalk } from "@codemirror/legacy-modes/mode/smalltalk"; +import { solr } from "@codemirror/legacy-modes/mode/solr"; +import { sparql } from "@codemirror/legacy-modes/mode/sparql"; +import { spreadsheet } from "@codemirror/legacy-modes/mode/spreadsheet"; +import { + cassandra, + esper, + gpSQL, + gql, + hive, + mariaDB, + msSQL, + mySQL, + pgSQL, + plSQL, + sparkSQL, + sqlite, + standardSQL +} from "@codemirror/legacy-modes/mode/sql"; +import { + stex, + stexMath +} from "@codemirror/legacy-modes/mode/stex"; +import { stylus } from "@codemirror/legacy-modes/mode/stylus"; +import { swift } from "@codemirror/legacy-modes/mode/swift"; +import { tcl } from "@codemirror/legacy-modes/mode/tcl"; +import { textile } from "@codemirror/legacy-modes/mode/textile"; +import { tiddlyWiki } from "@codemirror/legacy-modes/mode/tiddlywiki"; +import { tiki } from "@codemirror/legacy-modes/mode/tiki"; +import { toml } from "@codemirror/legacy-modes/mode/toml"; +import { troff } from "@codemirror/legacy-modes/mode/troff"; +import { ttcnCfg } from "@codemirror/legacy-modes/mode/ttcn-cfg"; +import { ttcn } from "@codemirror/legacy-modes/mode/ttcn"; +import { turtle } from "@codemirror/legacy-modes/mode/turtle"; +import { vb } from "@codemirror/legacy-modes/mode/vb"; +import { + vbScript, + vbScriptASP +} from "@codemirror/legacy-modes/mode/vbscript"; +import { velocity } from "@codemirror/legacy-modes/mode/velocity"; +import { + tlv, + verilog +} from "@codemirror/legacy-modes/mode/verilog"; +import { vhdl } from "@codemirror/legacy-modes/mode/vhdl"; +import { wast } from "@codemirror/legacy-modes/mode/wast"; +import { webIDL } from "@codemirror/legacy-modes/mode/webidl"; +import { + html, + xml +} from "@codemirror/legacy-modes/mode/xml"; +import { xQuery } from "@codemirror/legacy-modes/mode/xquery"; +import { yacas } from "@codemirror/legacy-modes/mode/yacas"; +import { yaml } from "@codemirror/legacy-modes/mode/yaml"; +import { + ez80, + z80 +} from "@codemirror/legacy-modes/mode/z80"; + +const legacyModeParsers = { + "apl": apl, + "asciiarmor": asciiArmor, + "asn.1": asn1({}), + "asterisk": asterisk, + "brainfuck": brainfuck, + "c": c, + "ceylon": ceylon, + "cpp": cpp, + "csharp": csharp, + "dart": dart, + "java": java, + "kotlin": kotlin, + "nesc": nesC, + "objectivec": objectiveC, + "objectivecpp": objectiveCpp, + "scala": scala, + "shader": shader, + "squirrel": squirrel, + "clojure": clojure, + "cmake": cmake, + "cobol": cobol, + "coffeescript": coffeeScript, + "commonlisp": commonLisp, + "crystal": crystal, + "css": css, + "gss": gss, + "less": less, + "scss": sCSS, + "cypher": cypher, + "d": d, + "diff": diff, + "dockerfile": dockerFile, + "dtd": dtd, + "dylan": dylan, + "ebnf": ebnf, + "ecl": ecl, + "eiffel": eiffel, + "elm": elm, + "erlang": erlang, + "factor": factor, + "fcl": fcl, + "forth": forth, + "fortran": fortran, + "gas": gas, + "gas-arm": gasArm, + "gherkin": gherkin, + "go": go, + "groovy": groovy, + "haskell": haskell, + "haxe": haxe, + "hxml": hxml, + "http": http, + "idl": idl, + "javascript": javascript, + "json": json, + "jsonld": jsonld, + "typescript": typescript, + "jinja2": jinja2, + "julia": julia, + "livescript": liveScript, + "lua": lua, + "mathematica": mathematica, + "mbox": mbox, + "mirc": mirc, + "mllike": oCaml, + "ocaml": oCaml, + "fsharp": fSharp, + "sml": sml, + "modelica": modelica, + "mscgen": mscgen, + "msgenny": msgenny, + "xu": xu, + "mumps": mumps, + "nginx": nginx, + "nsis": nsis, + "ntriples": ntriples, + "octave": octave, + "oz": oz, + "pascal": pascal, + "pegjs": pegjs, + "perl": perl, + "pig": pig, + "powershell": powerShell, + "properties": properties, + "protobuf": protobuf, + "pug": pug, + "puppet": puppet, + "python": python, + "cython": cython, + "q": q, + "r": r, + "rpm": rpmSpec, + "rpm-changes": rpmChanges, + "rpm-spec": rpmSpec, + "ruby": ruby, + "rust": rust, + "sas": sas, + "sass": sass, + "scheme": scheme, + "shell": shell, + "sieve": sieve, + "smalltalk": smalltalk, + "solr": solr, + "sparql": sparql, + "spreadsheet": spreadsheet, + "sql": standardSQL, + "cassandra": cassandra, + "esper": esper, + "gpsql": gpSQL, + "gql": gql, + "hive": hive, + "mariadb": mariaDB, + "mssql": msSQL, + "mysql": mySQL, + "pgsql": pgSQL, + "plsql": plSQL, + "sparksql": sparkSQL, + "sqlite": sqlite, + "stex": stex, + "stex-math": stexMath, + "stylus": stylus, + "swift": swift, + "tcl": tcl, + "textile": textile, + "tiddlywiki": tiddlyWiki, + "tiki": tiki, + "toml": toml, + "troff": troff, + "ttcn-cfg": ttcnCfg, + "ttcn": ttcn, + "turtle": turtle, + "vb": vb, + "vbscript": vbScript, + "vbscript-asp": vbScriptASP, + "velocity": velocity, + "verilog": verilog, + "tlv": tlv, + "vhdl": vhdl, + "wast": wast, + "webidl": webIDL, + "xml": xml, + "html": html, + "xquery": xQuery, + "yacas": yacas, + "yaml": yaml, + "z80": z80, + "ez80": ez80 +}; + +const legacyModeModules = { + "clike": [ + "clike", + "c", + "ceylon", + "cpp", + "csharp", + "dart", + "java", + "kotlin", + "nesc", + "objectivec", + "objectivecpp", + "scala", + "shader", + "squirrel" + ], + "css": ["css", "gss", "less", "scss"], + "gas": ["gas", "gas-arm"], + "haxe": ["haxe", "hxml"], + "javascript": ["javascript", "json", "jsonld", "typescript"], + "mllike": ["mllike", "ocaml", "fsharp", "sml"], + "mscgen": ["mscgen", "msgenny", "xu"], + "python": ["python", "cython"], + "rpm": ["rpm", "rpm-changes", "rpm-spec"], + "sql": [ + "sql", + "cassandra", + "esper", + "gpsql", + "gql", + "hive", + "mariadb", + "mssql", + "mysql", + "pgsql", + "plsql", + "sparksql", + "sqlite" + ], + "stex": ["stex", "stex-math"], + "vbscript": ["vbscript", "vbscript-asp"], + "verilog": ["verilog", "tlv"], + "xml": ["xml", "html"], + "z80": ["z80", "ez80"] +}; + +const legacyModeMIMEs = { + "application/ecmascript": "javascript", + "application/edn": "clojure", + "application/mbox": "mbox", + "application/n-quads": "ntriples", + "application/n-triples": "ntriples", + "application/pgp": "asciiarmor", + "application/pgp-encrypted": "asciiarmor", + "application/pgp-keys": "asciiarmor", + "application/pgp-signature": "asciiarmor", + "application/sieve": "sieve", + "application/sparql-query": "sparql", + "application/vnd.coffeescript": "coffeescript", + "application/xml-dtd": "dtd", + "application/x-aspx": { + name: "htmlembedded", + scriptingModeSpec: "text/x-csharp" + }, + "application/x-cypher-query": "cypher", + "application/x-javascript": "javascript", + "application/x-jsp": { + name: "htmlembedded", + scriptingModeSpec: "text/x-java" + }, + "application/x-json": { + name: "javascript", + json: true + }, + "application/x-powershell": "powershell", + "application/x-sh": "shell", + "application/x-slim": "slim", + "application/x-troff": "troff", + "application/xquery": "xquery", + "message/http": "http", + "text/apl": "apl", + "text/coffeescript": "coffeescript", + "text/ecmascript": "javascript", + "text/mirc": "mirc", + "text/n-triples": "ntriples", + "text/rust": "rust", + "text/tiki": "tiki", + "text/turtle": "turtle", + "text/troff": "troff", + "text/velocity": "velocity", + "text/vbscript": "vbscript", + "text/yaml": "yaml", + "text/webassembly": "wast", + "text/x-asterisk": "asterisk", + "text/x-brainfuck": "brainfuck", + "text/x-cassandra": "cassandra", + "text/x-ceylon": "ceylon", + "text/x-clojure": "clojure", + "text/x-clojurescript": "clojure", + "text/x-cmake": "cmake", + "text/x-cobol": "cobol", + "text/x-coffeescript": "coffeescript", + "text/x-common-lisp": "commonlisp", + "text/x-crystal": "crystal", + "text/x-cython": "cython", + "text/x-d": "d", + "text/x-diff": "diff", + "text/x-django": "django", + "text/x-dockerfile": "dockerfile", + "text/x-dylan": "dylan", + "text/x-ebnf": "ebnf", + "text/x-ecl": "ecl", + "text/x-eiffel": "eiffel", + "text/x-elm": "elm", + "text/x-erlang": "erlang", + "text/x-esper": "esper", + "text/x-factor": "factor", + "text/x-fcl": "fcl", + "text/x-feature": "gherkin", + "text/x-forth": "forth", + "text/x-fortran": "fortran", + "text/x-fsharp": "fsharp", + "text/x-gas": "gas", + "text/x-gfm": "gfm", + "text/x-go": "go", + "text/x-gpsql": "gpsql", + "text/x-gql": "gql", + "text/x-groovy": "groovy", + "text/x-gss": "gss", + "text/x-haml": "haml", + "text/x-haskell": "haskell", + "text/x-haxe": "haxe", + "text/x-hive": "hive", + "text/x-hxml": "hxml", + "text/x-idl": "idl", + "text/x-ini": "properties", + "text/x-julia": "julia", + "text/x-jade": "pug", + "text/x-latex": "stex", + "text/x-literate-haskell": "haskell-literate", + "text/x-livescript": "livescript", + "text/x-lua": "lua", + "text/x-mariadb": "mariadb", + "text/x-mathematica": "mathematica", + "text/x-modelica": "modelica", + "text/x-mscgen": "mscgen", + "text/x-msgenny": "msgenny", + "text/x-mssql": "mssql", + "text/x-mumps": "mumps", + "text/x-nginx-conf": "nginx", + "text/x-nsis": "nsis", + "text/x-objectivec++": "objectivecpp", + "text/x-ocaml": "ocaml", + "text/x-octave": "octave", + "text/x-oz": "oz", + "text/x-pascal": "pascal", + "text/x-perl": "perl", + "text/x-pgsql": "pgsql", + "text/x-pig": "pig", + "text/x-plsql": "plsql", + "text/x-protobuf": "protobuf", + "text/x-pug": "pug", + "text/x-puppet": "puppet", + "text/x-python": "python", + "text/x-q": "q", + "text/x-rst": "rst", + "text/x-rpm-changes": "rpm-changes", + "text/x-rpm-spec": "rpm-spec", + "text/x-ruby": "ruby", + "text/x-rsrc": "r", + "text/x-sas": "sas", + "text/x-sass": "sass", + "text/x-scheme": "scheme", + "text/x-sieve": "sieve", + "text/x-slim": "slim", + "text/x-sml": "sml", + "text/x-smarty": "smarty", + "text/x-solr": "solr", + "text/x-soy": "soy", + "text/x-sparksql": "sparksql", + "text/x-spreadsheet": "spreadsheet", + "text/x-sqlite": "sqlite", + "text/x-squirrel": "squirrel", + "text/x-stsrc": "smalltalk", + "text/x-swift": "swift", + "text/x-systemverilog": "verilog", + "text/x-tcl": "tcl", + "text/x-textile": "textile", + "text/x-tiddlywiki": "tiddlywiki", + "text/x-tlv": "tlv", + "text/x-ttcn": "ttcn", + "text/x-ttcn-asn": "asn.1", + "text/x-ttcn-cfg": "ttcn-cfg", + "text/x-tornado": "tornado", + "text/x-troff": "troff", + "text/x-vhdl": "vhdl", + "text/x-verilog": "verilog", + "text/x-webidl": "webidl", + "text/x-xu": "xu", + "text/x-yacas": "yacas", + "text/x-yaml": "yaml", + "text/x-twig": "twig", + "text/x-nesc": "nesc", + "text/x-z80": "z80", + "text/x-ez80": "ez80" +}; + +export { + legacyModeMIMEs, + legacyModeModules, + legacyModeParsers +}; diff --git a/build/test/validate-codemirror5.test.js b/build/test/validate-codemirror5.test.js new file mode 100644 index 0000000000..efd63f4da7 --- /dev/null +++ b/build/test/validate-codemirror5.test.js @@ -0,0 +1,1094 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/* eslint-env node */ + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const test = require("node:test"); +const { + CODEMIRROR6_COMPATIBILITY_RUNTIME_RELATIVE_PATHS, + assertNoCodeMirror5, + findCodeMirror5ArtifactViolations, + findCodeMirror5DependencyViolations, + findCodeMirror5DirectImportViolations, + findCodeMirror5HTMLAssetViolations, + findCodeMirror5ImplementationViolations, + findCodeMirror5LicenseNoticeViolations, + findCodeMirrorVimLicenseNoticeViolations, + findCodeMirror6ReleaseLicenseViolations, + findCodeMirror6RuntimeArtifactViolations, + findInstalledCodeMirror5Violations, + listProjectPackageMetadataFiles +} = require("../validate-codemirror5"); + +const CODEMIRROR5_DERIVED_LICENSE_NOTICE = [ + "# CodeMirror 5-derived compatibility code", + "CodeMirrorCompat.js", + "CodeMirrorLegacyAddons.js", + "CodeMirrorLegacyExtendedAddons.js", + "CodeMirrorLegacyModeMeta.js", + "CodeMirrorLegacyModesCompat.js", + "CodeMirrorLegacyRSTSlimCompat.js", + "CodeMirrorSublimeCompat.js", + "CodeMirrorTwigCompat.js", + "brackets_codemirror6_legacy_themes.less", + "foldcode.js", + "foldgutter.js", + "languageFold.js", + "Copyright (C) 2017 by Marijn Haverbeke and others", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + 'of this software and associated documentation files (the "Software"), to deal', + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,', + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", + "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.", + "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,", + "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR", + "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE", + "USE OR OTHER DEALINGS IN THE SOFTWARE." +].join("\n\n"); +const CODEMIRROR_VIM_DERIVED_LICENSE_NOTICE = [ + "# @replit CodeMirror Vim-derived compatibility code", + "CodeMirrorVimCompat.js", + "Copyright (C) 2018-2021 by Marijn Haverbeke and others", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + 'of this software and associated documentation files (the "Software"), to deal', + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR', + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." +].join("\n"); +const CODEMIRROR6_LICENSE_NOTICE = [ + "# CodeMirror 6 bundle licenses", + "", + "## @codemirror/state 6.7.1", + "", + "MIT License" +].join("\n"); +const CODEMIRROR6_LICENSE_BANNER = + "/*! DONT_STRIP_MINIFY: Third-party license notices: " + + "thirdparty/licences/codemirror6.markdown. */"; +const CODEMIRROR5_DERIVED_SOURCE_PATHS = [ + "editor/CodeMirrorCompat.js", + "editor/CodeMirrorLegacyAddons.js", + "editor/CodeMirrorLegacyExtendedAddons.js", + "editor/CodeMirrorLegacyModeMeta.js", + "editor/CodeMirrorLegacyModesCompat.js", + "editor/CodeMirrorLegacyRSTSlimCompat.js", + "editor/CodeMirrorSublimeCompat.js", + "editor/CodeMirrorTwigCompat.js", + "styles/brackets_codemirror6_legacy_themes.less", + "extensions/default/CodeFolding/foldhelpers/foldcode.js", + "extensions/default/CodeFolding/foldhelpers/foldgutter.js", + "extensions/default/CodeFolding/foldhelpers/languageFold.js" +]; +const CODEMIRROR5_DERIVED_SOURCE_BANNER = + "/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. " + + "See thirdparty/licences/codemirror5-derived.markdown. */"; + +function createRepository() { + const repositoryRoot = fs.mkdtempSync( + path.join(os.tmpdir(), "phoenix-cm5-validation-") + ); + fs.mkdirSync(path.join(repositoryRoot, "src"), { recursive: true }); + fs.writeFileSync( + path.join(repositoryRoot, "package.json"), + JSON.stringify({ + dependencies: { + "@codemirror/view": "^6.43.9" + } + }) + ); + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror5-derived.markdown", + CODEMIRROR5_DERIVED_LICENSE_NOTICE + ); + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror-vim-derived.markdown", + CODEMIRROR_VIM_DERIVED_LICENSE_NOTICE + ); + ["src", "dist", path.join("dist-test", "src")].forEach(codeRoot => { + CODEMIRROR5_DERIVED_SOURCE_PATHS.forEach(sourcePath => { + writeFile( + repositoryRoot, + path.join(codeRoot, sourcePath), + CODEMIRROR5_DERIVED_SOURCE_BANNER + ); + }); + }); + return repositoryRoot; +} + +function writeFile(repositoryRoot, relativePath, content) { + const absolutePath = path.join(repositoryRoot, relativePath); + fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); + fs.writeFileSync(absolutePath, content); +} + +function getCodeMirror6RuntimeArtifactContent(relativePath, minified) { + const contents = { + "brackets.js": [ + "define(function (require) {", + 'const CodeMirror = require("editor/CodeMirrorCompat");', + "return CodeMirror;", + "});" + ], + "editor/CodeMirror6Adapter.js": [ + "define(function (require) {", + 'const CM6 = require("thirdparty/CodeMirror6/codemirror6");', + "return CM6;", + "});" + ], + "editor/CodeMirrorCompat.js": [ + "define(function (require) {", + 'require("thirdparty/CodeMirror6/codemirror6");', + 'require("editor/CodeMirrorLegacyModeMeta");', + 'require("editor/CodeMirrorLegacyModesCompat");', + "});" + ], + "editor/CodeMirrorLegacyExtendedAddons.js": [ + "define(function () {", + 'return "addon/mode/loadmode";', + "});" + ], + "editor/CodeMirrorLegacyFileSystem.js": [ + "define(function (require) {", + 'require("editor/CodeMirrorLegacyModuleLoader");', + 'require("text");', + 'return "__phoenixCodeMirrorLegacyFileSystem";', + "});" + ], + "editor/CodeMirrorLegacyModesCompat.js": [ + "define(function (require) {", + 'return require("editor/CodeMirrorLegacyRSTSlimCompat");', + "});" + ], + "editor/CodeMirrorLegacyModuleLoader.js": [ + "define(function (require) {", + 'require("editor/CodeMirrorLegacyExtendedAddons");', + 'const modeMeta = "mode/meta";', + 'return modeMeta ? "extended-addon" : null;', + "});" + ], + "editor/CodeMirrorLegacyText.js": [ + 'define(["text-base"], function (BaseText) {', + "return BaseText;", + "});" + ], + "editor/CodeMirrorVimCompat.js": [ + "define(function (require) {", + 'return require("thirdparty/CodeMirror6/codemirror6");', + "});" + ], + "editor/Editor.js": [ + "define(function (require) {", + 'return require("editor/CodeMirror6Adapter");', + "});" + ], + "main.js": [ + "require.config({", + "paths: {", + 'text: "editor/CodeMirrorLegacyText"', + "},", + "map: {", + '"*": {', + '"thirdparty/CodeMirror/lib/codemirror": "editor/CodeMirrorCompat",', + '"thirdparty/CodeMirror2/lib/codemirror": "editor/CodeMirrorCompat"', + "}", + "}", + "});" + ], + "styles/brackets_codemirror6.less": [ + ".CodeMirror.phoenix-codemirror-6 {", + "position: relative;", + "}" + ], + "styles/brackets_codemirror6_legacy_themes.less": [ + ".CodeMirror.phoenix-codemirror-6.cm-s-test {", + "color: inherit;", + "}" + ], + "styles/brackets_shared.less": [ + '@import url("brackets_codemirror6.less");', + '@import (inline) "brackets_codemirror6_legacy_themes.less";' + ], + "thirdparty/CodeMirror6/codemirror6.js": [ + CODEMIRROR6_LICENSE_BANNER, + "define('thirdparty/CodeMirror6/codemirror6', ['exports'], " + + "function (exports) {});" + ], + "utils/ExtensionLoader.js": [ + "define(function (require) {", + "const CodeMirrorLegacyFileSystem =", + 'require("editor/CodeMirrorLegacyFileSystem");', + "CodeMirrorLegacyFileSystem.install();", + "});" + ], + "utils/Global.js": [ + "define(function (require) {", + "const CodeMirrorLegacyModuleLoader =", + 'require("editor/CodeMirrorLegacyModuleLoader");', + "return CodeMirrorLegacyModuleLoader.resolveLegacyModule(", + '"thirdparty/CodeMirror"', + ");", + "});" + ] + }; + const lines = contents[relativePath] || [ + "define(function () {", + "return {};", + "});" + ]; + const licenseBanner = CODEMIRROR5_DERIVED_SOURCE_PATHS.includes(relativePath) ? + `${CODEMIRROR5_DERIVED_SOURCE_BANNER}\n` : + ""; + return licenseBanner + lines.join(minified ? "" : "\n"); +} + +function writeCodeMirror6RuntimeArtifacts( + repositoryRoot, + releaseRoot, + minified +) { + CODEMIRROR6_COMPATIBILITY_RUNTIME_RELATIVE_PATHS.forEach( + function (relativePath) { + writeFile( + repositoryRoot, + path.join(releaseRoot, relativePath), + getCodeMirror6RuntimeArtifactContent(relativePath, minified) + ); + } + ); +} + +function writeReleaseLicenseFiles(repositoryRoot) { + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror6.markdown", + CODEMIRROR6_LICENSE_NOTICE + ); + ["dist", "dist-test/src"].forEach(function (releaseRoot) { + writeFile( + repositoryRoot, + `${releaseRoot}/thirdparty/licences/codemirror5-derived.markdown`, + CODEMIRROR5_DERIVED_LICENSE_NOTICE + ); + writeFile( + repositoryRoot, + `${releaseRoot}/thirdparty/licences/codemirror-vim-derived.markdown`, + CODEMIRROR_VIM_DERIVED_LICENSE_NOTICE + ); + writeFile( + repositoryRoot, + `${releaseRoot}/thirdparty/licences/codemirror6.markdown`, + CODEMIRROR6_LICENSE_NOTICE + ); + }); +} + +function createDirectorySymlink(targetPath, linkPath) { + fs.mkdirSync(path.dirname(linkPath), { recursive: true }); + fs.symlinkSync(path.resolve(targetPath), linkPath, "junction"); +} + +function validationOptions(repositoryRoot) { + return { + repositoryRoot, + packageMetadataFiles: ["package.json"], + artifactScanPaths: ["src", "dist", "dist-test"], + codeScanPaths: ["src", "dist", "dist-test"], + installedPackageScanPaths: ["node_modules", "src", "dist", "dist-test"] + }; +} + +test("allows CM6 packages, compatibility identifiers, CSS classes, and attribution", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "src/editor.js", + [ + 'const CodeMirror = require("editor/CodeMirrorCompat");', + 'const legacyId = "thirdparty/CodeMirror/lib/codemirror";', + 'element.classList.add("CodeMirror-selected");', + "// Adapted from CodeMirror 5 under the MIT license.", + "module.exports = { CodeMirror, legacyId };" + ].join("\n") + ); + writeFile( + repositoryRoot, + "deno.lock", + '{"specifier":"npm:codemirror@5.65.16"}' + ); + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror-compat.markdown", + "Retained attribution for CM5-derived compatibility algorithms." + ); + + assert.doesNotThrow(() => assertNoCodeMirror5( + validationOptions(repositoryRoot) + )); +}); + +test("unions tracked and filesystem manifests including Phoenix Pro and dist", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "src/extensionsIntegrated/phoenix-pro/package.json", + JSON.stringify({ + dependencies: { + codemirror: "^5.65.16" + } + }) + ); + writeFile( + repositoryRoot, + "dist/package-lock.json", + JSON.stringify({ + lockfileVersion: 3, + packages: { + "node_modules/codemirror": { + version: "5.65.16" + } + } + }) + ); + + const options = { + repositoryRoot, + trackedPackageMetadataFiles: ["package.json"], + packageMetadataScanPaths: ["src", "dist"] + }; + const metadataFiles = listProjectPackageMetadataFiles(options); + assert(metadataFiles.includes("package.json")); + assert(metadataFiles.includes( + "src/extensionsIntegrated/phoenix-pro/package.json" + )); + assert(metadataFiles.includes("dist/package-lock.json")); + + const findings = findCodeMirror5DependencyViolations(options); + assert(findings.some(finding => finding.includes("phoenix-pro/package.json"))); + assert(findings.some(finding => finding.includes("dist/package-lock.json"))); +}); + +test("scans a symlinked Phoenix Pro checkout for every CM5 violation class", (t) => { + const repositoryRoot = createRepository(); + const phoenixProRoot = fs.mkdtempSync( + path.join(os.tmpdir(), "phoenix-pro-cm5-validation-") + ); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + t.after(() => fs.rmSync(phoenixProRoot, { recursive: true, force: true })); + + const packageName = "code" + "mirror"; + writeFile( + phoenixProRoot, + "package.json", + JSON.stringify({ + dependencies: { + [packageName]: "^5.65.16" + } + }) + ); + writeFile( + phoenixProRoot, + "src/legacy-import.js", + `const legacyEditor = require("${packageName}");` + ); + writeFile( + phoenixProRoot, + "thirdparty/CodeMirror/lib/codemirror.js", + "legacy editor" + ); + writeFile( + phoenixProRoot, + "node_modules/codemirror/package.json", + JSON.stringify({ + name: packageName, + version: "5.65.16" + }) + ); + createDirectorySymlink( + phoenixProRoot, + path.join( + repositoryRoot, + "src/extensionsIntegrated/phoenix-pro" + ) + ); + + const options = { + repositoryRoot, + trackedPackageMetadataFiles: ["package.json"], + packageMetadataScanPaths: ["src"], + artifactScanPaths: ["src"], + codeScanPaths: ["src"], + installedPackageScanPaths: ["src"] + }; + + assert(listProjectPackageMetadataFiles(options).includes( + "src/extensionsIntegrated/phoenix-pro/package.json" + )); + assert(findCodeMirror5DependencyViolations(options).some(finding => { + return finding.includes( + "src/extensionsIntegrated/phoenix-pro/package.json" + ); + })); + assert(findCodeMirror5DirectImportViolations(options).some(finding => { + return finding.includes( + "src/extensionsIntegrated/phoenix-pro/src/legacy-import.js" + ); + })); + assert(findCodeMirror5ArtifactViolations(options).some(finding => { + return finding.includes( + "src/extensionsIntegrated/phoenix-pro/thirdparty/CodeMirror" + ); + })); + assert(findInstalledCodeMirror5Violations(options).some(finding => { + return finding.includes( + "src/extensionsIntegrated/phoenix-pro/node_modules/codemirror" + ); + })); +}); + +test("guards symlink cycles and scans duplicate directory targets once", (t) => { + const repositoryRoot = createRepository(); + const phoenixProRoot = fs.mkdtempSync( + path.join(os.tmpdir(), "phoenix-pro-cm5-cycle-validation-") + ); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + t.after(() => fs.rmSync(phoenixProRoot, { recursive: true, force: true })); + + const packageName = "code" + "mirror"; + writeFile( + phoenixProRoot, + "package.json", + JSON.stringify({ + dependencies: { + [packageName]: "^5.65.16" + } + }) + ); + writeFile( + phoenixProRoot, + "src/legacy-import.js", + `const legacyEditor = require("${packageName}");` + ); + writeFile( + phoenixProRoot, + "node_modules/codemirror/package.json", + JSON.stringify({ + name: packageName, + version: "5.65.16" + }) + ); + createDirectorySymlink( + phoenixProRoot, + path.join(phoenixProRoot, "src/cycle") + ); + const extensionsRoot = path.join(repositoryRoot, "src/extensionsIntegrated"); + createDirectorySymlink( + phoenixProRoot, + path.join(extensionsRoot, "phoenix-pro") + ); + createDirectorySymlink( + phoenixProRoot, + path.join(extensionsRoot, "phoenix-pro-duplicate") + ); + + const options = { + repositoryRoot, + trackedPackageMetadataFiles: [], + packageMetadataScanPaths: ["src"], + codeScanPaths: ["src"], + installedPackageScanPaths: ["src"] + }; + + const metadataFiles = listProjectPackageMetadataFiles(options); + assert.equal(metadataFiles.length, 1); + assert.equal( + findCodeMirror5DependencyViolations(options).length, + 1 + ); + assert.equal( + findCodeMirror5DirectImportViolations(options).length, + 1 + ); + assert.equal( + findInstalledCodeMirror5Violations(options).length, + 1 + ); +}); + +test("rejects CodeMirror package declarations and lock entries", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "package.json", + JSON.stringify({ + dependencies: { + codemirror: "^5.65.16" + } + }) + ); + writeFile( + repositoryRoot, + "package-lock.json", + JSON.stringify({ + lockfileVersion: 3, + packages: { + "": { + dependencies: { + codemirror: "^5.65.16" + } + }, + "node_modules/codemirror": { + version: "5.65.16" + } + } + }) + ); + + const findings = findCodeMirror5DependencyViolations({ + repositoryRoot, + packageMetadataFiles: ["package.json", "package-lock.json"] + }); + assert(findings.some(finding => finding.includes("dependencies.codemirror"))); + assert(findings.some(finding => finding.includes("node_modules/codemirror"))); +}); + +test("rejects legacy vendor trees, licenses, and installed packages", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "src/thirdparty/CodeMirror/lib/codemirror.js", + "legacy editor" + ); + writeFile( + repositoryRoot, + "dist/thirdparty/CODEMIRROR2/theme/legacy.css", + "legacy theme" + ); + writeFile( + repositoryRoot, + "dist-test/src/thirdparty/licences/codemirror.markdown", + "legacy license" + ); + writeFile( + repositoryRoot, + "node_modules/codemirror/package.json", + '{"version":"5.65.16"}' + ); + writeFile( + repositoryRoot, + "dist/node_modules/codemirror/package.json", + '{"version":"5.65.16"}' + ); + writeFile( + repositoryRoot, + "node_modules/cm5-alias/package.json", + '{"name":"codemirror","version":"5.65.16"}' + ); + writeFile( + repositoryRoot, + "node_modules/cm6-alias/package.json", + '{"name":"codemirror","version":"6.0.1"}' + ); + + const options = validationOptions(repositoryRoot); + assert(findCodeMirror5ArtifactViolations(options).some(finding => { + return finding.includes("src/thirdparty/CodeMirror"); + })); + assert(findCodeMirror5ArtifactViolations(options).some(finding => { + return finding.includes("dist/thirdparty/CODEMIRROR2"); + })); + assert(findCodeMirror5ArtifactViolations(options).some(finding => { + return finding.endsWith("codemirror.markdown"); + })); + const installedFindings = findInstalledCodeMirror5Violations(options); + assert(installedFindings.some(finding => { + return finding.includes("node_modules/codemirror"); + })); + assert(installedFindings.some(finding => { + return finding.includes("node_modules/cm5-alias"); + })); + assert(installedFindings.some(finding => { + return finding.includes("dist/node_modules/codemirror"); + })); + assert(!installedFindings.some(finding => { + return finding.includes("node_modules/cm6-alias"); + })); +}); + +test("requires the complete CM5-derived notice in source and release roots", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + assert.deepEqual( + findCodeMirror5LicenseNoticeViolations({repositoryRoot}), + [] + ); + + assert.deepEqual( + findCodeMirror5LicenseNoticeViolations({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + [ + "dist-test/src/thirdparty/licences/" + + "codemirror5-derived.markdown (missing)", + "dist/thirdparty/licences/codemirror5-derived.markdown (missing)" + ] + ); + writeFile( + repositoryRoot, + "dist/thirdparty/licences/codemirror5-derived.markdown", + CODEMIRROR5_DERIVED_LICENSE_NOTICE + ); + + assert.deepEqual( + findCodeMirror5LicenseNoticeViolations({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + [ + "dist-test/src/thirdparty/licences/" + + "codemirror5-derived.markdown (missing)" + ] + ); + writeFile( + repositoryRoot, + "dist-test/src/thirdparty/licences/codemirror5-derived.markdown", + CODEMIRROR5_DERIVED_LICENSE_NOTICE.replace( + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "" + ) + ); + assert.deepEqual( + findCodeMirror5LicenseNoticeViolations({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + [ + "dist-test/src/thirdparty/licences/" + + "codemirror5-derived.markdown " + + "(incomplete CodeMirror 5 MIT notice)" + ] + ); + + writeFile( + repositoryRoot, + "src/editor/CodeMirrorCompat.js", + "/* compatibility implementation without a preserved notice */" + ); + assert.deepEqual( + findCodeMirror5LicenseNoticeViolations({repositoryRoot}), + [ + "src/editor/CodeMirrorCompat.js " + + "(missing preserved CodeMirror 5-derived notice)" + ] + ); +}); + +test("requires the exact @replit Vim-derived notice in source and release roots", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + assert.deepEqual( + findCodeMirrorVimLicenseNoticeViolations({repositoryRoot}), + [] + ); + + assert.deepEqual( + findCodeMirrorVimLicenseNoticeViolations({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + [ + "dist-test/src/thirdparty/licences/" + + "codemirror-vim-derived.markdown (missing)", + "dist/thirdparty/licences/codemirror-vim-derived.markdown (missing)" + ] + ); + + writeFile( + repositoryRoot, + "dist/thirdparty/licences/codemirror-vim-derived.markdown", + CODEMIRROR_VIM_DERIVED_LICENSE_NOTICE + ); + writeFile( + repositoryRoot, + "dist-test/src/thirdparty/licences/codemirror-vim-derived.markdown", + CODEMIRROR_VIM_DERIVED_LICENSE_NOTICE.replace( + "Copyright (C) 2018-2021 by Marijn Haverbeke and others", + "Copyright (C) 2018 by somebody else" + ) + ); + + assert.deepEqual( + findCodeMirrorVimLicenseNoticeViolations({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + [ + "dist-test/src/thirdparty/licences/" + + "codemirror-vim-derived.markdown " + + "(incomplete @replit CodeMirror Vim MIT notice)" + ] + ); +}); + +test("keeps source validation independent of stale release trees", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + const codeMirrorVersionAssignment = + `${"Code" + "Mirror"}.version = "${["5", "65", "16"].join(".")}";`; + + writeFile( + repositoryRoot, + "dist/thirdparty/codemirror/lib/codemirror.js", + codeMirrorVersionAssignment + ); + writeFile( + repositoryRoot, + "dist-test/src/thirdparty/CodeMirror2/lib/codemirror.js", + codeMirrorVersionAssignment + ); + + assert.doesNotThrow(() => assertNoCodeMirror5({repositoryRoot})); + assert.throws( + () => assertNoCodeMirror5({ + repositoryRoot, + requireReleaseLicenseCopies: true + }), + /CodeMirror 5 validation failed/ + ); +}); + +test("requires CM6 bundles and their generated license in release roots", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + const options = { + repositoryRoot, + requireReleaseLicenseCopies: true + }; + + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror6.markdown", + CODEMIRROR6_LICENSE_NOTICE + ); + + let findings = findCodeMirror6ReleaseLicenseViolations(options); + assert.equal(findings.length, 4); + assert(findings.includes( + "dist/thirdparty/CodeMirror6/codemirror6.js (missing)" + )); + assert(findings.includes( + "dist/thirdparty/licences/codemirror6.markdown (missing)" + )); + assert(findings.includes( + "dist-test/src/thirdparty/CodeMirror6/codemirror6.js (missing)" + )); + assert(findings.includes( + "dist-test/src/thirdparty/licences/codemirror6.markdown (missing)" + )); + + [ + "dist", + "dist-test/src" + ].forEach(function (releaseRoot) { + writeFile( + repositoryRoot, + `${releaseRoot}/thirdparty/CodeMirror6/codemirror6.js`, + `${CODEMIRROR6_LICENSE_BANNER}\ndefine(function () {});` + ); + writeFile( + repositoryRoot, + `${releaseRoot}/thirdparty/licences/codemirror6.markdown`, + CODEMIRROR6_LICENSE_NOTICE + ); + }); + assert.deepEqual( + findCodeMirror6ReleaseLicenseViolations(options), + [] + ); + + writeFile( + repositoryRoot, + "dist-test/src/thirdparty/CodeMirror6/codemirror6.js", + "define(function () {});" + ); + writeFile( + repositoryRoot, + "dist/thirdparty/licences/codemirror6.markdown", + `${CODEMIRROR6_LICENSE_NOTICE}\ntruncated` + ); + findings = findCodeMirror6ReleaseLicenseViolations(options); + assert(findings.includes( + "dist-test/src/thirdparty/CodeMirror6/codemirror6.js " + + "(missing CodeMirror 6 license notice reference)" + )); + assert(findings.includes( + "dist/thirdparty/licences/codemirror6.markdown " + + "(does not match generated source notice)" + )); +}); + +test("requires every CM6 compatibility runtime artifact in release roots", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + const options = { + repositoryRoot, + requireReleaseLicenseCopies: true + }; + + writeCodeMirror6RuntimeArtifacts(repositoryRoot, "dist", false); + writeCodeMirror6RuntimeArtifacts(repositoryRoot, "dist-test/src", true); + assert.deepEqual( + findCodeMirror6RuntimeArtifactViolations(options), + [] + ); + + fs.rmSync(path.join( + repositoryRoot, + "dist/editor/CodeMirrorLegacyFileSystem.js" + )); + writeFile( + repositoryRoot, + "dist-test/src/editor/CodeMirrorLegacyText.js", + " \n" + ); + + assert.deepEqual( + findCodeMirror6RuntimeArtifactViolations(options), + [ + "dist-test/src/editor/CodeMirrorLegacyText.js " + + "(empty CM6 compatibility runtime artifact)", + "dist/editor/CodeMirrorLegacyFileSystem.js " + + "(missing required CM6 compatibility runtime artifact)" + ] + ); +}); + +test("validates minified and unminified CM6 compatibility wiring", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + const options = { + repositoryRoot, + requireReleaseLicenseCopies: true + }; + + writeCodeMirror6RuntimeArtifacts(repositoryRoot, "dist", false); + writeCodeMirror6RuntimeArtifacts(repositoryRoot, "dist-test/src", true); + writeReleaseLicenseFiles(repositoryRoot); + assert.doesNotThrow(() => assertNoCodeMirror5(options)); + + writeFile( + repositoryRoot, + "dist/utils/ExtensionLoader.js", + 'define(function(require){return require("editor/' + + 'CodeMirrorLegacyFileSystem")})' + ); + writeFile( + repositoryRoot, + "dist/editor/CodeMirrorLegacyModuleLoader.js", + 'define(function(require){require("editor/' + + 'CodeMirrorLegacyExtendedAddons");return "extended-addon"})' + ); + writeFile( + repositoryRoot, + "dist-test/src/editor/CodeMirrorLegacyModuleLoader.js", + 'define(function(){return "mode/meta extended-addon"})' + ); + + const findings = findCodeMirror6RuntimeArtifactViolations(options); + assert(findings.includes( + "dist/utils/ExtensionLoader.js " + + "(missing legacy filesystem compatibility installation)" + )); + assert(findings.includes( + "dist/editor/CodeMirrorLegacyModuleLoader.js " + + "(missing legacy mode metadata module mapping)" + )); + assert(findings.includes( + "dist-test/src/editor/CodeMirrorLegacyModuleLoader.js " + + "(missing extended addon compatibility dependency)" + )); + assert.throws( + () => assertNoCodeMirror5(options), + /CM6 release runtime:/ + ); +}); + +test("rejects direct script and stylesheet package imports", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + const packageName = "code" + "mirror"; + writeFile( + repositoryRoot, + "src/imports.js", + [ + `const first = require("${packageName}");`, + `const second = require.resolve("${packageName}/lib/codemirror");`, + `import("${packageName}");`, + `import Editor from "${packageName}";`, + `define(["${packageName}"], function (CodeMirror) {});`, + `require(["text!${packageName}/lib/codemirror.css"], function () {});` + ].join("\n") + ); + writeFile( + repositoryRoot, + "src/imports.less", + `@import "${packageName}/lib/codemirror.css";` + ); + + const findings = findCodeMirror5DirectImportViolations( + validationOptions(repositoryRoot) + ); + assert.equal(findings.length, 7); + assert(findings.some(finding => finding.includes("CommonJS require"))); + assert(findings.some(finding => finding.includes("dynamic import"))); + assert(findings.some(finding => finding.includes("static import/export"))); + assert(findings.some(finding => finding.includes("AMD dependency"))); + assert(findings.some(finding => finding.includes("stylesheet import"))); + assert.throws( + () => assertNoCodeMirror5(validationOptions(repositoryRoot)), + /CodeMirror 5 validation failed/ + ); +}); + +test("rejects legacy CodeMirror script and stylesheet loads in HTML", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "src/index.html", + [ + '
', + '', + '' + ].join("\n") + ); + writeFile( + repositoryRoot, + "src/legacy.htm", + [ + '', + '', + '' + ].join("\n") + ); + + const findings = findCodeMirror5HTMLAssetViolations( + validationOptions(repositoryRoot) + ); + assert.equal(findings.length, 3); + assert(findings.every(finding => finding.includes("src/legacy.htm"))); +}); + +test("rejects high-confidence CM5 implementation signatures only", (t) => { + const repositoryRoot = createRepository(); + t.after(() => fs.rmSync(repositoryRoot, { recursive: true, force: true })); + + writeFile( + repositoryRoot, + "src/fold-compat.js", + [ + "// CodeMirror, copyright (c) by Marijn Haverbeke and others", + "// Adapted for the Phoenix CodeMirror 6 compatibility layer.", + 'const CodeMirror = require("editor/CodeMirrorCompat");', + 'CodeMirror.defineExtension("foldCode", function () {});' + ].join("\n") + ); + const codeMirrorName = "Code" + "Mirror"; + writeFile( + repositoryRoot, + "src/vendor/editor-core.js", + `${codeMirrorName}.version = "5.65.16";` + ); + const relativeCore = ["..", "..", "lib", "codemirror"].join("/"); + writeFile( + repositoryRoot, + "src/vendor/fold-addon.js", + `define(["${relativeCore}"], function (${codeMirrorName}) {});` + ); + writeFile( + repositoryRoot, + "src/vendor/codemirror.css", + [ + ".CodeMirror-scroll {}", + ".CodeMirror-sizer {}", + ".CodeMirror-gutters {}", + ".CodeMirror-cursor {}" + ].join("\n") + ); + writeFile( + repositoryRoot, + "src/thirdparty/licences/codemirror-compat.markdown", + "CodeMirror 5 MIT attribution retained for adapted algorithms." + ); + + const options = validationOptions(repositoryRoot); + const findings = findCodeMirror5ImplementationViolations(options); + assert.equal(findings.length, 3); + assert(findings.some(finding => { + return finding.includes("CM5 runtime version assignment"); + })); + assert(findings.some(finding => { + return finding.includes("CM5 relative core dependency"); + })); + assert(findings.some(finding => { + return finding.includes("CM5 core stylesheet signature"); + })); + assert.deepEqual(findCodeMirror5ArtifactViolations(options), []); +}); diff --git a/build/validate-codemirror5.js b/build/validate-codemirror5.js new file mode 100644 index 0000000000..79be570ffa --- /dev/null +++ b/build/validate-codemirror5.js @@ -0,0 +1,1453 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/* eslint-env node */ + +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const PACKAGE_METADATA_FILE_NAMES = new Set([ + "package.json", + "package-lock.json", + "npm-shrinkwrap.json" +]); +const PACKAGE_LOCK_FILE_NAMES = new Set([ + "package-lock.json", + "npm-shrinkwrap.json" +]); +const PACKAGE_DEPENDENCY_SECTIONS = [ + "dependencies", + "devDependencies", + "optionalDependencies", + "peerDependencies" +]; +const LOCK_DEPENDENCY_SECTIONS = [ + ...PACKAGE_DEPENDENCY_SECTIONS, + "requires" +]; +const PACKAGE_SCAN_IGNORED_DIRECTORIES = new Set([ + ".git", + "node_modules" +]); +const WALK_IGNORED_DIRECTORIES = new Set([ + ".git", + "node_modules" +]); +const DEFAULT_PACKAGE_METADATA_SCAN_PATHS = [ + "package.json", + "package-lock.json", + "npm-shrinkwrap.json", + "src", + "src-mdviewer", + "src-node", + "build", + "gulpfile.js", + "phoenix-builder-mcp" +]; +const RELEASE_PACKAGE_METADATA_SCAN_PATHS = [ + ...DEFAULT_PACKAGE_METADATA_SCAN_PATHS, + "dist", + "dist-test" +]; +const CODE_FILE_EXTENSIONS = new Set([ + ".cjs", + ".css", + ".htm", + ".html", + ".js", + ".jsx", + ".less", + ".mjs", + ".scss", + ".ts", + ".tsx" +]); +const DEFAULT_CODE_SCAN_PATHS = [ + "src", + "src-mdviewer/src", + "src-node", + "build", + "gulpfile.js", + "phoenix-builder-mcp" +]; +const RELEASE_CODE_SCAN_PATHS = [ + ...DEFAULT_CODE_SCAN_PATHS, + "dist", + "dist-test" +]; +const DEFAULT_ARTIFACT_SCAN_PATHS = [ + "src" +]; +const RELEASE_ARTIFACT_SCAN_PATHS = [ + ...DEFAULT_ARTIFACT_SCAN_PATHS, + "dist", + "dist-test" +]; +const DEFAULT_INSTALLED_PACKAGE_SCAN_PATHS = [ + "node_modules", + "src", + "src-mdviewer", + "src-node", + "phoenix-builder-mcp" +]; +const RELEASE_INSTALLED_PACKAGE_SCAN_PATHS = [ + ...DEFAULT_INSTALLED_PACKAGE_SCAN_PATHS, + "dist", + "dist-test" +]; +const HTML_FILE_EXTENSIONS = new Set([ + ".htm", + ".html" +]); +const LOCKED_CODEMIRROR5_PATH_PATTERN = /(?:^|\/)node_modules\/codemirror$/; +const CODEMIRROR5_ALIAS_PATTERN = /^npm:codemirror(?:@|$)/i; +const LEGACY_VENDOR_PATH_PATTERN = + /(?:^|\/)thirdparty\/CodeMirror(?:2)?(?:\/|$)/i; +const LEGACY_LICENSE_PATH_PATTERN = + /(?:^|\/)thirdparty\/licences\/codemirror\.markdown$/i; +const CODEMIRROR5_DERIVED_LICENSE_RELATIVE_PATH = + "thirdparty/licences/codemirror5-derived.markdown"; +const CODEMIRROR5_DERIVED_LICENSE_BANNER_REFERENCE = + "thirdparty/licences/codemirror5-derived.markdown"; +const CODEMIRROR_VIM_DERIVED_LICENSE_RELATIVE_PATH = + "thirdparty/licences/codemirror-vim-derived.markdown"; +const CODEMIRROR6_BUNDLE_RELATIVE_PATH = + "thirdparty/CodeMirror6/codemirror6.js"; +const CODEMIRROR6_LICENSE_RELATIVE_PATH = + "thirdparty/licences/codemirror6.markdown"; +const CODEMIRROR6_LICENSE_BANNER_REFERENCE = + "Third-party license notices: thirdparty/licences/codemirror6.markdown."; +const CODEMIRROR5_DERIVED_LICENSE_REQUIRED_FILES = [ + "CodeMirrorCompat.js", + "CodeMirrorLegacyAddons.js", + "CodeMirrorLegacyExtendedAddons.js", + "CodeMirrorLegacyModeMeta.js", + "CodeMirrorLegacyModesCompat.js", + "CodeMirrorLegacyRSTSlimCompat.js", + "CodeMirrorSublimeCompat.js", + "CodeMirrorTwigCompat.js", + "brackets_codemirror6_legacy_themes.less", + "foldcode.js", + "foldgutter.js", + "languageFold.js" +]; +const CODEMIRROR5_DERIVED_SOURCE_RELATIVE_PATHS = [ + "editor/CodeMirrorCompat.js", + "editor/CodeMirrorLegacyAddons.js", + "editor/CodeMirrorLegacyExtendedAddons.js", + "editor/CodeMirrorLegacyModeMeta.js", + "editor/CodeMirrorLegacyModesCompat.js", + "editor/CodeMirrorLegacyRSTSlimCompat.js", + "editor/CodeMirrorSublimeCompat.js", + "editor/CodeMirrorTwigCompat.js", + "styles/brackets_codemirror6_legacy_themes.less", + "extensions/default/CodeFolding/foldhelpers/foldcode.js", + "extensions/default/CodeFolding/foldhelpers/foldgutter.js", + "extensions/default/CodeFolding/foldhelpers/languageFold.js" +]; +const CODEMIRROR6_COMPATIBILITY_RUNTIME_RELATIVE_PATHS = [ + "brackets.js", + "editor/CodeMirror6Adapter.js", + "editor/CodeMirrorCompat.js", + "editor/CodeMirrorLegacyAddons.js", + "editor/CodeMirrorLegacyExtendedAddons.js", + "editor/CodeMirrorLegacyFileSystem.js", + "editor/CodeMirrorLegacyModeMeta.js", + "editor/CodeMirrorLegacyModesCompat.js", + "editor/CodeMirrorLegacyModuleLoader.js", + "editor/CodeMirrorLegacyRSTSlimCompat.js", + "editor/CodeMirrorLegacyText.js", + "editor/CodeMirrorSublimeCompat.js", + "editor/CodeMirrorTwigCompat.js", + "editor/CodeMirrorVimCompat.js", + "editor/Editor.js", + "extensions/default/CodeFolding/foldhelpers/foldcode.js", + "extensions/default/CodeFolding/foldhelpers/foldgutter.js", + "extensions/default/CodeFolding/foldhelpers/languageFold.js", + "main.js", + "styles/brackets_codemirror6.less", + "styles/brackets_codemirror6_legacy_themes.less", + "styles/brackets_shared.less", + "thirdparty/CodeMirror6/codemirror6.js", + "utils/ExtensionLoader.js", + "utils/Global.js" +]; +const CODEMIRROR6_COMPATIBILITY_RUNTIME_SIGNATURES = { + "brackets.js": [ + { + label: "CodeMirrorCompat dependency", + expression: /["']editor\/CodeMirrorCompat["']/ + } + ], + "editor/CodeMirror6Adapter.js": [ + { + label: "CodeMirror 6 bundle dependency", + expression: /["']thirdparty\/CodeMirror6\/codemirror6["']/ + } + ], + "editor/CodeMirrorCompat.js": [ + { + label: "CodeMirror 6 bundle dependency", + expression: /["']thirdparty\/CodeMirror6\/codemirror6["']/ + }, + { + label: "legacy mode metadata dependency", + expression: /["']editor\/CodeMirrorLegacyModeMeta["']/ + }, + { + label: "legacy mode compatibility dependency", + expression: /["']editor\/CodeMirrorLegacyModesCompat["']/ + } + ], + "editor/CodeMirrorLegacyExtendedAddons.js": [ + { + label: "extended addon registry", + expression: /["']addon\/mode\/loadmode["']/ + } + ], + "editor/CodeMirrorLegacyFileSystem.js": [ + { + label: "legacy module-loader dependency", + expression: /["']editor\/CodeMirrorLegacyModuleLoader["']/ + }, + { + label: "legacy text compatibility dependency", + expression: /["']text["']/ + }, + { + label: "legacy filesystem installation marker", + expression: /["']__phoenixCodeMirrorLegacyFileSystem["']/ + } + ], + "editor/CodeMirrorLegacyModesCompat.js": [ + { + label: "RST and Slim compatibility dependency", + expression: /["']editor\/CodeMirrorLegacyRSTSlimCompat["']/ + } + ], + "editor/CodeMirrorLegacyModuleLoader.js": [ + { + label: "extended addon compatibility dependency", + expression: /["']editor\/CodeMirrorLegacyExtendedAddons["']/ + }, + { + label: "legacy mode metadata module mapping", + expression: /["']mode\/meta["']/ + }, + { + label: "extended addon module routing", + expression: /["']extended-addon["']/ + } + ], + "editor/CodeMirrorLegacyText.js": [ + { + label: "base RequireJS text-plugin dependency", + expression: /["']text-base["']/ + } + ], + "editor/CodeMirrorVimCompat.js": [ + { + label: "CodeMirror 6 bundle dependency", + expression: /["']thirdparty\/CodeMirror6\/codemirror6["']/ + } + ], + "editor/Editor.js": [ + { + label: "CodeMirror6Adapter dependency", + expression: /["']editor\/CodeMirror6Adapter["']/ + } + ], + "main.js": [ + { + label: "legacy text-plugin mapping", + expression: + /(?:["']text["']|\btext)\s*:\s*["']editor\/CodeMirrorLegacyText["']/ + }, + { + label: "historical CodeMirror core mapping", + expression: + /["']thirdparty\/CodeMirror\/lib\/codemirror["']\s*:\s*["']editor\/CodeMirrorCompat["']/ + }, + { + label: "historical CodeMirror2 core mapping", + expression: + /["']thirdparty\/CodeMirror2\/lib\/codemirror["']\s*:\s*["']editor\/CodeMirrorCompat["']/ + } + ], + "styles/brackets_codemirror6.less": [ + { + label: "CodeMirror 6 compatibility root styles", + expression: /\.CodeMirror\.phoenix-codemirror-6/ + } + ], + "styles/brackets_codemirror6_legacy_themes.less": [ + { + label: "legacy theme styles scoped to the CodeMirror 6 root", + expression: + /\.CodeMirror\.phoenix-codemirror-6\.cm-s-[a-z0-9-]+/ + } + ], + "styles/brackets_shared.less": [ + { + label: "CodeMirror 6 stylesheet import", + expression: /brackets_codemirror6\.less/ + }, + { + label: "legacy theme stylesheet import", + expression: /brackets_codemirror6_legacy_themes\.less/ + } + ], + "thirdparty/CodeMirror6/codemirror6.js": [ + { + label: "named CodeMirror 6 AMD module", + expression: + /define\s*\(\s*["']thirdparty\/CodeMirror6\/codemirror6["']/ + } + ], + "utils/ExtensionLoader.js": [ + { + label: "legacy filesystem compatibility dependency", + expression: /["']editor\/CodeMirrorLegacyFileSystem["']/ + }, + { + label: "legacy filesystem compatibility installation", + expression: + /CodeMirrorLegacyFileSystem\s*\.\s*install\s*\(\s*\)/ + } + ], + "utils/Global.js": [ + { + label: "legacy module-loader dependency", + expression: /["']editor\/CodeMirrorLegacyModuleLoader["']/ + }, + { + label: "legacy module resolution", + expression: + /CodeMirrorLegacyModuleLoader\s*\.\s*resolveLegacyModule\s*\(/ + } + ] +}; +const CODEMIRROR_VIM_DERIVED_LICENSE_REQUIRED_FILES = [ + "CodeMirrorVimCompat.js" +]; +const CODEMIRROR5_MIT_LICENSE_TEXT = [ + "Copyright (C) 2017 by Marijn Haverbeke and others", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + 'of this software and associated documentation files (the "Software"), to deal', + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR', + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." +].join("\n"); +const CODEMIRROR_VIM_MIT_LICENSE_TEXT = [ + "Copyright (C) 2018-2021 by Marijn Haverbeke and others", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + 'of this software and associated documentation files (the "Software"), to deal', + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR', + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." +].join("\n"); +const HTML_ASSET_TAG_PATTERN = + /<(?:script|link)\b[^>]*?\b(?:src|href)\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))[^>]*>/gi; +const CODEMIRROR5_RUNTIME_VERSION_PATTERN = + /\bCodeMirror(?:\.version|\[\s*["']version["']\s*\])\s*=\s*["']5(?:\.\d+){1,2}(?:[-+][^"']*)?["']/g; +const CODEMIRROR5_RELATIVE_CORE_PATTERN = + /(["'])(?:\.\.\/)+(?:lib\/)?codemirror(?:\.js)?\1/g; +const CODEMIRROR5_CORE_CSS_FILE_PATTERN = /^codemirror(?:\.min)?\.css$/i; +const CODEMIRROR5_CORE_CSS_SIGNATURES = [ + ".CodeMirror-scroll", + ".CodeMirror-sizer", + ".CodeMirror-gutters", + ".CodeMirror-cursor" +]; +const DIRECT_PACKAGE_IMPORT_PATTERNS = [ + { + label: "CommonJS require", + expression: /\brequire(?:\.resolve)?\s*\(\s*(["'])codemirror(?:\/[^"']*)?\1\s*\)/g + }, + { + label: "dynamic import", + expression: /\bimport\s*\(\s*(["'])codemirror(?:\/[^"']*)?\1\s*\)/g + }, + { + label: "static import/export", + expression: + /(? first.name.localeCompare(second.name)); + for (const entry of entries) { + if (PACKAGE_SCAN_IGNORED_DIRECTORIES.has(entry.name)) { + continue; + } + collectPackageMetadataFiles( + repositoryRoot, + path.join(absolutePath, entry.name), + findings, + visitedDirectories + ); + } +} + +function listPackageMetadataFilesFromFilesystem( + repositoryRoot, + scanPaths = DEFAULT_PACKAGE_METADATA_SCAN_PATHS +) { + const findings = new Set(); + const visitedDirectories = new Set(); + + scanPaths.forEach(relativePath => { + collectPackageMetadataFiles( + repositoryRoot, + path.resolve(repositoryRoot, relativePath), + findings, + visitedDirectories + ); + }); + + return [...findings].sort(); +} + +function listTrackedPackageMetadataFiles(repositoryRoot) { + try { + const gitRoot = execFileSync( + "git", + ["rev-parse", "--show-toplevel"], + { + cwd: repositoryRoot, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"] + } + ).trim(); + if (fs.realpathSync(gitRoot) !== fs.realpathSync(repositoryRoot)) { + return []; + } + + return execFileSync("git", ["ls-files", "-z"], { + cwd: repositoryRoot, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"] + }) + .split("\0") + .filter(Boolean) + .filter(filePath => { + return PACKAGE_METADATA_FILE_NAMES.has(path.basename(filePath)); + }) + .sort(); + } catch { + return []; + } +} + +function listProjectPackageMetadataFiles(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + if (options.packageMetadataFiles) { + return [...new Set(options.packageMetadataFiles.map(normalizePath))] + .filter(relativePath => { + return fs.existsSync(path.join(repositoryRoot, relativePath)); + }) + .sort(); + } + + const trackedFiles = options.trackedPackageMetadataFiles || + listTrackedPackageMetadataFiles(repositoryRoot); + const filesystemFiles = listPackageMetadataFilesFromFilesystem( + repositoryRoot, + options.packageMetadataScanPaths || + DEFAULT_PACKAGE_METADATA_SCAN_PATHS + ); + + return [...new Set(trackedFiles.concat(filesystemFiles).map(normalizePath))] + .filter(relativePath => { + return fs.existsSync(path.join(repositoryRoot, relativePath)); + }) + .sort(); +} + +function findDependencies(dependencyContainer, dependencySections) { + const findings = []; + + for (const section of dependencySections) { + for (const [dependencyName, dependencySpecifier] of + Object.entries(dependencyContainer?.[section] || {})) { + if (dependencyName === "codemirror" || + typeof dependencySpecifier === "string" && + CODEMIRROR5_ALIAS_PATTERN.test(dependencySpecifier.trim())) { + findings.push({ + dependencyName, + section + }); + } + } + } + + return findings; +} + +function inspectLegacyLockDependencies(dependencies, location, findings) { + if (!dependencies || typeof dependencies !== "object") { + return; + } + + if (Object.prototype.hasOwnProperty.call(dependencies, "codemirror")) { + findings.add(`${location}.codemirror`); + } + + for (const [dependencyName, dependencyDetails] of Object.entries(dependencies)) { + if (!dependencyDetails || typeof dependencyDetails !== "object") { + continue; + } + + for (const dependency of findDependencies( + dependencyDetails, + LOCK_DEPENDENCY_SECTIONS + )) { + findings.add( + `${location}.${dependencyName}.` + + `${dependency.section}.${dependency.dependencyName}` + ); + } + + inspectLegacyLockDependencies( + dependencyDetails.dependencies, + `${location}.${dependencyName}.dependencies`, + findings + ); + } +} + +function inspectPackageJSON(packageJSON, relativePath, findings) { + for (const dependency of findDependencies( + packageJSON, + PACKAGE_DEPENDENCY_SECTIONS + )) { + findings.add( + `${relativePath} ${dependency.section}.${dependency.dependencyName}` + ); + } + + for (const section of ["bundleDependencies", "bundledDependencies"]) { + if (Array.isArray(packageJSON[section]) && + packageJSON[section].includes("codemirror")) { + findings.add(`${relativePath} ${section}`); + } + } +} + +function inspectPackageLock(packageLock, relativePath, findings) { + inspectLegacyLockDependencies( + packageLock.dependencies, + `${relativePath} dependencies`, + findings + ); + + for (const [packagePath, packageDetails] of + Object.entries(packageLock.packages || {})) { + const normalizedPackagePath = normalizePath(packagePath); + const isInstalledPackage = + normalizedPackagePath.startsWith("node_modules/") || + normalizedPackagePath.includes("/node_modules/"); + if (LOCKED_CODEMIRROR5_PATH_PATTERN.test(normalizedPackagePath) || + isInstalledPackage && packageDetails?.name === "codemirror") { + findings.add( + `${relativePath} packages[${JSON.stringify(packagePath)}]` + ); + } + + for (const dependency of findDependencies( + packageDetails, + LOCK_DEPENDENCY_SECTIONS + )) { + findings.add( + `${relativePath} packages[${JSON.stringify(packagePath)}].` + + `${dependency.section}.${dependency.dependencyName}` + ); + } + } +} + +function findCodeMirror5DependencyViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const packageMetadataFiles = listProjectPackageMetadataFiles(options); + const findings = new Set(); + + for (const relativePath of packageMetadataFiles) { + const absolutePath = path.join(repositoryRoot, relativePath); + let packageMetadata; + try { + packageMetadata = JSON.parse(fs.readFileSync(absolutePath, "utf8")); + } catch (error) { + throw new Error( + `Unable to parse package metadata ${relativePath}: ` + + error.message + ); + } + + if (PACKAGE_LOCK_FILE_NAMES.has(path.basename(relativePath))) { + inspectPackageLock(packageMetadata, relativePath, findings); + } else { + inspectPackageJSON(packageMetadata, relativePath, findings); + } + } + + return [...findings].sort(); +} + +function walkPath(absolutePath, visitor, visitedDirectories) { + const stat = getTraversableStat(absolutePath); + if (!stat) { + return; + } + + const shouldDescend = visitor(absolutePath, stat); + if (shouldDescend === false || + !stat.isDirectory()) { + return; + } + if (!markDirectoryVisited(absolutePath, visitedDirectories)) { + return; + } + + const entries = fs.readdirSync(absolutePath, { withFileTypes: true }) + .sort((first, second) => first.name.localeCompare(second.name)); + for (const entry of entries) { + if (WALK_IGNORED_DIRECTORIES.has(entry.name)) { + if (entry.name === "node_modules") { + const codeMirrorPackagePath = path.join( + absolutePath, + entry.name, + "codemirror" + ); + const codeMirrorPackageStat = + getTraversableStat(codeMirrorPackagePath); + if (codeMirrorPackageStat) { + visitor( + codeMirrorPackagePath, + codeMirrorPackageStat + ); + } + } + continue; + } + walkPath( + path.join(absolutePath, entry.name), + visitor, + visitedDirectories + ); + } +} + +function findCodeMirror5ArtifactViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const artifactScanPaths = options.artifactScanPaths || + DEFAULT_ARTIFACT_SCAN_PATHS; + const findings = new Set(); + const visitedDirectories = new Set(); + + for (const relativeRoot of artifactScanPaths) { + const absoluteRoot = path.resolve(repositoryRoot, relativeRoot); + walkPath(absoluteRoot, function (absolutePath) { + const relativePath = normalizePath( + path.relative(repositoryRoot, absolutePath) + ); + if (LEGACY_VENDOR_PATH_PATTERN.test(relativePath) || + LEGACY_LICENSE_PATH_PATTERN.test(relativePath)) { + findings.add(relativePath); + return false; + } + }, visitedDirectories); + } + + return [...findings].sort(); +} + +function findCodeMirror5LicenseNoticeViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const requiredPaths = [ + path.join("src", CODEMIRROR5_DERIVED_LICENSE_RELATIVE_PATH) + ]; + const releaseRoots = [ + { + directory: "dist", + noticePath: path.join( + "dist", + CODEMIRROR5_DERIVED_LICENSE_RELATIVE_PATH + ) + }, + { + directory: "dist-test", + noticePath: path.join( + "dist-test", + "src", + CODEMIRROR5_DERIVED_LICENSE_RELATIVE_PATH + ) + } + ]; + + if (options.requireReleaseLicenseCopies) { + releaseRoots.forEach(function (releaseRoot) { + requiredPaths.push(releaseRoot.noticePath); + }); + } + + const findings = []; + requiredPaths.forEach(function (relativePath) { + const absolutePath = path.join(repositoryRoot, relativePath); + if (!fs.existsSync(absolutePath)) { + findings.push(`${normalizePath(relativePath)} (missing)`); + return; + } + + const content = fs.readFileSync(absolutePath, "utf8"); + const missingFileReferences = + CODEMIRROR5_DERIVED_LICENSE_REQUIRED_FILES.filter( + function (fileName) { + return !content.includes(fileName); + } + ); + const hasCompleteLicense = normalizeWhitespace(content).includes( + normalizeWhitespace(CODEMIRROR5_MIT_LICENSE_TEXT) + ); + if (missingFileReferences.length || !hasCompleteLicense) { + findings.push( + `${normalizePath(relativePath)} ` + + `(incomplete CodeMirror 5 MIT notice)` + ); + } + }); + + const codeRoots = ["src"]; + if (options.requireReleaseLicenseCopies) { + codeRoots.push("dist", path.join("dist-test", "src")); + } + codeRoots.forEach(function (codeRoot) { + CODEMIRROR5_DERIVED_SOURCE_RELATIVE_PATHS.forEach( + function (sourceRelativePath) { + const relativePath = path.join(codeRoot, sourceRelativePath); + const absolutePath = path.join(repositoryRoot, relativePath); + if (!fs.existsSync(absolutePath)) { + findings.push( + `${normalizePath(relativePath)} ` + + "(missing CodeMirror 5-derived source)" + ); + return; + } + + const content = fs.readFileSync(absolutePath, "utf8"); + const hasPreservedBanner = content.includes("DONT_STRIP_MINIFY"); + const hasLicenseReference = + content.includes(CODEMIRROR5_DERIVED_LICENSE_BANNER_REFERENCE) || + normalizeWhitespace(content).includes( + normalizeWhitespace(CODEMIRROR5_MIT_LICENSE_TEXT) + ); + if (!hasPreservedBanner || !hasLicenseReference) { + findings.push( + `${normalizePath(relativePath)} ` + + "(missing preserved CodeMirror 5-derived notice)" + ); + } + } + ); + }); + + return findings.sort(); +} + +function findCodeMirrorVimLicenseNoticeViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const requiredPaths = [ + path.join("src", CODEMIRROR_VIM_DERIVED_LICENSE_RELATIVE_PATH) + ]; + const releaseRoots = [ + path.join("dist", CODEMIRROR_VIM_DERIVED_LICENSE_RELATIVE_PATH), + path.join( + "dist-test", + "src", + CODEMIRROR_VIM_DERIVED_LICENSE_RELATIVE_PATH + ) + ]; + + if (options.requireReleaseLicenseCopies) { + requiredPaths.push(...releaseRoots); + } + + const findings = []; + requiredPaths.forEach(function (relativePath) { + const absolutePath = path.join(repositoryRoot, relativePath); + if (!fs.existsSync(absolutePath)) { + findings.push(`${normalizePath(relativePath)} (missing)`); + return; + } + + const content = fs.readFileSync(absolutePath, "utf8"); + const missingFileReferences = + CODEMIRROR_VIM_DERIVED_LICENSE_REQUIRED_FILES.filter( + function (fileName) { + return !content.includes(fileName); + } + ); + const hasCompleteLicense = normalizeWhitespace(content).includes( + normalizeWhitespace(CODEMIRROR_VIM_MIT_LICENSE_TEXT) + ); + if (missingFileReferences.length || !hasCompleteLicense) { + findings.push( + `${normalizePath(relativePath)} ` + + "(incomplete @replit CodeMirror Vim MIT notice)" + ); + } + }); + + return findings.sort(); +} + +function findCodeMirror6ReleaseLicenseViolations(options = {}) { + if (!options.requireReleaseLicenseCopies) { + return []; + } + + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const sourceLicensePath = path.join( + repositoryRoot, + "src", + CODEMIRROR6_LICENSE_RELATIVE_PATH + ); + const findings = []; + let sourceLicenseContent; + + if (!fs.existsSync(sourceLicensePath)) { + findings.push( + `src/${CODEMIRROR6_LICENSE_RELATIVE_PATH} (missing)` + ); + } else { + sourceLicenseContent = fs.readFileSync(sourceLicensePath, "utf8"); + } + + const releaseRoots = [ + "dist", + path.join("dist-test", "src") + ]; + releaseRoots.forEach(function (releaseRoot) { + const bundleRelativePath = path.join( + releaseRoot, + CODEMIRROR6_BUNDLE_RELATIVE_PATH + ); + const bundlePath = path.join(repositoryRoot, bundleRelativePath); + if (!fs.existsSync(bundlePath)) { + findings.push(`${normalizePath(bundleRelativePath)} (missing)`); + } else if (!fs.readFileSync(bundlePath, "utf8").includes( + CODEMIRROR6_LICENSE_BANNER_REFERENCE + )) { + findings.push( + `${normalizePath(bundleRelativePath)} ` + + "(missing CodeMirror 6 license notice reference)" + ); + } + + const licenseRelativePath = path.join( + releaseRoot, + CODEMIRROR6_LICENSE_RELATIVE_PATH + ); + const licensePath = path.join(repositoryRoot, licenseRelativePath); + if (!fs.existsSync(licensePath)) { + findings.push(`${normalizePath(licenseRelativePath)} (missing)`); + } else if (sourceLicenseContent !== undefined && + fs.readFileSync(licensePath, "utf8") !== sourceLicenseContent) { + findings.push( + `${normalizePath(licenseRelativePath)} ` + + "(does not match generated source notice)" + ); + } + }); + + return findings.sort(); +} + +function findCodeMirror6RuntimeArtifactViolations(options = {}) { + if (!options.requireReleaseLicenseCopies) { + return []; + } + + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const releaseRoots = [ + "dist", + path.join("dist-test", "src") + ]; + const findings = []; + + releaseRoots.forEach(function (releaseRoot) { + CODEMIRROR6_COMPATIBILITY_RUNTIME_RELATIVE_PATHS.forEach( + function (runtimeRelativePath) { + const relativePath = path.join( + releaseRoot, + runtimeRelativePath + ); + const absolutePath = path.join(repositoryRoot, relativePath); + if (!fs.existsSync(absolutePath) || + !fs.statSync(absolutePath).isFile()) { + findings.push( + `${normalizePath(relativePath)} ` + + "(missing required CM6 compatibility runtime artifact)" + ); + return; + } + + const content = fs.readFileSync(absolutePath, "utf8"); + if (!content.trim()) { + findings.push( + `${normalizePath(relativePath)} ` + + "(empty CM6 compatibility runtime artifact)" + ); + return; + } + + const signatures = + CODEMIRROR6_COMPATIBILITY_RUNTIME_SIGNATURES[ + runtimeRelativePath + ] || []; + signatures.forEach(function (signature) { + if (!signature.expression.test(content)) { + findings.push( + `${normalizePath(relativePath)} ` + + `(missing ${signature.label})` + ); + } + }); + } + ); + }); + + return findings.sort(); +} + +function lineNumberAt(content, index) { + return content.slice(0, index).split("\n").length; +} + +function visitScannableFiles(repositoryRoot, scanPaths, visitor) { + const visitedDirectories = new Set(); + + for (const relativeRoot of scanPaths) { + const absoluteRoot = path.resolve(repositoryRoot, relativeRoot); + walkPath(absoluteRoot, function (absolutePath, stat) { + if (!stat.isFile() || + !CODE_FILE_EXTENSIONS.has(path.extname(absolutePath))) { + return; + } + visitor( + absolutePath, + normalizePath(path.relative(repositoryRoot, absolutePath)) + ); + }, visitedDirectories); + } +} + +function findCodeMirror5DirectImportViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const codeScanPaths = options.codeScanPaths || DEFAULT_CODE_SCAN_PATHS; + const findings = new Set(); + + visitScannableFiles( + repositoryRoot, + codeScanPaths, + function (absolutePath, relativePath) { + const content = fs.readFileSync(absolutePath, "utf8"); + for (const pattern of DIRECT_PACKAGE_IMPORT_PATTERNS) { + pattern.expression.lastIndex = 0; + let match; + while ((match = pattern.expression.exec(content))) { + findings.add( + `${relativePath}:${lineNumberAt(content, match.index)} ` + + pattern.label + ); + } + } + } + ); + + return [...findings].sort(); +} + +function normalizeAssetReference(reference) { + let normalizedReference = String(reference || "") + .replaceAll("\\", "/") + .split(/[?#]/, 1)[0]; + try { + normalizedReference = decodeURIComponent(normalizedReference); + } catch { + // A malformed URL is not evidence of a CodeMirror dependency. + } + return normalizedReference.toLowerCase(); +} + +function isCodeMirror5AssetReference(reference) { + const normalizedReference = normalizeAssetReference(reference); + return /(?:^|\/)thirdparty\/codemirror(?:2)?(?:\/|$)/.test(normalizedReference) || + /(?:^|\/)codemirror@5(?:\.\d+){0,2}(?:\/|$)/.test(normalizedReference) || + /(?:^|\/)codemirror\/5(?:\.\d+){0,2}(?:\/|$)/.test(normalizedReference) || + /(?:^|\/)(?:node_modules\/)?codemirror\/(?:lib|addon|mode|theme|keymap)\//.test( + normalizedReference + ); +} + +function findCodeMirror5HTMLAssetViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const codeScanPaths = options.codeScanPaths || DEFAULT_CODE_SCAN_PATHS; + const findings = new Set(); + + visitScannableFiles( + repositoryRoot, + codeScanPaths, + function (absolutePath, relativePath) { + if (!HTML_FILE_EXTENSIONS.has(path.extname(absolutePath))) { + return; + } + + const content = fs.readFileSync(absolutePath, "utf8"); + HTML_ASSET_TAG_PATTERN.lastIndex = 0; + let match; + while ((match = HTML_ASSET_TAG_PATTERN.exec(content))) { + const assetReference = match[1] || match[2] || match[3]; + if (isCodeMirror5AssetReference(assetReference)) { + findings.add( + `${relativePath}:${lineNumberAt(content, match.index)} ` + + assetReference + ); + } + } + } + ); + + return [...findings].sort(); +} + +function findCodeMirror5ImplementationViolations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const codeScanPaths = options.codeScanPaths || DEFAULT_CODE_SCAN_PATHS; + const findings = new Set(); + + visitScannableFiles( + repositoryRoot, + codeScanPaths, + function (absolutePath, relativePath) { + const content = fs.readFileSync(absolutePath, "utf8"); + const signatures = [ + { + label: "CM5 runtime version assignment", + expression: CODEMIRROR5_RUNTIME_VERSION_PATTERN + }, + { + label: "CM5 relative core dependency", + expression: CODEMIRROR5_RELATIVE_CORE_PATTERN + } + ]; + + signatures.forEach(signature => { + signature.expression.lastIndex = 0; + let match; + while ((match = signature.expression.exec(content))) { + findings.add( + `${relativePath}:${lineNumberAt(content, match.index)} ` + + signature.label + ); + } + }); + + if (CODEMIRROR5_CORE_CSS_FILE_PATTERN.test( + path.basename(absolutePath) + ) && CODEMIRROR5_CORE_CSS_SIGNATURES.every(signature => { + return content.includes(signature); + })) { + findings.add(`${relativePath}:1 CM5 core stylesheet signature`); + } + } + ); + + return [...findings].sort(); +} + +function isCodeMirror5InstalledPackage(packageMetadata, packageDirectory) { + if (packageMetadata?.name !== "codemirror") { + return path.basename(packageDirectory) === "codemirror" && + !packageMetadata?.name; + } + + const version = String(packageMetadata.version || ""); + const majorVersionMatch = /^v?(\d+)(?:\.|$)/.exec(version); + return !majorVersionMatch || Number(majorVersionMatch[1]) === 5; +} + +function inspectInstalledPackage( + repositoryRoot, + packageDirectory, + findings, + seenNodeModules +) { + const packageJSONPath = path.join(packageDirectory, "package.json"); + let packageMetadata; + if (fs.existsSync(packageJSONPath)) { + try { + packageMetadata = JSON.parse(fs.readFileSync(packageJSONPath, "utf8")); + } catch (error) { + if (path.basename(packageDirectory) === "codemirror") { + findings.add( + `${normalizePath(path.relative(repositoryRoot, packageDirectory))} ` + + `(unreadable package.json: ${error.message})` + ); + } + } + } + + if (isCodeMirror5InstalledPackage(packageMetadata, packageDirectory)) { + const version = packageMetadata?.version || "unknown"; + findings.add( + `${normalizePath(path.relative(repositoryRoot, packageDirectory))} ` + + `(name=codemirror, version=${version})` + ); + } + + inspectNodeModulesDirectory( + repositoryRoot, + path.join(packageDirectory, "node_modules"), + findings, + seenNodeModules + ); +} + +function inspectNodeModulesDirectory( + repositoryRoot, + nodeModulesDirectory, + findings, + seenNodeModules +) { + if (!fs.existsSync(nodeModulesDirectory)) { + return; + } + + const realNodeModulesDirectory = fs.realpathSync(nodeModulesDirectory); + if (seenNodeModules.has(realNodeModulesDirectory)) { + return; + } + seenNodeModules.add(realNodeModulesDirectory); + + for (const entry of fs.readdirSync(nodeModulesDirectory, { + withFileTypes: true + })) { + if (!entry.isDirectory() && !entry.isSymbolicLink()) { + continue; + } + + const packageDirectory = path.join(nodeModulesDirectory, entry.name); + if (entry.name === ".pnpm") { + for (const storeEntry of fs.readdirSync(packageDirectory, { + withFileTypes: true + })) { + if (storeEntry.isDirectory()) { + inspectNodeModulesDirectory( + repositoryRoot, + path.join( + packageDirectory, + storeEntry.name, + "node_modules" + ), + findings, + seenNodeModules + ); + } + } + continue; + } + if (entry.name.startsWith(".")) { + continue; + } + if (entry.name.startsWith("@")) { + for (const scopedEntry of fs.readdirSync(packageDirectory, { + withFileTypes: true + })) { + if (scopedEntry.isDirectory() || scopedEntry.isSymbolicLink()) { + inspectInstalledPackage( + repositoryRoot, + path.join(packageDirectory, scopedEntry.name), + findings, + seenNodeModules + ); + } + } + continue; + } + + inspectInstalledPackage( + repositoryRoot, + packageDirectory, + findings, + seenNodeModules + ); + } +} + +function findNodeModulesDirectories( + absolutePath, + callback, + visitedDirectories +) { + const stat = getTraversableStat(absolutePath); + if (!stat) { + return; + } + + if (!stat.isDirectory() || + path.basename(absolutePath) === "node_modules") { + if (stat.isDirectory()) { + callback(absolutePath); + } + return; + } + if (!markDirectoryVisited(absolutePath, visitedDirectories)) { + return; + } + + const entries = fs.readdirSync(absolutePath, { withFileTypes: true }) + .sort((first, second) => first.name.localeCompare(second.name)); + for (const entry of entries) { + if (entry.name === ".git" || + !entry.isDirectory() && !entry.isSymbolicLink()) { + continue; + } + findNodeModulesDirectories( + path.join(absolutePath, entry.name), + callback, + visitedDirectories + ); + } +} + +function findInstalledCodeMirror5Violations(options = {}) { + const repositoryRoot = path.resolve(options.repositoryRoot || "."); + const installedPackageScanPaths = options.installedPackageScanPaths || + DEFAULT_INSTALLED_PACKAGE_SCAN_PATHS; + const findings = new Set(); + const seenNodeModules = new Set(); + const visitedDirectories = new Set(); + + installedPackageScanPaths.forEach(relativePath => { + findNodeModulesDirectories( + path.resolve(repositoryRoot, relativePath), + nodeModulesDirectory => { + inspectNodeModulesDirectory( + repositoryRoot, + nodeModulesDirectory, + findings, + seenNodeModules + ); + }, + visitedDirectories + ); + }); + + return [...findings].sort(); +} + +function assertNoCodeMirror5Dependencies(options = {}) { + const findings = findCodeMirror5DependencyViolations(options); + if (findings.length) { + throw new Error( + 'CodeMirror 5 dependency "codemirror" is not allowed in ' + + "package metadata:\n" + + findings.map(finding => `- ${finding}`).join("\n") + ); + } +} + +function assertNoCodeMirror5(options = {}) { + const scanOptions = addReleaseScanPaths(options); + const findings = [ + ...findCodeMirror5DependencyViolations(scanOptions).map(finding => { + return `package metadata: ${finding}`; + }), + ...findInstalledCodeMirror5Violations(scanOptions).map(finding => { + return `installed package: ${finding}`; + }), + ...findCodeMirror5ArtifactViolations(scanOptions).map(finding => { + return `legacy artifact: ${finding}`; + }), + ...findCodeMirror5LicenseNoticeViolations(scanOptions).map(finding => { + return `license notice: ${finding}`; + }), + ...findCodeMirrorVimLicenseNoticeViolations(scanOptions).map(finding => { + return `Vim license notice: ${finding}`; + }), + ...findCodeMirror6ReleaseLicenseViolations(scanOptions).map(finding => { + return `release license: ${finding}`; + }), + ...findCodeMirror6RuntimeArtifactViolations(scanOptions).map(finding => { + return `CM6 release runtime: ${finding}`; + }), + ...findCodeMirror5DirectImportViolations(scanOptions).map(finding => { + return `direct package import: ${finding}`; + }), + ...findCodeMirror5HTMLAssetViolations(scanOptions).map(finding => { + return `legacy HTML asset: ${finding}`; + }), + ...findCodeMirror5ImplementationViolations(scanOptions).map(finding => { + return `CM5 implementation signature: ${finding}`; + }) + ]; + + if (findings.length) { + throw new Error( + "CodeMirror 5 validation failed:\n" + + findings.map(finding => `- ${finding}`).join("\n") + ); + } +} + +module.exports = { + CODEMIRROR6_COMPATIBILITY_RUNTIME_RELATIVE_PATHS, + assertNoCodeMirror5, + assertNoCodeMirror5Dependencies, + findCodeMirror5ArtifactViolations, + findCodeMirror5DependencyViolations, + findCodeMirror5DirectImportViolations, + findCodeMirror5HTMLAssetViolations, + findCodeMirror5ImplementationViolations, + findCodeMirror5LicenseNoticeViolations, + findCodeMirrorVimLicenseNoticeViolations, + findCodeMirror6ReleaseLicenseViolations, + findCodeMirror6RuntimeArtifactViolations, + findInstalledCodeMirror5Violations, + listProjectPackageMetadataFiles, + listTrackedPackageMetadataFiles +}; diff --git a/docs/API-Reference/editor/Editor.md b/docs/API-Reference/editor/Editor.md index e139e3fbb8..a506278a74 100644 --- a/docs/API-Reference/editor/Editor.md +++ b/docs/API-Reference/editor/Editor.md @@ -62,12 +62,15 @@ const Editor = brackets.getModule("editor/Editor") * [.clearAllMarks([markType], [lineNumbers])](#Editor+clearAllMarks) * [.isSamePosition(position1, position2)](#Editor+isSamePosition) ⇒ boolean * [.getHistory()](#Editor+getHistory) ⇒ Array - * [.setHistory()](#Editor+setHistory) + * [.setHistory(history)](#Editor+setHistory) + * [.isClean([generation])](#Editor+isClean) ⇒ boolean + * [.markClean()](#Editor+markClean) ⇒ number + * [.getEditorEngine()](#Editor+getEditorEngine) ⇒ "codemirror6" * [.createHistoryRestorePoint(restorePointName)](#Editor+createHistoryRestorePoint) * [.restoreHistoryPoint(restorePointName)](#Editor+restoreHistoryPoint) * [.setSelection(start, [end], [center], [centerOptions], [origin])](#Editor+setSelection) - * [.replaceSelection(replacement, [select])](#Editor+replaceSelection) - * [.replaceSelections(replacement, [select])](#Editor+replaceSelections) + * [.replaceSelection(replacement, [select], [origin])](#Editor+replaceSelection) + * [.replaceSelections(replacement, [select], [origin])](#Editor+replaceSelections) * [.replaceRange(replacement, from, [to], origin)](#Editor+replaceRange) * [.replaceMultipleRanges(ranges, [origin])](#Editor+replaceMultipleRanges) * [.clearSelection()](#Editor+clearSelection) @@ -258,13 +261,13 @@ The Document we're bound to ### editor.getInlineWidgetsBelowCursor() ⇒ boolean Gets the inline widgets below the current cursor position or null. -**Kind**: instance method of [Editor](#Editor) +**Kind**: instance method of [Editor](#Editor) ### editor.canConsumeEscapeKeyEvent() returns true if the editor can do something an escape key event. Eg. Disable multi cursor escape -**Kind**: instance method of [Editor](#Editor) +**Kind**: instance method of [Editor](#Editor) ### editor.destroy() @@ -272,7 +275,7 @@ Removes this editor from the DOM and detaches from the Document. If this is the Editor that is secretly providing the Document's backing state, then the Document reverts to a read-only string-backed mode. -**Kind**: instance method of [Editor](#Editor) +**Kind**: instance method of [Editor](#Editor) ### editor.selectAllNoScroll() @@ -762,12 +765,40 @@ Get a (JSON-serializable) representation of the undo history. **Returns**: Array - The history of the editor. -### editor.setHistory() +### editor.setHistory(history) Replace the editor's undo history with the one provided, which must be a value as returned by getHistory. Note that this will have entirely undefined results if the editor content isn't also the same as it was when getHistory was called. **Kind**: instance method of [Editor](#Editor) + +| Param | Type | Description | +| --- | --- | --- | +| history | Object | A history object returned by getHistory(). | + + + +### editor.isClean([generation]) ⇒ boolean +Returns whether the editor's current history generation is clean. + +**Kind**: instance method of [Editor](#Editor) + +| Param | Type | Description | +| --- | --- | --- | +| [generation] | number | Optional generation returned by changeGeneration(). | + + + +### editor.markClean() ⇒ number +Marks the current history generation as clean. + +**Kind**: instance method of [Editor](#Editor) + + +### editor.getEditorEngine() ⇒ "codemirror6" +Returns the active editing-surface backend. + +**Kind**: instance method of [Editor](#Editor) ### editor.createHistoryRestorePoint(restorePointName) @@ -811,7 +842,7 @@ making the selection -### editor.replaceSelection(replacement, [select]) +### editor.replaceSelection(replacement, [select], [origin]) Replace the selection with the given string. **Kind**: instance method of [Editor](#Editor) @@ -820,10 +851,11 @@ Replace the selection with the given string. | --- | --- | --- | | replacement | string | the text to replace the current selection | | [select] | string | The optional select argument can be used to change selection. Passing "around" will cause the new text to be selected, passing "start" will collapse the selection to the start of the inserted text. | +| [origin] | string | An optional edit origin passed to change events and used for history grouping. | -### editor.replaceSelections(replacement, [select]) +### editor.replaceSelections(replacement, [select], [origin]) Replaces the content of multiple selections with the strings in the array. The length of the given array should be the same as the number of active selections. @@ -833,6 +865,7 @@ array should be the same as the number of active selections. | --- | --- | --- | | replacement | Array.<string> | the text array to replace the current selections with | | [select] | string | The optional select argument can be used to change selection. Passing "around" will cause the new text to be selected, passing "start" will collapse the selection to the start of the inserted text. | +| [origin] | string | An optional edit origin passed to change events and used for history grouping. | diff --git a/docs/API-Reference/language/CSSUtils.md b/docs/API-Reference/language/CSSUtils.md index df76ffeb94..1ed6d8ccd3 100644 --- a/docs/API-Reference/language/CSSUtils.md +++ b/docs/API-Reference/language/CSSUtils.md @@ -58,12 +58,11 @@ Returns a context info object for the given cursor position | editor | Editor | | | constPos | Object | A CM pos (likely from editor.getCursorPos()) | - + -### getInfoAtPos.\_contextCM -We will use this CM to cook css context in case of style attribute value -as CM in htmlmixed mode doesn't yet identify this as css context. We provide -a no-op display function to run CM without a DOM head. +### getInfoAtPos.contextCM +Use a detached CM6-backed compatibility editor to compute CSS +context for a style attribute without adding another visible editor. **Kind**: inner property of [getInfoAtPos](#getInfoAtPos) diff --git a/docs/API-Reference/language/LanguageManager.md b/docs/API-Reference/language/LanguageManager.md index 04ed0f51bc..fbc9d3157e 100644 --- a/docs/API-Reference/language/LanguageManager.md +++ b/docs/API-Reference/language/LanguageManager.md @@ -296,5 +296,5 @@ Defines a language. | definition.fileNames | Array.<string> | List of exact file names (e.g. ["Makefile"] or ["package.json]). Higher precedence than file extension. | | definition.blockComment | Array.<string> | Array with two entries defining the block comment prefix and suffix (e.g. ["< !--", "-->"]) | | definition.lineComment | string \| Array.<string> | Line comment prefixes (e.g. "//" or ["//", "#"]) | -| definition.mode | string \| Array.<string> | CodeMirror mode (e.g. "htmlmixed"), optionally with a MIME mode defined by that mode ["clike", "text/x-c++src"] Unless the mode is located in thirdparty/CodeMirror/mode/"name"/"name".js, you need to first load it yourself. | +| definition.mode | string \| Array.<string> | CodeMirror-compatible mode (e.g. "htmlmixed"), optionally with a MIME mode defined by that mode ["clike", "text/x-c++src"]. Custom modes must be registered before defining the language. | diff --git a/docs/CodeMirror6-Migration-Plan.md b/docs/CodeMirror6-Migration-Plan.md new file mode 100644 index 0000000000..42bcda76d1 --- /dev/null +++ b/docs/CodeMirror6-Migration-Plan.md @@ -0,0 +1,392 @@ +# CodeMirror 6 Editing Surface Migration + +Status: CM6-only architecture implemented on +`codemirror-6-editor-surface`. The authoritative Phoenix Builder/Xvfb matrix +passes all five supported categories: 4,811/4,811. Clean development and +production releases, post-build runtime inspection, installed-extension +smokes, Chromium native smoke (11/11), Firefox native smoke (10/10), and the +Electron and Tauri source-runtime theme/editor smokes all pass. + +## Required outcome + +Phoenix must have one editing engine and one live text model: CodeMirror 6. +The migration is complete only when full editors, inline editors, secondary +editors, and document-owner editor instances all use `EditorView.state.doc`. + +Compatibility with Phoenix's historical editor API is provided by Phoenix +code, not by retaining CodeMirror 5. `CodeMirrorCompat` is an API facade over +CM6 and `CodeMirror6Adapter`; it is not a second editor implementation. + +## Non-negotiable zero-CM5 invariant + +The finished tree and every generated distribution must satisfy all of these: + +- no `codemirror` version 5 package in any dependency section; +- no CodeMirror 5 entry in a tracked project manifest or lockfile, including + `package.json`, `package-lock.json`, and shipped subproject build inputs; +- no original CodeMirror 5 JavaScript, CSS, mode, addon, keymap, theme, or + vendor-tree file; +- no generated `thirdparty/CodeMirror` vendor directory in source, test, + cache, or release artifacts; +- no hidden or detached CodeMirror 5 document used for text storage, + tokenization, history, selections, markers, or language services; +- no CM5 initialization fallback if CM6 loading or editor creation fails; +- no build step that downloads, copies, restores, or embeds CM5 runtime + assets; +- no runtime branch that selects between CM5 and CM6; +- no extension shim that executes CM5 code. + +Historical names such as `CodeMirror`, `_codeMirror`, `CodeMirror-*` CSS +classes, and old RequireJS module IDs may remain only as compatibility +identifiers backed entirely by CM6. In particular, `.CodeMirror-*` classes are +recreated on the CM6 DOM rooted at `.CodeMirror.phoenix-codemirror-6`; they are +not evidence of a CM5 runtime. + +CM6-backed compatibility ports may retain historical attribution and +non-runtime license notices. They are not a CM5 engine, package, original +vendor asset, or runtime dependency. + +## Runtime architecture + +```text +Phoenix Editor / Document / extensions + | + | Phoenix and legacy-shaped editor contracts + v + CodeMirror6Adapter + CodeMirrorCompat + | + | CM6 transactions, state, views, syntax, decorations + v + thirdparty/CodeMirror6/codemirror6 (one AMD bundle) +``` + +### CM6 bundle + +`build/codemirror6-entry.js` exports the required CM6, Lezer, language, search, +lint, autocomplete, command, and legacy-stream-mode APIs. + +`build/build-codemirror6.mjs` creates one named AMD module: + +`thirdparty/CodeMirror6/codemirror6` + +The bundle task must: + +- reject a CM5 `codemirror` dependency in tracked project manifests and npm + lockfiles; +- remove a stale generated `src/thirdparty/CodeMirror` directory; +- emit one self-contained chunk with no external or dynamic imports; +- verify singleton CM6/Lezer packages resolve from one package root; +- reject duplicate package copies; +- regenerate the aggregate CM6 license notice. + +The current bundle is 2792 KiB (2,859,015 bytes) and contains 30 runtime +packages. Its generated license notice contains the exact installed version +and license text for every bundled package. Production minification preserves +the `DONT_STRIP_MINIFY` notice that points to that aggregate license file. + +`npm run build:codemirror6` regenerates the bundle, source map, and aggregate +license, but it does not regenerate `src/cacheManifest.json`. A normal Gulp +build must follow it before source-serving validation. Both release tasks +invoke the bundle task themselves. + +Release validation unions tracked package metadata with on-disk source and +shipped build inputs, including an optional Phoenix Pro checkout. It also +checks installed package identities, generated vendor trees, direct +npm-package imports, HTML script and stylesheet loads, and high-confidence +CM5 implementation signatures. The reusable +`build/validate-codemirror5.js` gate runs after source and debug builds and +again after development, staging, and production release artifacts are +assembled. Intentional compatibility module IDs, DOM classes, attributed +CM6-backed compatibility ports, and their non-runtime license notices are not +treated as dependencies. +Untracked user-owned workspace files are outside the branch deliverable and +must not be modified merely to satisfy this audit. + +### `CodeMirror6Adapter` + +`Editor` always constructs `CodeMirror6Adapter`, which owns the CM6 +`EditorView`. The adapter translates between CM6 state and the established +Phoenix contracts for: + +- document text, line and position APIs, and line-ending behavior; +- transactions, change origins, operation batching, and event ordering; +- single, multiple, reversed, and primary selections; +- undo/redo, selection history, clean generations, and history restore points; +- markers, bookmarks, collapsed/replaced ranges, and mapped marker history; +- line handles, line classes, line widgets, gutters, and folding; +- options, keymaps, commands, focus, clipboard, drag/drop, and overwrite mode; +- coordinates, viewport, scrolling, sizing, and refresh behavior; +- modes, token boundaries, parser state, mixed languages, helpers, and + overlays; +- full, inline, range-limited, secondary, and detached document-owner + editors. + +CM6 state is authoritative. Compatibility metadata may describe legacy +behavior, but it must never mirror text into a second editor model. +`CodeMirrorCompat.version = "5.65.16"` is extension-contract metadata, not the +loaded engine version. The facade also reports `backend: "codemirror6"` and +`isCodeMirror6: true`. + +### `CodeMirrorCompat` + +`CodeMirrorCompat` supplies the callable constructor and static API shape used +by Phoenix and existing extensions. It lazily resolves `CodeMirror6Adapter` to +avoid a RequireJS initialization cycle; the lazy lookup must not use a literal +static adapter dependency that RequireJS can pre-scan. + +The facade owns legacy-shaped registries and utilities such as: + +- `commands`, `keyMap`, options, extensions, and document extensions; +- `defineMode`, `defineMIME`, `getMode`, `resolveMode`, and mode state helpers; +- `StringStream`, `Pos`, position utilities, and event helpers; +- helper registration and lookup; +- multiplex and simple-mode compatibility; +- tag matching, fold helpers, and built-in stream-mode metadata. + +Every implementation delegates to CM6 primitives, Phoenix compatibility code, +or CM6 legacy stream modes. No facade API may import or execute CM5. + +## Extension import compatibility + +Existing extensions may continue to obtain the compatibility facade through: + +- `require("editor/CodeMirrorCompat")`; +- `brackets.getModule("editor/CodeMirrorCompat")`; +- the mapped historical core IDs + `thirdparty/CodeMirror/lib/codemirror`, + `thirdparty/CodeMirror2/lib/codemirror`; +- the historical root IDs `thirdparty/CodeMirror` and + `thirdparty/CodeMirror2`. + +The production and test RequireJS configurations map the two historical +`lib/codemirror` IDs to `editor/CodeMirrorCompat`. +`CodeMirrorLegacyModuleLoader` intercepts the historical root, addon, mode, +keymap, theme, and text-resource IDs before RequireJS attempts a network load. +These are virtual compatibility modules only; there is no file at an old +vendor path. + +Extensions may use the supported legacy-shaped instance/static API exposed by +the facade and may continue requesting supported historical addon, mode, +keymap, theme, CSS, and text-resource IDs. `CodeMirrorLegacyModuleLoader` +resolves them to CM6-backed virtual modules before any network load; no old +vendor file or CM5 code executes. + +The audited compatibility surface covers 62/62 addon and keymap JavaScript +paths, 121/121 mode paths, and all legacy CSS and theme paths. Theme imports +are virtual no-ops because Phoenix applies themes through CM6. Unsupported +module and resource IDs fail explicitly instead of falling back to CM5. +Phoenix Pro's retained historical module IDs have been runtime-verified to +resolve to the compatibility facade. + +The downloaded extension-registry cache may contain metadata describing a +third-party extension's CM5 development dependency. That catalog text is not +installed, executed, or bundled by Phoenix and is not a Phoenix CM5 +dependency. + +Direct access to `thirdparty/CodeMirror6/codemirror6` is reserved for tightly +scoped Phoenix integrations that need native CM6 types, such as syntax-tree +folding. General extensions should use the Phoenix `Editor` API first and +`CodeMirrorCompat` only where the historical extension contract requires it. + +## Compatibility contracts + +### Changes and documents + +Phoenix change records retain this shape: + +```js +{ + from: { line, ch }, + to: { line, ch }, + text: [line, ...], + removed: [line, ...], + origin: "..." +} +``` + +The adapter must preserve change origins, before-change filtering, operation +batching, master/secondary synchronization, dirty-state transitions, and +synchronous notification order. A secondary editor must not echo a mapped +change back into the document. + +### History + +- `getHistory()`, `setHistory()`, `historySize()`, `clearHistory()`, + `undo()`, `redo()`, `undoSelection()`, and `redoSelection()` retain their + observable CM5-era contract. +- Every recorded text change has the surrounding selection history needed by + Phoenix history restore points, including edits whose final selection + offsets are unchanged. +- `changeGeneration(true)` closes the current merge group. +- Undoing to the saved generation makes the document clean; redoing away from + it makes the document dirty. +- Marker locations and visibility round-trip with undo and redo. + +### Selections + +Phoenix selections remain sorted, non-overlapping ranges: + +```js +{ + start: { line, ch }, + end: { line, ch }, + reversed: false, + primary: true +} +``` + +Cursor association, primary-range identity, multi-range replacement, movement +units, and selection mapping through edits must match the existing Editor API. + +### Markers, gutters, and layout + +Marker and bookmark handles retain `clear()`, `find()`, `changed()`, `on()`, +and `off()`. The CM6 implementation must also preserve collapsed/replacement +ranges, line classes, widgets, registered gutters, original marker-node +identity, gutter mouse arguments, coordinate conversion, viewport events, and +programmatic scrolling. + +### Languages and tokens + +Registered Phoenix MIME and mode names resolve through `CodeMirrorCompat`. +Native CM6 language packages are used where available; CM6 legacy stream modes +cover the remaining registered languages. Token boundaries, token class names, +parser-state copies, inner modes, comments, indentation, folding, and mixed +HTML/CSS/JavaScript behavior remain extension-compatible. + +## Validation matrix + +Only completed, repeatable results are marked verified. A partial or currently +failing suite is not recorded with a stale pass count. + +| Area | Required validation | Current status | +| --- | --- | --- | +| CM6 contract | `Editor Surface Conformance` | Verified: 74/74 | +| Compatibility facade | `CodeMirrorCompatParity` and legacy-addon suites | Verified: 27/27 and 10/10 | +| Legacy language/keymap compatibility | Twig and Vim suites | Verified: 6/6 and 5/5 | +| CSS language compatibility | `CSS Parsing` | Verified: 53/53 | +| Core editor API | Full `Editor` suite | Verified: 246/246 | +| Unit category | Complete supported unit category | Verified on final tree: 2905/2905 | +| Integration category | Complete supported integration category | Verified on final tree: 833/833 | +| Legacy integration category | Complete supported legacy category | Verified on final tree: 510/510 | +| Inline editors | `LegacyInteg:InlineEditorProviders` | Verified: 55/55 after line-widget focus restoration | +| Extension loading | Current and legacy loader suites | Verified: 16/16 and 2/2 | +| Languages | Registered language resolution in the live runtime | Verified: 51/51 | +| Document, command, search, folding, hint, and lifecycle coverage | Owning unit/integration suites | Covered by the complete passing category runs | +| Integrated extensions | Historical import virtualization and representative installed extensions | Verified across the complete import surface, Code By Code, and brackets-compare; no CM5 resource loaded | +| Live preview | Full `livepreview` category | Verified on final tree: 257/257 | +| Main view | Full `mainview` category | Verified on final tree: 306/306 | +| Complete Phoenix Builder matrix | All five supported categories under Xvfb | Verified: 4811/4811 | +| Input behavior | Keyboard maps, clipboard, drag/drop, IME/composition, overwrite | Verified by Chromium 11/11 and Firefox 10/10 native smokes; Firefox omits Chromium-only CDP IME injection | +| Accessibility | Focus, screen-reader semantics, forced colors, and keyboard-only operation | Verified by native Chromium and Firefox smokes | +| Performance | 50,000-line render, scroll, and search smoke | Verified in Chromium and Firefox | +| Browser targets | Chromium, Firefox, Electron, and Tauri source-runtime paths | Verified under Xvfb: Chromium 11/11, Firefox 10/10, Electron theme/runtime, and Tauri theme/editor runtime | +| Runtime architecture | Phoenix Builder source-runtime inspection | Verified in Electron and Tauri: CM6 adapter, CM6 DOM/state, compatibility imports, and no loaded CM5 resource | +| Runtime errors | Phoenix Builder browser and PhNode error-log audit | Verified; no CM5 resource or critical CM6 adapter signature in the final matrix/native smokes | +| Static quality | Syntax, ESLint, Less compilation, Builder MCP tests, validators, and `git diff --cached --check` | Verified | +| CM6 bundle | `npm run build:codemirror6` | Verified: 2792 KiB, 30 runtime packages | +| Dependency removal | Tracked manifests/lockfiles, installed packages, generated bundle, and vendor trees | Verified; `npm ls codemirror --all` is empty | +| Release output | Clean build; no CM5 engine, package, original vendor tree, or runtime asset in `dist` or `dist-test` | Verified: dev 104.16/107 MB; prod 75.36/80 MB | + +## Build and distribution state + +Fresh development and production artifacts were built from the current +migration tree. `dist` and `dist-test` each contain all 25 required CM6 +compatibility artifacts and all three required CodeMirror license notices. +Source and artifact license hashes match. + +`release:dev` and `release:prod` both: + +1. clean `dist` and `dist-test`; +2. regenerate the CM6 AMD bundle and aggregate license; +3. copy or minify the current source modules into `dist`; +4. regenerate the appropriate cache manifest; +5. refresh `test/spec/test_folders.zip`; +6. copy the rebuilt distribution and tests into `dist-test`; +7. enforce the configured distribution-size limits. + +Production removes the CM6 source map while retaining the minified AMD bundle +and aggregate license. Development retains the source map. The current +on-disk artifacts are from the production build, correctly omit the CM6 source +map, and pass size validation at 75.36 MB against the 80 MB limit. The +development build passed earlier at 104.16 MB against the 107 MB limit. + +An isolated Tauri instance also loaded the current Phoenix source tree under +Xvfb through Phoenix Builder. The runtime reported `codemirror6` for the +editor and compatibility facade; historical core, addon, keymap, mode, and +theme imports resolved virtually; programmatic editor insertion using the +`+input` origin, selection, undo/redo, active-line styling, and Monokai styling +passed; the Phoenix document matched `EditorView.state.doc`; and no CM5 vendor +resource or CM5/adapter error was observed. The smoke used an existing Tauri +executable, so it validates the current web/editor source inside the Tauri +runtime rather than a newly compiled desktop shell. + +Do not use `npm run clean` as a release prerequisite: it also removes +`node_modules`. For focused verification, prefer `_buildonly` and +`_buildonlyDebug` over the broader `npm run build`, which also regenerates the +`src-node` package lock and stages generated API documentation. + +## Remaining work + +No CM6 migration blocker remains in the source, extension, browser, Electron, +Tauri source-runtime, test, or release gates covered by this branch. A fresh +Tauri shell rebuild was not possible on this Ubuntu host because the Tauri v1 +desktop project requires WebKitGTK 4.0 and librsvg development packages that +are unavailable here. That native-shell packaging build remains part of the +normal desktop release matrix. The migration commit is local only and remains +unpushed. + +## Phoenix Builder runtime gate + +Use Phoenix Builder against a freshly reloaded instance after each compatible +change: + +1. confirm the editor reports the CM6 engine and contains a CM6 editor DOM; +2. confirm no CM5 script, stylesheet, constructor, document, or vendor path is + loaded; +3. exercise typing, paste, delete, selections, undo/redo, save, split view, + language changes, and external document refresh; +4. exercise markers, gutters, folding, search, hints, snippets, inline editors, + themes, and representative installed extensions; +5. inspect browser and PhNode logs for new errors or warnings; +6. run the relevant focused suite, then its owning full suite; +7. reload again before the next suite when adapter or facade code changed. + +Use only supported Phoenix Builder categories: `unit`, `integration`, +`LegacyInteg`, `livepreview`, and `mainview`. Do not use the unsupported +`all`, `performance`, `extension`, or `individualrun` categories; validate +individual behaviors through a supported suite filter and direct runtime +smoke checks. + +## Release exit criteria + +The migration is ready to merge only when: + +1. all required compatibility suites and manual extension scenarios pass; +2. `npm run build:codemirror6` and a clean release build pass; +3. a clean dependency install contains no CM5 `codemirror` package; +4. tracked manifests, lockfiles, shipped inputs, generated source, `dist`, and + `dist-test` contain no CM5 engine, package, original vendor-tree file, or + runtime asset; attributed CM6-backed compatibility ports and non-runtime + notices are permitted; +5. historical core module IDs resolve only to `CodeMirrorCompat`; +6. browser/desktop smoke tests show no fallback, duplicate editor model, or + new console error; +7. migration documentation and extension guidance match the shipped API. + +Untracked user files that predate the branch are not release inputs and are +not part of this migration's dependency audit. They must remain untouched. + +## Failure behavior + +There is no CM5 rollback path. If the CM6 bundle, adapter, facade, language +support, or extension compatibility layer fails, Phoenix must report and fix +that CM6 failure. It must never silently load, construct, download, or restore +CodeMirror 5. + +## Primary references + +- +- +- +- diff --git a/docs/CodeMirror6-Migration-Review-Handoff.md b/docs/CodeMirror6-Migration-Review-Handoff.md new file mode 100644 index 0000000000..cb1635d8a7 --- /dev/null +++ b/docs/CodeMirror6-Migration-Review-Handoff.md @@ -0,0 +1,629 @@ +# CodeMirror 6 Migration Review Handoff + +Date: August 29, 2026 + +Branch: `codemirror-6-editor-surface` + +Base: `5fee600cc` (`origin/main` when this work began) + +## Purpose + +This document is the review guide for the Phoenix editing-surface migration +from CodeMirror 5 (CM5) to CodeMirror 6 (CM6), together with the Phoenix +Builder MCP improvements used to build and verify the migration from Codex. + +The migration has two simultaneous requirements: + +1. Phoenix must use CM6 as its only editor engine and + `EditorView.state.doc` as its only live text model. +2. Existing Phoenix modules and extensions must retain the historical + CodeMirror-shaped API and import contracts they depend on, without loading + or executing CM5. + +The implementation meets those requirements by combining a native CM6 editor +adapter with a Phoenix-owned compatibility facade. Historical names remain +where they are part of public extension contracts, but no CM5 runtime, +package, original vendor tree, or fallback editor remains. + +## Executive summary + +- `Editor` now always constructs `CodeMirror6Adapter`. +- The adapter owns one CM6 `EditorView`; its `state.doc` is authoritative. +- `CodeMirrorCompat` recreates the historical static and instance API shape + used by Phoenix and extensions. +- Historical `thirdparty/CodeMirror` and `thirdparty/CodeMirror2` imports are + intercepted and resolved to CM6-backed virtual modules before RequireJS + performs a network request. +- Legacy addons, keymaps, modes, theme imports, text-plugin imports, and + filesystem probes have compatibility implementations. +- Unsupported historical imports fail explicitly; they never fall back to + CM5. +- The old `codemirror` dependency and original CM5 vendor/license artifact + were removed. +- Build validation rejects any reintroduced CM5 dependency, runtime asset, or + original vendor tree. +- Integrated extensions and live-preview consumers were updated for CM6 + lifecycle, transaction, selection, token, gutter, and DOM behavior. +- The complete supported Phoenix Builder/Xvfb matrix passes: 4,811/4,811. +- Native Chromium, Firefox, Electron, and Tauri source-runtime smokes pass. + +## Runtime architecture + +```text +Phoenix Editor API and integrated/third-party extensions + | + +--------------+----------------+ + | | + v v + Phoenix Editor methods CodeMirrorCompat facade + | historical module/API surface + +--------------+----------------+ + | + v + CodeMirror6Adapter + | + v + CM6 EditorView.state.doc + only live text model +``` + +There is no CM5 fallback branch. A CM6 initialization failure must be exposed +and fixed rather than silently constructing an old editor. + +## Main implementation areas + +### Dependency and bundle pipeline + +Key files: + +- `package.json` +- `package-lock.json` +- `build/codemirror6-entry.js` +- `build/codemirror6-legacy-modes.js` +- `build/build-codemirror6.mjs` +- `gulpfile.js/index.js` +- `gulpfile.js/thirdparty-lib-copy.js` +- `gulpfile.js/validate-build.js` + +Changes: + +- Removed the `codemirror` version 5 package. +- Added the required `@codemirror/*`, `@lezer/*`, and Vim compatibility + dependencies. +- Added Rollup and the narrowly scoped Babel transform required to bundle the + Vim dependency. +- Added `npm run build:codemirror6`. +- Built one named AMD module: + `thirdparty/CodeMirror6/codemirror6`. +- Enforced one bundled copy of CM6/Lezer singleton packages. +- Rejected external and dynamic imports from the generated bundle. +- Removed stale generated `src/thirdparty/CodeMirror` output during builds. +- Integrated CM6 generation and zero-CM5 validation into source, debug, and + release builds. +- Generated an aggregate CM6 license notice from the exact bundled package + versions. + +The verified bundle is 2,859,015 bytes (2,792 KiB) and includes 30 runtime +packages. + +### CM6 editor adapter + +Primary file: + +- `src/editor/CodeMirror6Adapter.js` + +The adapter translates the established Phoenix editor contracts onto CM6. It +covers: + +- document text, positions, ranges, line endings, and linked documents; +- transactions, change origins, before-change filtering, and operation + batching; +- single, multiple, reversed, and primary selections; +- text history, selection history, clean generations, and named history + restore points; +- marks, bookmarks, collapsed/replaced ranges, and marker history; +- line handles, line classes, line widgets, gutters, and folding; +- input, clipboard, drag/drop, IME/composition, overwrite mode, and keymaps; +- coordinates, scrolling, sizing, viewport reporting, and refresh behavior; +- modes, tokens, parser state, nested languages, helpers, and overlays; +- full editors, inline editors, secondary editors, detached editors, and + document-owner editors. + +Compatibility state in the adapter is metadata only. Text is not mirrored into +a second document implementation. + +### Phoenix editor integration + +Key files: + +- `src/editor/Editor.js` +- `src/document/Document.js` +- `src/editor/EditorManager.js` +- `src/editor/InlineTextEditor.js` +- `src/editor/EditorCommandHandlers.js` +- `src/editor/EditorHelper/*` + +Important changes: + +- `Editor` constructs `CodeMirror6Adapter` unconditionally. +- `Editor._codeMirrorView` exposes the underlying CM6 view for tightly scoped + internal integration. +- `Editor.getEditorEngine()` reports `codemirror6`. +- Clean-state and history operations are exposed through the Phoenix + `Editor` API. +- Selection replacement accepts and preserves change origins. +- Editor destruction explicitly disposes the CM6 view. +- Scrolling, geometry, line-space, gutter, and focus handling use the adapter + surface rather than CM5 DOM assumptions. + +### Compatibility facade + +Primary file: + +- `src/editor/CodeMirrorCompat.js` + +The facade preserves the CodeMirror-shaped contracts used by existing Phoenix +code and extensions, including: + +- callable editor construction for detached editors; +- `commands`, `keyMap`, options, extensions, and document extensions; +- mode/MIME registration and resolution; +- `StringStream`, `Pos`, position helpers, and event helpers; +- helper registration and lookup; +- overlays, multiplex modes, simple modes, tags, brackets, and folding; +- static utilities and legacy-shaped instance detection. + +The facade intentionally reports: + +```js +CodeMirrorCompat.backend === "codemirror6"; +CodeMirrorCompat.isCodeMirror6 === true; +CodeMirrorCompat.version === "5.65.16"; +``` + +The version string is compatibility metadata for extensions that gate behavior +on the historical API version. It does not identify the runtime engine. + +### Historical module and filesystem virtualization + +Key files: + +- `src/editor/CodeMirrorLegacyModuleLoader.js` +- `src/editor/CodeMirrorLegacyText.js` +- `src/editor/CodeMirrorLegacyFileSystem.js` +- `src/utils/ExtensionLoader.js` +- `src/utils/Global.js` +- `src/phoenix/virtual-server-loader.js` +- `src/main.js` +- `src/brackets.js` + +Supported historical IDs include: + +- `thirdparty/CodeMirror` +- `thirdparty/CodeMirror2` +- both historical `lib/codemirror` IDs; +- supported addon and keymap paths; +- bundled mode paths and `mode/meta`; +- stock theme paths; +- CSS and `text!` resource paths used by extensions. + +The loader defines a virtual AMD module before RequireJS attempts a network +load. The text and filesystem layers similarly return compatibility content +and metadata without creating an old vendor tree. + +Coverage currently includes: + +- 62/62 historical addon and keymap JavaScript paths; +- 121/121 historical mode paths; +- 65 stock theme names and the supported legacy CSS paths. + +Unsupported paths produce a +`PHOENIX_UNSUPPORTED_CODEMIRROR5_MODULE` error instead of loading CM5. + +### Addons, modes, and keymaps + +Key files: + +- `src/editor/CodeMirrorLegacyAddons.js` +- `src/editor/CodeMirrorLegacyExtendedAddons.js` +- `src/editor/CodeMirrorLegacyModeMeta.js` +- `src/editor/CodeMirrorLegacyModesCompat.js` +- `src/editor/CodeMirrorLegacyRSTSlimCompat.js` +- `src/editor/CodeMirrorSublimeCompat.js` +- `src/editor/CodeMirrorTwigCompat.js` +- `src/editor/CodeMirrorVimCompat.js` + +These files provide CM6-backed behavior for the legacy extension surface. +Major areas include: + +- comment, bracket, tag, overlay, multiplex, and simple-mode helpers; +- search, hints, lint, dialogs, panels, rulers, and scroll annotations; +- folding, fold gutters, hard wrapping, trailing-space handling, and merge + helpers; +- Sublime commands/keymaps; +- Replit Vim integration adapted to the Phoenix CM6 surface; +- legacy stream modes, metadata lookup, Twig, RST, and Slim support. + +Some compatibility algorithms retain CM5 attribution because their behavior +was ported. The corresponding license notices are shipped, but the CM5 engine +and original addon/mode files are not. + +### Folding, languages, and tokens + +Key files: + +- `src/extensions/default/CodeFolding/*` +- `src/language/CSSUtils.js` +- `src/language/HTMLUtils.js` +- `src/language/JSUtils.js` +- `src/language/LanguageManager.js` +- `src/utils/TokenUtils.js` + +Changes include: + +- CM6 syntax-tree folding where native language data is available; +- compatibility fold helpers and gutters for historical extension APIs; +- stream-mode fallback for registered languages not covered by native CM6 + language packages; +- parser-state compatibility for embedded CSS/HTML/JavaScript; +- mode resolution and metadata parity; +- token boundary and token-class compatibility used by hints and language + services. + +### Integrated extension updates + +Updated consumers include: + +- CSS color preview; +- display shortcuts; +- HTML tag sync editing; +- navigation and history; +- indentation guides; +- Quick View; +- Handlebars support; +- search and scrollbar markers; +- Markdown live preview. + +These changes replace assumptions about CM5 internals with Phoenix Editor APIs +or explicit CM6-compatible adapter behavior. + +### Live preview and Markdown editor + +Key files: + +- `src/LiveDevelopment/BrowserScripts/DocumentObserver.js` +- `src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js` +- `src/LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument.js` +- `src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js` +- `src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js` +- `src/extensionsIntegrated/Phoenix-live-preview/main.js` +- `src-mdviewer/src/bridge.js` +- `src-mdviewer/src/components/editor.js` +- `src-mdviewer/src/components/link-popover.js` + +The migration hardened editor lifecycle checks so live-preview objects stop +using an editor as soon as its CM6 view is destroyed. It also preserves +preview scroll state, defers cursor-scroll synchronization during Markdown +file switches, prevents stale debounced edits from being replayed, and makes +Markdown popover/table behavior deterministic in the integration harness. + +### Styling and themes + +Key files: + +- `src/styles/brackets_codemirror6.less` +- `src/styles/brackets_codemirror6_legacy_themes.less` +- `src/styles/brackets_codemirror_override.less` +- `src/styles/brackets_shared.less` +- `src/styles/brackets_theme_default.less` +- `src/extensions/default/DarkTheme/main.less` + +The CM6 DOM receives the compatibility classes required by Phoenix and +extensions, including selected text, cursors, active lines, gutters, widgets, +dialogs, hints, lint markers, and fold markers. Stock historical theme names +are recreated on the CM6 surface. For example, the Monokai compatibility +theme was runtime-verified at background `rgb(39, 40, 34)` and keyword color +`rgb(249, 38, 114)`. + +## Zero-CM5 enforcement + +Key files: + +- `build/validate-codemirror5.js` +- `build/test/validate-codemirror5.test.js` +- `gulpfile.js/validate-build.js` + +The validator checks: + +- dependency sections in tracked package manifests; +- npm lockfiles and installed package identities; +- source, build scripts, and optional shipped subprojects; +- generated `src`, `dist`, and `dist-test` artifacts; +- HTML script and stylesheet references; +- old vendor directories and license paths; +- high-confidence CM5 implementation signatures; +- required CM6 compatibility modules and license notices. + +Intentional historical import strings, DOM class names, compatibility metadata, +ported compatibility implementations, and their attribution notices are +allowlisted narrowly. A new CM5 package, original vendor asset, fallback path, +or hidden CM5 document fails validation. + +## Phoenix Builder MCP work + +Key files: + +- `phoenix-builder-mcp/index.js` +- `phoenix-builder-mcp/config.js` +- `phoenix-builder-mcp/build-manager.js` +- `phoenix-builder-mcp/process-manager.js` +- `phoenix-builder-mcp/ws-control-server.js` +- `phoenix-builder-mcp/mcp-tools.js` +- `phoenix-builder-mcp/test/*` +- `phoenix-builder-mcp/README.md` + +Changes: + +- documented and verified Codex stdio registration; +- made the WebSocket port configurable and validated its range; +- bound the control socket to `127.0.0.1`; +- prevented a second server from killing or replacing an existing owner; +- added allowlisted asynchronous Phoenix build tools and build-log/status + inspection; +- restricted test categories to the supported matrix; +- improved process-tree termination and startup failure reporting; +- rejected pending runtime requests immediately when their Phoenix socket + disconnects; +- made shutdown await process, build, and WebSocket cleanup; +- added isolated tests for configuration, builds, process management, and + WebSocket behavior. + +The Codex registration points at the local +`phoenix-builder-mcp/index.js` using absolute paths and a dedicated WebSocket +port. No repository-local secret or machine credential is required. + +## Validation record + +### Complete Phoenix test matrix + +Executed through Phoenix Builder under Xvfb: + +| Category | Result | +| --- | ---: | +| `unit` | 2,905/2,905 | +| `integration` | 833/833 | +| `LegacyInteg` | 510/510 | +| `livepreview` | 257/257 | +| `mainview` | 306/306 | +| **Total** | **4,811/4,811** | + +Focused confirmation also includes: + +- Editor Surface Conformance: 74/74; +- core Editor suite: 246/246; +- CSS Parsing: 53/53; +- inline editor providers: 55/55; +- CodeMirror compatibility parity suite; +- legacy addon, extended-addon, mode, Twig, and Vim suites; +- extension-loader compatibility and filesystem probes; +- final Code Folding regression rerun: 52/52. + +### Browser and desktop runtime checks + +| Runtime | Result | +| --- | --- | +| Chromium | 11/11 native smoke checks | +| Firefox | 10/10 native smoke checks | +| Electron | Five theme/runtime checks passed | +| Tauri/WebKitGTK | CM6 source-runtime smoke passed under Xvfb | + +The Tauri smoke used Phoenix Builder against an isolated profile and the +current Phoenix source served at `http://localhost:8000/src/`. It verified: + +- `Editor.getEditorEngine() === "codemirror6"`; +- compatibility facade `backend === "codemirror6"`; +- a live CM6 `EditorView` and `.cm-editor` DOM; +- editor input/history semantics through `replaceSelection(..., "+input")`; +- undo, redo, selection state, and compatibility selection classes; +- active-line and active-gutter compatibility classes; +- Monokai wrapper, background, and token styling; +- equality between the Phoenix document and `EditorView.state.doc`; +- historical core/addon/keymap/mode/theme imports resolving to the facade; +- no requested `/thirdparty/CodeMirror/` or + `/thirdparty/CodeMirror2/` resource; +- no CM5 or CM6-adapter error signature in the browser log. + +The disposable untitled document was force-closed, its theme/options were +restored, test globals were removed, and the isolated Tauri, source server, +Builder bridge, and ports were stopped. + +### Build and static checks + +- `npm run build:codemirror6`: passed. +- `npm run test:codemirror-validation`: 15/15. +- `npm run validate:codemirror`: passed. +- `npm ls codemirror --all`: prints `(empty)` and exits with npm status 1. +- Phoenix Builder MCP tests: 16/16. +- Root ESLint and MCP ESLint error checks: passed. +- JavaScript syntax and Less compilation checks: passed. +- Development release: 104.16 MB against a 107 MB limit. +- Production release: 75.36 MB against an 80 MB limit. +- `git diff --cached --check`: passed for the reviewed commit scope. + +Useful temporary artifacts from the final verification session: + +- full Xvfb matrix: + `/tmp/phoenix-cm6-xvfb-authoritative.yXBshh`; +- focused Editor Surface Conformance rerun: + `/tmp/phoenix-cm6-editor-conformance-20260829-codex/editor-surface-conformance.json`; +- Chromium: + `/tmp/phoenix-cm6-native-chromium-authoritative.RXzymP/result.json`; +- Firefox: + `/tmp/phoenix-cm6-native-firefox-xvfb-final.Mdq4dO/result.json`; +- Electron: + `/tmp/phoenix-cm6-electron-theme-xvfb-final2.TAk8YF/result.json`; +- focused folding rerun: + `/tmp/phoenix-cm6-xvfb-code-folding-final.i1Em3k/code-folding.json`; +- Tauri logs: + `/tmp/phoenix-cm6-tauri-xvfb-final.yegYOd`. + +The `/tmp` paths are evidence from this workstation and are not part of the +commit. The browser and desktop smoke harnesses were purpose-built in `/tmp`, +so those results are one-off verification evidence rather than committed test +programs and will not be reproducible on another machine unless the harnesses +are preserved separately. + +## Suggested review order + +1. Read `docs/CodeMirror6-Migration-Plan.md` for the required invariants. +2. Review `build/validate-codemirror5.js` to understand what the build forbids. +3. Review `build/codemirror6-entry.js` and `build/build-codemirror6.mjs` for + bundle boundaries and singleton guarantees. +4. Review `src/editor/CodeMirror6Adapter.js`, concentrating on transactions, + history, selections, markers, linked documents, destruction, and DOM + compatibility. +5. Review `src/editor/CodeMirrorCompat.js` and the legacy loader/text/filesystem + modules for extension-facing contracts. +6. Review addon, keymap, and mode compatibility modules with their focused + tests. +7. Review integrated extension changes for accidental direct CM6 or CM5 + coupling. +8. Review live-preview and Markdown lifecycle changes. +9. Run the validators and focused tests before repeating the complete + Phoenix Builder matrix. + +## Reviewer risk checklist + +- Confirm every editor creation path reaches `CodeMirror6Adapter`. +- Confirm `EditorView.state.doc` is the only mutable text source. +- Check transaction-to-change conversion and event ordering. +- Check undo grouping, clean generations, marker restoration, and selection + history. +- Check linked-document rebasing and secondary editor synchronization. +- Check selection ordering, reversal, primary range identity, and origin + propagation. +- Check widget, gutter, fold, and marker cleanup on editor destruction. +- Check mixed-language token state and embedded CSS/JavaScript behavior. +- Check old module IDs resolve without network requests. +- Check unsupported old module IDs fail closed. +- Check extension filesystem probes cannot mutate virtual compatibility files. +- Check theme classes and legacy DOM selectors are attached only to CM6 DOM. +- Check release builds cannot copy a stale `thirdparty/CodeMirror` tree. +- Check retained CM5-derived code carries the required attribution notice. +- Check browser and desktop logs for new adapter, lifecycle, or resource-load + errors. + +## Reproduction commands + +From the repository root: + +```bash +npm run build:codemirror6 +npm run test:codemirror-validation +npm run validate:codemirror +# An empty tree is success even though npm exits with status 1. +npm ls codemirror --all +npm run _buildonly +npm run _buildonlyDebug +npm run release:dev +npm run release:prod +npm run validate:dist-size +git diff --cached --check +``` + +For the Builder server: + +```bash +npm --prefix phoenix-builder-mcp test +``` + +Build the source, start an isolated Xvfb display and browser test runner, set +the runner's Builder URL to the dedicated MCP WebSocket port, and then use this +Phoenix Builder sequence for each category: + +```text +get_phoenix_status() +run_tests(category="unit", instance="") +get_phoenix_status() +get_test_results(instance="") + +run_tests(category="integration", instance="") +run_tests(category="LegacyInteg", instance="") +run_tests(category="livepreview", instance="") +run_tests(category="mainview", instance="") +``` + +Poll `get_test_results` until `completed` is true after each run. Re-read +`get_phoenix_status` after every `run_tests` call because a reload can change +the runner instance name. Run one category at a time and give integration +windows OS focus when a suite requires it. + +Do not use the unsupported `all`, `performance`, `extension`, or +`individualrun` categories. + +## Intentional legacy identifiers + +Reviewers will still find the following strings: + +- `CodeMirror`, `_codeMirror`, and `CodeMirror-*`; +- `thirdparty/CodeMirror` and `thirdparty/CodeMirror2`; +- facade version `5.65.16`; +- CM5 attribution and derived-code license notices. + +These are compatibility identifiers or legal notices. They are acceptable only +when backed by CM6/Phoenix code. The validator distinguishes these cases from a +real CM5 package, engine, asset, or fallback. + +## Known limitations and environmental observations + +- A fresh Tauri 5.2.5 shell build could not be produced on this Ubuntu host + because the Tauri v1 project requires WebKitGTK 4.0 and librsvg development + packages that are unavailable here. +- The Tauri source-runtime smoke therefore used an existing 5.0.5 executable + while loading the current Phoenix source. This validates the migrated web + editor in Tauri/WebKitGTK, but not a newly compiled desktop shell. +- Native screenshot capture did not return image data from that existing Tauri + binary. Runtime assertions were performed through Phoenix Builder + `exec_js`. +- The isolated Tauri profile inherited a `C.UTF-8` locale and emitted an + unrelated `invalid language tag: C` message from unchanged filename-sorting + code. It did not affect the editor smoke and was not a CM5/CM6-adapter + failure. + +## Commit scope and intentionally excluded worktree files + +The migration commit includes the CM6 editor, compatibility modules, build and +validation pipeline, affected Phoenix integrations, tests, generated +CM6-related API documentation, license notices, and Phoenix Builder MCP +improvements. + +The following pre-existing or unrelated local files are intentionally excluded: + +- `.aider.chat.history.md` +- `.aider.input.history` +- `.aider.tags.cache.v3/` +- `.aider.tags.cache.v4/` +- `semantic_chunks.json` +- `structure.txt` +- `tree.txt` +- `src-node/jsconsole-node.js` +- `src/extensionsIntegrated/JSConsole/` +- `src/styles/Extn-JSConsole.less` +- `src/extensions/default/TypeScriptSupport/requirejs-config.json` +- incidental generated changes in `src-node/package-lock.json` +- unrelated generated `docs/API-Reference/command/Commands.md` drift + +No commit is intended to include generated `dist`, `dist-test`, +`src/cacheManifest.json`, CM6 bundle output ignored by the repository, MCP PID +files, browser profiles, or `/tmp` verification artifacts. + +## Completion criteria + +The migration is ready for review when: + +- all relevant source and test files are committed; +- all excluded files remain outside the commit; +- the staged commit passes `git diff --cached --check`; +- the zero-CM5 validator and Builder MCP tests pass; +- the commit contains no CM5 dependency or original vendor asset; +- no push is performed unless separately requested. diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js index 2da39623b6..eac8a43de1 100644 --- a/gulpfile.js/index.js +++ b/gulpfile.js/index.js @@ -82,6 +82,7 @@ function cleanUnwantedFilesInDistProd() { 'dist/nls/*/*.js.map', 'dist/extensions/default/*/unittests.js.map', 'dist/**/*no_dist.*', + 'dist/thirdparty/CodeMirror6/*.js.map', 'dist/thirdparty/no-minify/language-worker.js.map' ]); } @@ -503,8 +504,14 @@ function _isCacheableFile(path) { function _fixAndFilterPaths(basePath, entries) { let filtered = []; for(let entry of entries){ + const relativeEntry = entry.replace(`${basePath}/`, ""); + // A manifest cannot contain a stable hash of itself. This also makes + // repeated generation safe when release packaging changes afterward. + if (relativeEntry === "cacheManifest.json") { + continue; + } if(_isCacheableFile(entry)){ - filtered.push(entry.replace(`${basePath}/`, "")); + filtered.push(relativeEntry); } } return filtered; @@ -1094,7 +1101,7 @@ function _patchMinifiedCSSInDistIndex() { return new Promise((resolve)=>{ let content = fs.readFileSync("dist/index.html", "utf8"); if(!content.includes(``)){ - throw new Error(`Could not locate string in file dist/index.html`) + throw new Error(`Could not locate string in file dist/index.html`); } content = content.replace( ``, @@ -1104,31 +1111,39 @@ function _patchMinifiedCSSInDistIndex() { }); } -const createDistTest = series(copyDistToDistTestFolder, copyTestToDistTestFolder, copyIndexToDistTestFolder); +const createDistTest = series(zipTestFiles, copyDistToDistTestFolder, copyTestToDistTestFolder, + copyIndexToDistTestFolder); exports.build = series(optionalBuild.clonePhoenixProRepo, optionalBuild.generateProBuildInfo, copyThirdPartyLibs.copyAll, makeLoggerConfig, generateProLoaderFiles, zipDefaultProjectFiles, zipSampleProjectFiles, makeBracketsConcatJS, makeBracketsConcatJSWithMinifiedBrowserScripts, _compileLessSrc, _cleanReleaseBuildArtefactsInSrc, // these are here only as sanity check so as to catch release build minify fails not too late - createSrcCacheManifest, validatePackageVersions); + createSrcCacheManifest, validatePackageVersions, validateBuild.validateNoCodeMirror5); exports.buildDebug = series(optionalBuild.clonePhoenixProRepo, optionalBuild.generateProBuildInfo, copyThirdPartyLibs.copyAllDebug, makeLoggerConfig, generateProLoaderFiles, zipDefaultProjectFiles, makeBracketsConcatJS, makeBracketsConcatJSWithMinifiedBrowserScripts, _compileLessSrc, _cleanReleaseBuildArtefactsInSrc, // these are here only as sanity check so as to catch release build minify fails not too late - zipSampleProjectFiles, createSrcCacheManifest); + zipSampleProjectFiles, createSrcCacheManifest, validateBuild.validateNoCodeMirror5); exports.clean = series(cleanDist); exports.reset = series(cleanAll); +exports.bundleCodeMirror6 = copyThirdPartyLibs.bundleCodeMirror6; exports.releaseDev = series(cleanDist, exports.buildDebug, makeBracketsConcatJS, makeConcatExtensions, _compileLessSrc, makeDistAll, cleanUnwantedFilesInDistDev, releaseDev, _renameConcatExtensionsinDist, createDistCacheManifestDev, createDistTest, - _cleanPhoenixProGitFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions); + _cleanPhoenixProGitFolder, _cleanReleaseBuildArtefactsInSrc, + validateBuild.validateNoCodeMirror5Release, validateBuild.validateDistSizeRestrictions); +// dist-test intentionally retains Phoenix Pro sources for its test harness. +// Build its manifest/copy first, then regenerate dist's manifest after those +// sources are removed from the production artifact. exports.releaseStaging = series(cleanDist, exports.build, makeBracketsConcatJSWithMinifiedBrowserScripts, makeConcatExtensions, _compileLessSrc, makeDistNonJS, makeJSDist, makeJSPrettierDist, makeNonMinifyDist, cleanUnwantedFilesInDistProd, _renameBracketsConcatAsBracketsJSInDist, _renameConcatExtensionsinDist, _patchMinifiedCSSInDistIndex, releaseStaging, createDistCacheManifest, createDistTest, - _deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions); + _deletePhoenixProSourceFolder, createDistCacheManifest, _cleanReleaseBuildArtefactsInSrc, + validateBuild.validateNoCodeMirror5Release, validateBuild.validateDistSizeRestrictions); exports.releaseProd = series(cleanDist, exports.build, makeBracketsConcatJSWithMinifiedBrowserScripts, makeConcatExtensions, _compileLessSrc, makeDistNonJS, makeJSDist, makeJSPrettierDist, makeNonMinifyDist, cleanUnwantedFilesInDistProd, _renameBracketsConcatAsBracketsJSInDist, _renameConcatExtensionsinDist, _patchMinifiedCSSInDistIndex, releaseProd, createDistCacheManifest, createDistTest, - _deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions); + _deletePhoenixProSourceFolder, createDistCacheManifest, _cleanReleaseBuildArtefactsInSrc, + validateBuild.validateNoCodeMirror5Release, validateBuild.validateDistSizeRestrictions); exports.releaseWebCache = series(makeDistWebCache); exports.serve = series(exports.build, serve); exports.zipTestFiles = series(zipTestFiles); @@ -1139,4 +1154,5 @@ exports.default = series(exports.build); exports.patchVersionBump = series(patchVersionBump); exports.minorVersionBump = series(minorVersionBump); exports.majorVersionBump = series(majorVersionBump); +exports.validateNoCodeMirror5 = series(validateBuild.validateNoCodeMirror5); exports.validateDistSizeRestrictions = series(validateBuild.validateDistSizeRestrictions); diff --git a/gulpfile.js/thirdparty-lib-copy.js b/gulpfile.js/thirdparty-lib-copy.js index cc064520a2..9f70b094bb 100644 --- a/gulpfile.js/thirdparty-lib-copy.js +++ b/gulpfile.js/thirdparty-lib-copy.js @@ -21,6 +21,7 @@ /* eslint-env node */ const { src, dest, series } = require('gulp'); +const { execFileSync } = require("child_process"); const fs = require("fs"); const path = require('path'); @@ -69,6 +70,21 @@ function copyFiles(srcPathList, dstPath) { .pipe(dest(dstPath)); } +function cleanPrettierDirectory() { + const prettierDirectory = path.resolve(__dirname, "../src/thirdparty/prettier"); + fs.rmSync(prettierDirectory, { recursive: true, force: true }); + return Promise.resolve(); +} + +function bundleCodeMirror6() { + const buildScript = path.resolve(__dirname, "../build/build-codemirror6.mjs"); + execFileSync(process.execPath, [buildScript], { + cwd: path.resolve(__dirname, ".."), + stdio: "inherit" + }); + return Promise.resolve(); +} + function _copyMimeDB() { // mime-db return src(['node_modules/mime-db/db.json']) @@ -96,13 +112,8 @@ function _getConfigJSON() { * Add thirdparty libs copied to gitignore except the licence file. */ let copyThirdPartyLibs = series( - // codemirror - copyFiles.bind(copyFiles, ['node_modules/codemirror/addon/**/*'], 'src/thirdparty/CodeMirror/addon'), - copyFiles.bind(copyFiles, ['node_modules/codemirror/keymap/**/*'], 'src/thirdparty/CodeMirror/keymap'), - copyFiles.bind(copyFiles, ['node_modules/codemirror/lib/**/*'], 'src/thirdparty/CodeMirror/lib'), - copyFiles.bind(copyFiles, ['node_modules/codemirror/mode/**/*'], 'src/thirdparty/CodeMirror/mode'), - copyFiles.bind(copyFiles, ['node_modules/codemirror/theme/**/*'], 'src/thirdparty/CodeMirror/theme'), - copyLicence.bind(copyLicence, 'node_modules/codemirror/LICENSE', 'codemirror'), + // CodeMirror 6 is bundled because its packages are ESM-only and Phoenix loads browser modules through RequireJS. + bundleCodeMirror6, // @phcode/fs copyFiles.bind(copyFiles, ['node_modules/@phcode/fs/dist/virtualfs.js', 'node_modules/@phcode/fs/dist/virtualfs.js.map'], 'src/phoenix'), @@ -189,6 +200,9 @@ let copyThirdPartyLibs = series( copyFiles.bind(copyFiles, ['node_modules/@pixelbrackets/gfm-stylesheet/dist/gfm.min.css'], 'src/thirdparty/'), // AGPL 2.0 license added to licence md // prettier + // This directory is generated. Clear files left behind by older Prettier layouts before copying + // the current standalone runtime and plugins. + cleanPrettierDirectory, copyFiles.bind(copyFiles, ['node_modules/prettier/standalone.js'], 'src/thirdparty/prettier'), copyFiles.bind(copyFiles, ['node_modules/prettier/plugins/*.js'], @@ -306,3 +320,4 @@ function _patchTernLib() { exports.copyAll = series(copyThirdPartyLibs, _patchAcornLib, _patchTernLib); exports.copyAllDebug = series(copyThirdPartyLibs, copyThirdPartyDebugLibs, _patchAcornLib, _patchTernLib); +exports.bundleCodeMirror6 = bundleCodeMirror6; diff --git a/gulpfile.js/validate-build.js b/gulpfile.js/validate-build.js index 55bf8bf853..9e77322f78 100644 --- a/gulpfile.js/validate-build.js +++ b/gulpfile.js/validate-build.js @@ -22,10 +22,15 @@ const fs = require('fs'); const glob = require('glob'); +const { + assertNoCodeMirror5 +} = require('../build/validate-codemirror5'); // Size limits for development builds (in MB) const DEV_MAX_FILE_SIZE_MB = 6; -const DEV_MAX_TOTAL_SIZE_MB = 100; +// The CM6 development bundle intentionally includes its source map. Keep the +// aggregate guard about 5 MB above the current clean development release. +const DEV_MAX_TOTAL_SIZE_MB = 107; // Custom size limits for known large files (size in MB) For development builds const LARGE_FILE_LIST_DEV = { 'dist/thirdparty/no-minify/language-worker.js.map': 10, @@ -186,6 +191,29 @@ function validateDistSizeRestrictions() { }); } +function validateNoCodeMirror5() { + assertNoCodeMirror5({ + repositoryRoot: process.cwd() + }); + console.log("CodeMirror 5 validation passed: no dependency, vendor artifact, or direct package import found."); + return Promise.resolve(); +} + +function validateNoCodeMirror5Release() { + assertNoCodeMirror5({ + repositoryRoot: process.cwd(), + requireReleaseLicenseCopies: true + }); + console.log( + "CodeMirror 5 release validation passed: no dependency, vendor " + + "artifact, or direct package import found, and the derived-code " + + "license notices and CodeMirror 6 bundle licenses are packaged." + ); + return Promise.resolve(); +} + module.exports = { - validateDistSizeRestrictions + validateDistSizeRestrictions, + validateNoCodeMirror5, + validateNoCodeMirror5Release }; diff --git a/package-lock.json b/package-lock.json index d3d90c5def..6e340a8f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,39 @@ { "name": "phoenix", - "version": "5.2.0-0", + "version": "5.3.0-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "5.2.0-0", + "version": "5.3.0-0", "hasInstallScript": true, "dependencies": { "@bugsnag/js": "^7.18.0", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/commands": "^6.11.0", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.12", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.5.2", + "@codemirror/lang-php": "^6.0.2", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.4", + "@codemirror/legacy-modes": "^6.5.3", + "@codemirror/lint": "^6.9.7", + "@codemirror/search": "^6.7.1", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.9", "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", + "@lezer/highlight": "^1.2.3", "@phcode/fs": "^4.0.2", "@phcode/language-support": "^1.1.0", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "^0.22.2", + "@replit/codemirror-vim-core": "^0.1.0", "@uiw/file-icons": "^1.3.2", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-search": "^0.16.0", @@ -25,7 +42,6 @@ "@xterm/xterm": "^6.0.0", "bootstrap": "^5.1.3", "browser-mime": "^1.0.1", - "codemirror": "^5.65.16", "cross-env": "^7.0.3", "devicon": "^2.15.1", "emmet": "^2.4.11", @@ -46,9 +62,13 @@ "underscore": "^1.13.4" }, "devDependencies": { + "@babel/core": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", "@commitlint/cli": "^16.0.2", "@commitlint/config-conventional": "^16.0.0", "@playwright/test": "^1.38.1", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", "del": "^6.0.0", "eslint": "^8.18.0", "glob": "^8.1.0", @@ -72,67 +92,411 @@ "jsdoc-to-markdown": "^9.1.1", "lmdb": "^3.5.1", "readable-stream": "^3.6.0", + "rollup": "^4.63.0", "through2": "^4.0.2" } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@bugsnag/browser": { @@ -196,6 +560,193 @@ "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", + "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "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.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "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.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "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-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^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.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.3.tgz", + "integrity": "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.9", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.9.tgz", + "integrity": "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@commitlint/cli": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-16.3.0.tgz", @@ -839,7 +1390,51 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@jridgewell/resolve-uri": { + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", @@ -876,6 +1471,106 @@ "node": ">=v12.0.0" } }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.6.tgz", + "integrity": "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.2.tgz", + "integrity": "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lmdb/lmdb-darwin-arm64": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", @@ -974,6 +1669,12 @@ "win32" ] }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -1058,6 +1759,23 @@ "win32" ] }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1133,115 +1851,546 @@ "url": "https://paulmillr.com/funding/" } ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@phcode/fs/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@phcode/fs/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@phcode/fs/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@phcode/fs/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@phcode/language-support": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@phcode/language-support/-/language-support-1.1.0.tgz", + "integrity": "sha512-UcMbCJUTBWtIeFJwPZJSIHPFspP/QaZWphSTqvF70XASoGw1aaVeeb8X5sYYMOIl5wfS//d7KLsf0vCFE6Z0pg==", + "license": "GNU-AGPL3.0" + }, + "node_modules/@pixelbrackets/gfm-stylesheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pixelbrackets/gfm-stylesheet/-/gfm-stylesheet-1.1.0.tgz", + "integrity": "sha512-Dal+yZ5FpZN8yq6XfZNWFl0gNi4AM5c4cEIGc0yKQYQAbCY0+erwQjAVQUuzkD4kRsrJK+hwF+Mf58/pXV421w==" + }, + "node_modules/@playwright/test": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", + "dev": true, + "dependencies": { + "playwright": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@prettier/plugin-php": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.22.2.tgz", + "integrity": "sha512-md0+7tNbsP0oy+wIP3KZZc6fzx1k1jtWaMjOy/gM8yU9f2BDYEi+iHOc/UNPihYvPI28zFTbjvlhH4QXQjQwNg==", + "dependencies": { + "linguist-languages": "^7.27.0", + "php-parser": "^3.1.5" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/@replit/codemirror-vim-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-vim-core/-/codemirror-vim-core-0.1.0.tgz", + "integrity": "sha512-1i6EBKpcNfDKvTmTh6N6g9lL6udD5t+uFNh4JCqozRnVlvUGOps7h/QzS2ne4zcvPUjvApKpmcP7Grc3fNbZiQ==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", + "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", + "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", + "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", + "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", + "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", + "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", + "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", + "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", + "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", + "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", + "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", + "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", + "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", + "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", + "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@phcode/fs/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", + "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", + "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", + "cpu": [ + "s390x" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@phcode/fs/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", + "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@phcode/fs/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", + "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@phcode/fs/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", + "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@phcode/language-support": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@phcode/language-support/-/language-support-1.1.0.tgz", - "integrity": "sha512-UcMbCJUTBWtIeFJwPZJSIHPFspP/QaZWphSTqvF70XASoGw1aaVeeb8X5sYYMOIl5wfS//d7KLsf0vCFE6Z0pg==", - "license": "GNU-AGPL3.0" + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", + "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@pixelbrackets/gfm-stylesheet": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pixelbrackets/gfm-stylesheet/-/gfm-stylesheet-1.1.0.tgz", - "integrity": "sha512-Dal+yZ5FpZN8yq6XfZNWFl0gNi4AM5c4cEIGc0yKQYQAbCY0+erwQjAVQUuzkD4kRsrJK+hwF+Mf58/pXV421w==" + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", + "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@playwright/test": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", - "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", + "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "playwright": "1.38.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@popperjs/core": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", - "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", + "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@prettier/plugin-php": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.22.2.tgz", - "integrity": "sha512-md0+7tNbsP0oy+wIP3KZZc6fzx1k1jtWaMjOy/gM8yU9f2BDYEi+iHOc/UNPihYvPI28zFTbjvlhH4QXQjQwNg==", - "dependencies": { - "linguist-languages": "^7.27.0", - "php-parser": "^3.1.5" - }, - "peerDependencies": { - "prettier": "^3.0.0" - } + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", + "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@tsconfig/node10": { "version": "1.0.11", @@ -1267,6 +2416,13 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1278,6 +2434,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -1299,7 +2456,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -1313,6 +2471,13 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@uiw/file-icons": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@uiw/file-icons/-/file-icons-1.3.2.tgz", @@ -1378,6 +2543,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1520,18 +2686,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", @@ -2073,6 +3227,19 @@ "node": ">=0.10.0" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -2365,6 +3532,27 @@ "integrity": "sha512-5lzq/7B9e3Fj4puD8CnFpS/YleAmLsSvreMF/1KLvi4r71x3fPi4BtbPfDC3yE45sR2jYpTXUCIbphk2SKWrlg==", "dev": true }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -2864,11 +4052,6 @@ "node": ">=0.10.0" } }, - "node_modules/codemirror": { - "version": "5.65.16", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz", - "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==" - }, "node_modules/collection-map": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", @@ -3637,6 +4820,12 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -3901,6 +5090,16 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -4269,10 +5468,11 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz", - "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==", - "dev": true + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "dev": true, + "license": "ISC" }, "node_modules/emmet": { "version": "2.4.11", @@ -4359,6 +5559,16 @@ "stackframe": "^1.3.4" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es5-ext": { "version": "0.10.64", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", @@ -4409,10 +5619,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4440,6 +5651,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -4634,6 +5846,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5474,10 +6693,14 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -5485,6 +6708,16 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -6139,6 +7372,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "dependencies": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -7004,15 +8238,6 @@ "node": ">=0.10.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/has-gulplog": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", @@ -7100,6 +8325,19 @@ "node": ">=0.10.0" } }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -7467,12 +8705,16 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7574,6 +8816,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, "node_modules/is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -7796,7 +9045,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.7.0", @@ -7933,18 +9183,6 @@ } } }, - "node_modules/jsdoc/node_modules/@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/jsdoc/node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -7966,6 +9204,19 @@ "node": ">=10" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jshint": { "version": "2.13.5", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.5.tgz", @@ -8029,6 +9280,19 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9267,6 +10531,16 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/node.extend": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", @@ -9858,10 +11132,24 @@ "integrity": "sha512-jEY2DcbgCm5aclzBdfW86GM6VEIWcSlhTBSHN1qhJguVePlYe28GhwS0yoeLYXpM2K8y6wzLwrbq814n2PHSoQ==" }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/pify": { "version": "2.3.0", @@ -10389,6 +11677,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10994,13 +12283,22 @@ } }, "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11113,6 +12411,68 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", + "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.0", + "@rollup/rollup-android-arm64": "4.63.0", + "@rollup/rollup-darwin-arm64": "4.63.0", + "@rollup/rollup-darwin-x64": "4.63.0", + "@rollup/rollup-freebsd-arm64": "4.63.0", + "@rollup/rollup-freebsd-x64": "4.63.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", + "@rollup/rollup-linux-arm-musleabihf": "4.63.0", + "@rollup/rollup-linux-arm64-gnu": "4.63.0", + "@rollup/rollup-linux-arm64-musl": "4.63.0", + "@rollup/rollup-linux-loong64-gnu": "4.63.0", + "@rollup/rollup-linux-loong64-musl": "4.63.0", + "@rollup/rollup-linux-ppc64-gnu": "4.63.0", + "@rollup/rollup-linux-ppc64-musl": "4.63.0", + "@rollup/rollup-linux-riscv64-gnu": "4.63.0", + "@rollup/rollup-linux-riscv64-musl": "4.63.0", + "@rollup/rollup-linux-s390x-gnu": "4.63.0", + "@rollup/rollup-linux-x64-gnu": "4.63.0", + "@rollup/rollup-linux-x64-musl": "4.63.0", + "@rollup/rollup-openbsd-x64": "4.63.0", + "@rollup/rollup-openharmony-arm64": "4.63.0", + "@rollup/rollup-win32-arm64-msvc": "4.63.0", + "@rollup/rollup-win32-ia32-msvc": "4.63.0", + "@rollup/rollup-win32-x64-gnu": "4.63.0", + "@rollup/rollup-win32-x64-msvc": "4.63.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11940,16 +13300,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/sver-compat": { @@ -12483,6 +13850,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12924,6 +14292,12 @@ "source-map": "^0.5.1" } }, + "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/walk-back": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", @@ -13281,52 +14655,271 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "requires": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, - "@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true }, - "@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, + "peer": true, "requires": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, + "peer": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } }, + "@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true + }, + "@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "requires": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + } + }, + "@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "requires": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + } + }, + "@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "requires": { + "@babel/types": "^7.29.8" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + } + }, + "@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + } + }, + "@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + } + }, + "@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + } + }, "@bugsnag/browser": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.18.0.tgz", @@ -13385,10 +14978,182 @@ } } }, - "@bugsnag/safe-json-stringify": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", - "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" + "@bugsnag/safe-json-stringify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", + "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" + }, + "@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "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.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", + "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "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.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "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.12" + } + }, + "@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "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-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "requires": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "@codemirror/lang-markdown": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "requires": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "requires": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^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.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/legacy-modes": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.3.tgz", + "integrity": "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==", + "requires": { + "@codemirror/language": "^6.0.0" + } + }, + "@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "requires": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "@codemirror/view": { + "version": "6.43.9", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.9.tgz", + "integrity": "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==", + "requires": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } }, "@commitlint/cli": { "version": "16.3.0", @@ -13903,6 +15668,50 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, "@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -13934,6 +15743,96 @@ "lodash": "^4.17.21" } }, + "@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==" + }, + "@lezer/css": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.6.tgz", + "integrity": "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "requires": { + "@lezer/common": "^1.3.0" + } + }, + "@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/markdown": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.2.tgz", + "integrity": "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==", + "requires": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "@lmdb/lmdb-darwin-arm64": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", @@ -13983,6 +15882,11 @@ "dev": true, "optional": true }, + "@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==" + }, "@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -14025,6 +15929,13 @@ "dev": true, "optional": true }, + "@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "dev": true, + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -14153,6 +16064,220 @@ "php-parser": "^3.1.5" } }, + "@replit/codemirror-vim-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-vim-core/-/codemirror-vim-core-0.1.0.tgz", + "integrity": "sha512-1i6EBKpcNfDKvTmTh6N6g9lL6udD5t+uFNh4JCqozRnVlvUGOps7h/QzS2ne4zcvPUjvApKpmcP7Grc3fNbZiQ==" + }, + "@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + } + }, + "@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + } + }, + "@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", + "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", + "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", + "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", + "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", + "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", + "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", + "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", + "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", + "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", + "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", + "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", + "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", + "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", + "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", + "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", + "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", + "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", + "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", + "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openbsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", + "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", + "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", + "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", + "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", + "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", + "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", + "dev": true, + "optional": true + }, "@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -14177,6 +16302,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, "@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -14188,6 +16319,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "requires": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -14209,7 +16341,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "@types/normalize-package-data": { "version": "2.4.4", @@ -14223,6 +16356,12 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "@uiw/file-icons": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@uiw/file-icons/-/file-icons-1.3.2.tgz", @@ -14273,7 +16412,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -14376,15 +16516,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", @@ -14822,6 +16953,12 @@ } } }, + "baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -15055,6 +17192,12 @@ "integrity": "sha512-5lzq/7B9e3Fj4puD8CnFpS/YleAmLsSvreMF/1KLvi4r71x3fPi4BtbPfDC3yE45sR2jYpTXUCIbphk2SKWrlg==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true + }, "catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -15453,11 +17596,6 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, - "codemirror": { - "version": "5.65.16", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz", - "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==" - }, "collection-map": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", @@ -16067,6 +18205,11 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==" + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -16275,6 +18418,12 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -16577,9 +18726,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz", - "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==", + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", "dev": true }, "emmet": { @@ -16652,6 +18801,12 @@ "stackframe": "^1.3.4" } }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, "es5-ext": { "version": "0.10.64", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", @@ -16698,9 +18853,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, "escape-html": { @@ -16720,6 +18875,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -16872,6 +19028,12 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -17560,9 +19722,9 @@ } }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, "functional-red-black-tree": { @@ -17571,6 +19733,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -18073,6 +20241,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "requires": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -18798,12 +20967,6 @@ } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, "has-gulplog": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", @@ -18871,6 +21034,15 @@ } } }, + "hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -19163,12 +21335,12 @@ "dev": true }, "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.3" } }, "is-data-descriptor": { @@ -19241,6 +21413,12 @@ "is-extglob": "^2.1.1" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -19463,12 +21641,6 @@ "underscore": "~1.13.2" }, "dependencies": { - "@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", - "dev": true - }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -19526,6 +21698,12 @@ "walk-back": "^5.1.1" } }, + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true + }, "jshint": { "version": "2.13.5", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.5.tgz", @@ -19578,6 +21756,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -20614,6 +22798,12 @@ "detect-libc": "^2.0.1" } }, + "node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true + }, "node.extend": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", @@ -21063,9 +23253,15 @@ "integrity": "sha512-jEY2DcbgCm5aclzBdfW86GM6VEIWcSlhTBSHN1qhJguVePlYe28GhwS0yoeLYXpM2K8y6wzLwrbq814n2PHSoQ==" }, "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true }, "pify": { @@ -21529,7 +23725,8 @@ "prettier": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==" + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true }, "pretty-hrtime": { "version": "1.0.3", @@ -22011,13 +24208,15 @@ } }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-dir": { @@ -22097,6 +24296,52 @@ } } }, + "rollup": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", + "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", + "dev": true, + "peer": true, + "requires": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.0", + "@rollup/rollup-android-arm64": "4.63.0", + "@rollup/rollup-darwin-arm64": "4.63.0", + "@rollup/rollup-darwin-x64": "4.63.0", + "@rollup/rollup-freebsd-arm64": "4.63.0", + "@rollup/rollup-freebsd-x64": "4.63.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", + "@rollup/rollup-linux-arm-musleabihf": "4.63.0", + "@rollup/rollup-linux-arm64-gnu": "4.63.0", + "@rollup/rollup-linux-arm64-musl": "4.63.0", + "@rollup/rollup-linux-loong64-gnu": "4.63.0", + "@rollup/rollup-linux-loong64-musl": "4.63.0", + "@rollup/rollup-linux-ppc64-gnu": "4.63.0", + "@rollup/rollup-linux-ppc64-musl": "4.63.0", + "@rollup/rollup-linux-riscv64-gnu": "4.63.0", + "@rollup/rollup-linux-riscv64-musl": "4.63.0", + "@rollup/rollup-linux-s390x-gnu": "4.63.0", + "@rollup/rollup-linux-x64-gnu": "4.63.0", + "@rollup/rollup-linux-x64-musl": "4.63.0", + "@rollup/rollup-openbsd-x64": "4.63.0", + "@rollup/rollup-openharmony-arm64": "4.63.0", + "@rollup/rollup-win32-arm64-msvc": "4.63.0", + "@rollup/rollup-win32-ia32-msvc": "4.63.0", + "@rollup/rollup-win32-x64-gnu": "4.63.0", + "@rollup/rollup-win32-x64-msvc": "4.63.0", + "@types/estree": "1.0.9", + "fsevents": "~2.3.2" + }, + "dependencies": { + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -22774,14 +25019,16 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true }, "sver-compat": { "version": "1.5.0", @@ -23210,7 +25457,8 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "dev": true, + "peer": true }, "typical": { "version": "7.3.0", @@ -23573,6 +25821,11 @@ "source-map": "^0.5.1" } }, + "w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "walk-back": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", diff --git a/package.json b/package.json index 41d182138a..938a134819 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,13 @@ "SHA": "" }, "devDependencies": { + "@babel/core": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", "@commitlint/cli": "^16.0.2", "@commitlint/config-conventional": "^16.0.0", "@playwright/test": "^1.38.1", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", "del": "^6.0.0", "eslint": "^8.18.0", "glob": "^8.1.0", @@ -39,6 +43,7 @@ "jsdoc-to-markdown": "^9.1.1", "lmdb": "^3.5.1", "readable-stream": "^3.6.0", + "rollup": "^4.63.0", "through2": "^4.0.2" }, "scripts": { @@ -62,6 +67,7 @@ "_compileLessSrc": "lessc --math=always --compress src/styles/brackets.less src/styles/brackets-all.css --source-map && npm run _compileLessSrcGit", "_compileLessSrcGit": "lessc --math=always --compress src/extensions/default/Git/styles/git-styles.less src/extensions/default/Git/styles/git-styles-min.css --source-map", "buildMDViewer": "cd src-mdviewer && npm install && npx vite build", + "build:codemirror6": "node build/build-codemirror6.mjs", "_buildonly": "gulp build", "_buildonlyDebug": "gulp buildDebug", "_vulnerabilityCheck": "echo Scanning for vulnarabilities && npm audit --prod --audit-level=critical", @@ -72,6 +78,8 @@ "release:dev": "npm run buildMDViewer && gulp releaseDev", "release:staging": "npm run buildMDViewer && gulp releaseStaging", "release:prod": "npm run buildMDViewer && gulp releaseProd", + "test:codemirror-validation": "node --test build/test/validate-codemirror5.test.js", + "validate:codemirror": "gulp validateNoCodeMirror5", "validate:dist-size": "gulp validateDistSizeRestrictions", "_releaseWebCache": "gulp releaseWebCache", "_patchVersionBump": "gulp patchVersionBump", @@ -93,17 +101,38 @@ ], "dependencies": { "@bugsnag/js": "^7.18.0", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/commands": "^6.11.0", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.12", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.5.2", + "@codemirror/lang-php": "^6.0.2", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.4", + "@codemirror/legacy-modes": "^6.5.3", + "@codemirror/lint": "^6.9.7", + "@codemirror/search": "^6.7.1", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.9", "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", + "@lezer/highlight": "^1.2.3", "@phcode/fs": "^4.0.2", "@phcode/language-support": "^1.1.0", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "^0.22.2", + "@replit/codemirror-vim-core": "^0.1.0", "@uiw/file-icons": "^1.3.2", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-search": "^0.16.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/addon-webgl": "^0.19.0", + "@xterm/xterm": "^6.0.0", "bootstrap": "^5.1.3", "browser-mime": "^1.0.1", - "codemirror": "^5.65.16", "cross-env": "^7.0.3", "devicon": "^2.15.1", "emmet": "^2.4.11", @@ -121,11 +150,6 @@ "requirejs": "^2.3.7", "tern": "^0.24.3", "tinycolor2": "^1.4.2", - "underscore": "^1.13.4", - "@xterm/xterm": "^6.0.0", - "@xterm/addon-fit": "^0.11.0", - "@xterm/addon-search": "^0.16.0", - "@xterm/addon-web-links": "^0.12.0", - "@xterm/addon-webgl": "^0.19.0" + "underscore": "^1.13.4" } -} \ No newline at end of file +} diff --git a/phoenix-builder-mcp/README.md b/phoenix-builder-mcp/README.md index 9d088b8535..7de3247bb5 100644 --- a/phoenix-builder-mcp/README.md +++ b/phoenix-builder-mcp/README.md @@ -1,6 +1,8 @@ # Phoenix Builder MCP -An MCP (Model Context Protocol) server that lets Claude Code launch, control, and inspect a running Phoenix Code instance. It also includes a Chrome extension that enables screenshot capture when Phoenix runs in a browser. +An MCP (Model Context Protocol) server that lets Claude Code or Codex launch, +control, and inspect a running Phoenix Code instance. It also includes a Chrome +extension that enables screenshot capture when Phoenix runs in a browser. ## Prerequisites @@ -37,8 +39,76 @@ The project root already contains `.mcp.json` which registers the server automat Set `PHOENIX_DESKTOP_PATH` to the path of your phoenix-desktop checkout if it is not at `../phoenix-desktop`. You can also set `PHOENIX_MCP_WS_PORT` (default `38571`) to change the WebSocket port used for communication between the MCP server and the Phoenix browser runtime. +The control socket listens only on `127.0.0.1`. -### 3. Chrome extension (for browser screenshots) +### 3. Codex MCP configuration + +Register the same local stdio server with Codex. Use absolute paths because +Codex may start the server without your interactive shell's Node.js setup: + +```bash +codex mcp add phoenix-builder \ + --env PATH=/absolute/path/to/node/bin:/usr/local/bin:/usr/bin:/bin \ + --env PHOENIX_PROJECT_PATH=/absolute/path/to/phoenix \ + --env PHOENIX_DESKTOP_PATH=/absolute/path/to/phoenix-desktop \ + --env PHOENIX_MCP_WS_PORT=38572 \ + -- /absolute/path/to/node /absolute/path/to/phoenix/phoenix-builder-mcp/index.js +``` + +The CLI stores this registration in `~/.codex/config.toml` by default. The +separate WebSocket port keeps this Codex server independent from a Claude Code +server using the default port (`38571`). + +Verify the saved registration with: + +```bash +codex mcp get phoenix-builder --json +``` + +The result should report an enabled `stdio` transport and the absolute command, +arguments, and environment shown above. An `auth_status` of `unsupported` is +normal for a local stdio server; it does not require HTTP authentication. +Start a new Codex session after adding or changing an MCP registration so the +session receives the server's current tool inventory. + +Keep the Node.js installation directory in the configured `PATH` so the +`start_phoenix` and `build_phoenix` tools can find `npm`. The project path +defaults to the parent directory of `phoenix-builder-mcp`; set +`PHOENIX_PROJECT_PATH` explicitly when the server is installed elsewhere. +Adjust the remaining directories for your operating system. + +Each concurrently running MCP server process must use a unique +`PHOENIX_MCP_WS_PORT`, and each Phoenix app or test-runner instance must connect +to its matching URL. For the example above, set the Phoenix Builder connection +URL to `ws://127.0.0.1:38572`. The Phoenix connection URL is stored in the app; +setting the MCP environment variable does not rewrite it automatically. + +If another process already owns the configured port, the new MCP server exits +with an `EADDRINUSE` error. It never terminates or replaces the existing owner. +For a second concurrent Codex session, use a separate Codex configuration with +a different port, such as `38573`, and connect a separate Phoenix instance to +that port. + +Codex exposes server-level `enabled_tools` and `disabled_tools` filters in +`config.toml`. Nested per-tool `approval_mode` tables are not part of the +configuration returned by `codex mcp get`; use the supported filters when tool +availability must be restricted. + +On a Linux development host where Electron's setuid sandbox helper is +unavailable or does not have the required root ownership and mode, add +`--env ELECTRON_DISABLE_SANDBOX=1` to the command. This disables Electron's +sandbox for the launched development app, so use it only in an isolated local +development environment and omit it when the sandbox helper is configured +correctly. + +Run the MCP server's isolated tests with: + +```bash +cd phoenix-builder-mcp +npm test +``` + +### 4. Chrome extension (for browser screenshots) Screenshots work out of the box in the Electron/Tauri desktop app. If you are running Phoenix in a browser (e.g. `localhost` or `phcode.dev`), you need to install the Chrome extension: @@ -69,11 +139,30 @@ chrome --pack-extension=./phoenix-builder-mcp/chrome_extension --pack-extension- ## MCP Tools -Once the MCP server is running, the following tools are available in Claude Code: +Once the MCP server is running, the following tools are available in Claude Code +or Codex: ### `start_phoenix` Launches the Phoenix Code Electron app by running `npm run serve:electron` in the phoenix-desktop directory. Returns the process PID and WebSocket port. +### `build_phoenix` +Starts an allowlisted Phoenix build and returns immediately. Supported targets +include the CM6 bundle, source builds, full builds, development/staging/ +production release builds, and standalone distribution-size validation +(`validate-dist-size`). Use `get_build_status` and `get_build_logs` to monitor +it, or `stop_build` to terminate it. + +### `get_build_status` +Returns the current or most recent build state, including its process ID, npm +script, timestamps, exit code, and signal. + +### `get_build_logs` +Returns buffered stdout/stderr from the current or most recent build. + +### `stop_build` +Stops the active build process tree, escalating from SIGTERM to SIGKILL after +the configured grace period. + ### `stop_phoenix` Stops the running Phoenix Code process (SIGTERM, then SIGKILL after 5s). @@ -97,7 +186,27 @@ Reloads the Phoenix app. Prompts to save unsaved files before reloading. ### `force_reload_phoenix` Force-reloads the Phoenix app without saving unsaved changes. -## Typical Claude Code workflow +### `exec_js` +Executes asynchronous JavaScript in the connected Phoenix runtime. + +### `exec_js_in_live_preview` +Executes synchronous JavaScript in the active HTML live-preview iframe. + +### `exec_js_in_test_iframe` +Executes asynchronous JavaScript in the embedded Phoenix iframe created by +integration and legacy-integration tests. + +### `run_tests` +Reloads a connected Phoenix test runner with one supported category: `unit`, +`integration`, `LegacyInteg`, `livepreview`, or `mainview`. The optional `spec` +must use the exact Jasmine suite or test name; suite names are not consistently +category-prefixed. + +### `get_test_results` +Returns structured progress, counts, and failure details from the connected +test runner. + +## Typical agent workflow ``` > start_phoenix # launches the app @@ -111,13 +220,13 @@ Force-reloads the Phoenix app without saving unsaved changes. ## Architecture ``` -Claude Code <--stdio--> MCP Server (index.js) - | - +-- process-manager.js (spawns/kills Electron) - +-- ws-control-server.js (WebSocket on port 38571) - | - Phoenix browser runtime - (connects back over WS for logs, screenshots, reload) +Claude Code / Codex <--stdio--> MCP Server (index.js) + | + +-- process-manager.js (spawns/kills Electron) + +-- ws-control-server.js (WebSocket on configured port) + | + Phoenix browser runtime + (connects back over WS for logs, screenshots, reload) ``` For browser-mode screenshots the flow is: diff --git a/phoenix-builder-mcp/build-manager.js b/phoenix-builder-mcp/build-manager.js new file mode 100644 index 0000000000..61dc21d5a4 --- /dev/null +++ b/phoenix-builder-mcp/build-manager.js @@ -0,0 +1,248 @@ +import { spawn as nodeSpawn } from "node:child_process"; +import process from "node:process"; +import { LogBuffer } from "./log-buffer.js"; +import { terminateProcessTree } from "./process-manager.js"; + +const DEFAULT_STOP_GRACE_MS = 5000; +const DEFAULT_FORCE_EXIT_GRACE_MS = 1000; + +function _hasExited(child) { + return child.exitCode !== null && child.exitCode !== undefined + || child.signalCode !== null && child.signalCode !== undefined; +} + +function _now() { + return new Date().toISOString(); +} + +export function createBuildManager(options = {}) { + const spawnImpl = options.spawnImpl || nodeSpawn; + const terminateProcessTreeImpl = options.terminateProcessTreeImpl || terminateProcessTree; + const stopGraceMs = options.stopGraceMs ?? DEFAULT_STOP_GRACE_MS; + const forceExitGraceMs = options.forceExitGraceMs ?? DEFAULT_FORCE_EXIT_GRACE_MS; + const platform = options.platform || process.platform; + + let childProcess = null; + let buildState = null; + const buildLogs = new LogBuffer(); + + function _snapshot() { + if (!buildState) { + return { + status: "idle", + running: false, + pid: null + }; + } + + return { + ...buildState, + running: childProcess !== null + }; + } + + function _finishBuild(child, status, details = {}) { + if (childProcess !== child || !buildState) { + return; + } + + childProcess = null; + buildState = { + ...buildState, + status, + finishedAt: _now(), + ...details + }; + } + + function start(phoenixProjectPath, npmScript) { + if (childProcess) { + throw new Error( + `Phoenix build "${buildState.npmScript}" is already running (pid ${childProcess.pid})` + ); + } + + buildLogs.clear(); + return new Promise((resolve, reject) => { + let child; + try { + child = spawnImpl("npm", ["run", npmScript], { + cwd: phoenixProjectPath, + shell: platform === "win32", + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env }, + detached: platform !== "win32", + windowsHide: true + }); + } catch (error) { + reject(error); + return; + } + + childProcess = child; + buildState = { + status: "starting", + running: true, + pid: child.pid, + npmScript, + projectPath: phoenixProjectPath, + startedAt: _now(), + finishedAt: null, + exitCode: null, + signal: null + }; + + if (child.stdout) { + child.stdout.on("data", (data) => { + buildLogs.push({ + stream: "stdout", + text: data.toString(), + timestamp: _now() + }); + }); + } + if (child.stderr) { + child.stderr.on("data", (data) => { + buildLogs.push({ + stream: "stderr", + text: data.toString(), + timestamp: _now() + }); + }); + } + + let startupSettled = false; + child.once("spawn", () => { + if (buildState && childProcess === child) { + buildState.status = "running"; + buildState.pid = child.pid; + } + startupSettled = true; + resolve(_snapshot()); + }); + + child.once("error", (error) => { + buildLogs.push({ + stream: "stderr", + text: `Build process error: ${error.message}`, + timestamp: _now() + }); + _finishBuild(child, "failed", { + error: error.message + }); + if (!startupSettled) { + startupSettled = true; + reject(error); + } + }); + + child.once("exit", (code, signal) => { + buildLogs.push({ + stream: code === 0 ? "stdout" : "stderr", + text: `Build process exited with code=${code} signal=${signal}`, + timestamp: _now() + }); + const status = buildState && buildState.status === "stopping" + ? "stopped" + : code === 0 ? "succeeded" : "failed"; + _finishBuild(child, status, { + exitCode: code, + signal + }); + if (!startupSettled) { + startupSettled = true; + reject(new Error( + `Phoenix build exited before startup completed (code=${code}, signal=${signal})` + )); + } + }); + }); + } + + function stop() { + if (!childProcess) { + return Promise.resolve({ + success: true, + message: "No Phoenix build is running", + build: _snapshot() + }); + } + + const child = childProcess; + buildState.status = "stopping"; + return new Promise((resolve, reject) => { + let settled = false; + let forced = false; + let forceKillTimer = null; + let forceExitTimer = null; + + const cleanup = () => { + clearTimeout(forceKillTimer); + clearTimeout(forceExitTimer); + child.off("exit", onExit); + }; + const finish = (error) => { + if (settled) { + return; + } + settled = true; + cleanup(); + if (error) { + reject(error); + return; + } + if (childProcess === child) { + _finishBuild(child, "stopped", { + signal: forced ? "SIGKILL" : "SIGTERM" + }); + } + resolve({ + success: true, + forced, + build: _snapshot() + }); + }; + const onExit = () => finish(); + + child.once("exit", onExit); + forceKillTimer = setTimeout(() => { + if (settled || childProcess !== child) { + finish(); + return; + } + + forced = true; + Promise.resolve(terminateProcessTreeImpl(child, "SIGKILL")) + .then((signalSent) => { + if (!signalSent || _hasExited(child)) { + finish(); + return; + } + forceExitTimer = setTimeout(() => { + finish(new Error( + `Phoenix build process tree ${child.pid} did not exit after SIGKILL` + )); + }, forceExitGraceMs); + }) + .catch(finish); + }, stopGraceMs); + + Promise.resolve(terminateProcessTreeImpl(child, "SIGTERM")) + .then((signalSent) => { + if (!signalSent || _hasExited(child)) { + finish(); + } + }) + .catch(finish); + }); + } + + return { + start, + stop, + getStatus: _snapshot, + getLogs: (tail, before) => buildLogs.getTail(tail, before), + clearLogs: () => buildLogs.clear(), + getLogsTotalPushed: () => buildLogs.totalPushed() + }; +} diff --git a/phoenix-builder-mcp/config.js b/phoenix-builder-mcp/config.js new file mode 100644 index 0000000000..54ea9553bd --- /dev/null +++ b/phoenix-builder-mcp/config.js @@ -0,0 +1,23 @@ +export const DEFAULT_WS_PORT = 38571; + +export function parseWebSocketPort(value) { + if (value === undefined || value === null || String(value).trim() === "") { + return DEFAULT_WS_PORT; + } + + const text = String(value).trim(); + if (!/^\d+$/.test(text)) { + throw new Error( + `PHOENIX_MCP_WS_PORT must be an integer between 1 and 65535; received "${text}"` + ); + } + + const port = Number(text); + if (!Number.isSafeInteger(port) || port < 1 || port > 65535) { + throw new Error( + `PHOENIX_MCP_WS_PORT must be an integer between 1 and 65535; received "${text}"` + ); + } + + return port; +} diff --git a/phoenix-builder-mcp/index.js b/phoenix-builder-mcp/index.js index 4b8f7f282e..c005b67073 100644 --- a/phoenix-builder-mcp/index.js +++ b/phoenix-builder-mcp/index.js @@ -2,72 +2,77 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createWSControlServer } from "./ws-control-server.js"; import { createProcessManager } from "./process-manager.js"; +import { createBuildManager } from "./build-manager.js"; import { registerTools } from "./mcp-tools.js"; +import { parseWebSocketPort } from "./config.js"; import { fileURLToPath } from "url"; import path from "path"; -import fs from "fs"; +import process from "node:process"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const PID_FILE = path.join(__dirname, ".mcp-server.pid"); +async function main() { + const wsPort = parseWebSocketPort(process.env.PHOENIX_MCP_WS_PORT); + const phoenixDesktopPath = process.env.PHOENIX_DESKTOP_PATH + || path.resolve(__dirname, "../../phoenix-desktop"); + const phoenixProjectPath = process.env.PHOENIX_PROJECT_PATH + || path.resolve(__dirname, ".."); -// Kill any previous MCP server instance that wasn't cleaned up (e.g. parent crashed). -try { - const oldPid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10); - if (oldPid && oldPid !== process.pid) { - try { - process.kill(oldPid, "SIGTERM"); - // Wait up to 3 seconds for it to exit - const deadline = Date.now() + 3000; - while (Date.now() < deadline) { - try { - process.kill(oldPid, 0); // throws if process is gone - await new Promise(r => setTimeout(r, 100)); - } catch { - break; - } - } - } catch { - // Process already dead — nothing to do - } - } -} catch { - // No PID file or unreadable — first run -} -fs.writeFileSync(PID_FILE, String(process.pid)); + // Bind the control socket before advertising MCP readiness over stdio. + // A second server on the same port must fail without terminating the owner. + const wsControlServer = await createWSControlServer(wsPort); + const processManager = createProcessManager(); + const buildManager = createBuildManager(); -function removePidFile() { - try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ } -} + const server = new McpServer({ + name: "phoenix-builder", + version: "1.0.0" + }); -const wsPort = parseInt(process.env.PHOENIX_MCP_WS_PORT || "38571", 10); -const phoenixDesktopPath = process.env.PHOENIX_DESKTOP_PATH - || path.resolve(__dirname, "../../phoenix-desktop"); + registerTools( + server, + processManager, + wsControlServer, + phoenixDesktopPath, + buildManager, + phoenixProjectPath + ); -const wsControlServer = createWSControlServer(wsPort); -const processManager = createProcessManager(); + const transport = new StdioServerTransport(); + await server.connect(transport); -const server = new McpServer({ - name: "phoenix-builder", - version: "1.0.0" -}); + let shutdownPromise = null; -registerTools(server, processManager, wsControlServer, phoenixDesktopPath); + async function shutdown(exitCode) { + if (shutdownPromise) { + return shutdownPromise; + } -const transport = new StdioServerTransport(); -await server.connect(transport); + shutdownPromise = (async () => { + try { + await Promise.allSettled([ + processManager.stop(), + buildManager.stop() + ]); + } finally { + try { + await wsControlServer.close(); + } finally { + process.exit(exitCode); + } + } + })(); + return shutdownPromise; + } -process.on("SIGINT", async () => { - await processManager.stop(); - wsControlServer.close(); - removePidFile(); - process.exit(0); -}); + process.stdin.once("end", () => shutdown(0)); + process.stdin.once("close", () => shutdown(0)); + process.once("SIGINT", () => shutdown(0)); + process.once("SIGTERM", () => shutdown(0)); +} -process.on("SIGTERM", async () => { - await processManager.stop(); - wsControlServer.close(); - removePidFile(); - process.exit(0); +main().catch((error) => { + console.error(`[phoenix-builder] ${error.message}`); + process.exit(1); }); diff --git a/phoenix-builder-mcp/mcp-tools.js b/phoenix-builder-mcp/mcp-tools.js index 070ea24a91..a3f986a04d 100644 --- a/phoenix-builder-mcp/mcp-tools.js +++ b/phoenix-builder-mcp/mcp-tools.js @@ -1,6 +1,25 @@ import { z } from "zod"; const DEFAULT_MAX_CHARS = 10000; +const PHOENIX_BUILD_TARGETS = { + codemirror6: "build:codemirror6", + source: "_buildonly", + "source-debug": "_buildonlyDebug", + full: "build", + "full-debug": "build:debug", + "release-dev": "release:dev", + "release-staging": "release:staging", + "release-prod": "release:prod", + "validate-dist-size": "validate:dist-size" +}; + +const PHOENIX_TEST_CATEGORIES = [ + "unit", + "integration", + "LegacyInteg", + "livepreview", + "mainview" +]; function _trimToCharBudget(lines, maxChars) { let total = 0; @@ -15,7 +34,137 @@ function _trimToCharBudget(lines, maxChars) { return { lines: lines.slice(startIdx), trimmed: startIdx }; } -export function registerTools(server, processManager, wsControlServer, phoenixDesktopPath) { +export function registerTools( + server, + processManager, + wsControlServer, + phoenixDesktopPath, + buildManager, + phoenixProjectPath +) { + server.tool( + "build_phoenix", + "Start an allowlisted Phoenix repository build through npm. The build runs asynchronously; " + + "poll get_build_status and inspect get_build_logs until it succeeds or fails.", + { + target: z.enum(Object.keys(PHOENIX_BUILD_TARGETS)) + .default("full") + .describe("Build target: codemirror6, source, source-debug, full, full-debug, " + + "release-dev, release-staging, release-prod, or validate-dist-size.") + }, + async ({ target }) => { + try { + const npmScript = PHOENIX_BUILD_TARGETS[target]; + const result = await buildManager.start(phoenixProjectPath, npmScript); + return { + content: [{ + type: "text", + text: JSON.stringify({ + success: true, + target, + ...result + }) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ success: false, error: err.message }) + }] + }; + } + } + ); + + server.tool( + "get_build_status", + "Get the current or most recent Phoenix build status.", + {}, + async () => ({ + content: [{ + type: "text", + text: JSON.stringify(buildManager.getStatus()) + }] + }) + ); + + server.tool( + "get_build_logs", + "Get output from the current or most recent Phoenix build.", + { + clear: z.boolean().default(false).describe("Clear the build log buffer after reading."), + tail: z.number().default(50).describe("Return the last N entries. 0 = all."), + before: z.number().optional().describe("Return entries before this absolute log cursor."), + filter: z.string().optional().describe("Optional case-insensitive regex applied to log text."), + maxChars: z.number().default(DEFAULT_MAX_CHARS).describe( + "Maximum returned log characters. Oldest entries are dropped first. 0 = unlimited." + ) + }, + async ({ clear, tail, before, filter, maxChars }) => { + let logs = buildManager.getLogs(tail, before); + const totalEntries = buildManager.getLogsTotalPushed(); + if (clear) { + buildManager.clearLogs(); + } + + if (filter) { + let filterRe; + try { + filterRe = new RegExp(filter, "i"); + } catch (error) { + return { + content: [{ + type: "text", + text: `Invalid filter regex: ${error.message}` + }] + }; + } + logs = logs.filter((entry) => filterRe.test(entry.text)); + } + + let lines = logs.map((entry) => `[${entry.stream}] ${entry.text}`); + let trimmed = 0; + if (maxChars > 0) { + const result = _trimToCharBudget(lines, maxChars); + lines = result.lines; + trimmed = result.trimmed; + } + const header = `[Build logs: ${totalEntries} total, showing ${lines.length}` + + (trimmed ? `, ${trimmed} trimmed` : "") + "]"; + return { + content: [{ + type: "text", + text: lines.length ? `${header}\n${lines.join("")}` : "(no build logs)" + }] + }; + } + ); + + server.tool( + "stop_build", + "Stop the active Phoenix build process tree.", + {}, + async () => { + try { + const result = await buildManager.stop(); + return { + content: [{ + type: "text", + text: JSON.stringify(result) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ success: false, error: err.message }) + }] + }; + } + } + ); + server.tool( "start_phoenix", "Start the Phoenix Code desktop app (Electron). Launches npm run serve:electron in the phoenix-desktop directory.", @@ -427,15 +576,19 @@ export function registerTools(server, processManager, wsControlServer, phoenixDe "not actively supported and the full 'all' suite should never be run. " + "To run all tests in a category, omit the spec parameter. " + "To run a single suite, pass the suite name as spec (e.g. spec='unit: HTML Code Hinting'). " + - "Suite names are prefixed with the category and a colon, e.g. 'unit: Editor', 'unit: CSS Parsing'. " + + "Suite names are not consistently category-prefixed; use the exact Jasmine suite description. " + + "Examples include 'CSS Parsing', 'unit:Phoenix Platform Tests', and " + + "'LegacyInteg:ExtensionLoader'. " + "You can also run individual specs by passing the full spec name, but note that individual specs " + "may fail when run alone because suites often run tests in order with shared state — prefer " + "running the full suite instead of individual specs. " + "After calling run_tests, use get_test_results to poll for results.", { - category: z.string().describe("Test category to run: unit, integration, LegacyInteg, livepreview, or mainview."), + category: z.enum(PHOENIX_TEST_CATEGORIES) + .describe("Test category to run: unit, integration, LegacyInteg, livepreview, or mainview."), spec: z.string().optional().describe("Optional suite or spec name to run within the category. " + - "Use the full name including category prefix, e.g. 'unit: CSS Parsing' for a suite. " + + "Use the exact Jasmine suite/spec name; a category prefix is only present when the " + + "suite itself includes one. " + "Prefer running full suites over individual specs, as specs may depend on suite execution order. " + "Omit to run all tests in the category."), instance: z.string().optional().describe("Target a specific test runner instance by name. Required when multiple instances are connected.") diff --git a/phoenix-builder-mcp/package.json b/phoenix-builder-mcp/package.json index 48c700d3e3..5f12875112 100644 --- a/phoenix-builder-mcp/package.json +++ b/phoenix-builder-mcp/package.json @@ -4,6 +4,9 @@ "private": true, "type": "module", "main": "index.js", + "scripts": { + "test": "node --test test/build-manager.test.js test/config.test.js test/process-manager.test.js test/ws-control-server.test.js" + }, "dependencies": { "@modelcontextprotocol/sdk": "latest", "ws": "^8.0.0", diff --git a/phoenix-builder-mcp/process-manager.js b/phoenix-builder-mcp/process-manager.js index cafc8b0289..2d3de585c3 100644 --- a/phoenix-builder-mcp/process-manager.js +++ b/phoenix-builder-mcp/process-manager.js @@ -1,95 +1,306 @@ -import { spawn } from "child_process"; +import { spawn as nodeSpawn } from "child_process"; +import process from "node:process"; import { LogBuffer } from "./log-buffer.js"; -export function createProcessManager() { +const DEFAULT_STARTUP_GRACE_MS = 500; +const DEFAULT_STOP_GRACE_MS = 5000; +const DEFAULT_FORCE_EXIT_GRACE_MS = 1000; + +function _isMissingProcessError(error) { + return error && error.code === "ESRCH"; +} + +function _hasExited(child) { + return child.exitCode !== null && child.exitCode !== undefined + || child.signalCode !== null && child.signalCode !== undefined; +} + +function _killDirectChild(child, signal) { + try { + return child.kill(signal); + } catch (error) { + if (_isMissingProcessError(error)) { + return false; + } + throw error; + } +} + +/** + * Signal a spawned process and all descendants. + * + * POSIX children are launched as process-group leaders, so a negative PID + * targets the complete group. Windows uses taskkill /T and falls back to the + * direct child if taskkill is unavailable. + * + * @param {ChildProcess} child + * @param {string} signal + * @param {{platform?: string, spawnImpl?: Function}} options + * @return {Promise} Whether a signal was sent. + */ +export function terminateProcessTree(child, signal, options = {}) { + if (!child || !Number.isInteger(child.pid) || child.pid <= 0) { + return Promise.resolve(false); + } + + const platform = options.platform || process.platform; + const spawnImpl = options.spawnImpl || nodeSpawn; + + if (platform !== "win32") { + try { + process.kill(-child.pid, signal); + return Promise.resolve(true); + } catch (error) { + if (_isMissingProcessError(error)) { + return Promise.resolve(false); + } + return Promise.reject(error); + } + } + + return new Promise((resolve, reject) => { + const args = ["/PID", String(child.pid), "/T"]; + if (signal === "SIGKILL") { + args.push("/F"); + } + + let taskkill; + try { + taskkill = spawnImpl("taskkill.exe", args, { + stdio: "ignore", + windowsHide: true + }); + } catch (error) { + try { + resolve(_killDirectChild(child, signal)); + } catch (fallbackError) { + reject(fallbackError); + } + return; + } + + let settled = false; + const finishWithFallback = () => { + if (settled) { + return; + } + settled = true; + try { + resolve(_killDirectChild(child, signal)); + } catch (error) { + reject(error); + } + }; + + taskkill.once("error", finishWithFallback); + taskkill.once("exit", (code) => { + if (settled) { + return; + } + settled = true; + if (code === 0) { + resolve(true); + return; + } + try { + resolve(_killDirectChild(child, signal)); + } catch (error) { + reject(error); + } + }); + }); +} + +export function createProcessManager(options = {}) { + const spawnImpl = options.spawnImpl || nodeSpawn; + const terminateProcessTreeImpl = options.terminateProcessTreeImpl || terminateProcessTree; + const startupGraceMs = options.startupGraceMs ?? DEFAULT_STARTUP_GRACE_MS; + const stopGraceMs = options.stopGraceMs ?? DEFAULT_STOP_GRACE_MS; + const forceExitGraceMs = options.forceExitGraceMs ?? DEFAULT_FORCE_EXIT_GRACE_MS; + const platform = options.platform || process.platform; + let childProcess = null; const terminalLogs = new LogBuffer(); + function _pushProcessError(prefix, error) { + terminalLogs.push({ + stream: "stderr", + text: `${prefix}: ${error.message}`, + timestamp: new Date().toISOString() + }); + } + function start(phoenixDesktopPath) { if (childProcess) { throw new Error("Phoenix is already running. Stop it first."); } return new Promise((resolve, reject) => { - const child = spawn("npm", ["run", "serve:electron"], { - cwd: phoenixDesktopPath, - shell: true, - stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env } - }); + const npmCommand = "npm"; + let child; + try { + child = spawnImpl(npmCommand, ["run", "serve:electron"], { + cwd: phoenixDesktopPath, + shell: platform === "win32", + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env }, + detached: platform !== "win32", + windowsHide: true + }); + } catch (error) { + reject(error); + return; + } childProcess = child; + let startupSettled = false; + let startupTimer = null; - child.stdout.on("data", (data) => { - const text = data.toString(); - terminalLogs.push({ - stream: "stdout", - text, - timestamp: new Date().toISOString() + if (child.stdout) { + child.stdout.on("data", (data) => { + terminalLogs.push({ + stream: "stdout", + text: data.toString(), + timestamp: new Date().toISOString() + }); }); - }); + } - child.stderr.on("data", (data) => { - const text = data.toString(); - terminalLogs.push({ - stream: "stderr", - text, - timestamp: new Date().toISOString() + if (child.stderr) { + child.stderr.on("data", (data) => { + terminalLogs.push({ + stream: "stderr", + text: data.toString(), + timestamp: new Date().toISOString() + }); }); - }); + } - child.on("error", (err) => { - terminalLogs.push({ - stream: "stderr", - text: `Process error: ${err.message}`, - timestamp: new Date().toISOString() - }); - childProcess = null; - reject(err); + child.once("error", (error) => { + _pushProcessError("Process error", error); + if (childProcess === child) { + childProcess = null; + } + if (!startupSettled) { + startupSettled = true; + clearTimeout(startupTimer); + reject(error); + } }); - child.on("exit", (code, signal) => { + child.once("exit", (code, signal) => { terminalLogs.push({ stream: "stderr", text: `Process exited with code=${code} signal=${signal}`, timestamp: new Date().toISOString() }); - childProcess = null; + if (childProcess === child) { + childProcess = null; + } + if (!startupSettled) { + startupSettled = true; + clearTimeout(startupTimer); + reject(new Error( + `Phoenix exited before startup completed (code=${code}, signal=${signal})` + )); + } }); - // Give the process a moment to start or fail - setTimeout(() => { - if (childProcess) { + child.once("spawn", () => { + startupTimer = setTimeout(() => { + if (startupSettled) { + return; + } + if (childProcess !== child || _hasExited(child)) { + startupSettled = true; + reject(new Error("Phoenix exited before startup completed")); + return; + } + startupSettled = true; resolve({ pid: child.pid }); - } - }, 500); + }, startupGraceMs); + }); }); } function stop() { - return new Promise((resolve) => { - if (!childProcess) { - resolve({ success: true, message: "No process running" }); - return; - } + if (!childProcess) { + return Promise.resolve({ success: true, message: "No process running" }); + } - const child = childProcess; - let killed = false; + const child = childProcess; + return new Promise((resolve, reject) => { + let settled = false; + let forced = false; + let forceKillTimer = null; + let forceExitTimer = null; - const forceKillTimeout = setTimeout(() => { + const cleanupTimers = () => { + clearTimeout(forceKillTimer); + clearTimeout(forceExitTimer); + }; + + const finish = (result, error) => { + if (settled) { + return; + } + settled = true; + cleanupTimers(); + child.off("exit", onExit); + if (error) { + reject(error); + return; + } if (childProcess === child) { - child.kill("SIGKILL"); - killed = true; + childProcess = null; } - }, 5000); + resolve(result); + }; - child.on("exit", () => { - clearTimeout(forceKillTimeout); - childProcess = null; - resolve({ success: true, forced: killed }); - }); + const onExit = () => { + finish({ success: true, forced }); + }; + + child.once("exit", onExit); - child.kill("SIGTERM"); + forceKillTimer = setTimeout(() => { + if (settled || childProcess !== child) { + finish({ success: true, forced }); + return; + } + + forced = true; + Promise.resolve(terminateProcessTreeImpl(child, "SIGKILL")) + .then((signalSent) => { + if (!signalSent || _hasExited(child)) { + finish({ success: true, forced: true }); + return; + } + forceExitTimer = setTimeout(() => { + finish( + null, + new Error( + `Phoenix process tree ${child.pid} did not exit after SIGKILL` + ) + ); + }, forceExitGraceMs); + }) + .catch((error) => { + _pushProcessError("Failed to force-stop Phoenix process tree", error); + finish(null, error); + }); + }, stopGraceMs); + + Promise.resolve(terminateProcessTreeImpl(child, "SIGTERM")) + .then((signalSent) => { + if (!signalSent || _hasExited(child)) { + finish({ success: true, forced: false }); + } + }) + .catch((error) => { + _pushProcessError("Failed to stop Phoenix process tree", error); + finish(null, error); + }); }); } diff --git a/phoenix-builder-mcp/test/build-manager.test.js b/phoenix-builder-mcp/test/build-manager.test.js new file mode 100644 index 0000000000..5b981aa844 --- /dev/null +++ b/phoenix-builder-mcp/test/build-manager.test.js @@ -0,0 +1,93 @@ +import assert from "node:assert/strict"; +import { EventEmitter } from "node:events"; +import process from "node:process"; +import { PassThrough } from "node:stream"; +import test from "node:test"; +import { createBuildManager } from "../build-manager.js"; + +class FakeChild extends EventEmitter { + constructor(pid = 5151) { + super(); + this.pid = pid; + this.stdout = new PassThrough(); + this.stderr = new PassThrough(); + this.exitCode = null; + this.signalCode = null; + } + + emitExit(code, signal) { + this.exitCode = code; + this.signalCode = signal; + this.emit("exit", code, signal); + } +} + +test("starts an npm build without a POSIX shell and records success", async () => { + const child = new FakeChild(); + let spawnCall; + const manager = createBuildManager({ + spawnImpl(command, args, options) { + spawnCall = { command, args, options }; + queueMicrotask(() => child.emit("spawn")); + return child; + } + }); + + const started = await manager.start("/tmp/phoenix", "release:dev"); + assert.equal(started.status, "running"); + assert.equal(started.pid, child.pid); + assert.equal(spawnCall.command, "npm"); + assert.deepEqual(spawnCall.args, ["run", "release:dev"]); + assert.equal(spawnCall.options.cwd, "/tmp/phoenix"); + assert.equal(spawnCall.options.shell, process.platform === "win32"); + + child.stdout.write("building\n"); + child.emitExit(0, null); + + const finished = manager.getStatus(); + assert.equal(finished.status, "succeeded"); + assert.equal(finished.running, false); + assert.equal(finished.exitCode, 0); + assert.match(manager.getLogs(0)[0].text, /building/); +}); + +test("rejects a second build while one is running", async () => { + const child = new FakeChild(); + const manager = createBuildManager({ + spawnImpl() { + queueMicrotask(() => child.emit("spawn")); + return child; + } + }); + + await manager.start("/tmp/phoenix", "build"); + assert.throws( + () => manager.start("/tmp/phoenix", "release:prod"), + /already running/ + ); + child.emitExit(0, null); +}); + +test("stop terminates the complete build process tree", async () => { + const child = new FakeChild(); + const signals = []; + const manager = createBuildManager({ + spawnImpl() { + queueMicrotask(() => child.emit("spawn")); + return child; + }, + terminateProcessTreeImpl: async (target, signal) => { + signals.push(signal); + queueMicrotask(() => target.emitExit(null, signal)); + return true; + } + }); + + await manager.start("/tmp/phoenix", "build"); + const result = await manager.stop(); + + assert.equal(result.success, true); + assert.equal(result.forced, false); + assert.deepEqual(signals, ["SIGTERM"]); + assert.equal(manager.getStatus().running, false); +}); diff --git a/phoenix-builder-mcp/test/config.test.js b/phoenix-builder-mcp/test/config.test.js new file mode 100644 index 0000000000..fde0007316 --- /dev/null +++ b/phoenix-builder-mcp/test/config.test.js @@ -0,0 +1,22 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { DEFAULT_WS_PORT, parseWebSocketPort } from "../config.js"; + +test("parseWebSocketPort uses the default for an unset value", () => { + assert.equal(parseWebSocketPort(), DEFAULT_WS_PORT); + assert.equal(parseWebSocketPort(""), DEFAULT_WS_PORT); +}); + +test("parseWebSocketPort accepts a complete valid integer", () => { + assert.equal(parseWebSocketPort("38572"), 38572); + assert.equal(parseWebSocketPort(" 38573 "), 38573); +}); + +test("parseWebSocketPort rejects partial or out-of-range values", () => { + for (const value of ["38572extra", "0", "-1", "65536", "not-a-port"]) { + assert.throws( + () => parseWebSocketPort(value), + /must be an integer between 1 and 65535/ + ); + } +}); diff --git a/phoenix-builder-mcp/test/fixtures/process-tree-parent.js b/phoenix-builder-mcp/test/fixtures/process-tree-parent.js new file mode 100644 index 0000000000..ef1fd7ca27 --- /dev/null +++ b/phoenix-builder-mcp/test/fixtures/process-tree-parent.js @@ -0,0 +1,10 @@ +import { spawn } from "node:child_process"; + +const grandchild = spawn( + process.execPath, + ["-e", "setInterval(() => {}, 1000);"], + { stdio: "ignore" } +); + +process.stdout.write(`${grandchild.pid}\n`); +setInterval(() => {}, 1000); diff --git a/phoenix-builder-mcp/test/process-manager.test.js b/phoenix-builder-mcp/test/process-manager.test.js new file mode 100644 index 0000000000..1f33c2c8d6 --- /dev/null +++ b/phoenix-builder-mcp/test/process-manager.test.js @@ -0,0 +1,180 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { EventEmitter, once } from "node:events"; +import path from "node:path"; +import process from "node:process"; +import { PassThrough } from "node:stream"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { + createProcessManager, + terminateProcessTree +} from "../process-manager.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +class FakeChild extends EventEmitter { + constructor(pid = 4242) { + super(); + this.pid = pid; + this.stdout = new PassThrough(); + this.stderr = new PassThrough(); + this.exitCode = null; + this.signalCode = null; + } + + emitExit(code, signal) { + this.exitCode = code; + this.signalCode = signal; + this.emit("exit", code, signal); + } + + kill() { + return true; + } +} + +function _isProcessRunning(pid) { + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error.code !== "ESRCH"; + } +} + +async function _waitFor(condition, timeoutMs = 5000) { + const deadline = Date.now() + timeoutMs; + while (!condition()) { + if (Date.now() >= deadline) { + throw new Error("Timed out waiting for process state"); + } + await new Promise((resolve) => setTimeout(resolve, 20)); + } +} + +test("start forwards the configured environment and avoids a POSIX shell wrapper", async (t) => { + const previousSandboxValue = process.env.ELECTRON_DISABLE_SANDBOX; + process.env.ELECTRON_DISABLE_SANDBOX = "1"; + t.after(() => { + if (previousSandboxValue === undefined) { + delete process.env.ELECTRON_DISABLE_SANDBOX; + } else { + process.env.ELECTRON_DISABLE_SANDBOX = previousSandboxValue; + } + }); + + const child = new FakeChild(); + let spawnCall; + const signals = []; + const manager = createProcessManager({ + startupGraceMs: 0, + spawnImpl(command, args, options) { + spawnCall = { command, args, options }; + queueMicrotask(() => child.emit("spawn")); + return child; + }, + terminateProcessTreeImpl: async (target, signal) => { + signals.push(signal); + queueMicrotask(() => target.emitExit(null, signal)); + return true; + } + }); + + const result = await manager.start("/tmp/phoenix-desktop-fixture"); + assert.equal(result.pid, child.pid); + assert.equal(spawnCall.command, "npm"); + assert.deepEqual(spawnCall.args, ["run", "serve:electron"]); + assert.equal(spawnCall.options.shell, process.platform === "win32"); + assert.equal(spawnCall.options.detached, process.platform !== "win32"); + assert.equal(spawnCall.options.env.ELECTRON_DISABLE_SANDBOX, "1"); + + const stopResult = await manager.stop(); + assert.deepEqual(stopResult, { success: true, forced: false }); + assert.deepEqual(signals, ["SIGTERM"]); +}); + +test("start rejects when the child exits during the startup grace period", async () => { + const child = new FakeChild(); + const manager = createProcessManager({ + startupGraceMs: 50, + spawnImpl() { + queueMicrotask(() => { + child.emit("spawn"); + child.emitExit(127, null); + }); + return child; + } + }); + + await assert.rejects( + manager.start("/tmp/phoenix-desktop-fixture"), + /exited before startup completed \(code=127, signal=null\)/ + ); + assert.equal(manager.isRunning(), false); +}); + +test("stop escalates to the complete process tree after the grace period", async () => { + const child = new FakeChild(); + const signals = []; + const manager = createProcessManager({ + startupGraceMs: 0, + stopGraceMs: 5, + forceExitGraceMs: 100, + spawnImpl() { + queueMicrotask(() => child.emit("spawn")); + return child; + }, + terminateProcessTreeImpl: async (target, signal) => { + signals.push(signal); + if (signal === "SIGKILL") { + queueMicrotask(() => target.emitExit(null, signal)); + } + return true; + } + }); + + await manager.start("/tmp/phoenix-desktop-fixture"); + const result = await manager.stop(); + assert.deepEqual(result, { success: true, forced: true }); + assert.deepEqual(signals, ["SIGTERM", "SIGKILL"]); +}); + +test("terminateProcessTree stops a detached POSIX process and its descendant", { + skip: process.platform === "win32" +}, async (t) => { + const fixturePath = path.join(__dirname, "fixtures", "process-tree-parent.js"); + const child = spawn(process.execPath, [fixturePath], { + detached: true, + stdio: ["ignore", "pipe", "pipe"] + }); + let grandchildPid = null; + + t.after(async () => { + if (_isProcessRunning(child.pid)) { + await terminateProcessTree(child, "SIGKILL"); + } + if (grandchildPid && _isProcessRunning(grandchildPid)) { + try { + process.kill(grandchildPid, "SIGKILL"); + } catch { + // The fixture already exited. + } + } + }); + + const [data] = await once(child.stdout, "data"); + grandchildPid = Number(data.toString().trim()); + assert.ok(Number.isInteger(grandchildPid)); + assert.equal(_isProcessRunning(child.pid), true); + assert.equal(_isProcessRunning(grandchildPid), true); + + const exitPromise = once(child, "exit"); + assert.equal(await terminateProcessTree(child, "SIGTERM"), true); + await exitPromise; + await _waitFor(() => !_isProcessRunning(grandchildPid)); + + assert.equal(_isProcessRunning(child.pid), false); + assert.equal(_isProcessRunning(grandchildPid), false); +}); diff --git a/phoenix-builder-mcp/test/ws-control-server.test.js b/phoenix-builder-mcp/test/ws-control-server.test.js new file mode 100644 index 0000000000..52f113d589 --- /dev/null +++ b/phoenix-builder-mcp/test/ws-control-server.test.js @@ -0,0 +1,213 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { once } from "node:events"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import WebSocket from "ws"; +import { + createWSControlServer, + DEFAULT_WS_HOST +} from "../ws-control-server.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const packageRoot = path.resolve(__dirname, ".."); + +async function _connect(port) { + const client = new WebSocket(`ws://127.0.0.1:${port}`); + await once(client, "open"); + return client; +} + +async function _waitFor(condition, timeoutMs = 1000) { + const deadline = Date.now() + timeoutMs; + while (!condition()) { + if (Date.now() >= deadline) { + throw new Error("Timed out waiting for condition"); + } + await new Promise((resolve) => setTimeout(resolve, 5)); + } +} + +test("the control socket binds to loopback by default", async (t) => { + const server = await createWSControlServer(0); + t.after(() => server.close()); + + assert.equal(server.getAddress().address, DEFAULT_WS_HOST); +}); + +test("an occupied port rejects without replacing the active owner", async (t) => { + const owner = await createWSControlServer(0); + const port = owner.getPort(); + t.after(() => owner.close()); + + await assert.rejects( + createWSControlServer(port), + (error) => { + assert.equal(error.code, "EADDRINUSE"); + assert.match(error.message, /unique PHOENIX_MCP_WS_PORT/); + return true; + } + ); + + const client = await _connect(port); + client.terminate(); +}); + +test("index completes an MCP stdio handshake and releases a free port on close", async (t) => { + const portAllocator = await createWSControlServer(0); + const port = portAllocator.getPort(); + await portAllocator.close(); + + const transport = new StdioClientTransport({ + command: process.execPath, + args: [path.join(packageRoot, "index.js")], + env: { + PHOENIX_DESKTOP_PATH: "/tmp/phoenix-desktop-unused", + PHOENIX_MCP_WS_PORT: String(port) + }, + stderr: "pipe" + }); + const client = new Client({ + name: "phoenix-builder-test", + version: "1.0.0" + }); + let clientClosed = false; + let stderr = ""; + transport.stderr.on("data", (data) => { + stderr += data.toString(); + }); + t.after(async () => { + if (!clientClosed) { + await client.close(); + } + }); + + await client.connect(transport); + const tools = await client.listTools(); + const statusResult = await client.callTool({ + name: "get_phoenix_status", + arguments: {} + }); + const buildTool = tools.tools.find((tool) => tool.name === "build_phoenix"); + const runTestsTool = tools.tools.find((tool) => tool.name === "run_tests"); + + assert.ok(tools.tools.some((tool) => tool.name === "start_phoenix")); + assert.ok(tools.tools.some((tool) => tool.name === "get_phoenix_status")); + assert.ok(buildTool); + assert.ok(runTestsTool); + assert.ok( + buildTool.inputSchema.properties.target.enum.includes("validate-dist-size") + ); + assert.deepEqual( + runTestsTool.inputSchema.properties.category.enum, + ["unit", "integration", "LegacyInteg", "livepreview", "mainview"] + ); + const unsupportedCategoryResult = await client.callTool({ + name: "run_tests", + arguments: { category: "all" } + }); + assert.equal(unsupportedCategoryResult.isError, true); + assert.deepEqual(JSON.parse(statusResult.content[0].text), { + processRunning: false, + pid: null, + wsConnected: false, + connectedInstances: [], + wsPort: port + }); + + await client.close(); + clientClosed = true; + assert.equal(stderr, ""); + + const rebound = await createWSControlServer(port); + await rebound.close(); +}); + +test("server shutdown terminates clients that have not sent hello", async (t) => { + const server = await createWSControlServer(0); + const port = server.getPort(); + const client = await _connect(port); + let serverClosed = false; + t.after(async () => { + if (!serverClosed) { + client.terminate(); + await server.close(); + } + }); + + const clientClosed = once(client, "close"); + await server.close(); + serverClosed = true; + await clientClosed; + + const rebound = await createWSControlServer(port); + await rebound.close(); +}); + +test("disconnecting a Phoenix client rejects its pending requests immediately", async (t) => { + const server = await createWSControlServer(0); + const client = await _connect(server.getPort()); + let serverClosed = false; + t.after(async () => { + client.terminate(); + if (!serverClosed) { + await server.close(); + } + }); + + client.send(JSON.stringify({ type: "hello", name: "disconnect-test" })); + await _waitFor(() => server.getConnectedInstances().includes("disconnect-test")); + + const screenshotRequest = server.requestScreenshot(undefined, "disconnect-test"); + client.terminate(); + + await assert.rejects(screenshotRequest, /Phoenix client disconnected/); + await server.close(); + serverClosed = true; +}); + +test("index exits before stdio readiness when its configured port is occupied", async (t) => { + const owner = await createWSControlServer(0); + const port = owner.getPort(); + t.after(() => owner.close()); + + const child = spawn(process.execPath, [path.join(packageRoot, "index.js")], { + cwd: packageRoot, + env: { + ...process.env, + PHOENIX_MCP_WS_PORT: String(port) + }, + stdio: ["pipe", "pipe", "pipe"] + }); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (data) => { + stdout += data.toString(); + }); + child.stderr.on("data", (data) => { + stderr += data.toString(); + }); + t.after(() => { + if (child.exitCode === null) { + child.kill("SIGKILL"); + } + }); + + const [exitCode] = await once(child, "exit"); + assert.notEqual(exitCode, 0); + assert.equal(stdout, ""); + assert.match(stderr, new RegExp(`WebSocket port ${port} is already in use`)); + assert.equal( + existsSync(path.join(packageRoot, `.mcp-server-${port}.pid`)), + false + ); + + const client = await _connect(port); + client.terminate(); +}); diff --git a/phoenix-builder-mcp/ws-control-server.js b/phoenix-builder-mcp/ws-control-server.js index f09efab1e0..c813d44981 100644 --- a/phoenix-builder-mcp/ws-control-server.js +++ b/phoenix-builder-mcp/ws-control-server.js @@ -1,14 +1,53 @@ import { WebSocketServer } from "ws"; import { LogBuffer } from "./log-buffer.js"; -export function createWSControlServer(port) { - const wss = new WebSocketServer({ port }); +export const DEFAULT_WS_HOST = "127.0.0.1"; + +export async function createWSControlServer(port) { + const wss = new WebSocketServer({ + host: DEFAULT_WS_HOST, + port + }); + await new Promise((resolve, reject) => { + const onListening = () => { + wss.off("error", onError); + resolve(); + }; + const onError = (error) => { + wss.off("listening", onListening); + if (error.code === "EADDRINUSE") { + const portError = new Error( + `WebSocket port ${port} is already in use. ` + + "Configure a unique PHOENIX_MCP_WS_PORT for each concurrent " + + "Phoenix Builder MCP/Phoenix pair." + ); + portError.code = error.code; + portError.cause = error; + reject(portError); + return; + } + reject(error); + }; + + wss.once("listening", onListening); + wss.once("error", onError); + }); + const clients = new Map(); // name -> { ws, logs, isAlive } let unknownCounter = 0; let requestIdCounter = 0; const pendingRequests = new Map(); let heartbeatInterval = null; + function _rejectPendingRequestsForSocket(ws, message) { + for (const [id, pending] of pendingRequests) { + if (pending.ws === ws) { + pendingRequests.delete(id); + pending.reject(new Error(message)); + } + } + } + wss.on("connection", (ws) => { // Name is assigned when the client sends a "hello" message. // Track the ws temporarily so we can map it back on close/error. @@ -177,12 +216,14 @@ export function createWSControlServer(port) { }); ws.on("close", () => { + _rejectPendingRequestsForSocket(ws, "Phoenix client disconnected"); if (clientName && clients.get(clientName)?.ws === ws) { clients.delete(clientName); } }); ws.on("error", () => { + _rejectPendingRequestsForSocket(ws, "Phoenix client connection failed"); if (clientName && clients.get(clientName)?.ws === ws) { clients.delete(clientName); } @@ -256,6 +297,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -295,6 +337,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -334,6 +377,7 @@ export function createWSControlServer(port) { }, 10000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -376,6 +420,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -411,6 +456,7 @@ export function createWSControlServer(port) { }, 60000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -446,6 +492,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -481,6 +528,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -520,6 +568,7 @@ export function createWSControlServer(port) { }, 30000); pendingRequests.set(id, { + ws: client.ws, resolve: (data) => { clearTimeout(timeout); resolve(data); @@ -565,19 +614,27 @@ export function createWSControlServer(port) { function close() { clearInterval(heartbeatInterval); - for (const [id, pending] of pendingRequests) { + for (const pending of pendingRequests.values()) { pending.reject(new Error("Server shutting down")); } pendingRequests.clear(); - for (const [name, client] of clients) { + for (const ws of wss.clients) { try { - client.ws.close(1000, "Server shutting down"); + ws.terminate(); } catch { // ignore } } clients.clear(); - wss.close(); + return new Promise((resolve, reject) => { + wss.close((error) => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); } return { @@ -594,6 +651,10 @@ export function createWSControlServer(port) { isClientConnected, getConnectedInstances, close, - getPort: () => port + getAddress: () => wss.address(), + getPort: () => { + const address = wss.address(); + return address && typeof address === "object" ? address.port : port; + } }; } diff --git a/src-mdviewer/CLAUDE-markdown-viewer.md b/src-mdviewer/CLAUDE-markdown-viewer.md index d72e72dedf..8ea55e2a0a 100644 --- a/src-mdviewer/CLAUDE-markdown-viewer.md +++ b/src-mdviewer/CLAUDE-markdown-viewer.md @@ -8,7 +8,7 @@ The markdown viewer (`src-mdviewer/`) is a standalone web app loaded inside an i ``` Test Runner Window └── Test Phoenix iframe (testWindow) - ├── CM5 editor (CodeMirror) + ├── Phoenix editor (CodeMirror 6) ├── Live Preview panel │ └── #panel-md-preview-frame (md viewer iframe) │ ├── #viewer-content (contenteditable in edit mode) diff --git a/src-mdviewer/src/bridge.js b/src-mdviewer/src/bridge.js index 684313c2fa..870a88e2df 100644 --- a/src-mdviewer/src/bridge.js +++ b/src-mdviewer/src/bridge.js @@ -9,6 +9,7 @@ import { setLocale } from "./core/i18n.js"; import { marked } from "marked"; import * as docCache from "./core/doc-cache.js"; import { broadcastSelectionStateSync, flushPendingContentChange } from "./components/editor.js"; +import { updateLinkPopoverForTest } from "./components/link-popover.js"; let _syncId = 0; let _lastReceivedSyncId = -1; @@ -17,6 +18,8 @@ let _scrollFromCM = false; let _scrollFromViewer = false; let _scrollFromViewerTimer = null; let _suppressScrollToLine = false; +let _suppressScrollToLineTimer = null; +let _pendingScrollToLine = null; let _baseURL = ""; let _cursorPosBeforeEdit = null; // cursor position before current edit batch let _cursorPosDirty = false; // true after content changes, reset when emitted @@ -254,6 +257,12 @@ export function initBridge() { window.__broadcastSelectionStateForTest = function () { broadcastSelectionStateSync(); }; + window.__updateLinkPopoverForTest = function () { + updateLinkPopoverForTest(); + }; + window.__flushPendingContentChangeForTest = function () { + flushPendingContentChange(); + }; window.__saveScrollPos = function () { docCache.saveActiveScrollPos(); }; @@ -359,7 +368,7 @@ export function initBridge() { }); // Intercept keyboard shortcuts in capture phase before the mdviewr editor handles them. - // Undo/redo is routed through CM5's undo stack so both editors stay in sync. + // Undo/redo is routed through Phoenix's editor history so both editors stay in sync. // Unhandled modifier shortcuts are forwarded to Phoenix's keybinding manager. const _mdEditorHandledKeys = new Set(["b", "i", "k", "u", "z", "y", "a", "c", "v", "x"]); // Ctrl/Cmd + key const _mdEditorHandledShiftKeys = new Set(["x", "X", "z", "Z"]); // Ctrl/Cmd + Shift + key @@ -471,7 +480,7 @@ export function initBridge() { }, true); // Detect source line from data-source-line attributes for scroll sync. - // In read mode, also refocus CM5 unless the user has a text selection. + // In read mode, also refocus the Phoenix editor unless the user has a text selection. // Disabled in preview mode (no cursor sync). document.addEventListener("click", (e) => { const sourceLine = _getSourceLineFromElement(e.target); @@ -733,8 +742,20 @@ function handleSwitchFile(data) { // Suppress scroll-to-line from CM during file switch — the doc cache // restores the correct scroll position; CM cursor activity would override it. + if (_suppressScrollToLineTimer) { + clearTimeout(_suppressScrollToLineTimer); + } + _pendingScrollToLine = null; _suppressScrollToLine = true; - setTimeout(() => { _suppressScrollToLine = false; }, 500); + _suppressScrollToLineTimer = setTimeout(() => { + _suppressScrollToLine = false; + _suppressScrollToLineTimer = null; + const pendingScroll = _pendingScrollToLine; + _pendingScrollToLine = null; + if (pendingScroll) { + handleScrollToLine(pendingScroll); + } + }, 500); // Edit mode is global for the md editor frame — preserve it across file switches const wasEditMode = getState().editMode; @@ -1060,8 +1081,13 @@ function handleScrollToLine(data) { const { line, fromScroll, tableCol } = data; if (line == null) return; - // Suppress during file switch — doc cache restores the correct scroll - if (_suppressScrollToLine) return; + // Defer during file switch while the doc cache restores its scroll + // position. Keep only the newest request so a cursor move made during + // this window is applied once the new document is ready. + if (_suppressScrollToLine) { + _pendingScrollToLine = { line, fromScroll, tableCol }; + return; + } // Ignore scroll-based sync that originated from the viewer itself // (feedback loop: viewer scroll/click → CM scroll → scroll sync back). diff --git a/src-mdviewer/src/components/editor.js b/src-mdviewer/src/components/editor.js index c7230a1c95..21c85877d7 100644 --- a/src-mdviewer/src/components/editor.js +++ b/src-mdviewer/src/components/editor.js @@ -1154,9 +1154,15 @@ function hideTableContextMenu() { if (menu) menu.classList.remove("open"); } +function closestElement(target, selector) { + return target && typeof target.closest === "function" ? + target.closest(selector) : + null; +} + function setupTableContextMenu(contentEl) { const contextHandler = (e) => { - const td = e.target.closest("td, th"); + const td = closestElement(e.target, "td, th"); if (!td || !contentEl.contains(td)) return; const ctx = getTableContext(); if (!ctx) return; @@ -1169,7 +1175,7 @@ function setupTableContextMenu(contentEl) { const menu = document.getElementById("table-context-menu"); if (!menu || !menu.classList.contains("open")) return; if (menu.contains(e.target)) return; - if (e.target.closest(".table-row-handle, .table-col-handle")) return; + if (closestElement(e.target, ".table-row-handle, .table-col-handle")) return; hideTableContextMenu(); }; @@ -1859,6 +1865,7 @@ function _updateSourceLineAttrs(contentEl, markdown) { function emitContentChange(contentEl) { clearTimeout(contentChangeTimer); contentChangeTimer = setTimeout(() => { + contentChangeTimer = null; const markdown = convertToMarkdown(contentEl); emit("bridge:contentChanged", { markdown }); }, CONTENT_CHANGE_DEBOUNCE); diff --git a/src-mdviewer/src/components/link-popover.js b/src-mdviewer/src/components/link-popover.js index 97b8be7364..0950e798f6 100644 --- a/src-mdviewer/src/components/link-popover.js +++ b/src-mdviewer/src/components/link-popover.js @@ -346,53 +346,65 @@ function hide() { currentAnchor = null; } -function updatePosition() { - if (rafId) cancelAnimationFrame(rafId); - rafId = requestAnimationFrame(() => { - rafId = null; - if (editMode || createMode) return; // don't reposition while editing +function updatePositionSync() { + if (editMode || createMode) return; // don't reposition while editing - // If format bar is visible, hide link popover - const formatBar = document.getElementById("format-bar"); - if (formatBar && formatBar.classList.contains("visible")) { - hide(); - return; - } + // If format bar is visible, hide link popover + const formatBar = document.getElementById("format-bar"); + if (formatBar && formatBar.classList.contains("visible")) { + hide(); + return; + } - // If lang picker dropdown is open, hide link popover - const langPicker = document.getElementById("lang-picker"); - if (langPicker && langPicker.classList.contains("visible") && langPicker.querySelector(".lang-picker-dropdown.open")) { - hide(); - return; - } + // If lang picker dropdown is open, hide link popover + const langPicker = document.getElementById("lang-picker"); + if (langPicker && langPicker.classList.contains("visible") && langPicker.querySelector(".lang-picker-dropdown.open")) { + hide(); + return; + } - const sel = window.getSelection(); - if (!sel || !sel.rangeCount) { - hide(); - return; - } + const sel = window.getSelection(); + if (!sel || !sel.rangeCount) { + hide(); + return; + } - // Check selection is inside contentEl - if (!contentEl || !contentEl.contains(sel.anchorNode)) { - hide(); - return; - } + // Check selection is inside contentEl + if (!contentEl || !contentEl.contains(sel.anchorNode)) { + hide(); + return; + } - // If text is selected (non-collapsed), let format bar handle it - if (!sel.isCollapsed) { - hide(); - return; - } + // If text is selected (non-collapsed), let format bar handle it + if (!sel.isCollapsed) { + hide(); + return; + } - const anchor = findAnchorAtSelection(); - if (anchor) { - show(anchor); - } else { - hide(); - } + const anchor = findAnchorAtSelection(); + if (anchor) { + show(anchor); + } else { + hide(); + } +} + +function updatePosition() { + if (rafId) cancelAnimationFrame(rafId); + rafId = requestAnimationFrame(() => { + rafId = null; + updatePositionSync(); }); } +export function updateLinkPopoverForTest() { + if (rafId) { + cancelAnimationFrame(rafId); + rafId = null; + } + updatePositionSync(); +} + export function initLinkPopover(editorEl) { contentEl = editorEl; buildPopover(); @@ -431,6 +443,10 @@ export function initLinkPopover(editorEl) { } export function destroyLinkPopover() { + if (rafId) { + cancelAnimationFrame(rafId); + rafId = null; + } hide(); document.removeEventListener("selectionchange", updatePosition); if (contentEl) { diff --git a/src/JSUtils/ScopeManager.js b/src/JSUtils/ScopeManager.js index ab6944cac1..ee466b4b8d 100644 --- a/src/JSUtils/ScopeManager.js +++ b/src/JSUtils/ScopeManager.js @@ -35,7 +35,7 @@ define(function (require, exports, module) { var _ = require("thirdparty/lodash"); - const CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + const CodeMirror = require("editor/CodeMirrorCompat"), DefaultDialogs = require("widgets/DefaultDialogs"), Dialogs = require("widgets/Dialogs"), DocumentManager = require("document/DocumentManager"), diff --git a/src/LiveDevelopment/BrowserScripts/DocumentObserver.js b/src/LiveDevelopment/BrowserScripts/DocumentObserver.js index 4b12f3a564..2595340ad5 100644 --- a/src/LiveDevelopment/BrowserScripts/DocumentObserver.js +++ b/src/LiveDevelopment/BrowserScripts/DocumentObserver.js @@ -28,6 +28,9 @@ var _document = null; var _transport; + const LIVE_PREVIEW_SCROLL_POSITION = "PHOENIX_LIVE_PREVIEW_SCROLL_POSITION"; + const LIVE_PREVIEW_SCROLL_READY = "PHOENIX_LIVE_PREVIEW_SCROLL_READY"; + const LIVE_PREVIEW_RESTORE_SCROLL_POSITION = "PHOENIX_LIVE_PREVIEW_RESTORE_SCROLL_POSITION"; function inIframe () { try { @@ -48,23 +51,57 @@ }, false); } - window.addEventListener('scroll', function () { - // save scroll position - sessionStorage.setItem("saved-scroll-" + location.href, JSON.stringify({ - scrollX: window.scrollX, - scrollY: window.scrollY - })); - }); - function scrollToLastPosition() { - let saved = JSON.parse(sessionStorage.getItem("saved-scroll-" + location.href)); - if(saved){ + function _restoreScrollPosition(position) { + if (!position) { + return; + } + const applyScrollPosition = function () { window.scrollTo({ - left: saved.scrollX, - top: saved.scrollY, + left: position.scrollX, + top: position.scrollY, behavior: "instant" }); + }; + if (document.readyState === "complete") { + applyScrollPosition(); + } else { + window.addEventListener("load", applyScrollPosition, { once: true }); + } + } + + window.addEventListener("scroll", function () { + const position = { + scrollX: window.scrollX, + scrollY: window.scrollY + }; + // Keep sessionStorage support for reloads in the same browsing context. + sessionStorage.setItem("saved-scroll-" + location.href, JSON.stringify(position)); + if (inIframe()) { + window.parent.postMessage({ + type: LIVE_PREVIEW_SCROLL_POSITION, + url: location.href, + scrollX: position.scrollX, + scrollY: position.scrollY + }, "*"); } + }); + + function scrollToLastPosition() { + const saved = JSON.parse(sessionStorage.getItem("saved-scroll-" + location.href)); + _restoreScrollPosition(saved); } + + window.addEventListener("message", function (event) { + const data = event.data; + if (!inIframe() || event.source !== window.parent || !data || + data.type !== LIVE_PREVIEW_RESTORE_SCROLL_POSITION || + data.url !== location.href || + !Number.isFinite(data.scrollX) || !Number.isFinite(data.scrollY)) { + return; + } + _restoreScrollPosition(data); + }); + window.addEventListener("load", scrollToLastPosition); /** @@ -357,6 +394,12 @@ _document = document; // start listening to node changes _enableListeners(); + if (inIframe()) { + window.parent.postMessage({ + type: LIVE_PREVIEW_SCROLL_READY, + url: location.href + }, "*"); + } var rel = related(); diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument.js index d5b098ff61..b013758b45 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument.js @@ -129,10 +129,10 @@ define(function LiveCSSDocumentModule(require, exports, module) { * Update the highlights in the browser based on the cursor position. */ LiveCSSDocument.prototype.updateHighlight = function () { - if (this.isHighlightEnabled() && this.editor) { - var editor = this.editor, - selectors = []; - _.each(this.editor.getSelections(), function (sel) { + const editor = this._getUsableEditor(); + if (this.isHighlightEnabled() && editor) { + const selectors = []; + _.each(editor.getSelections(), function (sel) { var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); if (selector) { selectors.push(selector); diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js index 9d5beb6260..24823d3d8a 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js @@ -72,6 +72,7 @@ define(function (require, exports, module) { this.roots = roots || []; this._onActiveEditorChange = this._onActiveEditorChange.bind(this); + this._onEditorBeforeDestroy = this._onEditorBeforeDestroy.bind(this); this._onCursorActivity = this._onCursorActivity.bind(this); // we cant use file paths for event registration - paths may have spaces(treated as an event list separator) @@ -154,21 +155,63 @@ define(function (require, exports, module) { if (this.editor) { this.setInstrumentationEnabled(true, true); + this.editor.off("beforeDestroy", this._onEditorBeforeDestroy); + this.editor.on("beforeDestroy", this._onEditorBeforeDestroy); this.editor.off("cursorActivity", this._onCursorActivity); this.editor.on("cursorActivity", this._onCursorActivity); this.updateHighlight(); } }; + /** + * @private + * Detaches before the editor destroys its underlying CodeMirror instance. + * @param {$.Event} event + * @param {Editor} editor + */ + LiveDocument.prototype._onEditorBeforeDestroy = function (event, editor) { + if (!editor || editor === this.editor) { + this._detachFromEditor(); + } + }; + /** * @private * Detaches from the current editor. */ LiveDocument.prototype._detachFromEditor = function () { if (this.editor) { + const editor = this.editor; this.hideHighlight(); - this.editor.off("cursorActivity", this._onCursorActivity); + this.editor = null; + editor.off("beforeDestroy", this._onEditorBeforeDestroy); + editor.off("cursorActivity", this._onCursorActivity); + } + }; + + /** + * Returns the attached editor only while its CodeMirror surface is usable. + * CodeMirror 6 clears `_view` when destroyed, while CodeMirror 5 does not + * define that property. + * @return {?Editor} + */ + LiveDocument.prototype._getUsableEditor = function () { + const editor = this.editor; + const codeMirror = editor && editor._codeMirror; + const hasCodeMirror6View = codeMirror && + Object.prototype.hasOwnProperty.call(codeMirror, "_view"); + + if (!editor || + !codeMirror || + codeMirror._destroyed || + (hasCodeMirror6View && !codeMirror._view)) { + if (editor) { + this._detachFromEditor(); + } + return null; } + + return editor; }; let _disableHighlightOnCursor = false; @@ -189,7 +232,7 @@ define(function (require, exports, module) { * @param {Editor} editor */ LiveDocument.prototype._onCursorActivity = function (event, editor) { - if (!this.editor) { + if (!this._getUsableEditor()) { return; } if(!_disableHighlightOnCursor){ @@ -208,13 +251,14 @@ define(function (require, exports, module) { endLine, i, lineHandle; + const editor = this._getUsableEditor(); - if (!this.editor) { + if (!editor) { return; } // Buffer addLineClass DOM changes in a CodeMirror operation - this.editor._codeMirror.operation(function () { + editor._codeMirror.operation(function () { // Remove existing errors before marking new ones self._clearErrorDisplay(); @@ -225,7 +269,7 @@ define(function (require, exports, module) { endLine = error.endPos.line; for (i = startLine; i < endLine + 1; i++) { - lineHandle = self.editor._codeMirror.addLineClass(i, "wrap", SYNC_ERROR_CLASS); + lineHandle = editor._codeMirror.addLineClass(i, "wrap", SYNC_ERROR_CLASS); self._errorLineHandles.push(lineHandle); } }); @@ -241,14 +285,15 @@ define(function (require, exports, module) { LiveDocument.prototype._clearErrorDisplay = function () { var self = this, lineHandle; + const editor = this._getUsableEditor(); - if (!this.editor || + if (!editor || !this._errorLineHandles || !this._errorLineHandles.length) { return; } - this.editor._codeMirror.operation(function () { + editor._codeMirror.operation(function () { while (true) { // Iterate over all lines that were previously marked with an error lineHandle = self._errorLineHandles.pop(); @@ -257,7 +302,7 @@ define(function (require, exports, module) { break; } - self.editor._codeMirror.removeLineClass(lineHandle, "wrap", SYNC_ERROR_CLASS); + editor._codeMirror.removeLineClass(lineHandle, "wrap", SYNC_ERROR_CLASS); } }); }; diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js index 195e11c444..bf705dfe12 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js @@ -106,7 +106,8 @@ define(function (require, exports, module) { * @param {boolean} [force] */ LiveHTMLDocument.prototype.setInstrumentationEnabled = function (enabled, force) { - if (!this.editor) { + const editor = this._getUsableEditor(); + if (!editor) { // TODO: error return; } @@ -114,7 +115,7 @@ define(function (require, exports, module) { // TODO: not clear why we do this here instead of waiting for the next time we want to // generate the instrumented HTML. This won't work if the dom offsets are out of date. HTMLInstrumentation.scanDocument(this.doc); - HTMLInstrumentation._markText(this.editor); + HTMLInstrumentation._markText(editor); } this._instrumentationEnabled = enabled; @@ -126,8 +127,9 @@ define(function (require, exports, module) { */ LiveHTMLDocument.prototype.getResponseData = function (enabled) { var body; - if (this._instrumentationEnabled) { - body = HTMLInstrumentation.generateInstrumentedHTML(this.editor, this.protocol.getRemoteScript()); + const editor = this._getUsableEditor(); + if (this._instrumentationEnabled && editor) { + body = HTMLInstrumentation.generateInstrumentedHTML(editor, this.protocol.getRemoteScript()); } if (!body) { @@ -183,11 +185,11 @@ define(function (require, exports, module) { * Update the highlights in the browser based on the cursor position. */ LiveHTMLDocument.prototype.updateHighlight = function () { - if (!this.editor || !this.isHighlightEnabled()) { + const editor = this._getUsableEditor(); + if (!editor || !this.isHighlightEnabled()) { return; } - var editor = this.editor, - mode = editor.getModeForSelection(), + var mode = editor.getModeForSelection(), ids = [], selectors = []; @@ -202,7 +204,7 @@ define(function (require, exports, module) { if (!isInlineStyle) { // find the css selector - _.each(this.editor.getSelections(), function (sel) { + _.each(editor.getSelections(), function (sel) { let selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); if (selector) { selectors.push(selector); @@ -218,7 +220,7 @@ define(function (require, exports, module) { } // its not found in css context, then it must be a inline style or a normal html element - _.each(this.editor.getSelections(), function (sel) { + _.each(editor.getSelections(), function (sel) { var tagID = HTMLInstrumentation._getTagIDAtDocumentPos( editor, sel.reversed ? sel.end : sel.start @@ -262,6 +264,10 @@ define(function (require, exports, module) { if (!this._instrumentationEnabled) { return; } + const editor = this._getUsableEditor(); + if (!editor) { + return; + } // Apply DOM edits is async, so previous PerfUtils timer may still be // running. PerfUtils does not support running multiple timers with same @@ -273,7 +279,7 @@ define(function (require, exports, module) { } var self = this, - result = HTMLInstrumentation.getUnappliedEditList(this.editor, change), + result = HTMLInstrumentation.getUnappliedEditList(editor, change), applyEditsPromise; if (result.edits) { diff --git a/src/brackets.js b/src/brackets.js index 8cc2e89296..0d55efead0 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -44,25 +44,6 @@ define(function (require, exports, module) { require("thirdparty/jquery.knob.modified"); require('thirdparty/marked.min'); - // Load CodeMirror add-ons--these attach themselves to the CodeMirror module - require("thirdparty/CodeMirror/addon/comment/continuecomment"); - require("thirdparty/CodeMirror/addon/edit/closebrackets"); - require("thirdparty/CodeMirror/addon/edit/closetag"); - require("thirdparty/CodeMirror/addon/edit/matchbrackets"); - require("thirdparty/CodeMirror/addon/edit/matchtags"); - require("thirdparty/CodeMirror/addon/fold/xml-fold"); - require("thirdparty/CodeMirror/addon/mode/multiplex"); - require("thirdparty/CodeMirror/addon/mode/overlay"); - require("thirdparty/CodeMirror/addon/mode/simple"); - require("thirdparty/CodeMirror/addon/scroll/scrollpastend"); - require("thirdparty/CodeMirror/addon/search/match-highlighter"); - require("thirdparty/CodeMirror/addon/search/searchcursor"); - require("thirdparty/CodeMirror/addon/selection/active-line"); - require("thirdparty/CodeMirror/addon/selection/mark-selection"); - require("thirdparty/CodeMirror/addon/display/rulers"); - require("thirdparty/CodeMirror/addon/comment/comment"); - require("thirdparty/CodeMirror/keymap/sublime"); - require("utils/EventDispatcher"); require("worker/WorkerComm"); require("utils/ZipUtils"); @@ -145,14 +126,24 @@ define(function (require, exports, module) { require("phoenix-builder/main"); require("phoenix-builder/debug-overrides"); - // DEPRECATED: In future we want to remove the global CodeMirror, but for now we - // expose our required CodeMirror globally so as to avoid breaking extensions in the - // interim. - const CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); + // DEPRECATED: Keep a legacy-shaped global API while the editor surface is + // backed exclusively by CodeMirror 6. + const CodeMirror = require("editor/CodeMirrorCompat"), + CodeMirrorLegacyAddons = require("editor/CodeMirrorLegacyAddons"), + CodeMirrorSublimeCompat = require("editor/CodeMirrorSublimeCompat"); + + // CodeMirror 5 loaded these addons eagerly during application startup. + // Preserve that observable API surface with CM6-backed implementations, + // while keeping legacy-path imports idempotent for third-party extensions. + CodeMirrorLegacyAddons.installAll(CodeMirror); + CodeMirrorSublimeCompat.install(CodeMirror); Object.defineProperty(window, "CodeMirror", { get: function () { - DeprecationWarning.deprecationWarning('Use brackets.getModule("thirdparty/CodeMirror/lib/codemirror") instead of global CodeMirror.', true); + DeprecationWarning.deprecationWarning( + 'Use brackets.getModule("editor/CodeMirrorCompat") instead of global CodeMirror.', + true + ); return CodeMirror; } }); diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 6361f3cb91..12574c5690 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -418,7 +418,7 @@ define(function (require, exports, module) { if($(e.target).closest('.tab-bar-container').length) { return; } require(["editor/EditorManager"], function (EditorManager) { - if ($(e.target).parents(".CodeMirror-gutter").length !== 0) { + if ($(e.target).closest(".cm-gutters").length !== 0) { return; } diff --git a/src/document/Document.js b/src/document/Document.js index 43a81d97bf..ddf6e39932 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -30,7 +30,7 @@ define(function (require, exports, module) { InMemoryFile = require("document/InMemoryFile"), PerfUtils = require("utils/PerfUtils"), LanguageManager = require("language/LanguageManager"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror = require("editor/CodeMirrorCompat"), _ = require("thirdparty/lodash"); /** @@ -559,7 +559,7 @@ define(function (require, exports, module) { if (!this._refreshInProgress) { // Sync isDirty from CodeMirror state var wasDirty = this.isDirty; - this.isDirty = !editor._codeMirror.isClean(); + this.isDirty = !editor.isClean(); // Notify if isDirty just changed (this also auto-adds us to working set if needed) if (wasDirty !== this.isDirty) { @@ -577,7 +577,7 @@ define(function (require, exports, module) { Document.prototype._markClean = function () { this.isDirty = false; if (this._masterEditor) { - this._masterEditor._codeMirror.markClean(); + this._masterEditor.markClean(); } exports.trigger("_dirtyFlagChange", this); }; diff --git a/src/editor/CodeMirror6Adapter.js b/src/editor/CodeMirror6Adapter.js new file mode 100644 index 0000000000..36644d97b2 --- /dev/null +++ b/src/editor/CodeMirror6Adapter.js @@ -0,0 +1,9923 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/** + * CodeMirror 6 editing surface with the compatibility contracts Phoenix + * historically consumed from the previous editor surface. + * + * EditorView.state.doc is the only live text model. Compatibility state kept + * here is metadata only (events, marks, line handles, options, and history). + */ +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + CM6 = require("thirdparty/CodeMirror6/codemirror6"); + + const DEFAULT_LINE_HEIGHT = 15; + const DEFAULT_CHARACTER_WIDTH = 8; + const HORIZONTAL_SCROLL_MARGIN = 10; + const LEGACY_SCROLLER_GAP = 30; + const LINE_NUMBER_GUTTER = "CodeMirror-linenumbers"; + const LEGACY_CLOSE_BRACKET_DEFAULTS = { + pairs: "()[]{}''\"\"", + closeBefore: ")]}'\":;>", + triples: "", + explode: "[]{}" + }; + const LEGACY_SELECTION_MATCH_DEFAULTS = { + annotateScrollbar: false, + delay: 100, + minChars: 2, + showToken: false, + style: "matchhighlight", + trim: true, + wordsOnly: false + }; + const LEGACY_UPDATE_OPTIONS = new Set([ + "addModeClass", + "direction", + "firstLineNumber", + "gutters", + "indentUnit", + "lineNumberFormatter", + "lineNumbers", + "lineWrapping", + "mode", + "scrollPastEnd", + "styleActiveLine", + "tabSize", + "theme" + ]); + + function _clamp(value, min, max) { + return Math.max(min, Math.min(value, max)); + } + + function _indentUnitText(options) { + const tabSize = Math.max(1, Number(options.tabSize) || 4); + const indentUnit = Math.max(1, Number(options.indentUnit) || tabSize); + if (!options.indentWithTabs) { + return " ".repeat(indentUnit); + } + + if (indentUnit % tabSize !== 0) { + // CM6 requires an indent unit to contain only one whitespace + // character. Spaces preserve the requested column width until a + // matching tab size is applied. + return " ".repeat(indentUnit); + } + return "\t".repeat(indentUnit / tabSize); + } + + function _drawSelectionExtension(options) { + const configuredBlinkRate = Number(options.cursorBlinkRate); + return CM6.drawSelection({ + cursorBlinkRate: Number.isFinite(configuredBlinkRate) ? + configuredBlinkRate : + 530, + drawRangeCursor: Boolean(options.showCursorWhenSelecting) + }); + } + + function _legacyCloseBracketOption(configuration, name) { + if (name === "pairs" && typeof configuration === "string") { + return configuration; + } + if (configuration && typeof configuration === "object" && + configuration[name] !== null && + configuration[name] !== undefined) { + return configuration[name]; + } + return LEGACY_CLOSE_BRACKET_DEFAULTS[name]; + } + + function _legacyCloseBracketConfigurationAt(adapter, position) { + const configured = adapter.getOption("autoCloseBrackets"); + if (!configured || + typeof configured === "object" && configured.override) { + return configured; + } + const mode = adapter.getModeAt(position || adapter.getCursor()); + return mode && mode.closeBrackets || configured; + } + + function _closeBracketsExtension(adapter, value) { + if (!value) { + return []; + } + return CM6.EditorView.inputHandler.of(function (view, from, to, insert) { + const selection = view.state.selection.main; + if (view.composing || view.compositionStarted || + view.state.readOnly || + insert.length !== 1 || + from !== selection.from || + to !== selection.to) { + return false; + } + return adapter._handleAutoCloseBracketCharacter(insert); + }); + } + + function _selectionMatchOptions(value) { + const supplied = value && typeof value === "object" ? value : {}; + const options = {}; + Object.keys(LEGACY_SELECTION_MATCH_DEFAULTS).forEach(function (name) { + options[name] = Object.prototype.hasOwnProperty.call(supplied, name) ? + supplied[name] : + LEGACY_SELECTION_MATCH_DEFAULTS[name]; + }); + return options; + } + + function _matchesSelectionWordCharacter(expression, character) { + expression.lastIndex = 0; + return expression.test(character); + } + + function _selectionMatchQuery(state, options) { + const range = state.selection.main; + if (range.empty) { + if (!options.showToken) { + return null; + } + const expression = options.showToken === true ? + /[\w$]/ : + options.showToken; + if (!expression || typeof expression.test !== "function") { + return null; + } + const line = state.doc.lineAt(range.head); + let start = range.head - line.from; + let end = start; + while (start && + _matchesSelectionWordCharacter( + expression, + line.text.charAt(start - 1) + )) { + start--; + } + while (end < line.length && + _matchesSelectionWordCharacter( + expression, + line.text.charAt(end) + )) { + end++; + } + if (start === end) { + return null; + } + return { + boundaryExpression: expression, + text: line.text.slice(start, end) + }; + } + + const fromLine = state.doc.lineAt(range.from); + const toLine = state.doc.lineAt(range.to); + if (fromLine.number !== toLine.number) { + return null; + } + const selectedText = state.sliceDoc(range.from, range.to); + if (options.wordsOnly) { + if (!/^\w+$/.test(selectedText)) { + return null; + } + if (range.from > fromLine.from && + /\w/.test(state.sliceDoc(range.from - 1, range.from))) { + return null; + } + if (range.to < fromLine.to && + /\w/.test(state.sliceDoc(range.to, range.to + 1))) { + return null; + } + } + const query = options.trim ? + selectedText.replace(/^\s+|\s+$/g, "") : + selectedText; + return query.length >= Number(options.minChars) ? { + boundaryExpression: null, + text: query + } : null; + } + + function _selectionMatchScrollbarQuery(query) { + if (!query || !query.text || !query.boundaryExpression) { + return query && query.text; + } + const escaped = query.text.replace(/[\\[\].+*?(){|^$]/g, "\\$&"); + return new RegExp( + (/\w/.test(query.text.charAt(0)) ? "\\b" : "") + + escaped + + (/\w/.test(query.text.charAt(query.text.length - 1)) ? + "\\b" : + "") + ); + } + + function _selectionMatchExtension(adapter, value) { + if (!value) { + return []; + } + const options = _selectionMatchOptions(value); + const className = String(options.style || "matchhighlight") + .split(/\s+/) + .filter(Boolean) + .map(function (style) { + return `cm-${style}`; + }) + .join(" "); + const decoration = CM6.Decoration.mark({ + class: className || "cm-matchhighlight" + }); + const refreshEffect = CM6.StateEffect["define"](); + + function decorationsForView(view, query) { + if (!query || !query.text) { + return CM6.Decoration.none; + } + const ranges = []; + const seenLines = new Set(); + view.visibleRanges.forEach(function (visibleRange) { + let line = view.state.doc.lineAt(visibleRange.from); + while (line.from <= visibleRange.to) { + if (!seenLines.has(line.number)) { + seenLines.add(line.number); + let index = line.text.indexOf(query.text); + while (index !== -1) { + const end = index + query.text.length; + const beforeMatches = index > 0 && + query.boundaryExpression && + _matchesSelectionWordCharacter( + query.boundaryExpression, + line.text.charAt(index - 1) + ); + const afterMatches = end < line.length && + query.boundaryExpression && + _matchesSelectionWordCharacter( + query.boundaryExpression, + line.text.charAt(end) + ); + if (!beforeMatches && !afterMatches) { + ranges.push(decoration.range( + line.from + index, + line.from + end + )); + } + index = line.text.indexOf( + query.text, + index + query.text.length + ); + } + } + if (line.number >= view.state.doc.lines) { + break; + } + line = view.state.doc.line(line.number + 1); + } + }); + return CM6.Decoration.set(ranges, true); + } + + return CM6.ViewPlugin.fromClass(class { + constructor(view) { + this.active = view.hasFocus; + this.decorations = CM6.Decoration.none; + this.matchesOnScrollbar = null; + this.timeout = null; + if (this.active) { + this.schedule(view); + } + } + + refresh(view) { + const query = _selectionMatchQuery(view.state, options); + this.decorations = decorationsForView(view, query); + if (this.matchesOnScrollbar) { + this.matchesOnScrollbar.clear(); + this.matchesOnScrollbar = null; + } + if (options.annotateScrollbar && query && query.text) { + this.matchesOnScrollbar = adapter.showMatchesOnScrollbar( + _selectionMatchScrollbarQuery(query), + false, + { + className: + "CodeMirror-selection-highlight-scrollbar" + } + ); + } + } + + schedule(view) { + if (this.timeout !== null) { + window.clearTimeout(this.timeout); + } + const delay = Math.max(0, Number(options.delay) || 0); + if (!delay) { + this.refresh(view); + return; + } + this.timeout = window.setTimeout(function () { + this.timeout = null; + if (view.dom.isConnected) { + view.dispatch({ + effects: refreshEffect.of(null) + }); + } + }.bind(this), delay); + } + + update(update) { + const refreshRequested = update.transactions.some(function (transaction) { + return transaction.effects.some(function (effect) { + return effect.is(refreshEffect); + }); + }); + if (refreshRequested) { + this.refresh(update.view); + return; + } + if (update.focusChanged && update.view.hasFocus) { + this.active = true; + } + if ((this.active || update.view.hasFocus) && ( + update.docChanged || + update.selectionSet || + update.viewportChanged || + update.focusChanged + )) { + this.decorations = update.docChanged ? + this.decorations.map(update.changes) : + this.decorations; + this.schedule(update.view); + } + } + + destroy() { + if (this.timeout !== null) { + window.clearTimeout(this.timeout); + this.timeout = null; + } + if (this.matchesOnScrollbar) { + this.matchesOnScrollbar.clear(); + this.matchesOnScrollbar = null; + } + } + }, { + decorations: function (plugin) { + return plugin.decorations; + } + }); + } + + function _activeLineExtension(value) { + if (!value) { + return []; + } + const allowNonEmpty = typeof value === "object" && + Boolean(value.nonEmpty); + const lineDecoration = CM6.Decoration.line({ + attributes: { + class: "cm-activeLine" + } + }); + + function activeLineStarts(state) { + const starts = []; + const seen = new Set(); + state.selection.ranges.forEach(function (range) { + const anchorLine = state.doc.lineAt(range.anchor); + const headLine = state.doc.lineAt(range.head); + if (allowNonEmpty ? + anchorLine.number !== headLine.number : + !range.empty) { + return; + } + if (!seen.has(headLine.from)) { + seen.add(headLine.from); + starts.push(headLine.from); + } + }); + return starts; + } + + const lineHighlighter = CM6.ViewPlugin.fromClass(class { + constructor(view) { + this.decorations = CM6.Decoration.set( + activeLineStarts(view.state).map(function (position) { + return lineDecoration.range(position); + }) + ); + } + + update(update) { + if (update.docChanged || update.selectionSet) { + this.decorations = CM6.Decoration.set( + activeLineStarts(update.state).map(function (position) { + return lineDecoration.range(position); + }) + ); + } + } + }, { + decorations: function (plugin) { + return plugin.decorations; + } + }); + const gutterHighlighter = CM6.gutterLineClass.compute( + ["selection"], + function (state) { + return CM6.RangeSet.of(activeLineStarts(state).map(function (position) { + return new PhoenixGutterLineClass( + "cm-activeLineGutter" + ).range(position); + })); + } + ); + return [lineHighlighter, gutterHighlighter]; + } + + function _bracketMatchingExtension(adapter, value) { + if (!value) { + return []; + } + const configuration = typeof value === "object" ? value : {}; + const matchingDecoration = CM6.Decoration.mark({ + class: "cm-matchingBracket CodeMirror-matchingbracket" + }); + const nonMatchingDecoration = CM6.Decoration.mark({ + class: "cm-nonmatchingBracket CodeMirror-nonmatchingbracket" + }); + + function decorationsForView(view) { + if (!view.hasFocus) { + return CM6.Decoration.none; + } + const maxHighlightLineLength = + configuration.maxHighlightLineLength || 1000; + const ranges = []; + const seen = new Set(); + view.state.selection.ranges.forEach(function (selection) { + if (!selection.empty) { + return; + } + const match = CodeMirror.findMatchingBracket( + adapter, + adapter.posFromIndex(selection.head), + configuration + ); + if (!match || + !match.match && + configuration.highlightNonMatching === false) { + return; + } + const decoration = match.match ? + matchingDecoration : + nonMatchingDecoration; + [match.from, match.to].forEach(function (position) { + if (!position || typeof position !== "object") { + return; + } + const lineText = adapter.getLine(position.line); + if (lineText === undefined || + lineText.length > maxHighlightLineLength) { + return; + } + const from = adapter.indexFromPos(position); + const key = `${from}:${match.match}`; + if (!seen.has(key)) { + seen.add(key); + ranges.push(decoration.range(from, from + 1)); + } + }); + }); + return CM6.Decoration.set(ranges, true); + } + + return CM6.ViewPlugin.fromClass(class { + constructor(view) { + this.decorations = decorationsForView(view); + } + + update(update) { + if (update.docChanged || + update.selectionSet || + update.focusChanged) { + this.decorations = decorationsForView(update.view); + } + } + }, { + decorations: function (plugin) { + return plugin.decorations; + } + }); + } + + function _normalizeGutterSpec(gutter) { + if (typeof gutter === "string") { + return { + className: gutter, + style: null + }; + } + return { + className: gutter && gutter.className, + style: gutter && gutter.style || null + }; + } + + function _readModeToken(mode, stream, state) { + for (let attempt = 0; attempt < 10; attempt++) { + const style = mode.token ? mode.token(stream, state) : null; + if (stream.pos > stream.start) { + return style; + } + } + throw new Error(`Mode ${mode.name || "unknown"} failed to advance stream.`); + } + + function _readBlankLineStyle(mode, state) { + if (mode && typeof mode.blankLine === "function") { + return mode.blankLine(state); + } + if (!mode || typeof mode.innerMode !== "function") { + return null; + } + const inner = CodeMirror.innerMode(mode, state); + return inner && inner.mode && + typeof inner.mode.blankLine === "function" ? + inner.mode.blankLine(inner.state) : + null; + } + + function _legacyLineOracle(adapter, lineNumber, styles) { + return { + lookAhead: function (distance) { + return adapter.getLine(lineNumber + distance); + }, + baseToken: function (position) { + return styles ? + _baseTokenAtPosition(styles, position) : + null; + } + }; + } + + function _processModeLine(adapter, mode, state, lineNumber) { + const text = adapter.getLine(lineNumber) || ""; + if (!text.length) { + if (mode.blankLine) { + mode.blankLine(state); + } + return; + } + + const stream = new CodeMirror.StringStream( + text, + adapter.getOption("tabSize") || 4, + _legacyLineOracle(adapter, lineNumber) + ); + while (!stream.eol()) { + stream.start = stream.pos; + _readModeToken(mode, stream, state); + } + } + + function _modeStateBefore(adapter, mode, lineNumber) { + const firstLine = adapter.firstLine(); + const lineLimit = Math.min( + Math.max(Number(lineNumber) || firstLine, firstLine), + adapter.lastLine() + 1 + ); + let cache = adapter._legacyModeStateCache; + if (!cache || cache.mode !== mode || + cache.firstLine !== firstLine || + cache.tabSize !== (adapter.getOption("tabSize") || 4)) { + cache = { + firstLine: firstLine, + frontier: firstLine, + mode: mode, + states: new Map(), + tabSize: adapter.getOption("tabSize") || 4 + }; + cache.states.set(firstLine, CodeMirror.startState(mode)); + adapter._legacyModeStateCache = cache; + } + + const startLine = lineLimit <= cache.frontier ? + lineLimit : + cache.frontier; + const state = CodeMirror.copyState(mode, cache.states.get(startLine)); + for (let currentLine = startLine; currentLine < lineLimit; currentLine++) { + _processModeLine(adapter, mode, state, currentLine); + adapter._legacyModeParseCount++; + cache.states.set( + currentLine + 1, + CodeMirror.copyState(mode, state) + ); + cache.frontier = currentLine + 1; + } + return state; + } + + function _copyLegacyChange(change) { + return { + from: _copyPosition(change.from), + to: _copyPosition(change.to), + text: change.text.slice(), + removed: change.removed.slice(), + origin: change.origin + }; + } + + function _extractLineClasses(style, lineClasses) { + const tokenClasses = []; + String(style || "").trim().split(/\s+/).filter(Boolean).forEach(function (className) { + const lineClass = className.match(/^line-(background-)?(\S+)$/); + if (!lineClass) { + tokenClasses.push(className); + return; + } + if (lineClasses) { + const destination = lineClass[1] ? + lineClasses.background : + lineClasses.text; + destination.add(lineClass[2]); + } + }); + return tokenClasses.length ? tokenClasses.join(" ") : null; + } + + function _stripLineClasses(style) { + return _extractLineClasses(style); + } + + function _stripOverlayClasses(style) { + return style && style.replace(/( |^)overlay .*/, ""); + } + + function _styleAtPosition(styles, position) { + if (!styles.length) { + return null; + } + if (position === 0) { + return styles[0].type; + } + const matchingStyle = styles.find(function (style) { + return style.from < position && style.to >= position; + }); + return matchingStyle ? matchingStyle.type : null; + } + + function _baseTokenAtPosition(styles, position) { + const matchingStyle = styles.find(function (style) { + return style.to > position; + }); + if (!matchingStyle) { + return null; + } + return { + type: _stripOverlayClasses(matchingStyle.type), + size: matchingStyle.to - position + }; + } + + function _applyOverlayStyle(styles, from, to, overlayStyle, opaque) { + const result = []; + styles.forEach(function (style) { + if (style.to <= from || style.from >= to) { + result.push(style); + return; + } + + if (style.from < from) { + result.push({ + from: style.from, + to: from, + type: style.type + }); + } + + result.push({ + from: Math.max(style.from, from), + to: Math.min(style.to, to), + type: opaque ? + `overlay ${overlayStyle}` : + `${style.type ? `${style.type} ` : ""}overlay ${overlayStyle}` + }); + + if (style.to > to) { + result.push({ + from: to, + to: style.to, + type: style.type + }); + } + }); + return result; + } + + function _lineStylesWithOverlays(adapter, lineNumber) { + const text = adapter.getLine(lineNumber) || ""; + let styles = adapter.getLineTokens(lineNumber, true).map(function (token) { + return { + from: token.start, + to: token.end, + type: _stripLineClasses(token.type) + }; + }); + if (!styles.length && text.length) { + styles = [{ + from: 0, + to: text.length, + type: null + }]; + } + + adapter.state.overlays.forEach(function (overlayRecord) { + const stream = new CodeMirror.StringStream( + text, + adapter.getOption("tabSize") || 4, + { + lookAhead: function (distance) { + return adapter.getLine(lineNumber + distance); + }, + baseToken: function (position) { + return _baseTokenAtPosition(styles, position); + } + } + ); + while (!stream.eol()) { + stream.start = stream.pos; + const overlayStyle = _stripLineClasses( + _readModeToken(overlayRecord.mode, stream, true) + ); + if (overlayStyle) { + styles = _applyOverlayStyle( + styles, + stream.start, + stream.pos, + overlayStyle, + overlayRecord.opaque + ); + } + } + }); + return styles; + } + + function _sameSelection(left, right) { + if (!left || !right || left.ranges.length !== right.ranges.length || + left.mainIndex !== right.mainIndex) { + return false; + } + + return left.ranges.every(function (range, index) { + const other = right.ranges[index]; + return range.anchor === other.anchor && range.head === other.head; + }); + } + + function _historySelection(entry) { + return entry && entry.type === "selection" ? + entry.afterSelection : + null; + } + + function _pushSelectionHistoryEntry(destination, entry) { + const selection = _historySelection(entry); + const previousSelection = _historySelection( + destination[destination.length - 1] + ); + if (!selection || previousSelection && + _sameSelection(previousSelection, selection)) { + return; + } + destination.push(entry); + } + + function _modeName(mode) { + if (!mode) { + return "text/plain"; + } + if (typeof mode === "string") { + return mode.toLowerCase(); + } + if (mode.name) { + return String(mode.name).toLowerCase(); + } + return "text/plain"; + } + + function _prepareLegacyStream(stream) { + if (stream._phoenixCodeMirror5Compatible) { + return; + } + + stream._phoenixCodeMirror5Compatible = true; + stream.lineStart = 0; + stream.lineOracle = null; + [ + "sol", + "column", + "indentation", + "hideFirstChars", + "lookAhead", + "baseToken", + "match" + ].forEach(function (methodName) { + stream[methodName] = CodeMirror.StringStream.prototype[methodName]; + }); + } + + const LEGACY_STREAM_TOKEN_ALIASES = new Set([ + "attribute", + "builtin", + "def", + "error", + "header", + "property", + "qualifier", + "string-2", + "tag", + "type", + "variable", + "variable-2" + ]); + + function _isRecognizedCM6StreamToken(tokenName) { + if (LEGACY_STREAM_TOKEN_ALIASES.has(tokenName)) { + return true; + } + + const tokenParts = tokenName.split("."); + if (!tokenParts.length || + !CM6.tags[tokenParts[0]] || + typeof CM6.tags[tokenParts[0]] === "function") { + return false; + } + return tokenParts.slice(1).every(function (modifierName) { + return typeof CM6.tags[modifierName] === "function"; + }); + } + + function _cm6StreamTokenStyle(style) { + if (!style) { + return style; + } + const supportedTokens = String(style).trim().split(/\s+/) + .filter(Boolean) + .filter(_isRecognizedCM6StreamToken); + return supportedTokens.length ? supportedTokens.join(" ") : null; + } + + function _legacyLanguageExtensionForMode(mode, options) { + if (!CodeMirror.hasMode(mode)) { + return []; + } + + const legacyMode = CodeMirror.getMode(options || {}, mode); + if (!legacyMode || legacyMode.name === "null" || + typeof legacyMode.token !== "function") { + return []; + } + + const legacyToken = legacyMode.token; + const streamParser = Object.assign({}, legacyMode, { + token: function (stream, state) { + _prepareLegacyStream(stream); + return _cm6StreamTokenStyle( + legacyToken.call(legacyMode, stream, state) + ); + } + }); + return CM6.StreamLanguage["define"](streamParser); + } + + function _nestedHTMLParserForMode(mode, options) { + const resolvedMode = CodeMirror.resolveMode(mode); + const name = _modeName(resolvedMode); + + if (CodeMirror.isModeOverridden && + CodeMirror.isModeOverridden(resolvedMode)) { + const overriddenLanguage = _legacyLanguageExtensionForMode( + mode, + options + ); + return overriddenLanguage && overriddenLanguage.parser || null; + } + + if (name === "htmlmixed") { + return CM6.html({ + autoCloseTags: false + }).language.parser; + } + if (name === "jsx") { + return CM6.javascript({ + jsx: true, + typescript: Boolean( + resolvedMode.base && resolvedMode.base.typescript + ) + }).language.parser; + } + if (name === "javascript") { + if (resolvedMode.json || resolvedMode.jsonld) { + return CM6.json().language.parser; + } + return CM6.javascript({ + typescript: Boolean(resolvedMode.typescript) + }).language.parser; + } + + const legacyLanguage = _legacyLanguageExtensionForMode(mode, options); + return legacyLanguage && legacyLanguage.parser || null; + } + + function _languageFromExtension(extension) { + if (Array.isArray(extension)) { + for (let index = 0; index < extension.length; index++) { + const language = _languageFromExtension(extension[index]); + if (language) { + return language; + } + } + return null; + } + if (extension && extension.language) { + return extension.language; + } + return extension && extension.parser ? extension : null; + } + + function _markdownCodeLanguage(info, options) { + const languageName = String(info || "").trim().toLowerCase(); + const aliases = { + bash: "text/x-sh", + c: "text/x-csrc", + "c++": "text/x-c++src", + cpp: "text/x-c++src", + cs: "text/x-csharp", + csharp: "text/x-csharp", + html: "text/html", + js: "javascript", + javascript: "javascript", + json: "application/json", + jsx: "text/jsx", + kt: "text/x-kotlin", + kotlin: "text/x-kotlin", + less: "text/x-less", + mysql: "text/x-mysql", + php: "application/x-httpd-php-open", + py: "python", + rb: "ruby", + scss: "text/x-scss", + sh: "text/x-sh", + sql: "text/x-sql", + ts: "application/typescript", + tsx: "text/typescript-jsx", + typescript: "application/typescript", + xml: "application/xml", + yml: "text/x-yaml" + }; + const mode = aliases[languageName] || languageName; + if (!mode || mode === "markdown" || mode === "gfm" || mode === "md") { + return null; + } + return _languageFromExtension(_languageExtensionForMode(mode, options)); + } + + function _languageExtensionForMode(mode, options) { + const resolvedMode = CodeMirror.resolveMode(mode); + const resolvedName = _modeName(resolvedMode); + + if (CodeMirror.isModeOverridden && + CodeMirror.isModeOverridden(resolvedMode)) { + return _legacyLanguageExtensionForMode(mode, options); + } + + if (resolvedName === "css" && + (resolvedMode.variant === "scss" || resolvedMode.variant === "less")) { + return _legacyLanguageExtensionForMode(mode, options); + } + if (resolvedName === "handlebars" || + resolvedName === "htmlhandlebars" || + resolvedName === "htmlembedded") { + return _legacyLanguageExtensionForMode(mode, options); + } + + if (resolvedName === "javascript") { + if (resolvedMode.json || resolvedMode.jsonld) { + return CM6.json(); + } + return CM6.javascript({ + typescript: Boolean(resolvedMode.typescript) + }); + } + if (resolvedName === "jsx") { + return CM6.javascript({ + jsx: true, + typescript: Boolean( + resolvedMode.base && resolvedMode.base.typescript + ) + }); + } + if (resolvedName === "json") { + return CM6.json(); + } + if (resolvedName === "css") { + return CM6.css(); + } + if (resolvedName === "clike" && resolvedMode.variant === "php" || + resolvedName === "php" && resolvedMode.startOpen) { + return CM6.php({ + baseLanguage: null, + plain: true + }); + } + if (resolvedName === "php") { + const htmlSupport = CM6.html({ + autoCloseTags: !options || options.autoCloseTags !== false + }); + return [ + CM6.php({ + baseLanguage: htmlSupport.language + }), + htmlSupport.support + ]; + } + if (resolvedName === "htmlmixed") { + const htmlOptions = { + autoCloseTags: !options || options.autoCloseTags !== false + }; + if (resolvedMode.scriptTypes) { + htmlOptions.nestedLanguages = resolvedMode.scriptTypes + .map(function (scriptType) { + const parser = _nestedHTMLParserForMode( + scriptType.mode, + options + ); + if (!parser) { + return null; + } + return { + tag: "script", + attrs: function (attributes) { + scriptType.matches.lastIndex = 0; + return scriptType.matches.test( + attributes.type || "" + ); + }, + parser: parser + }; + }) + .filter(Boolean); + } + return CM6.html(htmlOptions); + } + if (resolvedName === "xml") { + return CM6.xml(); + } + if (resolvedName === "markdown" || resolvedName === "gfm") { + const markdownOptions = { + codeLanguages: function (info) { + return _markdownCodeLanguage(info, options); + } + }; + if (resolvedName === "gfm") { + markdownOptions.base = CM6.markdownLanguage; + } + return CM6.markdown(markdownOptions); + } + + const legacyModeMap = { + "clike": CM6.c, + "text/x-csrc": CM6.c, + "text/x-c++src": CM6.cpp, + "text/x-csharp": CM6.csharp, + "text/x-java": CM6.java, + "text/x-kotlin": CM6.kotlin, + "text/x-objectivec": CM6.objectiveC, + "text/x-scala": CM6.scala, + "clojure": CM6.clojure, + "coffeescript": CM6.coffeeScript, + "diff": CM6.diff, + "dart": CM6.dart, + "application/dart": CM6.dart, + "erlang": CM6.erlang, + "go": CM6.go, + "groovy": CM6.groovy, + "haskell": CM6.haskell, + "haxe": CM6.haxe, + "lua": CM6.lua, + "pascal": CM6.pascal, + "perl": CM6.perl, + "properties": CM6.properties, + "text/x-properties": CM6.properties, + "pug": CM6.pug, + "python": CM6.python, + "ruby": CM6.ruby, + "rust": CM6.rust, + "text/x-rustsrc": CM6.rust, + "sass": CM6.sass, + "scheme": CM6.scheme, + "shell": CM6.shell, + "text/x-sh": CM6.shell, + "sql": CM6.standardSQL, + "text/x-sql": CM6.standardSQL, + "text/x-mysql": CM6.mySQL, + "stex": CM6.stex, + "text/x-stex": CM6.stex, + "stylus": CM6.stylus, + "text/x-styl": CM6.stylus, + "swift": CM6.swift, + "toml": CM6.toml, + "turtle": CM6.turtle, + "vb": CM6.vb, + "text/x-vb": CM6.vb, + "vbscript": CM6.vbScript, + "yaml": CM6.yaml, + "text/x-yaml": CM6.yaml + }; + const legacyMode = legacyModeMap[_modeName(mode)] || + legacyModeMap[resolvedName]; + if (legacyMode) { + return CM6.StreamLanguage["define"](legacyMode); + } + return _legacyLanguageExtensionForMode(mode, options); + } + + const phoenixHighlightStyle = CM6.HighlightStyle["define"]([ + { tag: CM6.tags.keyword, class: "cm-keyword" }, + { tag: [CM6.tags.atom, CM6.tags.bool, CM6.tags.null], class: "cm-atom" }, + { tag: [CM6.tags.number, CM6.tags.integer, CM6.tags.float], class: "cm-number" }, + { tag: [CM6.tags.definitionKeyword, CM6.tags.definitionOperator], class: "cm-def" }, + { tag: CM6.tags.variableName, class: "cm-variable" }, + { tag: CM6.tags.local(CM6.tags.variableName), class: "cm-variable-2" }, + { tag: [CM6.tags.typeName, CM6.tags.className], class: "cm-type" }, + { tag: CM6.tags.operator, class: "cm-operator" }, + { tag: [CM6.tags.comment, CM6.tags.lineComment, CM6.tags.blockComment], class: "cm-comment" }, + { tag: [CM6.tags.string, CM6.tags.special(CM6.tags.string)], class: "cm-string" }, + { tag: CM6.tags.regexp, class: "cm-string-2" }, + { tag: [CM6.tags.meta, CM6.tags.processingInstruction], class: "cm-meta" }, + { tag: CM6.tags.labelName, class: "cm-qualifier" }, + { tag: CM6.tags.standard(CM6.tags.variableName), class: "cm-builtin" }, + { tag: [CM6.tags.bracket, CM6.tags.paren, CM6.tags.squareBracket, CM6.tags.brace], class: "cm-bracket" }, + { tag: CM6.tags.tagName, class: "cm-tag" }, + { tag: CM6.tags.attributeName, class: "cm-attribute" }, + { tag: CM6.tags.heading, class: "cm-header" }, + { tag: CM6.tags.quote, class: "cm-quote" }, + { tag: CM6.tags.link, class: "cm-link" }, + { tag: CM6.tags.propertyName, class: "cm-property" }, + { tag: CM6.tags.invalid, class: "cm-error" }, + { tag: CM6.tags.emphasis, class: "cm-em" }, + { tag: CM6.tags.strong, class: "cm-strong" } + ]); + + /** + * Convert a CM6 offset into a Phoenix/CM5 position. + * @param {!Text} doc + * @param {number} offset + * @return {{line:number, ch:number}} + */ + function _positionFromOffset(doc, offset, firstLine) { + const safeOffset = _clamp(offset, 0, doc.length); + const line = doc.lineAt(safeOffset); + return { + line: line.number - 1 + (firstLine || 0), + ch: safeOffset - line.from + }; + } + + function _splitLines(text) { + return String(text).split(/\r\n?|\n/); + } + + function _copyPosition(position) { + return { + line: position.line, + ch: position.ch + }; + } + + function _copySelection(selection) { + return { + anchor: _copyPosition(selection.anchor), + head: _copyPosition(selection.head) + }; + } + + function _copyHistoryValue(value) { + if (Array.isArray(value)) { + return value.map(_copyHistoryValue); + } + if (!value || typeof value !== "object") { + return value; + } + if (value instanceof CM6.EditorSelection) { + return value.toJSON(); + } + const copy = {}; + Object.keys(value).forEach(function (key) { + copy[key] = _copyHistoryValue(value[key]); + }); + return copy; + } + + function _copyHistoryArray(entries, copySelections) { + return (entries || []).map(function (entry) { + if (!copySelections && entry && entry.type === "selection") { + // Match CM5's copyHistoryArray contract. Selection events are + // intentionally shared by getHistory(), which Phoenix uses to + // attach named restore-point metadata. Change events are + // copied so callers cannot mutate the live undo payload. + return entry; + } + return _copyHistoryValue(entry); + }); + } + + function _restoreHistorySelection(selection) { + if (!selection || selection instanceof CM6.EditorSelection) { + return selection; + } + if (Array.isArray(selection.ranges)) { + return CM6.EditorSelection.fromJSON(selection); + } + return selection; + } + + function _prepareHistoryEntry(entry) { + if (!entry || typeof entry !== "object") { + return entry; + } + + entry.beforeSelection = _restoreHistorySelection(entry.beforeSelection); + entry.afterSelection = _restoreHistorySelection(entry.afterSelection); + if (entry.type === "change" && !entry.changes) { + entry.changes = entry.steps && entry.steps.length ? + entry.steps : + [{}]; + } + return entry; + } + + const HISTORY_STATE_PROPERTIES = [ + "_historyDone", + "_historyUndone", + "_historyClosed", + "_historyLastModTime", + "_historyLastSelectionTime", + "_historyLastOperationId", + "_historyLastSelectionOperationId", + "_historyLastOrigin", + "_historyLastSelectionOrigin", + "_currentGeneration", + "_nextGeneration", + "_cleanGeneration" + ]; + + function _createHistoryState(source) { + const state = source || {}; + return { + _historyDone: state._historyDone || [], + _historyUndone: state._historyUndone || [], + _historyClosed: state._historyClosed !== false, + _historyLastModTime: state._historyLastModTime || 0, + _historyLastSelectionTime: state._historyLastSelectionTime || 0, + _historyLastOperationId: state._historyLastOperationId === undefined ? + null : + state._historyLastOperationId, + _historyLastSelectionOperationId: + state._historyLastSelectionOperationId === undefined ? + null : + state._historyLastSelectionOperationId, + _historyLastOrigin: state._historyLastOrigin || null, + _historyLastSelectionOrigin: state._historyLastSelectionOrigin || null, + _currentGeneration: state._currentGeneration || 0, + _nextGeneration: state._nextGeneration || 1, + _cleanGeneration: state._cleanGeneration || 0 + }; + } + + function _installHistoryState(adapter) { + adapter._historyState = _createHistoryState(adapter); + HISTORY_STATE_PROPERTIES.forEach(function (propertyName) { + Object.defineProperty(adapter, propertyName, { + configurable: true, + get: function () { + return this._historyState[propertyName]; + }, + set: function (value) { + this._historyState[propertyName] = value; + } + }); + }); + } + + function _selectionFromOffsets(selection, doc, firstLine) { + const result = { + anchor: _positionFromOffset(doc, selection.anchor, firstLine), + head: _positionFromOffset(doc, selection.head, firstLine) + }; + if (selection.goalColumn !== null && selection.goalColumn !== undefined) { + result.goalColumn = selection.goalColumn; + } + return result; + } + + function _normalizeStyleClasses(style) { + return String(style || "").trim().split(/\s+/).filter(Boolean).map(function (className) { + return className.indexOf("cm-") === 0 ? className : `cm-${className}`; + }).join(" "); + } + + function _legacyClassPattern(className) { + return new RegExp(`(^|\\s)${className}(?:$|\\s)\\s*`); + } + + function _nodeForGutterMarker(marker) { + if (marker && marker.nodeType) { + return marker; + } + + const node = window.document.createElement("span"); + if (marker !== null && marker !== undefined) { + node.textContent = String(marker); + } + return node; + } + + class LegacyNodeWidget extends CM6.WidgetType { + constructor(node, handleMouseEvents) { + super(); + this.node = node; + this.handleMouseEvents = Boolean(handleMouseEvents); + } + + eq(other) { + return other instanceof LegacyNodeWidget && other.node === this.node; + } + + toDOM() { + return this.node; + } + + ignoreEvent() { + return !this.handleMouseEvents; + } + } + + class LegacyLineWidget extends CM6.WidgetType { + constructor(adapter, record) { + super(); + this.adapter = adapter; + this.record = record; + this.version = record.version; + } + + eq(other) { + return other instanceof LegacyLineWidget && + other.record === this.record && + other.version === this.version; + } + + toDOM() { + const wrapper = window.document.createElement("div"); + const options = this.record.options || {}; + wrapper.className = "CodeMirror-linewidget phoenix-cm6-line-widget"; + if (options.className) { + wrapper.classList.add(...String(options.className).split(/\s+/).filter(Boolean)); + } + if (!options.handleMouseEvents) { + wrapper.setAttribute("cm-ignore-events", "true"); + } + wrapper.appendChild(this.record.node); + this.record.renderedWrapper = wrapper; + this.adapter._applyLineWidgetLayout(this.record); + Promise.resolve().then(() => { + if (!this.record.cleared && this.record.renderedWrapper === wrapper) { + this.adapter._measureLineWidget(this.record); + CodeMirror.signal(this.record.widget, "redraw"); + } + }); + return wrapper; + } + + destroy(dom) { + if (this.record.renderedWrapper === dom) { + this.record.renderedWrapper = null; + } + } + + ignoreEvent() { + const options = this.record.options || {}; + return !options.handleMouseEvents; + } + } + + class PhoenixGutterMarker extends CM6.GutterMarker { + constructor(record) { + super(); + this.record = record; + } + + eq(other) { + return other instanceof PhoenixGutterMarker && other.record === this.record; + } + + toDOM() { + const wrapper = window.document.createElement("span"); + wrapper.className = "phoenix-cm6-gutter-marker-wrapper"; + wrapper.style.display = "contents"; + wrapper.appendChild(this.record.renderedNode); + return wrapper; + } + } + + class PhoenixGutterLineClass extends CM6.GutterMarker { + constructor(className) { + super(); + this.elementClass = className; + } + + eq(other) { + return other instanceof PhoenixGutterLineClass && + other.elementClass === this.elementClass; + } + } + + class LegacyScrollbarAnnotation { + constructor(adapter, suppliedOptions) { + this.cm = adapter; + this.options = typeof suppliedOptions === "string" ? + {className: suppliedOptions} : + Object.assign({}, suppliedOptions || {}); + this.buttonHeight = Number( + this.options.scrollButtonHeight || + adapter.getOption("scrollButtonHeight") || + 0 + ); + this.annotations = []; + this.doRedraw = null; + this.doUpdate = null; + this.cleared = false; + this.div = window.document.createElement("div"); + this.div.className = "phoenix-cm6-scrollbar-annotations"; + this.div.style.cssText = + "position:absolute;right:0;top:0;z-index:7;pointer-events:none"; + adapter.getWrapperElement().appendChild(this.div); + adapter._scrollbarAnnotations.add(this); + + const scheduleRedraw = delay => { + if (this.doRedraw !== null) { + window.clearTimeout(this.doRedraw); + } + this.doRedraw = window.setTimeout(() => { + this.doRedraw = null; + this.redraw(); + }, delay); + }; + this.resizeHandler = () => { + if (this.doUpdate !== null) { + window.clearTimeout(this.doUpdate); + } + this.doUpdate = window.setTimeout(() => { + this.doUpdate = null; + scheduleRedraw(20); + }, 100); + }; + adapter.on("refresh", this.resizeHandler); + adapter.on("markerAdded", this.resizeHandler); + adapter.on("markerCleared", this.resizeHandler); + if (this.options.listenForChanges !== false) { + this.changeHandler = function () { + scheduleRedraw(250); + }; + adapter.on("changes", this.changeHandler); + } + } + + computeScale() { + if (this.cleared || !this.cm._view) { + return false; + } + const wrapper = this.cm.getWrapperElement(); + const scroller = this.cm.getScrollerElement(); + const availableHeight = Math.max( + 0, + (wrapper.clientHeight || scroller.clientHeight) - + this.buttonHeight * 2 + ); + const nextScale = scroller.scrollHeight ? + availableHeight / scroller.scrollHeight : + 0; + const nextWidth = Math.max( + scroller.offsetWidth - scroller.clientWidth, + 2 + ); + const changed = nextScale !== this.hScale || + nextWidth !== this.scrollbarWidth; + this.hScale = nextScale; + this.scrollbarWidth = nextWidth; + return changed; + } + + update(annotations) { + if (this.cleared) { + return; + } + this.annotations = Array.isArray(annotations) ? + annotations.slice() : + []; + this.redraw(); + } + + _measure() { + this.computeScale(); + return this.annotations.map(annotation => { + const from = this.cm.clipPos(annotation.from); + const to = this.cm.clipPos(annotation.to); + const fromCoordinates = this.cm.charCoords(from, "local"); + const toCoordinates = this.cm.charCoords(to, "local"); + return { + annotation: annotation, + bottom: toCoordinates.bottom * this.hScale, + top: fromCoordinates.top * this.hScale + }; + }).filter(function (annotation) { + return Number.isFinite(annotation.top) && + Number.isFinite(annotation.bottom); + }).sort(function (left, right) { + return left.top - right.top || + left.bottom - right.bottom; + }); + } + + _draw(positionedAnnotations) { + if (this.cleared || !this.div.isConnected) { + return; + } + const fragment = window.document.createDocumentFragment(); + for (let index = 0; index < positionedAnnotations.length; index++) { + const positioned = positionedAnnotations[index]; + let bottom = positioned.bottom; + while (index < positionedAnnotations.length - 1 && + positionedAnnotations[index + 1].top <= bottom + 0.9) { + index++; + bottom = Math.max( + bottom, + positionedAnnotations[index].bottom + ); + } + + const marker = window.document.createElement("div"); + marker.style.cssText = + `position:absolute;right:0;width:${this.scrollbarWidth}px;` + + `top:${positioned.top + this.buttonHeight}px;` + + `height:${Math.max(bottom - positioned.top, 3)}px`; + marker.className = this.options.className || ""; + if (positioned.annotation.id) { + marker.setAttribute( + "annotation-id", + positioned.annotation.id + ); + } + fragment.appendChild(marker); + } + this.div.textContent = ""; + this.div.appendChild(fragment); + } + + redraw() { + if (this.cleared || !this.cm._view || !this.div.isConnected) { + return; + } + this.cm._view.requestMeasure({ + key: this, + read: () => { + return this.cleared ? [] : this._measure(); + }, + write: positionedAnnotations => { + this._draw(positionedAnnotations); + } + }); + } + + clear() { + if (this.cleared) { + return; + } + this.cleared = true; + if (this.doRedraw !== null) { + window.clearTimeout(this.doRedraw); + this.doRedraw = null; + } + if (this.doUpdate !== null) { + window.clearTimeout(this.doUpdate); + this.doUpdate = null; + } + this.cm.off("refresh", this.resizeHandler); + this.cm.off("markerAdded", this.resizeHandler); + this.cm.off("markerCleared", this.resizeHandler); + if (this.changeHandler) { + this.cm.off("changes", this.changeHandler); + } + this.div.remove(); + this.cm._scrollbarAnnotations.delete(this); + } + } + + class LegacySearchAnnotation { + constructor(adapter, query, caseFold, suppliedOptions) { + this.cm = adapter; + this.query = query; + this.caseFold = caseFold; + this.options = typeof suppliedOptions === "string" ? + {className: suppliedOptions} : + Object.assign({}, suppliedOptions || {}); + const annotationOptions = Object.assign( + {listenForChanges: false}, + this.options + ); + if (!annotationOptions.className) { + annotationOptions.className = "CodeMirror-search-match"; + } + this.annotation = adapter.annotateScrollbar(annotationOptions); + this.matches = []; + this.update = null; + this.cleared = false; + adapter._searchAnnotations.add(this); + this.findMatches(); + this.annotation.update(this.matches); + + this.changeHandler = () => { + if (this.update !== null) { + window.clearTimeout(this.update); + } + this.update = window.setTimeout(() => { + this.update = null; + this.updateAfterChange(); + }, 250); + }; + adapter.on("change", this.changeHandler); + } + + findMatches() { + this.matches = []; + if (this.cleared || !this.cm._view || + typeof this.query === "string" && !this.query.length) { + return; + } + const cursor = this.cm.getSearchCursor( + this.query, + CodeMirror.Pos(this.cm.firstLine(), 0), + { + caseFold: this.caseFold, + multiline: this.options.multiline + } + ); + const maxMatches = Math.max( + 1, + Number(this.options.maxMatches) || 1000 + ); + while (this.matches.length < maxMatches && cursor.findNext()) { + this.matches.push({ + from: cursor.from(), + to: cursor.to() + }); + } + } + + updateAfterChange() { + if (this.cleared) { + return; + } + this.findMatches(); + this.annotation.update(this.matches); + } + + clear() { + if (this.cleared) { + return; + } + this.cleared = true; + if (this.update !== null) { + window.clearTimeout(this.update); + this.update = null; + } + this.cm.off("change", this.changeHandler); + this.annotation.clear(); + this.cm._searchAnnotations.delete(this); + } + } + + /** + * @constructor + * @param {!Element} container + * @param {!Object} options + */ + function CodeMirror6Adapter(container, options) { + options = options || {}; + const suppliedDoc = options._compatDoc || null; + const suppliedDocSource = options._compatDocSource || null; + this.isCodeMirror6 = true; + this._detachedDoc = Boolean(options._detachedDoc); + this._firstLine = Number.isFinite(Number(options._firstLine)) ? + Math.floor(Number(options._firstLine)) : + 0; + this._options = Object.assign({}, CodeMirror.defaults || {}, options); + if (this._options.inputStyle === "textarea") { + // CM6 always edits through its contenteditable content DOM. Keep + // persisted CM5 preferences working without reporting a backend + // that is not actually in use. + this._options.inputStyle = "contenteditable"; + } + const InputStyle = CodeMirror.inputStyles[this._options.inputStyle]; + if (typeof InputStyle !== "function") { + throw new Error( + `Unsupported CodeMirror inputStyle "${this._options.inputStyle}"` + ); + } + delete this._options._compatDoc; + delete this._options._compatDocSource; + delete this._options._detachedDoc; + delete this._options._firstLine; + this.options = this._options; + const overlays = []; + this.state = { + keyMaps: [], + keySeq: null, + matchBrackets: null, + overlays: overlays, + overwrite: false, + suppressEdits: false + }; + this.extend = false; + this.doc = suppliedDoc || CodeMirror.createDocumentForAdapter(this, { + editor: this._detachedDoc ? null : this, + mode: this._options.mode, + lineSeparator: this._options.lineSeparator, + direction: this._options.direction + }); + this._listeners = new Map(); + this._markers = []; + this._lineHandles = new Set(); + this._gutterMarkers = []; + this._lineClasses = []; + this._lineWidgets = []; + this._lineFolds = {}; + this._overlays = overlays; + this._operationDepth = 0; + this.curOp = null; + this.virtualSelection = null; + this.$lastChangeEndOffset = 0; + this._pendingChangeEvents = []; + this._pendingMarkerVisibilityEvents = []; + this._pendingCursorActivity = false; + this._pendingDocumentCursorActivityCount = 0; + this._pendingUpdate = false; + this._historyDone = []; + this._historyUndone = []; + const adapter = this; + this.history = {}; + Object.defineProperties(this.history, { + done: { + enumerable: true, + get: function () { + return adapter._historyDone; + } + }, + undone: { + enumerable: true, + get: function () { + return adapter._historyUndone; + } + } + }); + this._historyClosed = true; + this._historyApplying = false; + this._historyLastModTime = 0; + this._historyLastSelectionTime = 0; + this._historyLastOperationId = null; + this._historyLastSelectionOperationId = null; + this._historyLastOrigin = null; + this._historyLastSelectionOrigin = null; + this._activeOperationId = null; + this._nextOperationId = 1; + this._currentGeneration = 0; + this._nextGeneration = 1; + this._cleanGeneration = 0; + _installHistoryState(this); + this._nextMarkerId = 1; + this.$mid = 1; + this.marks = Object.create(null); + this._legacyMode = null; + this._legacyModeStateCache = null; + this._legacyModeParseCount = 0; + this._legacyDecorationsDirty = false; + this._renderLineRefreshScheduled = false; + this._keySequenceTimer = null; + this._gutterRefreshScheduled = false; + this._rulerRefreshScheduled = false; + this._rulerElement = null; + this._legacyDOM = null; + this._scrollbarAnnotations = new Set(); + this._searchAnnotations = new Set(); + this._scrollbarModel = null; + this._scrollbarModelNodes = []; + this._scrollbarModelName = null; + this._destroyed = false; + this._focusState = false; + this._lastViewport = null; + this._renderedLineDOMState = new WeakMap(); + this._matchingBracketDOM = new WeakSet(); + this._nonmatchingBracketDOM = new WeakSet(); + this._managedRootClasses = new Set(["cm-editor", "cm-focused"]); + this._originAnnotation = CM6.Annotation["define"](); + this._selectionBiasAnnotation = CM6.Annotation["define"](); + this._addToHistoryAnnotation = CM6.Annotation["define"](); + this._bypassReadOnlyAnnotation = CM6.Annotation["define"](); + this._linkedChangeAnnotation = CM6.Annotation["define"](); + this._skipBeforeChangeAnnotation = CM6.Annotation["define"](); + this._syntheticChangesAnnotation = CM6.Annotation["define"](); + this._fullChangeAnnotation = CM6.Annotation["define"](); + this._setValueSelectionResetAnnotation = CM6.Annotation["define"](); + this._legacyUpdateAnnotation = CM6.Annotation["define"](); + + this._readOnlyCompartment = new CM6.Compartment(); + this._editableCompartment = new CM6.Compartment(); + this._lineNumbersCompartment = new CM6.Compartment(); + this._lineWrappingCompartment = new CM6.Compartment(); + this._activeLineCompartment = new CM6.Compartment(); + this._closeBracketsCompartment = new CM6.Compartment(); + this._bracketMatchingCompartment = new CM6.Compartment(); + this._selectionMatchesCompartment = new CM6.Compartment(); + this._drawSelectionCompartment = new CM6.Compartment(); + this._tabSizeCompartment = new CM6.Compartment(); + this._indentUnitCompartment = new CM6.Compartment(); + this._languageCompartment = new CM6.Compartment(); + this._scrollPastEndCompartment = new CM6.Compartment(); + this._smartIndentCompartment = new CM6.Compartment(); + this._dragDropCompartment = new CM6.Compartment(); + this._contentAttributesCompartment = new CM6.Compartment(); + this._placeholderCompartment = new CM6.Compartment(); + this._decorationsCompartment = new CM6.Compartment(); + this._compatHighlightCompartment = new CM6.Compartment(); + this._gutterLineClassesCompartment = new CM6.Compartment(); + this._guttersCompartment = new CM6.Compartment(); + + const state = CM6.EditorState.create({ + doc: options.value || "", + extensions: this._createExtensions() + }); + + this._view = new CM6.EditorView({ + state: state, + parent: container, + dispatchTransactions: this._dispatchTransactions.bind(this) + }); + this.cm6 = this._view; + this._wrapperElement = this._view.dom; + this._scrollerElement = this._view.scrollDOM; + this._contentElement = this._view.contentDOM; + this._scrollHandler = this._handleScroll.bind(this); + this._view.scrollDOM.addEventListener("scroll", this._scrollHandler, { passive: true }); + this._lastViewport = this.getViewport(); + this.display = { + barHeight: 0, + barWidth: 0, + input: new InputStyle(this), + scroller: this._scrollerElement, + scrollbars: null, + wrapper: this._wrapperElement, + sizer: this._contentElement + }; + Object.defineProperty(this, "inVirtualSelectionMode", { + configurable: true, + enumerable: true, + get: function () { + return Boolean(this.virtualSelection); + } + }); + let maxLineLengthDocument = null; + let maxLineLength = 0; + Object.defineProperty(this.display, "maxLineLength", { + enumerable: true, + get: () => { + if (!this._view) { + return 0; + } + const document = this._view.state.doc; + if (document !== maxLineLengthDocument) { + maxLineLengthDocument = document; + maxLineLength = 0; + for (let lineNumber = 1; + lineNumber <= document.lines; + lineNumber++) { + maxLineLength = Math.max( + maxLineLength, + document.line(lineNumber).length + ); + } + } + return maxLineLength; + } + }); + + if (suppliedDocSource && suppliedDocSource !== this) { + this._restoreDocumentState(suppliedDocSource._takeDocumentState()); + suppliedDocSource._disposeDetachedBackend(); + } + this.doc._adapter = this; + this.doc.cm = this._detachedDoc ? null : this; + this._syncDocumentMetadata(); + if (!this._detachedDoc && CodeMirror.registerInstance) { + CodeMirror.registerInstance(this, this.doc); + } else if (CodeMirror.installExtensions) { + CodeMirror.installExtensions( + this._detachedDoc ? null : this, + this.doc + ); + } + if (CodeMirror.initOptions) { + this._options = CodeMirror.initOptions(this, this._options); + this.options = this._options; + } + this.state.matchBrackets = this._options.matchBrackets ? + typeof this._options.matchBrackets === "object" ? + this._options.matchBrackets : + {} : + null; + this._reconfigureSilently( + this._bracketMatchingCompartment, + _bracketMatchingExtension(this, this._options.matchBrackets) + ); + if (!suppliedDocSource) { + this.clearHistory(); + } + this._decorateDOM(); + this._applyThemeClass(this._options.theme); + this._refreshLegacyDecorations(); + this._refreshLegacyHighlighting(); + this._refreshGutters(); + this._scheduleRulerRefresh(); + this._applyScrollbarStyle(this._options.scrollbarStyle); + if (!this._detachedDoc && this.getOption("autofocus")) { + this.focus(); + } + + } + + CodeMirror6Adapter.prototype._instance = function () { + return this; + }; + + CodeMirror6Adapter.prototype._syncDocumentMetadata = function () { + if (!this.doc) { + return; + } + this.doc._modeOption = this._options.mode; + this.doc._lineSeparator = this._options.lineSeparator; + this.doc._direction = this._options.direction === "rtl" ? "rtl" : "ltr"; + }; + + CodeMirror6Adapter.prototype._invalidateLegacyModeStateCache = function ( + recreateMode, + fromLine + ) { + if (recreateMode) { + this._legacyMode = null; + } + const cache = this._legacyModeStateCache; + if (recreateMode || !cache || !Number.isFinite(Number(fromLine))) { + this._legacyModeStateCache = null; + return; + } + + const frontier = Math.max( + cache.firstLine, + Math.floor(Number(fromLine)) + ); + if (frontier >= cache.frontier) { + return; + } + cache.frontier = frontier; + cache.states.forEach(function (_state, lineNumber) { + if (lineNumber > frontier) { + cache.states.delete(lineNumber); + } + }); + }; + + CodeMirror6Adapter.prototype._signalDocument = function (eventName) { + if (!this.doc) { + return; + } + const args = Array.prototype.slice.call(arguments, 1); + CodeMirror.signal.apply(null, [this.doc, eventName].concat(args)); + }; + + CodeMirror6Adapter.prototype._signalBeforeChange = function (change) { + this._signalDocument("beforeChange", this.doc, change); + this._emit("beforeChange", this._instance(), change); + }; + + CodeMirror6Adapter.prototype._signalBeforeSelectionChange = function (selection) { + this._signalDocument("beforeSelectionChange", this.doc, selection); + this._emit("beforeSelectionChange", this._instance(), selection); + }; + + CodeMirror6Adapter.prototype._takeDocumentState = function () { + const scrollInfo = this._view ? this.getScrollInfo() : { + left: this.doc && this.doc._scrollLeft || 0, + top: this.doc && this.doc._scrollTop || 0 + }; + const snapshot = { + text: this._view ? this._view.state.doc.toString() : "", + selection: this._view ? this._view.state.selection : CM6.EditorSelection.single(0), + firstLine: this._firstLine, + extend: this.extend, + mode: this._options.mode, + lineSeparator: this._options.lineSeparator, + direction: this._options.direction, + scrollLeft: scrollInfo.left, + scrollTop: scrollInfo.top, + markers: this._markers, + lineHandles: this._lineHandles, + gutterMarkers: this._gutterMarkers, + lineClasses: this._lineClasses, + lineWidgets: this._lineWidgets, + lineFolds: this._lineFolds, + historyState: this._historyState, + nextMarkerId: this._nextMarkerId + }; + + this._markers = []; + this._lineHandles = new Set(); + this._gutterMarkers = []; + this._lineClasses = []; + this._lineWidgets = []; + this._lineFolds = {}; + this._historyState = _createHistoryState(); + this.marks = Object.create(null); + this._resetHistoryMergeState(); + this._invalidateLegacyModeStateCache(true); + return snapshot; + }; + + CodeMirror6Adapter.prototype._restoreDocumentState = function (snapshot) { + if (!snapshot) { + return; + } + + this._firstLine = snapshot.firstLine; + this.extend = Boolean(snapshot.extend); + this._options.mode = snapshot.mode; + this._options.lineSeparator = snapshot.lineSeparator; + this._options.direction = snapshot.direction === "rtl" ? "rtl" : "ltr"; + this._markers = snapshot.markers || []; + this._lineHandles = snapshot.lineHandles || new Set(); + this._gutterMarkers = snapshot.gutterMarkers || []; + this._lineClasses = snapshot.lineClasses || []; + this._lineWidgets = snapshot.lineWidgets || []; + this._lineFolds = snapshot.lineFolds || {}; + this._historyState = snapshot.historyState || + _createHistoryState(); + this._nextMarkerId = snapshot.nextMarkerId || 1; + this.$mid = this._nextMarkerId; + this.marks = Object.create(null); + this._invalidateLegacyModeStateCache(true); + + const text = String(snapshot.text || ""); + const maxOffset = text.length; + const selection = snapshot.selection ? + CM6.EditorSelection.create(snapshot.selection.ranges.map(function (range) { + return CM6.EditorSelection.range( + _clamp(range.anchor, 0, maxOffset), + _clamp(range.head, 0, maxOffset) + ); + }), Math.min( + snapshot.selection.mainIndex, + snapshot.selection.ranges.length - 1 + )) : + CM6.EditorSelection.single(0); + this._view.setState(CM6.EditorState.create({ + doc: text, + selection: selection, + extensions: this._createExtensions() + })); + this.state.matchBrackets = this._options.matchBrackets ? + typeof this._options.matchBrackets === "object" ? + this._options.matchBrackets : + {} : + null; + this._reconfigureSilently( + this._bracketMatchingCompartment, + _bracketMatchingExtension(this, this._options.matchBrackets) + ); + + this._lineHandles.forEach(handle => { + handle._adapter = this; + handle.parent = this.doc; + }); + this._markers.forEach(marker => { + marker._adapter = this; + marker.doc = this.doc; + this.marks[marker.id] = marker; + }); + this._lineWidgets.forEach(record => { + if (record.widget) { + record.widget.doc = this.doc; + } + record.renderedWrapper = null; + }); + if (this.doc) { + this.doc._scrollLeft = snapshot.scrollLeft || 0; + this.doc._scrollTop = snapshot.scrollTop || 0; + } + this._syncDocumentMetadata(); + this._refreshLegacyDecorations(); + this._refreshLegacyHighlighting(); + this._refreshGutters(); + this.scrollTo(snapshot.scrollLeft || 0, snapshot.scrollTop || 0); + }; + + CodeMirror6Adapter.prototype._disposeDetachedBackend = function () { + if (!this._detachedDoc) { + throw new Error("Only detached document backends may be disposed."); + } + this.doc = null; + this.destroy(); + }; + + CodeMirror6Adapter.prototype._shareHistoryWith = function (otherAdapter) { + this._historyState = otherAdapter._historyState; + }; + + CodeMirror6Adapter.prototype._splitSharedHistory = function () { + const sourceState = this._historyState; + const splitState = _createHistoryState({ + _historyDone: _copyHistoryValue(sourceState._historyDone), + _historyUndone: _copyHistoryValue(sourceState._historyUndone), + _historyClosed: sourceState._historyClosed, + _historyLastModTime: sourceState._historyLastModTime, + _historyLastSelectionTime: sourceState._historyLastSelectionTime, + _historyLastOperationId: sourceState._historyLastOperationId, + _historyLastSelectionOperationId: + sourceState._historyLastSelectionOperationId, + _historyLastOrigin: sourceState._historyLastOrigin, + _historyLastSelectionOrigin: sourceState._historyLastSelectionOrigin, + _currentGeneration: sourceState._currentGeneration, + _nextGeneration: sourceState._nextGeneration, + _cleanGeneration: sourceState._cleanGeneration + }); + const visited = new Set(); + const assign = function (doc) { + if (visited.has(doc)) { + return; + } + visited.add(doc); + if (doc._adapter) { + doc._adapter._historyState = splitState; + } + doc._links.forEach(function (link) { + if (link.sharedHist) { + assign(link.doc); + } + }); + }; + assign(this.doc); + }; + + CodeMirror6Adapter.prototype._rebaseHistoryForLinkedChange = function (change) { + const lineDelta = change.text.length - 1 - + (change.to.line - change.from.line); + const rebaseStack = function (entries) { + let conflictIndex = -1; + entries.forEach(function (entry, entryIndex) { + if (!entry || entry.type !== "change" || !entry.steps) { + return; + } + let conflicts = false; + entry.steps.forEach(function (step) { + (step.redoChanges || []).forEach(function (storedChange) { + const from = storedChange.fromPos; + const to = storedChange.toPos; + if (!from || !to) { + return; + } + if (change.to.line < from.line) { + [step.redoChanges, step.undoChanges].forEach(function (changes) { + (changes || []).forEach(function (candidate) { + if (candidate.fromPos) { + candidate.fromPos.line += lineDelta; + } + if (candidate.toPos) { + candidate.toPos.line += lineDelta; + } + }); + }); + entry._linkedRebased = true; + } else if (change.from.line <= to.line) { + conflicts = true; + } + }); + }); + if (conflicts) { + conflictIndex = Math.max(conflictIndex, entryIndex); + } + }); + if (conflictIndex !== -1) { + entries.splice(0, conflictIndex + 1); + } + }; + rebaseStack(this._historyDone); + rebaseStack(this._historyUndone); + if (!this._historyDone.length || + this._historyDone[0].type !== "selection") { + const selection = this._view.state.selection; + this._historyDone.unshift({ + type: "selection", + beforeSelection: selection, + afterSelection: selection, + generationBefore: this._currentGeneration, + generationAfter: this._currentGeneration + }); + } + }; + + CodeMirror6Adapter.prototype._historyChangeSpecs = function (changes) { + return (changes || []).map(change => { + if (!change.fromPos || !change.toPos) { + return { + from: change.from, + to: change.to, + insert: change.insert + }; + } + return { + from: this.indexFromPos(change.fromPos), + to: this.indexFromPos(change.toPos), + insert: change.insert + }; + }); + }; + + CodeMirror6Adapter.prototype._historyTransaction = function ( + changes, + selection, + origin + ) { + const annotations = [ + this._originAnnotation.of(origin), + this._addToHistoryAnnotation.of(false), + this._bypassReadOnlyAnnotation.of(true) + ]; + const transactionSpec = { + changes: changes, + annotations: annotations + }; + if (selection) { + transactionSpec.selection = selection; + } + + let transaction = this._view.state.update(transactionSpec); + if (!transaction.docChanged && changes.length) { + transactionSpec.annotations = annotations.concat( + this._syntheticChangesAnnotation.of(changes.map(function (change) { + return { + from: change.from, + to: change.to, + insert: change.insert, + origin: origin + }; + })) + ); + transaction = this._view.state.update(transactionSpec); + } + return transaction; + }; + + CodeMirror6Adapter.prototype._createLinkedDocument = function (options) { + const settings = options || {}; + const from = Math.max( + this.firstLine(), + settings.from === null || settings.from === undefined ? + this.firstLine() : + Math.floor(settings.from) + ); + const to = Math.min( + this.lastLine() + 1, + settings.to === null || settings.to === undefined ? + this.lastLine() + 1 : + Math.floor(settings.to) + ); + const lines = []; + for (let lineNumber = from; lineNumber < to; lineNumber++) { + const line = this.getLine(lineNumber); + if (line !== undefined) { + lines.push(line); + } + } + if (!lines.length) { + lines.push(""); + } + + const linkedDoc = new CodeMirror.Doc( + lines.join(this.lineSeparator()), + settings.mode === undefined ? this.getOption("mode") : settings.mode, + from, + this.getOption("lineSeparator"), + this.getOption("direction") + ); + const linkFromThis = { + doc: linkedDoc, + sharedHist: Boolean(settings.sharedHist) + }; + const linkFromOther = { + doc: this.doc, + isParent: true, + sharedHist: Boolean(settings.sharedHist) + }; + this.doc._links.push(linkFromThis); + linkedDoc._links.push(linkFromOther); + if (settings.sharedHist) { + linkedDoc._adapter._shareHistoryWith(this); + } + this._copySharedMarkersTo(linkedDoc); + return linkedDoc; + }; + + CodeMirror6Adapter.prototype._unlinkDocument = function (otherDoc) { + const ownLinkIndex = this.doc._links.findIndex(function (link) { + return link.doc === otherDoc; + }); + if (ownLinkIndex === -1) { + return; + } + const sharedHistory = Boolean( + this.doc._links[ownLinkIndex].sharedHist + ); + this.doc._links.splice(ownLinkIndex, 1); + const otherLinkIndex = otherDoc._links.findIndex(link => { + return link.doc === this.doc; + }); + if (otherLinkIndex !== -1) { + otherDoc._links.splice(otherLinkIndex, 1); + } + if (sharedHistory && otherDoc._adapter) { + otherDoc._adapter._splitSharedHistory(); + } + this._partitionSharedMarkers(otherDoc); + }; + + CodeMirror6Adapter.prototype._shiftFirstLine = function (distance) { + if (!distance) { + return; + } + this._firstLine += distance; + this._invalidateLegacyModeStateCache(false); + this._lastViewport = null; + this._refreshGutters(); + }; + + CodeMirror6Adapter.prototype._applyLinkedChange = function (change, sharedHistory) { + const lineDelta = change.text.length - 1 - + (change.to.line - change.from.line); + if (change.to.line < this.firstLine()) { + this._shiftFirstLine(lineDelta); + if (!sharedHistory) { + this._rebaseHistoryForLinkedChange(change); + } + return; + } + if (change.from.line > this.lastLine()) { + if (!sharedHistory) { + this._rebaseHistoryForLinkedChange(change); + } + return; + } + + let from = _copyPosition(change.from); + let to = _copyPosition(change.to); + let text = change.text.slice(); + if (from.line < this.firstLine()) { + const shift = text.length - 1 - + (this.firstLine() - from.line); + this._shiftFirstLine(shift); + from = { + line: this.firstLine(), + ch: 0 + }; + to = { + line: to.line + shift, + ch: to.ch + }; + text = [text[text.length - 1]]; + } + if (to.line > this.lastLine()) { + to = { + line: this.lastLine(), + ch: (this.getLine(this.lastLine()) || "").length + }; + text = [text[0]]; + } + if (!sharedHistory) { + this._rebaseHistoryForLinkedChange(change); + } + this._view.dispatch({ + changes: { + from: this.indexFromPos(from), + to: this.indexFromPos(to), + insert: text.join("\n") + }, + annotations: [ + this._originAnnotation.of(change.origin), + this._addToHistoryAnnotation.of(false), + this._bypassReadOnlyAnnotation.of(true), + this._linkedChangeAnnotation.of(true) + ] + }); + }; + + CodeMirror6Adapter.prototype._propagateLinkedChange = function (change) { + const visited = new Set([this.doc]); + const visit = function (doc, sharedHistory) { + doc._links.forEach(function (link) { + if (visited.has(link.doc)) { + return; + } + visited.add(link.doc); + const sharesHistory = sharedHistory && Boolean(link.sharedHist); + if (link.doc._adapter) { + link.doc._adapter._applyLinkedChange(change, sharesHistory); + } + visit(link.doc, sharesHistory); + }); + }; + visit(this.doc, true); + }; + + CodeMirror6Adapter.prototype._dragDropExtension = function (enabled) { + if (enabled !== false) { + return []; + } + + return CM6.EditorView.domEventHandlers({ + dragstart: function (event) { + event.preventDefault(); + return true; + }, + drop: function (event) { + event.preventDefault(); + return true; + } + }); + }; + + CodeMirror6Adapter.prototype._contentAttributesExtension = function () { + return CM6.EditorView.contentAttributes.of({ + autocapitalize: this.getOption("autocapitalize") ? "on" : "off", + autocomplete: "off", + autocorrect: this.getOption("autocorrect") ? "on" : "off", + spellcheck: this.getOption("spellcheck") ? "true" : "false" + }); + }; + + CodeMirror6Adapter.prototype._placeholderExtension = function () { + const placeholder = this.getOption("placeholder"); + if (!placeholder) { + return []; + } + return CM6.placeholder(function () { + let element = placeholder; + if (!element || !element.nodeType) { + element = window.document.createElement("span"); + element.textContent = String(placeholder); + } + element.classList.add( + "CodeMirror-placeholder", + "CodeMirror-line-like" + ); + return element; + }); + }; + + CodeMirror6Adapter.prototype._forwardGutterEvent = function (eventName, gutterName, view, line, event) { + if (eventName === "gutterClick") { + event.preventDefault(); + } + + const lineNumber = view.state.doc.lineAt(line.from).number - 1 + + this._firstLine; + this._emit(eventName, this, lineNumber, gutterName, event); + return event.defaultPrevented; + }; + + CodeMirror6Adapter.prototype._gutterDomEventHandlers = function (gutterName) { + return { + mousedown: (view, line, event) => { + return this._forwardGutterEvent("gutterClick", gutterName, view, line, event); + }, + contextmenu: (view, line, event) => { + return this._forwardGutterEvent("gutterContextMenu", gutterName, view, line, event); + } + }; + }; + + CodeMirror6Adapter.prototype._lineNumbersExtension = function () { + const lineNumberOptions = { + domEventHandlers: this._gutterDomEventHandlers(LINE_NUMBER_GUTTER) + }; + const firstLineNumber = this.getOption("firstLineNumber"); + const lineNumberFormatter = this.getOption("lineNumberFormatter"); + if (typeof lineNumberFormatter === "function") { + lineNumberOptions.formatNumber = function (lineNumber) { + return lineNumberFormatter(lineNumber + (firstLineNumber || 1) - 1); + }; + } else if (firstLineNumber && firstLineNumber !== 1) { + lineNumberOptions.formatNumber = function (lineNumber) { + return String(lineNumber + firstLineNumber - 1); + }; + } + + const extensions = [ + CM6.lineNumbers(lineNumberOptions) + ]; + const markerSet = this._gutterRangeSetForName( + this._view ? this._view.state.doc : null, + LINE_NUMBER_GUTTER + ); + if (CM6.lineNumberMarkers) { + extensions.push(CM6.lineNumberMarkers.of(markerSet)); + } + return extensions; + }; + + CodeMirror6Adapter.prototype._removeGutterMarkerRecords = function (lineNumber, gutterName) { + const keepRecord = record => { + return record.gutterName !== gutterName || + this.getLineNumber(record.lineHandle) !== lineNumber; + }; + this._gutterMarkers = this._gutterMarkers.filter(keepRecord); + }; + + CodeMirror6Adapter.prototype._scheduleGutterRefresh = function () { + if (this._gutterRefreshScheduled || !this._view || this._destroyed) { + return; + } + this._gutterRefreshScheduled = true; + Promise.resolve().then(() => { + this._gutterRefreshScheduled = false; + this._refreshGutters(); + }); + }; + + CodeMirror6Adapter.prototype._createCustomGutterExtensions = function (gutters) { + return gutters.map(gutterSpec => { + const gutterName = gutterSpec.className; + return CM6.gutter({ + class: gutterName, + renderEmptyElements: true, + markers: view => this._gutterRangeSet(view, gutterName), + domEventHandlers: this._gutterDomEventHandlers(gutterName) + }); + }); + }; + + CodeMirror6Adapter.prototype._gutterExtensionGroups = function () { + const configuredGutters = (this.getOption("gutters") || []) + .map(_normalizeGutterSpec); + const lineNumbersEnabled = Boolean(this.getOption("lineNumbers")); + const lineNumberIndex = configuredGutters.findIndex(function (gutterSpec) { + return gutterSpec.className === LINE_NUMBER_GUTTER; + }); + const customGutters = configuredGutters.filter(function (gutterSpec) { + return gutterSpec.className !== LINE_NUMBER_GUTTER; + }); + + if (!lineNumbersEnabled) { + return { + leading: customGutters, + lineNumbers: null, + trailing: [] + }; + } + if (lineNumberIndex === -1) { + return { + leading: customGutters, + lineNumbers: _normalizeGutterSpec(LINE_NUMBER_GUTTER), + trailing: [] + }; + } + return { + leading: configuredGutters.slice(0, lineNumberIndex).filter(function (gutterSpec) { + return gutterSpec.className !== LINE_NUMBER_GUTTER; + }), + lineNumbers: configuredGutters[lineNumberIndex], + trailing: configuredGutters.slice(lineNumberIndex + 1).filter(function (gutterSpec) { + return gutterSpec.className !== LINE_NUMBER_GUTTER; + }) + }; + }; + + CodeMirror6Adapter.prototype._createLeadingGutterExtensions = function () { + const groups = this._gutterExtensionGroups(); + const extensions = this._createCustomGutterExtensions(groups.leading); + if (groups.lineNumbers) { + extensions.push(this._lineNumbersExtension()); + } + return extensions; + }; + + CodeMirror6Adapter.prototype._createTrailingGutterExtensions = function () { + return this._createCustomGutterExtensions( + this._gutterExtensionGroups().trailing + ); + }; + + CodeMirror6Adapter.prototype._applyConfiguredGutterStyles = function () { + if (!this._view) { + return; + } + const groups = this._gutterExtensionGroups(); + const specs = groups.leading.concat( + groups.lineNumbers ? [groups.lineNumbers] : [], + groups.trailing + ); + const gutters = Array.from(this._view.dom.querySelectorAll(".cm-gutter")); + gutters.forEach(function (gutter, index) { + const style = specs[index] && specs[index].style || ""; + const previousStyle = + gutter.dataset.phoenixLegacyGutterStyle || ""; + if (style === previousStyle) { + return; + } + gutter.style.cssText = style; + if (style) { + gutter.dataset.phoenixLegacyGutterStyle = style; + } else { + delete gutter.dataset.phoenixLegacyGutterStyle; + } + }); + }; + + CodeMirror6Adapter.prototype._editorAttributes = function (view) { + const currentManagedClasses = new Set(["cm-editor", "cm-focused"]); + String(view.themeClasses || "").split(/\s+/).filter(Boolean).forEach(function (className) { + currentManagedClasses.add(className); + }); + + const managedClasses = new Set(this._managedRootClasses); + currentManagedClasses.forEach(function (className) { + managedClasses.add(className); + }); + managedClasses.add("CodeMirror-focused"); + + const compatibilityClasses = new Set(["CodeMirror", "phoenix-codemirror-6"]); + Array.from(view.dom.classList).forEach(function (className) { + if (!managedClasses.has(className)) { + compatibilityClasses.add(className); + } + }); + String(this.getOption("theme") || "default") + .split(/\s+/) + .filter(Boolean) + .forEach(function (themeName) { + compatibilityClasses.add(`cm-s-${themeName}`); + }); + if (view.hasFocus) { + compatibilityClasses.add("CodeMirror-focused"); + } + + this._managedRootClasses = currentManagedClasses; + return { + class: Array.from(compatibilityClasses).join(" "), + "data-editor-engine": "codemirror6" + }; + }; + + CodeMirror6Adapter.prototype._setFocusState = function (focused) { + focused = Boolean(focused); + if (this._view) { + this._view.dom.classList.toggle("CodeMirror-focused", focused); + } + if (this._focusState === focused) { + return; + } + this._focusState = focused; + this._emit(focused ? "focus" : "blur", this._instance()); + }; + + CodeMirror6Adapter.prototype._captureFocusedLineWidget = function () { + if (!this._view) { + return null; + } + const activeElement = this._view.root.activeElement; + const record = this._lineWidgets.find(function (candidate) { + return !candidate.cleared && candidate.node && + (candidate.node === activeElement || + candidate.node.contains(activeElement)); + }); + if (!record) { + return null; + } + const editorRoot = activeElement.closest && + activeElement.closest(".CodeMirror"); + return { + element: activeElement, + editor: editorRoot && editorRoot.CodeMirror !== this ? + editorRoot.CodeMirror : + null + }; + }; + + CodeMirror6Adapter.prototype._restoreFocusedLineWidget = function (focusedWidget) { + if (!focusedWidget || !this._view) { + return; + } + if (focusedWidget.editor && typeof focusedWidget.editor.focus === "function") { + const editorRoot = typeof focusedWidget.editor.getWrapperElement === "function" ? + focusedWidget.editor.getWrapperElement() : + null; + const activeElement = this._view.root.activeElement; + const focusWasDropped = !activeElement || + activeElement === this._view.dom.ownerDocument.body; + const hostTookFocus = activeElement === this._contentElement; + const editorStillHasFocus = editorRoot && + editorRoot.contains(activeElement); + if (editorRoot && editorRoot.isConnected && + !focusedWidget.editor._destroyed && + (focusWasDropped || hostTookFocus || editorStillHasFocus)) { + focusedWidget.editor.focus(); + return; + } + } + if (!focusedWidget.element || !focusedWidget.element.isConnected) { + return; + } + const activeElement = this._view.root.activeElement; + if (activeElement && activeElement !== this._view.dom.ownerDocument.body) { + return; + } + focusedWidget.element.focus({ preventScroll: true }); + }; + + CodeMirror6Adapter.prototype._gutterRangeSetForName = function (doc, gutterName) { + const ranges = []; + const addMarkerRange = record => { + const lineNumber = this.getLineNumber(record.lineHandle); + if (lineNumber === null || lineNumber === undefined) { + return false; + } + if (record.gutterName !== gutterName || !doc || + lineNumber < this.firstLine() || + lineNumber > this.lastLine()) { + return true; + } + + const line = doc.line(lineNumber - this._firstLine + 1); + ranges.push(new PhoenixGutterMarker(record).range(line.from)); + return true; + }; + + this._gutterMarkers = this._gutterMarkers.filter(addMarkerRange); + return CM6.RangeSet.of(ranges, true); + }; + + CodeMirror6Adapter.prototype._gutterRangeSet = function (view, gutterName) { + return this._gutterRangeSetForName(view.state.doc, gutterName); + }; + + CodeMirror6Adapter.prototype._gutterLineClassRangeSet = function (doc) { + const ranges = []; + this._lineClasses = this._lineClasses.filter(record => { + const lineNumber = this.getLineNumber(record.lineHandle); + if (lineNumber === null || lineNumber === undefined || + lineNumber < this.firstLine() || + lineNumber > this.lastLine()) { + return false; + } + if (record.where === "gutter" || record.where === "wrap") { + const line = doc.line(lineNumber - this._firstLine + 1); + ranges.push(new PhoenixGutterLineClass(record.className).range(line.from)); + } + return true; + }); + return CM6.RangeSet.of(ranges, true); + }; + + CodeMirror6Adapter.prototype._overwriteInputHandler = function () { + return CM6.EditorView.inputHandler.of((view, _from, _to, text, insert) => { + if (!this.state.overwrite || !text) { + return false; + } + + const defaultTransaction = insert(); + if (!defaultTransaction.isUserEvent("input.type")) { + return false; + } + + const lastInsertedLineLength = text.slice(text.lastIndexOf("\n") + 1).length; + const replacement = view.state.changeByRange(function (range) { + let to = range.to; + if (range.empty) { + const line = view.state.doc.lineAt(range.head); + to = Math.min(line.to, range.head + lastInsertedLineLength); + } + return { + changes: { + from: range.from, + to: to, + insert: text + }, + range: CM6.EditorSelection.cursor(range.from + text.length, -1) + }; + }); + + view.dispatch({ + annotations: defaultTransaction.annotations, + changes: replacement.changes, + effects: replacement.effects.concat(defaultTransaction.effects), + scrollIntoView: defaultTransaction.scrollIntoView, + selection: replacement.selection + }); + return true; + }); + }; + + CodeMirror6Adapter.prototype._compatHighlightDecorationSet = function (view) { + const ranges = []; + const visibleLines = new Set(); + const lineStyleClasses = new Map(); + view.visibleRanges.forEach(function (range) { + let line = view.state.doc.lineAt(range.from); + while (line.from <= range.to) { + visibleLines.add(line.number); + if (line.number >= view.state.doc.lines) { + break; + } + line = view.state.doc.line(line.number + 1); + } + }); + + visibleLines.forEach(lineNumber => { + const line = view.state.doc.line(lineNumber); + const legacyLineNumber = lineNumber - 1 + this._firstLine; + const modeLineClasses = { + background: new Set(), + text: new Set() + }; + lineStyleClasses.set(lineNumber, modeLineClasses); + this.getLineTokens(legacyLineNumber, true).forEach(function (token) { + const classes = _normalizeStyleClasses( + _extractLineClasses(token.type, modeLineClasses) + ); + if (!classes || token.end <= token.start) { + return; + } + ranges.push(CM6.Decoration.mark({ + class: classes + }).range(line.from + token.start, line.from + token.end)); + }); + if (!line.length) { + const mode = this.getMode(); + const state = _modeStateBefore( + this, + mode, + legacyLineNumber + ); + _extractLineClasses( + _readBlankLineStyle(mode, state), + modeLineClasses + ); + } + }); + + if (this.getOption("addModeClass")) { + const modeName = _modeName(this.getOption("mode")) + .replace(/^text\//, "") + .replace(/^application\//, "") + .replace(/[^a-z0-9_-]+/g, "-"); + visibleLines.forEach(function (lineNumber) { + const line = view.state.doc.line(lineNumber); + if (line.to > line.from) { + ranges.push(CM6.Decoration.mark({ + class: `cm-m-${modeName}` + }).range(line.from, line.to)); + } + }); + } + + this._overlays.forEach(overlayRecord => { + const mode = overlayRecord.mode; + visibleLines.forEach(lineNumber => { + const line = view.state.doc.line(lineNumber); + const legacyLineNumber = lineNumber - 1 + this._firstLine; + const modeLineClasses = lineStyleClasses.get(lineNumber); + const baseStyles = this.getLineTokens( + legacyLineNumber, + true + ).map(function (token) { + return { + from: token.start, + to: token.end, + type: _stripLineClasses(token.type) + }; + }); + const stream = new CodeMirror.StringStream( + line.text, + this.getOption("tabSize") || 4, + _legacyLineOracle( + this, + legacyLineNumber, + baseStyles + ) + ); + while (!stream.eol()) { + stream.start = stream.pos; + const style = _readModeToken(mode, stream, true); + const tokenStyle = _extractLineClasses( + style, + modeLineClasses + ); + if (visibleLines.has(lineNumber) && tokenStyle && + stream.pos > stream.start) { + const classes = [ + _normalizeStyleClasses(tokenStyle), + overlayRecord.opaque ? "cm-overlay-opaque" : "" + ].filter(Boolean).join(" "); + if (classes) { + ranges.push(CM6.Decoration.mark({ + class: classes + }).range(line.from + stream.start, line.from + stream.pos)); + } + } + } + if (line.text.length === 0) { + _extractLineClasses( + _readBlankLineStyle(mode, true), + modeLineClasses + ); + } + }); + }); + + visibleLines.forEach(function (lineNumber) { + const modeLineClasses = lineStyleClasses.get(lineNumber); + const classes = Array.from(new Set( + Array.from(modeLineClasses.text) + .concat(Array.from(modeLineClasses.background)) + )).join(" "); + if (!classes) { + return; + } + const line = view.state.doc.line(lineNumber); + ranges.push(CM6.Decoration.line({ + attributes: { + class: classes + } + }).range(line.from)); + }); + + return CM6.Decoration.set(ranges, true); + }; + + CodeMirror6Adapter.prototype._compatHighlightExtension = function () { + const adapter = this; + return CM6.ViewPlugin.fromClass(class { + constructor(view) { + this.decorations = adapter._compatHighlightDecorationSet(view); + } + + update(update) { + if (update.docChanged || update.viewportChanged || update.geometryChanged) { + this.decorations = adapter._compatHighlightDecorationSet(update.view); + } + } + }, { + decorations: function (plugin) { + return plugin.decorations; + } + }); + }; + + CodeMirror6Adapter.prototype._createExtensions = function () { + const self = this; + const options = this._options; + const tabSize = options.tabSize || 4; + const indentText = _indentUnitText(options); + + return [ + CM6.EditorState.allowMultipleSelections.of(true), + this._readOnlyCompartment.of(CM6.EditorState.readOnly.of(Boolean(options.readOnly))), + this._editableCompartment.of(CM6.EditorView.editable.of( + options.readOnly !== "nocursor" + )), + this._lineNumbersCompartment.of(this._createLeadingGutterExtensions()), + this._lineWrappingCompartment.of(options.lineWrapping ? CM6.EditorView.lineWrapping : []), + this._activeLineCompartment.of( + _activeLineExtension(options.styleActiveLine) + ), + this._closeBracketsCompartment.of( + _closeBracketsExtension(this, options.autoCloseBrackets) + ), + this._bracketMatchingCompartment.of([]), + this._selectionMatchesCompartment.of( + _selectionMatchExtension( + this, + options.highlightSelectionMatches + ) + ), + this._drawSelectionCompartment.of(_drawSelectionExtension(options)), + this._tabSizeCompartment.of(CM6.EditorState.tabSize.of(tabSize)), + this._indentUnitCompartment.of(CM6.indentUnit.of(indentText)), + this._languageCompartment.of(_languageExtensionForMode(options.mode, options)), + this._scrollPastEndCompartment.of(options.scrollPastEnd ? CM6.scrollPastEnd() : []), + this._smartIndentCompartment.of(options.smartIndent === false ? [] : CM6.indentOnInput()), + this._dragDropCompartment.of(this._dragDropExtension(options.dragDrop)), + this._contentAttributesCompartment.of(this._contentAttributesExtension()), + this._placeholderCompartment.of(this._placeholderExtension()), + this._decorationsCompartment.of(CM6.EditorView.decorations.of(CM6.Decoration.none)), + this._compatHighlightCompartment.of(this._compatHighlightExtension()), + this._gutterLineClassesCompartment.of(CM6.gutterLineClass.of(CM6.RangeSet.empty)), + this._guttersCompartment.of(this._createTrailingGutterExtensions()), + this._overwriteInputHandler(), + CM6.EditorView.updateListener.of(function (update) { + if (update.focusChanged) { + self._setFocusState(update.view.hasFocus); + } + }), + CM6.EditorState.transactionFilter.of(function (transaction) { + if (!transaction.docChanged || + transaction.annotation(self._bypassReadOnlyAnnotation)) { + return transaction; + } + if (self.getOption("disableInput") && ( + transaction.isUserEvent("input") || + transaction.isUserEvent("delete") || + transaction.isUserEvent("move.drop") + )) { + return []; + } + let blocked = false; + transaction.changes.iterChangedRanges(function (from, to) { + if (blocked) { + return; + } + blocked = self._markers.some(function (marker) { + if (marker._cleared || !marker.readOnly) { + return false; + } + if (from === to) { + return from > marker._from && from < marker._to; + } + return from < marker._to && to > marker._from; + }); + }); + return blocked ? [] : transaction; + }), + CM6.highlightSpecialChars(), + CM6.dropCursor(), + CM6.rectangularSelection(), + CM6.crosshairCursor(), + CM6.syntaxHighlighting(phoenixHighlightStyle), + CM6.EditorView.domEventHandlers({ + keydown: function (event) { + return self._handleKeyDown(event); + } + }), + CM6.keymap.of([{ + key: "Mod-z", + preventDefault: true, + run: function () { + self.undo(); + return true; + } + }, { + key: "Shift-Mod-z", + preventDefault: true, + run: function () { + self.redo(); + return true; + } + }, { + key: "Mod-y", + preventDefault: true, + run: function () { + self.redo(); + return true; + } + }].concat(CM6.defaultKeymap, CM6.searchKeymap)), + CM6.EditorView.clickAddsSelectionRange.of(function (event) { + return event.altKey; + }), + CM6.EditorView.editorAttributes.of(function (view) { + return self._editorAttributes(view); + }), + CM6.EditorView.domEventHandlers({ + keypress: function (event) { + return self._handleKeyPress(event); + }, + keyup: function (event) { + return self._handleKeyUp(event); + }, + focus: function (_event, view) { + self._setFocusState(view.hasFocus); + return false; + }, + blur: function (_event, view) { + self._setFocusState(view.hasFocus); + return false; + }, + cut: function (event) { + return self._handleClipboardEvent("cut", event); + }, + copy: function (event) { + return self._handleClipboardEvent("copy", event); + }, + paste: function (event) { + self._emit("paste", self._instance(), event); + return event.defaultPrevented; + }, + drop: function (event) { + self._emit("drop", self._instance(), event); + return event.defaultPrevented; + }, + dragstart: function (event) { + self._emit("dragstart", self._instance(), event); + return event.defaultPrevented; + }, + dragenter: function (event) { + self._emit("dragenter", self._instance(), event); + return event.defaultPrevented; + }, + dragover: function (event) { + self._emit("dragover", self._instance(), event); + return event.defaultPrevented; + }, + dragleave: function (event) { + self._emit("dragleave", self._instance(), event); + return event.defaultPrevented; + }, + mousedown: function (event) { + return self._handleMouseDown(event); + }, + dblclick: function (event) { + self._emit("dblclick", self._instance(), event); + return event.defaultPrevented; + }, + contextmenu: function (event) { + self._emit("contextmenu", self._instance(), event); + return event.defaultPrevented; + }, + touchstart: function (event) { + self._emit("touchstart", self._instance(), event); + return event.defaultPrevented; + } + }), + CM6.EditorView.updateListener.of(function (update) { + if (update.geometryChanged) { + self._scheduleRulerRefresh(); + self._refreshLineWidgetLayouts(); + self._invalidateRenderedLines(); + } + if (update.docChanged || + update.geometryChanged || + update.viewportChanged) { + self._refreshScrollbarModel(); + self._decorateDOM(); + if (update.viewportChanged) { + self._emitViewportChange(); + } + if (update.viewportChanged && + !update.docChanged && + !update.geometryChanged) { + self._scheduleRenderLines(); + } + } else if (update.selectionSet) { + self._decorateDOM(); + } + }), + CM6.EditorView.theme({ + "&": { + height: "100%" + }, + ".cm-scroller": { + overflow: "auto" + } + }) + ]; + }; + + CodeMirror6Adapter.prototype._originForTransaction = function (transaction, edit) { + const annotatedOrigin = transaction.annotation(this._originAnnotation); + if (annotatedOrigin !== undefined) { + return annotatedOrigin; + } + if (transaction.isUserEvent("input.paste")) { + return "paste"; + } + if (transaction.isUserEvent("input.drop")) { + return "paste"; + } + if (transaction.isUserEvent("move.drop")) { + return edit && edit.text ? "paste" : "drag"; + } + if (transaction.isUserEvent("delete.cut")) { + return "cut"; + } + if (transaction.isUserEvent("delete")) { + return "+delete"; + } + if (transaction.isUserEvent("input")) { + return "+input"; + } + return undefined; + }; + + CodeMirror6Adapter.prototype._createLegacyChange = function ( + doc, + from, + to, + text, + origin + ) { + const change = { + from: _positionFromOffset( + doc, + from, + this._firstLine + ), + to: _positionFromOffset( + doc, + to, + this._firstLine + ), + text: Array.isArray(text) ? text.slice() : _splitLines(text), + removed: _splitLines(doc.sliceString(from, to)), + origin: origin, + _fromIndex: from, + _toIndex: to, + _cancelled: false, + cancel: function () { + this._cancelled = true; + } + }; + if (origin !== "undo" && origin !== "redo") { + change.update = function (newFrom, newTo, newText, newOrigin) { + if (newFrom) { + this.from = _copyPosition(newFrom); + } + if (newTo) { + this.to = _copyPosition(newTo); + } + if (newText !== undefined) { + this.text = Array.isArray(newText) ? newText.slice() : _splitLines(newText); + } + if (newOrigin !== undefined) { + this.origin = newOrigin; + } + this._updated = true; + }; + } + return change; + }; + + CodeMirror6Adapter.prototype._changeObjectsForTransaction = function (transaction) { + const changes = []; + transaction.changes.iterChanges((from, to, _newFrom, _newTo, inserted) => { + const origin = this._originForTransaction(transaction, { + from: from, + to: to, + text: inserted.toString() + }); + changes.push(this._createLegacyChange( + transaction.startState.doc, + from, + to, + inserted.toString(), + origin + )); + }); + return changes.reverse(); + }; + + CodeMirror6Adapter.prototype._selectionAfterBeforeChange = function ( + selection, + originalChanges, + replacementChanges, + finalDoc + ) { + if (!selection) { + return null; + } + const maxOffset = finalDoc.length; + const mapPosition = function (position, association) { + const originalPosition = originalChanges.invertedDesc.mapPos( + position, + association + ); + return replacementChanges.mapPos(originalPosition, association); + }; + return CM6.EditorSelection.create(selection.ranges.map(function (range) { + const forward = range.anchor <= range.head; + const anchorAssociation = range.empty ? 1 : forward ? -1 : 1; + const headAssociation = range.empty ? 1 : forward ? 1 : -1; + return CM6.EditorSelection.range( + _clamp( + mapPosition(range.anchor, anchorAssociation), + 0, + maxOffset + ), + _clamp( + mapPosition(range.head, headAssociation), + 0, + maxOffset + ) + ); + }), Math.min(selection.mainIndex, selection.ranges.length - 1)); + }; + + CodeMirror6Adapter.prototype._selectionAfterLegacyChange = function ( + selection, + change, + changeSet + ) { + const from = change._fromIndex; + const to = change._toIndex; + const changeEnd = from + change.text.join("\n").length; + const mapPosition = function (position) { + if (position < from) { + return position; + } + if (position <= to) { + return changeEnd; + } + return changeSet.mapPos(position, 1); + }; + return CM6.EditorSelection.create(selection.ranges.map(function (range) { + return CM6.EditorSelection.range( + mapPosition(range.anchor), + mapPosition(range.head) + ); + }), selection.mainIndex); + }; + + CodeMirror6Adapter.prototype._skipAtomicSelectionOffset = function ( + offset, + oldOffset, + bias, + mayClear, + doc, + depth + ) { + if (depth > this._markers.length * 2 + 2) { + return offset; + } + + const markers = this._markers.slice(); + for (let index = 0; index < markers.length; index++) { + const marker = markers[index]; + if (marker._cleared || marker._hidden || marker.type !== "range") { + continue; + } + + const preventCursorLeft = Object.prototype.hasOwnProperty.call(marker, "selectLeft") ? + !marker.selectLeft : + Boolean(marker.inclusiveLeft); + const preventCursorRight = Object.prototype.hasOwnProperty.call(marker, "selectRight") ? + !marker.selectRight : + Boolean(marker.inclusiveRight); + const insideMarker = + (offset > marker._from || + preventCursorLeft && offset === marker._from) && + (offset < marker._to || + preventCursorRight && offset === marker._to); + if (!insideMarker) { + continue; + } + + if (mayClear) { + CodeMirror.signal(marker, "beforeCursorEnter"); + if (marker._cleared) { + return this._skipAtomicSelectionOffset( + offset, + oldOffset, + bias, + mayClear, + doc, + depth + 1 + ); + } + if (marker.clearOnEnter) { + marker.clear(); + return this._skipAtomicSelectionOffset( + offset, + oldOffset, + bias, + mayClear, + doc, + depth + 1 + ); + } + } + if (!marker.atomic && !marker.collapsed && !marker.replacedWith) { + continue; + } + + let preferBefore; + if (oldOffset < marker._from) { + preferBefore = true; + } else if (oldOffset > marker._to) { + preferBefore = false; + } else { + preferBefore = bias < 0; + } + + const before = preventCursorLeft ? marker._from - 1 : marker._from; + const after = preventCursorRight ? marker._to + 1 : marker._to; + const candidates = preferBefore ? [before, after] : [after, before]; + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + const candidate = candidates[candidateIndex]; + if (candidate < 0 || candidate > doc.length || candidate === offset) { + continue; + } + const result = this._skipAtomicSelectionOffset( + candidate, + offset, + bias, + mayClear, + doc, + depth + 1 + ); + if (result !== null) { + return result; + } + } + return null; + } + + return offset; + }; + + CodeMirror6Adapter.prototype._applyBeforeSelectionChange = function ( + selection, + doc, + origin, + bias, + oldSelection + ) { + let updatedRanges = selection.ranges.map(range => { + return _selectionFromOffsets(range, doc, this._firstLine); + }); + let selectionUpdated = false; + const selectionObject = { + ranges: updatedRanges.map(_copySelection), + origin: origin, + update: function (ranges) { + updatedRanges = ranges.map(_copySelection); + selectionUpdated = true; + } + }; + this._signalBeforeSelectionChange(selectionObject); + const previousSelection = oldSelection && + oldSelection.ranges.length === updatedRanges.length ? + oldSelection : + null; + const primaryIndex = selectionUpdated ? + updatedRanges.length - 1 : + Math.min(selection.mainIndex, updatedRanges.length - 1); + let selectionBias = bias; + if (!selectionBias) { + const primaryHead = this.indexFromPos(updatedRanges[primaryIndex].head); + const previousHead = previousSelection ? + previousSelection.ranges[primaryIndex].head : + primaryHead; + selectionBias = primaryHead < previousHead ? -1 : 1; + } + + updatedRanges.forEach((range, index) => { + const anchor = this.indexFromPos(range.anchor); + const head = this.indexFromPos(range.head); + const previousRange = previousSelection && previousSelection.ranges[index]; + const adjustedAnchor = this._skipAtomicSelectionOffset( + anchor, + previousRange ? previousRange.anchor : anchor, + selectionBias, + true, + doc, + 0 + ); + const adjustedHead = head === anchor ? + adjustedAnchor : + this._skipAtomicSelectionOffset( + head, + previousRange ? previousRange.head : head, + selectionBias, + true, + doc, + 0 + ); + range.anchor = _positionFromOffset( + doc, + adjustedAnchor === null ? 0 : adjustedAnchor, + this._firstLine + ); + range.head = _positionFromOffset( + doc, + adjustedHead === null ? 0 : adjustedHead, + this._firstLine + ); + }); + return CM6.EditorSelection.create(updatedRanges.map(range => { + return CM6.EditorSelection.range( + this.indexFromPos(range.anchor), + this.indexFromPos(range.head) + ); + }), primaryIndex); + }; + + CodeMirror6Adapter.prototype._prepareTransaction = function (transaction, view) { + const linkedChange = Boolean( + transaction.annotation(this._linkedChangeAnnotation) + ); + const skipBeforeChange = Boolean( + transaction.annotation(this._skipBeforeChangeAnnotation) + ); + const syntheticChangeSpecs = + transaction.annotation(this._syntheticChangesAnnotation) || []; + let preparedTransaction = transaction; + if (!transaction.docChanged) { + if (transaction.selection) { + const selection = this._applyBeforeSelectionChange( + transaction.newSelection, + transaction.newDoc, + transaction.annotation(this._originAnnotation), + transaction.annotation(this._selectionBiasAnnotation), + view.state.selection + ); + if (!_sameSelection(selection, transaction.newSelection)) { + preparedTransaction = view.state.update({ + selection: selection, + annotations: transaction.annotations, + effects: transaction.effects, + scrollIntoView: transaction.scrollIntoView + }); + } + } + if (!syntheticChangeSpecs.length) { + return { + transaction: preparedTransaction, + changes: [] + }; + } + } + + const changes = transaction.docChanged ? + this._changeObjectsForTransaction(transaction) : + syntheticChangeSpecs.map(change => { + return this._createLegacyChange( + transaction.startState.doc, + change.from, + change.to, + change.insert, + change.origin + ); + }); + if (linkedChange || skipBeforeChange) { + return { + transaction: preparedTransaction, + changes: changes, + forceHistory: syntheticChangeSpecs.length > 0 + }; + } + changes.forEach(change => { + this._signalBeforeChange(change); + }); + const activeChanges = changes.filter(function (change) { + return !change._cancelled; + }); + const changedByListener = activeChanges.length !== changes.length || + activeChanges.some(function (change) { + return change._updated; + }); + if (!activeChanges.length) { + return transaction.docChanged ? null : { + transaction: preparedTransaction, + changes: [] + }; + } + if (!changedByListener) { + return { + transaction: preparedTransaction, + changes: activeChanges, + forceHistory: syntheticChangeSpecs.length > 0 + }; + } + + const changeSpecs = activeChanges.map(change => { + return { + from: this.indexFromPos(change.from), + to: this.indexFromPos(change.to), + insert: change.text.join("\n") + }; + }).sort(function (left, right) { + return left.from - right.from || left.to - right.to; + }); + const preliminary = view.state.update({ + changes: changeSpecs + }); + const selection = this._selectionAfterBeforeChange( + transaction.selection ? transaction.newSelection : null, + transaction.changes, + preliminary.changes, + preliminary.newDoc + ); + const replacement = view.state.update({ + changes: changeSpecs, + selection: selection || undefined, + annotations: transaction.annotations, + scrollIntoView: transaction.scrollIntoView + }); + return { + transaction: replacement, + changes: this._changeObjectsForTransaction(replacement).map(function (change, index) { + change.origin = activeChanges[index] ? activeChanges[index].origin : change.origin; + return change; + }), + forceHistory: syntheticChangeSpecs.length > 0 && !replacement.docChanged + }; + }; + + CodeMirror6Adapter.prototype._lineHandleChangeEvents = function ( + legacyChanges, + documentChanges, + oldDoc + ) { + const firstLine = this._firstLine; + const wholeLineUpdateBefore = + this.getOption("wholeLineUpdateBefore") !== false; + const eventsByChange = legacyChanges.map(function () { + return []; + }); + legacyChanges.forEach((change, changeIndex) => { + const fromLineNumber = _clamp( + change.from.line - firstLine + 1, + 1, + oldDoc.lines + ); + const toLineNumber = _clamp( + change.to.line - firstLine + 1, + 1, + oldDoc.lines + ); + const fromLine = oldDoc.line(fromLineNumber); + const toLine = oldDoc.line(toLineNumber); + const wholeLineUpdate = wholeLineUpdateBefore && + change.from.ch === 0 && + change.to.ch === 0 && + change.text[change.text.length - 1] === ""; + const changedPositions = new Set(); + if (wholeLineUpdate) { + changedPositions.add(toLine.from); + } else { + changedPositions.add(fromLine.from); + if (fromLine.number !== toLine.number && + change.text.length > 1) { + changedPositions.add(toLine.from); + } + } + + this._lineHandles.forEach(handle => { + if (!handle._deleted && + changedPositions.has(handle._position)) { + eventsByChange[changeIndex].push({ + handle: handle, + change: documentChanges[changeIndex] + }); + } + }); + }); + return eventsByChange; + }; + + CodeMirror6Adapter.prototype._mapMetadata = function (changes, oldDoc, newDoc) { + const wholeLineUpdateBefore = this.getOption("wholeLineUpdateBefore") !== false; + const deletedHandles = []; + this._lineHandles.forEach(handle => { + if (handle._deleted) { + return; + } + const handlePosition = _clamp( + handle._position, + 0, + oldDoc.length + ); + let deletedBoundary = false; + changes.iterChangedRanges(function (from, to, newFrom, newTo) { + const fromLineStart = oldDoc.lineAt( + _clamp(from, 0, oldDoc.length) + ).from; + const toLineStart = oldDoc.lineAt( + _clamp(to, 0, oldDoc.length) + ).from; + const insertedSpansMultipleLines = + newDoc.lineAt(_clamp(newFrom, 0, newDoc.length)).number !== + newDoc.lineAt(_clamp(newTo, 0, newDoc.length)).number; + const insertedText = newDoc.sliceString(newFrom, newTo); + const wholeLineUpdate = wholeLineUpdateBefore && + from === fromLineStart && + to === toLineStart && + (!insertedText || insertedText.endsWith("\n")); + const removesHandle = wholeLineUpdate ? + handlePosition >= fromLineStart && + handlePosition < toLineStart : + fromLineStart !== toLineStart && + handlePosition > fromLineStart && + (handlePosition < toLineStart || + handlePosition === toLineStart && + !insertedSpansMultipleLines); + if (removesHandle) { + deletedBoundary = true; + } + }); + if (deletedBoundary) { + handle._deleted = true; + handle.parent = null; + deletedHandles.push(handle); + return; + } + const mapped = changes.mapPos(handlePosition, 1); + handle._position = newDoc.lineAt(_clamp(mapped, 0, newDoc.length)).from; + }); + deletedHandles.sort(function (left, right) { + return left._position - right._position; + }); + + this._markers.forEach(marker => { + if (marker._cleared || marker._hidden) { + return; + } + if (marker.type === "bookmark") { + let deletedInterior = false; + let association = marker.insertLeft ? 1 : -1; + changes.iterChangedRanges(function (from, to) { + if (marker._from > from && marker._from < to) { + deletedInterior = true; + } else if (from !== to && marker._from === from) { + association = -1; + } else if (from !== to && marker._from === to) { + association = 1; + } + }); + if (deletedInterior) { + const wasHidden = marker._hidden; + marker._hidden = true; + this._syncMarkerLines(marker); + if (!wasHidden && !this._historyApplying) { + this._pendingMarkerVisibilityEvents.push({ + marker: marker, + eventName: "hide" + }); + } + return; + } + marker._from = changes.mapPos( + marker._from, + association + ); + marker._to = marker._from; + this._syncMarkerLines(marker); + return; + } + const previousFrom = marker._from; + const previousTo = marker._to; + const wasHidden = marker._hidden; + marker._from = changes.mapPos( + previousFrom, + marker.inclusiveLeft ? -1 : 1 + ); + marker._to = changes.mapPos( + previousTo, + marker.inclusiveRight ? 1 : -1 + ); + changes.iterChangedRanges(function (fromA, toA, fromB, toB) { + if (fromA === toA) { + return; + } + if (!marker.inclusiveLeft && + previousFrom === fromA && + previousTo > fromA) { + marker._from = toB; + } + if (!marker.inclusiveRight && + previousTo === toA && + previousFrom < toA) { + marker._to = fromB; + } + }); + if (marker._to < marker._from) { + marker._to = marker._from; + } + marker._hidden = marker._from === marker._to && + marker.clearWhenEmpty !== false; + this._syncMarkerLines(marker); + if (wasHidden !== marker._hidden && !this._historyApplying) { + this._pendingMarkerVisibilityEvents.push({ + marker: marker, + eventName: marker._hidden ? "hide" : "unhide" + }); + } + }); + return deletedHandles; + }; + + CodeMirror6Adapter.prototype._replaceMetadataForFullChange = function () { + const deletedHandles = Array.from(this._lineHandles).filter(function (handle) { + return !handle._deleted; + }).sort(function (left, right) { + return left._position - right._position; + }); + deletedHandles.forEach(function (handle) { + handle._deleted = true; + handle.parent = null; + }); + + this._markers.forEach(marker => { + if (marker._cleared) { + return; + } + const wasHidden = marker._hidden; + marker._hidden = true; + this._syncMarkerLines(marker); + if (!wasHidden && !this._historyApplying) { + this._pendingMarkerVisibilityEvents.push({ + marker: marker, + eventName: "hide" + }); + } + }); + return deletedHandles; + }; + + CodeMirror6Adapter.prototype._captureMarkerSnapshot = function () { + return this._markers.filter(function (marker) { + return !marker._cleared; + }).map(function (marker) { + return { + id: marker._id, + from: marker._from, + to: marker._to, + hidden: marker._hidden + }; + }); + }; + + CodeMirror6Adapter.prototype._historyStepForTransaction = function (transaction) { + const undoChanges = []; + const redoChanges = []; + transaction.changes.iterChanges(function (fromA, toA, fromB, toB, inserted) { + undoChanges.push({ + from: fromB, + to: toB, + fromPos: _positionFromOffset( + transaction.newDoc, + fromB, + this._firstLine + ), + toPos: _positionFromOffset( + transaction.newDoc, + toB, + this._firstLine + ), + insert: transaction.startState.doc.sliceString(fromA, toA) + }); + redoChanges.push({ + from: fromA, + to: toA, + fromPos: _positionFromOffset( + transaction.startState.doc, + fromA, + this._firstLine + ), + toPos: _positionFromOffset( + transaction.startState.doc, + toA, + this._firstLine + ), + insert: inserted.toString() + }); + }.bind(this)); + return { + undoChanges: undoChanges, + redoChanges: redoChanges + }; + }; + + CodeMirror6Adapter.prototype._historyStepForLegacyChanges = function ( + changes, + oldDoc, + newDoc + ) { + const undoChanges = []; + const redoChanges = []; + changes.forEach(change => { + const from = change._fromIndex; + const to = change._toIndex; + const inserted = change.text.join("\n"); + const removed = change.removed.join("\n"); + const newTo = from + inserted.length; + undoChanges.push({ + from: from, + to: newTo, + fromPos: _positionFromOffset( + newDoc, + from, + this._firstLine + ), + toPos: _positionFromOffset( + newDoc, + newTo, + this._firstLine + ), + insert: removed + }); + redoChanges.push({ + from: from, + to: to, + fromPos: _positionFromOffset( + oldDoc, + from, + this._firstLine + ), + toPos: _positionFromOffset( + oldDoc, + to, + this._firstLine + ), + insert: inserted + }); + }); + return { + undoChanges: undoChanges, + redoChanges: redoChanges + }; + }; + + CodeMirror6Adapter.prototype._restoreMarkerSnapshot = function (snapshot, previousVisibility) { + const markerStates = new Map((snapshot || []).map(function (state) { + return [state.id, state]; + })); + this._markers.forEach(marker => { + const state = markerStates.get(marker._id); + if (state && !marker._cleared) { + marker._from = state.from; + marker._to = state.to; + marker._hidden = state.hidden; + this._syncMarkerLines(marker); + } + }); + if (previousVisibility) { + this._markers.forEach(marker => { + const wasHidden = previousVisibility.get(marker); + if (wasHidden === undefined || wasHidden === marker._hidden) { + return; + } + this._pendingMarkerVisibilityEvents.push({ + marker: marker, + eventName: marker._hidden ? "hide" : "unhide" + }); + }); + } + this._refreshLegacyDecorations(); + }; + + CodeMirror6Adapter.prototype._resetHistoryMergeState = function () { + this._historyClosed = true; + this._historyLastModTime = 0; + this._historyLastSelectionTime = 0; + this._historyLastOperationId = null; + this._historyLastSelectionOperationId = null; + this._historyLastOrigin = null; + this._historyLastSelectionOrigin = null; + }; + + CodeMirror6Adapter.prototype._trimHistoryToUndoDepth = function () { + const configuredDepth = Number(this.getOption("undoDepth")); + const undoDepth = Number.isFinite(configuredDepth) ? + Math.max(0, Math.floor(configuredDepth)) : + Infinity; + let changeCount = this._historyDone.reduce(function (count, entry) { + return count + (entry.type === "change" ? 1 : 0); + }, 0); + + while (changeCount > undoDepth) { + const changeIndex = this._historyDone.findIndex(function (entry) { + return entry.type === "change"; + }); + if (changeIndex === -1) { + break; + } + this._historyDone.splice(0, changeIndex + 1); + changeCount--; + } + + if (!this._historyDone.length || + this._historyDone[0].type !== "selection") { + const firstChange = this._historyDone.find(function (entry) { + return entry.type === "change"; + }); + const selection = firstChange ? + firstChange.beforeSelection : + this._view.state.selection; + this._historyDone.unshift({ + type: "selection", + beforeSelection: selection, + afterSelection: selection, + generationBefore: this._currentGeneration, + generationAfter: this._currentGeneration + }); + } + }; + + CodeMirror6Adapter.prototype._recordHistory = function ( + beforeText, + afterText, + beforeSelection, + afterSelection, + origin, + operationId, + beforeMarkers, + afterMarkers, + historyStep, + force + ) { + if (this._historyApplying || beforeText === afterText && !force) { + return null; + } + const now = Date.now(); + const previousGeneration = this._currentGeneration; + const nextGeneration = this._nextGeneration++; + const steps = historyStep ? [historyStep] : []; + const entry = { + type: "change", + docId: this.doc && this.doc.id, + beforeText: beforeText, + afterText: afterText, + beforeSelection: beforeSelection, + afterSelection: afterSelection, + origin: origin, + generationBefore: previousGeneration, + generationAfter: nextGeneration, + markerBefore: beforeMarkers, + markerAfter: afterMarkers, + steps: steps, + changes: steps + }; + let previousIndex = this._historyDone.length - 1; + let trailingSelections = 0; + while (previousIndex >= 0 && + this._historyDone[previousIndex].type === "selection") { + previousIndex--; + trailingSelections++; + } + const previous = this._historyDone[previousIndex]; + const sameOperation = operationId !== null && + operationId === this._historyLastOperationId; + const sameOrigin = origin && this._historyLastOrigin === origin; + const mergeByOrigin = sameOrigin && ( + origin.charAt(0) === "*" || + origin.charAt(0) === "+" && + now - this._historyLastModTime <= + (this.getOption("historyEventDelay") || 500) + ); + const canMerge = !this._historyClosed && previous && + previous.type === "change" && + (sameOperation || mergeByOrigin && trailingSelections <= 1); + if (canMerge) { + this._historyDone.splice(previousIndex + 1); + previous.afterText = afterText; + previous.afterSelection = afterSelection; + previous.generationAfter = nextGeneration; + previous.markerAfter = afterMarkers; + if (historyStep) { + previous.steps = previous.steps || []; + previous.changes = previous.steps; + previous.steps.push(historyStep); + } + } else { + this._historyDone.push(entry); + this._trimHistoryToUndoDepth(); + this._signalDocument("historyAdded"); + } + this._historyUndone.length = 0; + this._historyClosed = false; + this._historyLastModTime = now; + this._historyLastOperationId = operationId; + this._historyLastOrigin = origin; + this._currentGeneration = nextGeneration; + return canMerge ? previous : entry; + }; + + CodeMirror6Adapter.prototype._recordSelectionHistory = function ( + beforeSelection, + afterSelection, + origin, + operationId, + force + ) { + if (this._historyApplying || + !force && _sameSelection(beforeSelection, afterSelection)) { + return; + } + const now = Date.now(); + const previous = this._historyDone[this._historyDone.length - 1]; + const sameOperation = operationId !== null && + operationId === this._historyLastSelectionOperationId; + const mergeByOrigin = origin && previous && previous.type === "selection" && + this._historyLastSelectionOrigin === origin && ( + origin.charAt(0) === "*" || + origin.charAt(0) === "+" && + now - this._historyLastSelectionTime <= + (this.getOption("historyEventDelay") || 500) + ); + if (!this._historyClosed && previous && previous.type === "selection" && + (sameOperation || mergeByOrigin)) { + previous.afterSelection = afterSelection; + } else { + this._historyDone.push({ + type: "selection", + beforeSelection: beforeSelection, + afterSelection: afterSelection, + generationBefore: this._currentGeneration, + generationAfter: this._currentGeneration + }); + } + while (this._historyUndone.length && + this._historyUndone[this._historyUndone.length - 1].type === "selection") { + this._historyUndone.pop(); + } + this._historyClosed = false; + this._historyLastSelectionTime = now; + this._historyLastSelectionOperationId = operationId; + this._historyLastSelectionOrigin = origin; + }; + + CodeMirror6Adapter.prototype._queueEvents = function ( + changes, + documentChanges, + lineChangeEvents, + lineDeleteEvents, + selectionChanged, + inputRead, + updateNeeded + ) { + if (selectionChanged) { + this.onSelectionChange(); + this._pendingDocumentCursorActivityCount++; + } + if (changes.length) { + changes.forEach((change, index) => { + this._pendingChangeEvents.push({ + editorChange: change, + documentChange: documentChanges[index], + inputRead: inputRead, + lineChangeEvents: lineChangeEvents[index] || [], + lineDeleteEvents: index === 0 ? lineDeleteEvents : [] + }); + }); + } + if (updateNeeded) { + this._pendingUpdate = true; + } + if (!this._operationDepth) { + this._flushOperationEvents(); + } + }; + + CodeMirror6Adapter.prototype._flushOperationEvents = function () { + const pendingChangeEvents = this._pendingChangeEvents; + const pendingChanges = pendingChangeEvents.map(function (event) { + return event.editorChange; + }); + const pendingMarkerVisibilityEvents = this._pendingMarkerVisibilityEvents; + const pendingCursorActivity = this._pendingCursorActivity; + const pendingDocumentCursorActivityCount = + this._pendingDocumentCursorActivityCount; + const pendingUpdate = this._pendingUpdate; + this._pendingChangeEvents = []; + this._pendingMarkerVisibilityEvents = []; + this._pendingCursorActivity = false; + this._pendingDocumentCursorActivityCount = 0; + this._pendingUpdate = false; + + let firstError; + const runEvent = callback => { + try { + callback(); + } catch (error) { + firstError = firstError || error; + } + }; + try { + pendingChangeEvents.forEach(event => { + event.lineChangeEvents.forEach(function (lineEvent) { + runEvent(function () { + CodeMirror.signal( + lineEvent.handle, + "change", + lineEvent.handle, + lineEvent.change + ); + }); + }); + event.lineDeleteEvents.forEach(function (handle) { + runEvent(function () { + CodeMirror.signal(handle, "delete"); + }); + }); + runEvent(() => { + this._signalDocument( + "change", + this.doc, + event.documentChange + ); + }); + runEvent(() => { + this._emit( + "change", + this._instance(), + event.editorChange + ); + }); + if (event.inputRead) { + runEvent(() => { + this._emit( + "inputRead", + this._instance(), + event.editorChange + ); + }); + } + }); + for (let index = 0; + index < pendingDocumentCursorActivityCount; + index++) { + runEvent(() => { + this._signalDocument("cursorActivity", this.doc); + }); + } + if (pendingCursorActivity) { + runEvent(() => { + this._emit("cursorActivity", this._instance()); + }); + } + pendingMarkerVisibilityEvents.forEach(function (event) { + runEvent(function () { + CodeMirror.signal(event.marker, event.eventName); + }); + }); + if (pendingChanges.length) { + runEvent(() => { + this._emit("changes", this._instance(), pendingChanges); + }); + } + if (pendingUpdate) { + runEvent(() => { + this._emit("update", this._instance()); + }); + } + } finally { + this._decorateDOM(); + this._emitRenderLines(); + this._emitViewportChange(); + } + if (firstError) { + throw firstError; + } + }; + + CodeMirror6Adapter.prototype._rebaseTransaction = function (transaction, state) { + if (transaction.startState === state) { + return transaction; + } + if (!transaction.startState.doc.eq(state.doc)) { + throw new RangeError( + "Cannot rebase a CodeMirror transaction after a reentrant document change." + ); + } + return state.update({ + changes: transaction.changes, + selection: transaction.selection, + effects: transaction.effects, + annotations: transaction.annotations, + scrollIntoView: transaction.scrollIntoView, + filter: false + }); + }; + + CodeMirror6Adapter.prototype._dispatchTransactions = function (transactions, view) { + if (this._destroyed) { + return; + } + + const operationId = this._activeOperationId !== null ? + this._activeOperationId : + this._nextOperationId++; + transactions.forEach(originalTransaction => { + const focusedLineWidget = this._captureFocusedLineWidget(); + const beforeMarkers = this._captureMarkerSnapshot(); + const prepared = this._prepareTransaction(originalTransaction, view); + if (!prepared) { + this._restoreFocusedLineWidget(focusedLineWidget); + return; + } + const transaction = this._rebaseTransaction(prepared.transaction, view.state); + const beforeText = view.state.doc.toString(); + const beforeSelection = view.state.selection; + const hasLegacyChanges = prepared.changes.length > 0; + const documentChanges = prepared.changes.map(_copyLegacyChange); + const fullChange = Boolean( + transaction.annotation(this._fullChangeAnnotation) + ); + const setValueSelectionReset = transaction.docChanged && Boolean( + transaction.annotation(this._setValueSelectionResetAnnotation) + ); + const historyStep = transaction.docChanged ? + this._historyStepForTransaction(transaction) : + prepared.forceHistory ? + this._historyStepForLegacyChanges( + prepared.changes, + transaction.startState.doc, + transaction.newDoc + ) : + null; + const setValueMappedSelection = setValueSelectionReset ? + this._selectionAfterLegacyChange( + beforeSelection, + prepared.changes[0], + transaction.changes + ) : + null; + const appliedTransaction = setValueSelectionReset ? + view.state.update({ + changes: transaction.changes, + selection: setValueMappedSelection, + effects: transaction.effects, + annotations: transaction.annotations, + scrollIntoView: transaction.scrollIntoView, + filter: false + }) : + transaction; + view.update([appliedTransaction]); + if (appliedTransaction.docChanged) { + this.onChange(appliedTransaction, prepared.changes); + if (this.virtualSelection) { + this.virtualSelection = CM6.EditorSelection.create( + this.virtualSelection.ranges.map(function (range) { + return range.map(appliedTransaction.changes); + }), + this.virtualSelection.mainIndex + ); + } + } + const afterText = view.state.doc.toString(); + let lineChangeEvents = prepared.changes.map(function () { + return []; + }); + let lineDeleteEvents = []; + if (hasLegacyChanges && !fullChange) { + lineChangeEvents = this._lineHandleChangeEvents( + prepared.changes, + documentChanges, + transaction.startState.doc + ); + } + if (hasLegacyChanges && fullChange) { + lineDeleteEvents = this._replaceMetadataForFullChange(); + } else if (transaction.docChanged) { + lineDeleteEvents = this._mapMetadata( + transaction.changes, + transaction.startState.doc, + view.state.doc + ); + } + if (transaction.docChanged) { + // Direct EditorView.decorations values are static and are not + // mapped through document changes. Rebuild them after legacy + // metadata moves, before a follow-up selection-only update can + // compare stale points against the new identity ChangeSet. + this._refreshLegacyDecorations(true); + } + if (transaction.docChanged || hasLegacyChanges) { + const firstChangedLine = prepared.changes.reduce(function ( + earliestLine, + change + ) { + return Math.min(earliestLine, change.from.line); + }, Infinity); + this._invalidateLegacyModeStateCache( + false, + firstChangedLine + ); + } + let recordedHistoryEntry = null; + if (hasLegacyChanges) { + const addToHistory = + transaction.annotation(this._addToHistoryAnnotation); + if (addToHistory !== false && + (beforeText !== afterText || prepared.forceHistory)) { + recordedHistoryEntry = this._recordHistory( + beforeText, + afterText, + beforeSelection, + view.state.selection, + prepared.changes[0] && prepared.changes[0].origin, + operationId, + beforeMarkers, + this._captureMarkerSnapshot(), + historyStep, + prepared.forceHistory + ); + } + } + if (transaction.docChanged) { + let previousSelection = beforeSelection.map(transaction.changes); + if (setValueSelectionReset) { + const mappedSelection = this._applyBeforeSelectionChange( + view.state.selection, + view.state.doc, + undefined, + transaction.annotation(this._selectionBiasAnnotation), + beforeSelection + ); + if (!_sameSelection(mappedSelection, view.state.selection)) { + view.update([view.state.update({ + selection: mappedSelection + })]); + } + previousSelection = view.state.selection; + } + const filteredSelection = this._applyBeforeSelectionChange( + setValueSelectionReset ? + transaction.newSelection : + view.state.selection, + view.state.doc, + undefined, + transaction.annotation(this._selectionBiasAnnotation), + previousSelection + ); + if (!_sameSelection(filteredSelection, view.state.selection)) { + view.update([view.state.update({ + selection: filteredSelection + })]); + } + } + const selectionChanged = !_sameSelection(beforeSelection, view.state.selection); + if (recordedHistoryEntry) { + recordedHistoryEntry.afterSelection = view.state.selection; + this._recordSelectionHistory( + beforeSelection, + view.state.selection, + transaction.annotation(this._originAnnotation), + operationId, + true + ); + } else if (!hasLegacyChanges && selectionChanged) { + this._recordSelectionHistory( + beforeSelection, + view.state.selection, + transaction.annotation(this._originAnnotation), + operationId + ); + } + + const inputRead = transaction.isUserEvent("input.type") || + transaction.isUserEvent("input.paste") || + transaction.isUserEvent("delete.cut"); + if (transaction.docChanged && + !transaction.annotation(this._linkedChangeAnnotation) && + this.doc && this.doc._links.length) { + prepared.changes.forEach(change => { + this._propagateLinkedChange(change); + }); + } + if (!transaction.docChanged || this._legacyDecorationsDirty) { + this._refreshLegacyDecorations(); + } + this._scheduleGutterRefresh(); + try { + this._queueEvents( + prepared.changes, + documentChanges, + lineChangeEvents, + lineDeleteEvents, + selectionChanged || hasLegacyChanges, + inputRead, + transaction.docChanged || + hasLegacyChanges || + Boolean(transaction.annotation(this._legacyUpdateAnnotation)) + ); + } finally { + // Legacy change listeners can synchronously redraw line widgets. + this._restoreFocusedLineWidget(focusedLineWidget); + } + }); + }; + + CodeMirror6Adapter.prototype._compatDecorationSet = function () { + const ranges = []; + + this._markers.forEach(marker => { + if (marker._cleared || marker._hidden) { + return; + } + const found = marker.find(); + if (!found) { + return; + } + + const isBookmark = marker.type === "bookmark"; + const fromPosition = isBookmark ? found : found.from; + const toPosition = isBookmark ? found : found.to; + const from = this.indexFromPos(fromPosition); + const to = this.indexFromPos(toPosition); + const widgetNode = marker._widgetNode || marker.replacedWith; + const widget = widgetNode ? + new LegacyNodeWidget(widgetNode, marker.handleMouseEvents) : + null; + const inclusiveStart = marker.inclusiveLeft === true; + const inclusiveEnd = marker.inclusiveRight === true; + + if (isBookmark) { + if (widget) { + ranges.push(CM6.Decoration.widget({ + widget: widget, + side: marker.insertLeft ? 1 : -1 + }).range(from)); + } + return; + } + + if (marker.collapsed || widget) { + const replaceOptions = { + inclusiveStart: inclusiveStart, + inclusiveEnd: inclusiveEnd + }; + if (widget) { + replaceOptions.widget = widget; + } + ranges.push(CM6.Decoration.replace(replaceOptions).range(from, to)); + return; + } + + if (from === to) { + return; + } + + const attributes = Object.assign({}, marker.attributes); + if (marker.css) { + attributes.style = attributes.style ? + `${attributes.style};${marker.css}` : + marker.css; + } + if (marker.title) { + attributes.title = marker.title; + } + if (marker.className || Object.keys(attributes).length) { + ranges.push(CM6.Decoration.mark({ + class: marker.className || undefined, + attributes: Object.keys(attributes).length ? attributes : undefined, + inclusiveStart: inclusiveStart, + inclusiveEnd: inclusiveEnd + }).range(from, to)); + } + + if (marker.startStyle) { + const firstLine = this._view.state.doc.lineAt(from); + const startStyleEnd = Math.min(to, firstLine.to); + if (startStyleEnd > from) { + ranges.push(CM6.Decoration.mark({ + class: marker.startStyle, + inclusiveStart: inclusiveStart + }).range(from, startStyleEnd)); + } + } + + if (marker.endStyle) { + const lastLine = this._view.state.doc.lineAt(Math.max(from, to - 1)); + const endStyleStart = Math.max(from, lastLine.from); + if (to > endStyleStart) { + ranges.push(CM6.Decoration.mark({ + class: marker.endStyle, + inclusiveEnd: inclusiveEnd + }).range(endStyleStart, to)); + } + } + }); + + this._lineClasses = this._lineClasses.filter(record => { + const lineNumber = this.getLineNumber(record.lineHandle); + if (lineNumber === null || lineNumber === undefined || + lineNumber < this.firstLine() || + lineNumber > this.lastLine()) { + return false; + } + + const line = this._view.state.doc.line( + lineNumber - this._firstLine + 1 + ); + if (record.where === "gutter") { + return true; + } + ranges.push(CM6.Decoration.line({ + attributes: { + class: record.className + } + }).range(line.from)); + return true; + }); + + this._lineWidgets = this._lineWidgets.filter(record => { + const lineNumber = this.getLineNumber(record.widget.line); + if (lineNumber === null || lineNumber === undefined || + lineNumber < this.firstLine() || + lineNumber > this.lastLine()) { + return false; + } + + const line = this._view.state.doc.line( + lineNumber - this._firstLine + 1 + ); + const above = Boolean(record.options && record.options.above); + const position = above ? line.from : line.to; + const widgets = record.widget.line.widgets || []; + const widgetIndex = Math.max(0, widgets.indexOf(record.widget)); + ranges.push(CM6.Decoration.widget({ + widget: new LegacyLineWidget(this, record), + block: true, + side: above ? -10000 + widgetIndex : 1 + widgetIndex + }).range(position)); + return true; + }); + + return CM6.Decoration.set(ranges, true); + }; + + CodeMirror6Adapter.prototype._refreshLegacyDecorations = function (force) { + if (!this._view || this._destroyed) { + return; + } + if (this._operationDepth && !force) { + this._legacyDecorationsDirty = true; + return; + } + this._legacyDecorationsDirty = false; + this._reconfigureSilently( + this._decorationsCompartment, + CM6.EditorView.decorations.of(this._compatDecorationSet()) + ); + this._reconfigureSilently( + this._gutterLineClassesCompartment, + CM6.gutterLineClass.of(this._gutterLineClassRangeSet(this._view.state.doc)) + ); + this._refreshLineWidgetLayouts(); + this._scheduleRenderLines(); + }; + + CodeMirror6Adapter.prototype._refreshLegacyHighlighting = function () { + if (!this._view || this._destroyed) { + return; + } + this._reconfigureSilently( + this._compatHighlightCompartment, + this._compatHighlightExtension() + ); + this._invalidateRenderedLines(); + }; + + CodeMirror6Adapter.prototype._refreshGutters = function () { + if (!this._view || this._destroyed) { + return; + } + this._reconfigureSilently( + this._lineNumbersCompartment, + this._createLeadingGutterExtensions() + ); + this._reconfigureSilently( + this._guttersCompartment, + this._createTrailingGutterExtensions() + ); + }; + + CodeMirror6Adapter.prototype._reconfigureSilently = function (compartment, extension) { + const transaction = this._view.state.update({ + effects: compartment.reconfigure(extension) + }); + this._view.update([transaction]); + this._decorateDOM(); + }; + + CodeMirror6Adapter.prototype._scheduleRenderLines = function () { + if (this._renderLineRefreshScheduled || + !this._view || + this._destroyed) { + return; + } + this._renderLineRefreshScheduled = true; + Promise.resolve().then(() => { + this._renderLineRefreshScheduled = false; + if (!this._view || this._destroyed) { + return; + } + this._decorateDOM(); + this._emitRenderLines(); + }); + }; + + CodeMirror6Adapter.prototype._invalidateRenderedLines = function () { + this._renderedLineDOMState = new WeakMap(); + this._scheduleRenderLines(); + }; + + CodeMirror6Adapter.prototype._emitRenderLines = function () { + const listeners = this._listeners.get("renderLine"); + if (!this._view || !listeners || !listeners.length) { + return; + } + + const renderedLines = new Set(); + this._view.visibleRanges.forEach(range => { + let line = this._view.state.doc.lineAt(range.from); + while (line.from <= range.to) { + if (!renderedLines.has(line.number)) { + renderedLines.add(line.number); + let dom; + try { + dom = this._view.domAtPos(line.from).node; + } catch (error) { + dom = null; + } + if (dom) { + if (dom.nodeType === window.Node.TEXT_NODE) { + dom = dom.parentElement; + } + if (dom && dom.nodeType === window.Node.ELEMENT_NODE && + !dom.classList.contains("cm-line")) { + dom = dom.closest(".cm-line"); + } + const lineHandle = this.getLineHandle( + line.number - 1 + this._firstLine + ); + if (dom && lineHandle) { + const previousState = this._renderedLineDOMState.get(dom); + if (!previousState || + previousState.lineHandle !== lineHandle || + previousState.text !== line.text || + previousState.contentNode !== dom.firstChild) { + this._renderedLineDOMState.set(dom, { + contentNode: dom.firstChild, + lineHandle: lineHandle, + text: line.text + }); + this._emit( + "renderLine", + this._instance(), + lineHandle, + dom + ); + } + } + } + } + + if (line.number >= this._view.state.doc.lines) { + break; + } + line = this._view.state.doc.line(line.number + 1); + } + }); + }; + + CodeMirror6Adapter.prototype._emitViewportChange = function () { + const viewport = this.getViewport(); + if (!this._lastViewport || viewport.from !== this._lastViewport.from || + viewport.to !== this._lastViewport.to) { + this._lastViewport = viewport; + this._emit("viewportChange", this._instance(), viewport.from, viewport.to); + } + }; + + CodeMirror6Adapter.prototype._handleScroll = function () { + if (this.doc && this._view) { + this.doc._scrollLeft = this._view.scrollDOM.scrollLeft; + this.doc._scrollTop = this._view.scrollDOM.scrollTop; + } + if (this._scrollbarModel) { + this._scrollbarModel.setScrollLeft( + this._view.scrollDOM.scrollLeft + ); + this._scrollbarModel.setScrollTop( + this._view.scrollDOM.scrollTop + ); + } + this._emit("scroll", this._instance()); + this._emitViewportChange(); + this._scheduleRulerRefresh(); + this._refreshLineWidgetLayouts(); + }; + + CodeMirror6Adapter.prototype._clearScrollbarModel = function () { + if (this._scrollbarModel && + typeof this._scrollbarModel.clear === "function") { + this._scrollbarModel.clear(); + } + this._scrollbarModelNodes.forEach(function (node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + }); + if (this._wrapperElement && this._scrollbarModel && + this._scrollbarModel.addClass) { + CodeMirror.rmClass( + this._wrapperElement, + this._scrollbarModel.addClass + ); + } + if (this._wrapperElement) { + CodeMirror.rmClass( + this._wrapperElement, + "phoenix-cm6-custom-scrollbars phoenix-cm6-null-scrollbars" + ); + this._wrapperElement.style.removeProperty( + "--phoenix-cm6-scrollbar-bottom" + ); + this._wrapperElement.style.removeProperty( + "--phoenix-cm6-scrollbar-right" + ); + } + this._scrollbarModel = null; + this._scrollbarModelNodes = []; + this._scrollbarModelName = null; + if (this.display) { + this.display.barHeight = 0; + this.display.barWidth = 0; + this.display.scrollbars = null; + } + }; + + CodeMirror6Adapter.prototype._scrollbarMeasurements = function () { + const scroller = this._view.scrollDOM; + const gutter = this.getGutterElement(); + const gutterWidth = gutter && gutter !== this._view.dom ? + gutter.getBoundingClientRect().width : + 0; + return { + barLeft: this.getOption("fixedGutter") ? gutterWidth : 0, + clientHeight: scroller.clientHeight, + clientWidth: scroller.clientWidth, + docHeight: scroller.scrollHeight, + gutterWidth: gutterWidth, + nativeBarWidth: Math.max( + 0, + scroller.offsetWidth - scroller.clientWidth + ), + scrollHeight: scroller.scrollHeight, + scrollWidth: scroller.scrollWidth, + viewHeight: this._view.dom.clientHeight, + viewWidth: this._view.dom.clientWidth + }; + }; + + CodeMirror6Adapter.prototype._refreshScrollbarModel = function () { + if (!this._view || this._destroyed || !this._scrollbarModel) { + return; + } + const sizes = typeof this._scrollbarModel.update === "function" ? + this._scrollbarModel.update(this._scrollbarMeasurements()) : + null; + const bottom = Math.max(0, Number(sizes && sizes.bottom) || 0); + const right = Math.max(0, Number(sizes && sizes.right) || 0); + this.display.barHeight = bottom; + this.display.barWidth = right; + this._wrapperElement.style.setProperty( + "--phoenix-cm6-scrollbar-bottom", + `${bottom}px` + ); + this._wrapperElement.style.setProperty( + "--phoenix-cm6-scrollbar-right", + `${right}px` + ); + if (typeof this._scrollbarModel.setScrollLeft === "function") { + this._scrollbarModel.setScrollLeft( + this._view.scrollDOM.scrollLeft + ); + } + if (typeof this._scrollbarModel.setScrollTop === "function") { + this._scrollbarModel.setScrollTop( + this._view.scrollDOM.scrollTop + ); + } + }; + + CodeMirror6Adapter.prototype._applyScrollbarStyle = function (styleName) { + if (!this._view || this._destroyed) { + return; + } + const normalizedName = styleName === null ? + "null" : + String(styleName || "native"); + if (normalizedName === this._scrollbarModelName) { + this._refreshScrollbarModel(); + return; + } + const Model = CodeMirror.scrollbarModel && + CodeMirror.scrollbarModel[normalizedName]; + if (typeof Model !== "function") { + throw new Error(`Unknown scrollbar style "${normalizedName}"`); + } + + this._clearScrollbarModel(); + const adapter = this; + const place = function (node) { + if (!node) { + return; + } + node.setAttribute("cm-not-content", "true"); + node.setAttribute("cm-ignore-events", "true"); + node.setAttribute("aria-hidden", "true"); + node.addEventListener("mousedown", function () { + if (adapter.hasFocus()) { + const ownerWindow = + adapter._wrapperElement.ownerDocument.defaultView || + window; + ownerWindow.setTimeout(function () { + adapter.focus(); + }, 0); + } + }); + adapter._wrapperElement.appendChild(node); + adapter._scrollbarModelNodes.push(node); + }; + const scroll = function (position, orientation) { + if (orientation === "horizontal") { + adapter.scrollTo(position, null); + } else { + adapter.scrollTo(null, position); + } + }; + const model = new Model(place, scroll, this); + this._scrollbarModel = model; + this._scrollbarModelName = normalizedName; + this.display.scrollbars = model; + + if (normalizedName !== "native") { + CodeMirror.addClass( + this._wrapperElement, + "phoenix-cm6-custom-scrollbars" + ); + } + if (normalizedName === "null") { + CodeMirror.addClass( + this._wrapperElement, + "phoenix-cm6-null-scrollbars" + ); + } + if (model.addClass) { + CodeMirror.addClass(this._wrapperElement, model.addClass); + } + this._refreshScrollbarModel(); + }; + + CodeMirror6Adapter.prototype._applyLineWidgetLayout = function (record) { + const wrapper = record && record.renderedWrapper; + if (!wrapper || !this._view) { + return; + } + + const options = record.options || {}; + const scroller = this._view.scrollDOM; + const gutter = this.getGutterElement(); + const gutterWidth = gutter && gutter !== this._view.dom ? + gutter.getBoundingClientRect().width : + 0; + + wrapper.style.boxSizing = "border-box"; + wrapper.style.left = ""; + wrapper.style.marginLeft = ""; + wrapper.style.paddingLeft = ""; + wrapper.style.position = ""; + wrapper.style.width = ""; + wrapper.style.zIndex = ""; + + if (options.noHScroll) { + wrapper.style.position = "sticky"; + wrapper.style.left = options.coverGutter ? `${-gutterWidth}px` : "0px"; + wrapper.style.width = options.coverGutter ? + `${scroller.clientWidth}px` : + `${Math.max(0, scroller.clientWidth - gutterWidth)}px`; + if (!options.coverGutter && gutterWidth) { + wrapper.style.paddingLeft = `${gutterWidth}px`; + } + } + if (options.coverGutter) { + wrapper.style.zIndex = "5"; + if (!options.noHScroll && gutterWidth) { + wrapper.style.marginLeft = `${-gutterWidth}px`; + } + } + }; + + CodeMirror6Adapter.prototype._measureLineWidget = function (record) { + if (!record || record.cleared || !record.widget) { + return 0; + } + const measuredNode = record.node && record.node.isConnected ? + record.node : + record.renderedWrapper; + const height = measuredNode && measuredNode.isConnected ? + measuredNode.offsetHeight : + 0; + record.widget.height = height; + return height; + }; + + CodeMirror6Adapter.prototype._refreshLineWidgetLayouts = function () { + this._lineWidgets.forEach(record => { + this._applyLineWidgetLayout(record); + this._measureLineWidget(record); + }); + }; + + CodeMirror6Adapter.prototype._scheduleRulerRefresh = function () { + if (this._rulerRefreshScheduled || !this._view || this._destroyed) { + return; + } + this._rulerRefreshScheduled = true; + Promise.resolve().then(() => { + this._rulerRefreshScheduled = false; + this._refreshRulers(); + }); + }; + + CodeMirror6Adapter.prototype._refreshRulers = function () { + if (!this._view || this._destroyed) { + return; + } + + const rulers = this.getOption("rulers"); + if (!rulers || !rulers.length) { + if (this._rulerElement) { + this._rulerElement.remove(); + this._rulerElement = null; + } + return; + } + + if (!this._rulerElement) { + this._rulerElement = window.document.createElement("div"); + this._rulerElement.className = "CodeMirror-rulers phoenix-cm6-rulers"; + this._view.dom.appendChild(this._rulerElement); + } + + const rootRect = this._view.dom.getBoundingClientRect(); + const contentRect = this._view.contentDOM.getBoundingClientRect(); + const contentLeft = contentRect.left - rootRect.left; + const charWidth = this.defaultCharWidth(); + this._rulerElement.textContent = ""; + this._rulerElement.style.minHeight = `${this._view.scrollDOM.clientHeight + 30}px`; + + rulers.forEach(configuration => { + const ruler = window.document.createElement("div"); + ruler.className = "CodeMirror-ruler"; + let column = configuration; + if (typeof configuration === "object") { + column = configuration.column; + if (configuration.className) { + ruler.classList.add( + ...String(configuration.className).split(/\s+/).filter(Boolean) + ); + } + if (configuration.color) { + ruler.style.borderColor = configuration.color; + } + if (configuration.lineStyle) { + ruler.style.borderLeftStyle = configuration.lineStyle; + } + if (configuration.width) { + ruler.style.borderLeftWidth = configuration.width; + } + } + ruler.style.left = `${contentLeft + (Number(column) * charWidth)}px`; + this._rulerElement.appendChild(ruler); + }); + }; + + CodeMirror6Adapter.prototype._ensureLegacyDOM = function () { + if (!this._view) { + return null; + } + const root = this._view.dom; + if (this._legacyDOM && + this._legacyDOM.sizer.parentNode === root && + this._legacyDOM.verticalScrollbar.parentNode === root) { + return this._legacyDOM; + } + + const sizer = window.document.createElement("div"); + sizer.className = "CodeMirror-sizer phoenix-cm6-legacy-sizer"; + sizer.setAttribute("aria-hidden", "true"); + sizer.dataset.phoenixCm6LegacyProxy = "sizer"; + + const width = window.document.createElement("div"); + width.className = "phoenix-cm6-legacy-content-width"; + const lines = window.document.createElement("div"); + lines.className = "CodeMirror-lines phoenix-cm6-legacy-lines"; + const measurement = window.document.createElement("pre"); + measurement.className = + "CodeMirror-line-like phoenix-cm6-legacy-measure"; + measurement.textContent = "\u200b"; + sizer.appendChild(width); + sizer.appendChild(lines); + sizer.appendChild(measurement); + + const verticalScrollbar = window.document.createElement("div"); + verticalScrollbar.className = + "CodeMirror-vscrollbar phoenix-cm6-legacy-vscrollbar"; + verticalScrollbar.setAttribute("aria-hidden", "true"); + verticalScrollbar.dataset.phoenixCm6LegacyProxy = "vertical-scrollbar"; + + root.insertBefore(sizer, this._view.scrollDOM); + root.insertBefore(verticalScrollbar, this._view.scrollDOM); + this._legacyDOM = { + lines: lines, + measurement: measurement, + sizer: sizer, + verticalScrollbar: verticalScrollbar, + width: width + }; + return this._legacyDOM; + }; + + CodeMirror6Adapter.prototype._syncLegacyDOMGeometry = function () { + if (!this._view || this._destroyed) { + return; + } + const legacyDOM = this._ensureLegacyDOM(); + if (!legacyDOM) { + return; + } + + const content = this._view.contentDOM; + const contentBounds = content.getBoundingClientRect(); + const contentHeight = Math.max( + Number(this._view.contentHeight) || 0, + content.scrollHeight || 0, + contentBounds.height || 0 + ); + const contentWidth = Math.max( + content.scrollWidth || 0, + contentBounds.width || 0 + ); + legacyDOM.sizer.style.height = `${Math.ceil(contentHeight)}px`; + legacyDOM.sizer.style.width = `${Math.ceil(contentWidth)}px`; + legacyDOM.width.style.width = `${Math.ceil(contentWidth)}px`; + }; + + CodeMirror6Adapter.prototype._decorateDOM = function () { + if (!this._view) { + return; + } + + const root = this._view.dom; + if (!root.classList.contains("CodeMirror")) { + root.classList.add("CodeMirror"); + } + if (!root.classList.contains("phoenix-codemirror-6")) { + root.classList.add("phoenix-codemirror-6"); + } + root.classList.toggle("CodeMirror-wrap", Boolean(this.getOption("lineWrapping"))); + root.classList.toggle("CodeMirror-overwrite", Boolean(this.state.overwrite)); + root.classList.toggle( + "CodeMirror-empty", + Boolean(this.getOption("placeholder")) && + this._view.state.doc.length === 0 + ); + if (root.dataset.editorEngine !== "codemirror6") { + root.dataset.editorEngine = "codemirror6"; + } + root.CodeMirror = this._instance(); + if (!this._view.scrollDOM.classList.contains("CodeMirror-scroll")) { + this._view.scrollDOM.classList.add("CodeMirror-scroll"); + } + if (!this._view.scrollDOM.classList.contains("CodeMirror-lines")) { + this._view.scrollDOM.classList.add("CodeMirror-lines"); + } + this._view.scrollDOM.classList.remove("CodeMirror-vscrollbar"); + if (!this._view.contentDOM.classList.contains("CodeMirror-code")) { + this._view.contentDOM.classList.add("CodeMirror-code"); + } + if (!this._view.contentDOM.classList.contains("CodeMirror-sizer")) { + this._view.contentDOM.classList.add("CodeMirror-sizer"); + } + this._syncLegacyDOMGeometry(); + + const tabIndex = this.getOption("tabindex"); + if (tabIndex === null || tabIndex === undefined) { + this._view.contentDOM.removeAttribute("tabindex"); + } else { + this._view.contentDOM.tabIndex = tabIndex; + } + const screenReaderLabel = this.getOption("screenReaderLabel"); + if (screenReaderLabel) { + this._view.contentDOM.setAttribute("aria-label", screenReaderLabel); + } else { + this._view.contentDOM.removeAttribute("aria-label"); + } + const direction = this.getOption("direction"); + if (direction) { + this._view.contentDOM.dir = direction; + } else { + this._view.contentDOM.removeAttribute("dir"); + } + root.classList.toggle("CodeMirror-rtl", direction === "rtl"); + + root.querySelectorAll(".cm-gutters:not(.CodeMirror-gutters)").forEach(function (element) { + element.classList.add("CodeMirror-gutters"); + }); + root.querySelectorAll(".cm-gutter:not(.CodeMirror-gutter)").forEach(function (element) { + element.classList.add("CodeMirror-gutter"); + }); + root.querySelectorAll(".cm-gutterElement:not(.CodeMirror-gutter-elt)").forEach(function (element) { + element.classList.add("CodeMirror-gutter-elt"); + }); + root.querySelectorAll(".cm-lineNumbers:not(.CodeMirror-linenumbers)").forEach(function (element) { + element.classList.add("CodeMirror-linenumbers"); + }); + this._applyConfiguredGutterStyles(); + root.querySelectorAll( + ".cm-lineNumbers .cm-gutterElement:not(.CodeMirror-linenumber)" + ).forEach(function (element) { + element.classList.add("CodeMirror-linenumber"); + }); + root.querySelectorAll(".cm-cursor:not(.CodeMirror-cursor)").forEach(function (element) { + element.classList.add("CodeMirror-cursor"); + }); + root.querySelectorAll(".cm-cursorLayer:not(.CodeMirror-cursors)").forEach(function (element) { + element.classList.add("CodeMirror-cursors"); + }); + root.querySelectorAll(".cm-line:not(.CodeMirror-line)").forEach(function (element) { + element.classList.add("CodeMirror-line"); + }); + root.querySelectorAll( + ".cm-selectionBackground:not(.CodeMirror-selected)" + ).forEach(function (element) { + element.classList.add("CodeMirror-selected"); + }); + + root.querySelectorAll(".CodeMirror-matchingbracket").forEach(element => { + if (this._matchingBracketDOM.has(element) && + !element.classList.contains("cm-matchingBracket")) { + element.classList.remove("CodeMirror-matchingbracket"); + this._matchingBracketDOM.delete(element); + } + }); + root.querySelectorAll(".cm-matchingBracket").forEach(element => { + if (!element.classList.contains("CodeMirror-matchingbracket")) { + element.classList.add("CodeMirror-matchingbracket"); + } + this._matchingBracketDOM.add(element); + }); + root.querySelectorAll(".CodeMirror-nonmatchingbracket").forEach(element => { + if (this._nonmatchingBracketDOM.has(element) && + !element.classList.contains("cm-nonmatchingBracket")) { + element.classList.remove("CodeMirror-nonmatchingbracket"); + this._nonmatchingBracketDOM.delete(element); + } + }); + root.querySelectorAll(".cm-nonmatchingBracket").forEach(element => { + if (!element.classList.contains("CodeMirror-nonmatchingbracket")) { + element.classList.add("CodeMirror-nonmatchingbracket"); + } + this._nonmatchingBracketDOM.add(element); + }); + + root.querySelectorAll(".CodeMirror-activeline").forEach(function (element) { + if (!element.classList.contains("cm-activeLine")) { + element.classList.remove( + "CodeMirror-activeline", + "CodeMirror-activeline-background" + ); + } + }); + root.querySelectorAll(".cm-activeLine").forEach(function (element) { + if (!element.classList.contains("CodeMirror-activeline")) { + element.classList.add("CodeMirror-activeline"); + } + if (!element.classList.contains("CodeMirror-activeline-background")) { + element.classList.add("CodeMirror-activeline-background"); + } + }); + root.querySelectorAll(".CodeMirror-activeline-gutter").forEach(function (element) { + if (!element.classList.contains("cm-activeLineGutter")) { + element.classList.remove("CodeMirror-activeline-gutter"); + } + }); + root.querySelectorAll(".cm-activeLineGutter").forEach(function (element) { + if (!element.classList.contains("CodeMirror-activeline-gutter")) { + element.classList.add("CodeMirror-activeline-gutter"); + } + }); + }; + + CodeMirror6Adapter.prototype._handleClipboardEvent = function (eventName, event) { + this._emit(eventName, this._instance(), event); + if (event.defaultPrevented) { + return true; + } + + if (this.getOption("lineWiseCopyCut") === false && + !this.somethingSelected()) { + return true; + } + return false; + }; + + CodeMirror6Adapter.prototype._runLegacyKeyBinding = function (binding, motionOnly) { + let command = binding; + if (typeof command === "string") { + if (motionOnly && !/^go[A-Z]/.test(command)) { + return false; + } + command = CodeMirror.commands[command]; + } else if (motionOnly && !command.motion) { + return false; + } + if (typeof command !== "function") { + return false; + } + + const previousSuppressEdits = this.state.suppressEdits; + if (this.getOption("readOnly")) { + this.state.suppressEdits = true; + } + try { + return this.operation(() => { + return command(this._instance()) !== CodeMirror.Pass; + }); + } finally { + this.state.suppressEdits = previousSuppressEdits; + } + }; + + CodeMirror6Adapter.prototype._lookupLegacyKey = function (keyName, motionOnly) { + const keyMaps = this.state.keyMaps.slice(); + if (this.options.extraKeys) { + keyMaps.push(this.options.extraKeys); + } + const configuredKeyMap = CodeMirror.keyMap[this.getOption("keyMap") || "default"]; + if (configuredKeyMap) { + keyMaps.push(configuredKeyMap); + } + + for (const keyMap of keyMaps) { + const result = CodeMirror.lookupKey(keyName, keyMap, binding => { + return this._runLegacyKeyBinding(binding, motionOnly); + }, this._instance()); + if (result) { + return result; + } + } + return undefined; + }; + + CodeMirror6Adapter.prototype._dispatchLegacyKey = function (keyName, event, motionOnly) { + const keySequence = this.state.keySeq; + let result; + if (keySequence) { + result = this._lookupLegacyKey(`${keySequence} ${keyName}`, motionOnly); + if (!result) { + this.state.keySeq = null; + } + } + if (!result) { + result = this._lookupLegacyKey(keyName, motionOnly); + } + + if (result === "multi") { + this.state.keySeq = keyName; + if (this._keySequenceTimer) { + clearTimeout(this._keySequenceTimer); + } + this._keySequenceTimer = setTimeout(() => { + this.state.keySeq = null; + this._keySequenceTimer = null; + }, 50); + } else if (result === "handled") { + this.state.keySeq = null; + this._emit("keyHandled", this._instance(), keyName, event); + } + + if (result === "handled" || result === "multi") { + event.preventDefault(); + return true; + } + return result === "nothing" ? "nothing" : false; + }; + + CodeMirror6Adapter.prototype._handleAutoCloseBracketEnter = function () { + const configuration = _legacyCloseBracketConfigurationAt( + this, + this.getCursor() + ); + const explode = configuration && + String(_legacyCloseBracketOption(configuration, "explode") || ""); + if (!explode || this.getOption("disableInput")) { + return false; + } + const selections = this.listSelections(); + for (const selection of selections) { + if (!selection.empty()) { + return false; + } + const cursor = selection.head; + const around = this.getRange( + CodeMirror.Pos(cursor.line, cursor.ch - 1), + CodeMirror.Pos(cursor.line, cursor.ch + 1) + ); + if (around.length !== 2 || + explode.indexOf(around) % 2 !== 0) { + return false; + } + } + + this.operation(() => { + const separator = this.lineSeparator() || "\n"; + this.replaceSelection(separator + separator, null); + const mainIndex = this._view.state.selection.mainIndex; + this.setSelections(this.listSelections().map(selection => { + const position = this.posFromIndex( + this.indexFromPos(selection.head) - separator.length + ); + return { + anchor: position, + head: position + }; + }), mainIndex); + this.listSelections().forEach(selection => { + this.indentLine(selection.head.line, null, true); + this.indentLine(selection.head.line + 1, null, true); + }); + }); + return true; + }; + + CodeMirror6Adapter.prototype._moveAutoCloseBracketSelections = function ( + direction + ) { + const mainIndex = this._view.state.selection.mainIndex; + const ranges = this.listSelections().map(selection => { + let position; + if (selection.head.ch || direction > 0) { + position = { + line: selection.head.line, + ch: selection.head.ch + direction + }; + } else { + const previousLine = Math.max( + this.firstLine(), + selection.head.line - 1 + ); + position = { + line: previousLine, + ch: (this.getLine(previousLine) || "").length + }; + } + return { + anchor: position, + head: position + }; + }); + this.setSelections(ranges, mainIndex, { + scroll: false + }); + }; + + CodeMirror6Adapter.prototype._stringStartsAfter = function (position) { + const token = this.getTokenAt(CodeMirror.Pos( + position.line, + position.ch + 1 + )); + return /\bstring/.test(token.type || "") && + token.start === position.ch && + (position.ch === 0 || + !/\bstring/.test(this.getTokenTypeAt(position) || "")); + }; + + CodeMirror6Adapter.prototype._handleAutoCloseBracketCharacter = function ( + character + ) { + const configuration = _legacyCloseBracketConfigurationAt( + this, + this.getCursor() + ); + if (!configuration || this.getOption("disableInput")) { + return false; + } + + const pairs = String( + _legacyCloseBracketOption(configuration, "pairs") || "" + ); + const position = pairs.indexOf(character); + if (position === -1) { + return false; + } + + const closeBefore = String( + _legacyCloseBracketOption(configuration, "closeBefore") || "" + ); + const triples = String( + _legacyCloseBracketOption(configuration, "triples") || "" + ); + const identical = pairs.charAt(position + 1) === character; + const opening = position % 2 === 0; + const selections = this.listSelections(); + let action; + + for (const selection of selections) { + const cursor = selection.head; + const next = this.getRange( + cursor, + CodeMirror.Pos(cursor.line, cursor.ch + 1) + ); + let currentAction; + if (opening && !selection.empty()) { + currentAction = "surround"; + } else if ((identical || !opening) && next === character) { + if (identical && this._stringStartsAfter(cursor)) { + currentAction = "both"; + } else if (triples.indexOf(character) !== -1 && + this.getRange( + cursor, + CodeMirror.Pos(cursor.line, cursor.ch + 3) + ) === character + character + character) { + currentAction = "skipThree"; + } else { + currentAction = "skip"; + } + } else if (identical && + cursor.ch > 1 && + triples.indexOf(character) !== -1 && + this.getRange( + CodeMirror.Pos(cursor.line, cursor.ch - 2), + cursor + ) === character + character) { + if (cursor.ch > 2 && + /\bstring/.test(this.getTokenTypeAt( + CodeMirror.Pos(cursor.line, cursor.ch - 2) + ) || "")) { + return false; + } + currentAction = "addFour"; + } else if (identical) { + const previous = cursor.ch === 0 ? + " " : + this.getRange( + CodeMirror.Pos(cursor.line, cursor.ch - 1), + cursor + ); + if (!CodeMirror.isWordChar(next) && + previous !== character && + !CodeMirror.isWordChar(previous)) { + currentAction = "both"; + } else { + return false; + } + } else if (opening && ( + !next.length || + /\s/.test(next) || + closeBefore.indexOf(next) !== -1 + )) { + currentAction = "both"; + } else { + return false; + } + + if (!action) { + action = currentAction; + } else if (action !== currentAction) { + return false; + } + } + + const left = position % 2 ? + pairs.charAt(position - 1) : + character; + const right = position % 2 ? + character : + pairs.charAt(position + 1); + this.operation(() => { + if (action === "skip") { + this._moveAutoCloseBracketSelections(1); + } else if (action === "skipThree") { + this._moveAutoCloseBracketSelections(3); + } else if (action === "surround") { + const replacements = this.getSelections().map(function (text) { + return left + text + right; + }); + this.replaceSelections(replacements, "around", "+input"); + this.setSelections( + this.listSelections().map(function (selection) { + const inverted = CodeMirror.cmpPos( + selection.anchor, + selection.head + ) > 0; + return { + anchor: CodeMirror.Pos( + selection.anchor.line, + selection.anchor.ch + (inverted ? -1 : 1) + ), + head: CodeMirror.Pos( + selection.head.line, + selection.head.ch + (inverted ? 1 : -1) + ) + }; + }), + this._view.state.selection.mainIndex, + {scroll: false} + ); + } else if (action === "both") { + this.replaceSelection(left + right, null, "+input"); + this.triggerElectric(left + right); + this._moveAutoCloseBracketSelections(-1); + } else if (action === "addFour") { + this.replaceSelection( + left + left + left + left, + "start", + "+input" + ); + this._moveAutoCloseBracketSelections(1); + } + }); + return true; + }; + + CodeMirror6Adapter.prototype._handleAutoCloseBracketBackspace = function () { + const configuration = _legacyCloseBracketConfigurationAt( + this, + this.getCursor() + ); + if (!configuration || this.getOption("disableInput")) { + return false; + } + const pairs = String( + _legacyCloseBracketOption(configuration, "pairs") || "" + ); + const selections = this.listSelections(); + for (const selection of selections) { + if (!selection.empty()) { + return false; + } + const cursor = selection.head; + const around = this.getRange( + CodeMirror.Pos(cursor.line, cursor.ch - 1), + CodeMirror.Pos(cursor.line, cursor.ch + 1) + ); + if (around.length !== 2 || + pairs.indexOf(around) % 2 !== 0) { + return false; + } + } + + this.operation(() => { + for (let index = selections.length - 1; index >= 0; index--) { + const cursor = selections[index].head; + this.replaceRange( + "", + CodeMirror.Pos(cursor.line, cursor.ch - 1), + CodeMirror.Pos(cursor.line, cursor.ch + 1), + "+delete" + ); + } + }); + return true; + }; + + CodeMirror6Adapter.prototype._handleKeyDown = function (event) { + this._emit("keydown", this._instance(), event); + if (event.defaultPrevented) { + return true; + } + + this.state.shift = event.keyCode === 16 || event.shiftKey; + const keyName = CodeMirror.keyName(event, true); + if (!keyName) { + return false; + } + + if (keyName === "Enter" && + !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && + this.getOption("autoCloseBrackets") !== false && + this._handleAutoCloseBracketEnter()) { + event.preventDefault(); + return true; + } + + if (keyName === "Backspace" && + !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && + this.getOption("autoCloseBrackets") !== false && + this._handleAutoCloseBracketBackspace()) { + event.preventDefault(); + return true; + } + + let handled; + if (event.shiftKey && !this.state.keySeq) { + const shiftedResult = this._dispatchLegacyKey(`Shift-${keyName}`, event, false); + if (shiftedResult === true) { + return true; + } + if (shiftedResult === "nothing") { + return false; + } + handled = this._dispatchLegacyKey(keyName, event, true) === true; + } else { + handled = this._dispatchLegacyKey(keyName, event, false) === true; + } + if (handled) { + return true; + } + return false; + }; + + CodeMirror6Adapter.prototype._handleKeyPress = function (event) { + this._emit("keypress", this._instance(), event); + if (!event.defaultPrevented && this._handleCharacterBinding(event)) { + return true; + } + const hasCommandModifier = event.ctrlKey && !event.altKey || event.metaKey; + if (!event.defaultPrevented && !hasCommandModifier && + this.getOption("autoCloseBrackets") !== false) { + const keyCode = event.charCode === null || event.charCode === undefined ? + event.keyCode : + event.charCode; + const character = String.fromCharCode(keyCode); + if (character && + this._handleAutoCloseBracketCharacter(character)) { + event.preventDefault(); + return true; + } + } + return event.defaultPrevented; + }; + + CodeMirror6Adapter.prototype._handleKeyUp = function (event) { + if (event.keyCode === 16) { + this.state.shift = false; + } + this._emit("keyup", this._instance(), event); + return event.defaultPrevented; + }; + + CodeMirror6Adapter.prototype._handleMouseDown = function (event) { + this._emit("mousedown", this._instance(), event); + if (event.defaultPrevented) { + return true; + } + this.state.shift = Boolean(event.shiftKey); + const configureMouse = this.getOption("configureMouse"); + if (typeof configureMouse === "function") { + configureMouse(this._instance(), "single", event); + } + return event.defaultPrevented; + }; + + CodeMirror6Adapter.prototype._handleCharacterBinding = function (event) { + if (event.ctrlKey && !event.altKey || event.metaKey) { + return false; + } + const keyCode = event.charCode === null || event.charCode === undefined ? + event.keyCode : + event.charCode; + const character = String.fromCharCode(keyCode); + if (!character || character === "\b") { + return false; + } + return this._dispatchLegacyKey(`'${character}'`, event, false) === true; + }; + + CodeMirror6Adapter.prototype.triggerOnKeyDown = function (event) { + return this._handleKeyDown(event); + }; + + CodeMirror6Adapter.prototype.triggerOnKeyPress = function (event) { + return this._handleKeyPress(event); + }; + + CodeMirror6Adapter.prototype.triggerOnKeyUp = function (event) { + return this._handleKeyUp(event); + }; + + CodeMirror6Adapter.prototype.triggerOnMouseDown = function (event) { + return this._handleMouseDown(event); + }; + + CodeMirror6Adapter.prototype._emit = function (eventName) { + const listeners = this._listeners.get(eventName); + if (!listeners || !listeners.length) { + return; + } + const args = Array.prototype.slice.call(arguments, 1); + listeners.slice().forEach(function (listener) { + listener.apply(null, args); + }); + }; + + CodeMirror6Adapter.prototype.on = function (eventName, listener) { + if (!this._listeners.has(eventName)) { + this._listeners.set(eventName, []); + } + this._listeners.get(eventName).push(listener); + }; + + CodeMirror6Adapter.prototype.off = function (eventName, listener) { + if (!eventName) { + this._listeners.clear(); + return; + } + const listeners = this._listeners.get(eventName); + if (!listeners) { + return; + } + if (!listener) { + listeners.length = 0; + return; + } + const index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + }; + + CodeMirror6Adapter.prototype.getWrapperElement = function () { + return this._wrapperElement; + }; + + CodeMirror6Adapter.prototype.getScrollerElement = function () { + return this._scrollerElement; + }; + + CodeMirror6Adapter.prototype.annotateScrollbar = function (options) { + return new LegacyScrollbarAnnotation(this, options); + }; + + CodeMirror6Adapter.prototype.showMatchesOnScrollbar = function ( + query, + caseFold, + options + ) { + return new LegacySearchAnnotation( + this, + query, + caseFold, + options + ); + }; + + CodeMirror6Adapter.prototype.getGutterElement = function () { + if (!this._view) { + return null; + } + return this._view.dom.querySelector(".cm-gutters") || this._view.dom; + }; + + CodeMirror6Adapter.prototype.getInputField = function () { + return this._contentElement; + }; + + CodeMirror6Adapter.prototype.getLineSpaceElement = function () { + return this._contentElement; + }; + + CodeMirror6Adapter.prototype.focus = function () { + if (!this._view || this._destroyed || + this.getOption("readOnly") === "nocursor") { + return; + } + this._view.focus(); + this._setFocusState( + this._contentElement === this._view.root.activeElement || + this._contentElement.contains(this._view.root.activeElement) + ); + }; + + CodeMirror6Adapter.prototype.hasFocus = function () { + return Boolean(this._view && !this._destroyed && this._view.hasFocus); + }; + + CodeMirror6Adapter.prototype.isReadOnly = function () { + return Boolean(this.getOption("readOnly")); + }; + + CodeMirror6Adapter.prototype.refresh = function () { + if (!this._view || this._destroyed) { + return; + } + this._view.requestMeasure(); + this._decorateDOM(); + this._refreshLegacyHighlighting(); + this._refreshLegacyDecorations(); + this._refreshGutters(); + this._scheduleRulerRefresh(); + this._refreshScrollbarModel(); + this._invalidateRenderedLines(); + this._emit("refresh", this._instance()); + }; + + CodeMirror6Adapter.prototype.setSize = function (width, height) { + if (!this._view || this._destroyed) { + return; + } + if (width !== null && width !== undefined) { + this._view.dom.style.width = typeof width === "number" ? `${width}px` : width; + } + if (height !== null && height !== undefined) { + this._view.dom.style.height = typeof height === "number" ? `${height}px` : height; + } + this.refresh(); + }; + + CodeMirror6Adapter.prototype.getViewport = function () { + if (!this._view) { + return { from: 0, to: 0 }; + } + if (this._view.scrollDOM.clientHeight === 0) { + return { from: 0, to: 0 }; + } + const doc = this._view.state.doc; + return { + from: doc.lineAt(this._view.viewport.from).number - 1 + + this._firstLine, + to: doc.lineAt(this._view.viewport.to).number + + this._firstLine + }; + }; + + CodeMirror6Adapter.prototype.addWidget = function (position, node, scroll, vertical, horizontal) { + if (!this._view || this._destroyed || !node) { + return; + } + + const coordinates = this.cursorCoords(this.clipPos(position), "local"); + const scroller = this._view.scrollDOM; + const content = this._view.contentDOM; + let top = content.offsetTop + coordinates.bottom; + let left = content.offsetLeft + coordinates.left; + + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + node.setAttribute("contenteditable", "false"); + scroller.appendChild(node); + + if (vertical === "over") { + top = content.offsetTop + coordinates.top; + } else if (vertical === "above" || vertical === "near") { + const verticalSpace = Math.max(scroller.clientHeight, scroller.scrollHeight); + const horizontalSpace = Math.max(scroller.clientWidth, scroller.scrollWidth); + if ((vertical === "above" || top + node.offsetHeight > verticalSpace) && + content.offsetTop + coordinates.top > node.offsetHeight) { + top = content.offsetTop + coordinates.top - node.offsetHeight; + } else if (top + node.offsetHeight <= verticalSpace) { + top = content.offsetTop + coordinates.bottom; + } + if (left + node.offsetWidth > horizontalSpace) { + left = Math.max(0, horizontalSpace - node.offsetWidth); + } + } + + node.style.top = `${top}px`; + node.style.left = ""; + node.style.right = ""; + const horizontalSpace = Math.max(scroller.clientWidth, scroller.scrollWidth); + if (horizontal === "right") { + left = Math.max(0, horizontalSpace - node.offsetWidth); + node.style.right = "0px"; + } else { + if (horizontal === "left") { + left = 0; + } else if (horizontal === "middle") { + left = Math.max(0, (horizontalSpace - node.offsetWidth) / 2); + } + node.style.left = `${left}px`; + } + + if (scroll) { + const widgetRectangle = { + left: left, + right: left + node.offsetWidth, + top: top, + bottom: top + node.offsetHeight + }; + this._scrollRectIntoView(widgetRectangle, widgetRectangle, 0); + } + }; + + CodeMirror6Adapter.prototype.getScrollInfo = function () { + if (!this._view) { + return { + left: this.doc && this.doc._scrollLeft || 0, + top: this.doc && this.doc._scrollTop || 0, + height: 0, + width: 0, + clientHeight: 0, + clientWidth: 0 + }; + } + const scroller = this._view.scrollDOM; + return { + left: scroller.scrollLeft, + top: scroller.scrollTop, + height: scroller.scrollHeight, + width: scroller.scrollWidth, + clientHeight: scroller.clientHeight, + clientWidth: scroller.clientWidth + }; + }; + + CodeMirror6Adapter.prototype.scrollTo = function (x, y) { + if (!this._view || this._destroyed) { + if (this.doc) { + if (x !== null && x !== undefined) { + this.doc._scrollLeft = x; + } + if (y !== null && y !== undefined) { + this.doc._scrollTop = y; + } + } + return; + } + const previousLeft = this._view.scrollDOM.scrollLeft; + const previousTop = this._view.scrollDOM.scrollTop; + if (x !== null && x !== undefined) { + this._view.scrollDOM.scrollLeft = x; + if (this.doc) { + this.doc._scrollLeft = x; + } + } + if (y !== null && y !== undefined) { + this._view.scrollDOM.scrollTop = y; + if (this.doc) { + this.doc._scrollTop = y; + } + } + if (this._view.scrollDOM.scrollLeft !== previousLeft || + this._view.scrollDOM.scrollTop !== previousTop) { + this._refreshScrollbarModel(); + this._view.dispatch({ + effects: this._view.scrollSnapshot() + }); + } + }; + + CodeMirror6Adapter.prototype._scrollRectIntoView = function (from, to, margin) { + const scroller = this._view.scrollDOM; + const safeMargin = Math.max(0, margin || 0); + const top = Math.min(from.top, to.top) - safeMargin; + const bottom = Math.max(from.bottom, to.bottom) + safeMargin; + const left = Math.min(from.left, to.left) - safeMargin; + const right = Math.max(from.right, to.right) + safeMargin; + + if (top < scroller.scrollTop) { + scroller.scrollTop = Math.max(0, top); + } else if (bottom > scroller.scrollTop + scroller.clientHeight) { + scroller.scrollTop = bottom - scroller.clientHeight; + } + if (left < scroller.scrollLeft) { + scroller.scrollLeft = Math.max(0, left); + } else if (right > scroller.scrollLeft + scroller.clientWidth) { + scroller.scrollLeft = right - scroller.clientWidth; + } + }; + + CodeMirror6Adapter.prototype._scrollContentRectIntoView = function (from, to, margin) { + const scroller = this._view.scrollDOM; + const safeMargin = Math.max(0, margin || 0); + const top = Math.min(from.top, to.top) - safeMargin; + const bottom = Math.max(from.bottom, to.bottom) + safeMargin; + const left = Math.min(from.left, to.left); + const right = Math.max(from.right, to.right); + const gutter = this.getGutterElement(); + const fixedGutterWidth = this.getOption("fixedGutter") !== false && + gutter && gutter !== this._view.dom ? + gutter.offsetWidth : + 0; + const nativeScrollbarWidth = Math.max( + 0, + scroller.offsetWidth - scroller.clientWidth + ); + const scrollGap = Math.max( + 0, + LEGACY_SCROLLER_GAP - nativeScrollbarWidth + ); + const verticalScrollbarWidth = + scroller.scrollHeight > scroller.clientHeight + 1 ? + nativeScrollbarWidth : + 0; + const visibleWidth = Math.max( + 0, + scroller.clientWidth - + scrollGap - + verticalScrollbarWidth - + fixedGutterWidth + ); + const tooWide = right - left > visibleWidth; + const visibleRight = tooWide ? left + visibleWidth : right; + + if (top < scroller.scrollTop) { + scroller.scrollTop = Math.max(0, top); + } else if (bottom > scroller.scrollTop + scroller.clientHeight) { + scroller.scrollTop = bottom - scroller.clientHeight; + } + if (left < HORIZONTAL_SCROLL_MARGIN) { + scroller.scrollLeft = 0; + } else if (left < scroller.scrollLeft) { + scroller.scrollLeft = Math.max( + 0, + left - (tooWide ? 0 : HORIZONTAL_SCROLL_MARGIN) + ); + } else if (visibleRight > + scroller.scrollLeft + visibleWidth - 3) { + scroller.scrollLeft = visibleRight + + (tooWide ? 0 : HORIZONTAL_SCROLL_MARGIN) - + visibleWidth; + } + }; + + CodeMirror6Adapter.prototype.scrollIntoView = function (range, margin) { + if (!this._view || this._destroyed) { + return; + } + if (range === null || range === undefined) { + range = this.getCursor(); + if (margin === null || margin === undefined) { + margin = this._options.cursorScrollMargin || 0; + } + } else if (typeof range === "number") { + range = { + line: range, + ch: 0 + }; + } else if (range.left !== undefined) { + range = { + from: range, + to: range + }; + } + + if (range.from && range.from.line === undefined) { + const from = range.from; + const to = range.to || from; + const scroller = this._view.scrollDOM; + const previousLeft = scroller.scrollLeft; + const previousTop = scroller.scrollTop; + this._scrollContentRectIntoView(from, to, margin); + if (scroller.scrollLeft !== previousLeft || + scroller.scrollTop !== previousTop) { + this._dispatchLegacyUpdate(); + } + return; + } + + const from = this.indexFromPos(range.from || range); + const to = range && range.to ? this.indexFromPos(range.to) : from; + const scroller = this._view.scrollDOM; + const safeMargin = Math.max(0, margin || 0); + const previousLeft = scroller.scrollLeft; + const previousTop = scroller.scrollTop; + this._scrollContentRectIntoView( + this._coordsForOffset(from, "local"), + this._coordsForOffset(to, "local"), + safeMargin + ); + const transactionSpec = { + effects: CM6.EditorView.scrollIntoView( + CM6.EditorSelection.range(from, to), + { + yMargin: Math.min(safeMargin, Math.max(0, scroller.clientHeight - 1)), + xMargin: Math.min(safeMargin, Math.max(0, scroller.clientWidth - 1)) + } + ) + }; + if (scroller.scrollLeft !== previousLeft || + scroller.scrollTop !== previousTop) { + transactionSpec.annotations = this._legacyUpdateAnnotation.of(true); + } + this._view.dispatch(transactionSpec); + }; + + CodeMirror6Adapter.prototype.cursorCoords = function (start, mode) { + if (!this._view) { + return this._coordsForOffset(0, mode); + } + let position; + if (!start) { + position = this._view.state.selection.main.head; + } else if (start === "start" || start === "from") { + position = this._view.state.selection.main.from; + } else if (start === "end" || start === "to") { + position = this._view.state.selection.main.to; + } else { + position = this.indexFromPos(start); + } + return this._coordsForOffset(position, mode); + }; + + CodeMirror6Adapter.prototype.charCoords = function (position, mode) { + return this._coordsForOffset(this.indexFromPos(position), mode); + }; + + CodeMirror6Adapter.prototype._coordsForOffset = function (offset, mode) { + if (!this._view) { + return { + left: 0, + right: 0, + top: 0, + bottom: DEFAULT_LINE_HEIGHT + }; + } + if (mode === true) { + mode = "page"; + } else if (mode === false) { + mode = "local"; + } + mode = mode || "page"; + + const coords = this._view.coordsAtPos(_clamp(offset, 0, this._view.state.doc.length)); + const line = this._view.state.doc.lineAt( + _clamp(offset, 0, this._view.state.doc.length) + ); + const block = this._view.lineBlockAt(line.from); + const contentRect = this._view.contentDOM.getBoundingClientRect(); + const fallbackTop = this._view.documentTop + block.top; + const result = coords || { + left: contentRect.left, + right: contentRect.left, + top: fallbackTop, + bottom: fallbackTop + this.defaultTextHeight() + }; + + if (mode === "local") { + const lineTop = this._view.documentTop + block.top; + const topWithinLine = result.top - lineTop; + const bottomWithinLine = result.bottom - lineTop; + return { + left: result.left - contentRect.left, + right: result.right - contentRect.left, + top: this._view.documentPadding.top + block.top + topWithinLine, + bottom: this._view.documentPadding.top + block.top + bottomWithinLine + }; + } + if (mode === "div") { + return { + left: result.left - contentRect.left, + right: result.right - contentRect.left, + top: result.top - contentRect.top, + bottom: result.bottom - contentRect.top + }; + } + if (mode === "page" || !mode) { + return { + left: result.left + window.scrollX, + right: result.right + window.scrollX, + top: result.top + window.scrollY, + bottom: result.bottom + window.scrollY + }; + } + return result; + }; + + CodeMirror6Adapter.prototype.coordsChar = function (coordinates, mode) { + if (!this._view) { + return { + line: this._firstLine, + ch: 0, + sticky: "after", + outside: 0, + xRel: 0 + }; + } + if (mode === true) { + mode = "page"; + } else if (mode === false) { + mode = "local"; + } + mode = mode || "page"; + + let left = coordinates.left; + let top = coordinates.top; + if (mode === "local") { + const contentRect = this._view.contentDOM.getBoundingClientRect(); + left += contentRect.left; + top += contentRect.top; + } else if (mode === "div") { + const contentRect = this._view.contentDOM.getBoundingClientRect(); + left += contentRect.left; + top += contentRect.top; + } else if (mode === "page" || !mode) { + left -= window.scrollX; + top -= window.scrollY; + } + const offset = this._view.posAtCoords({ x: left, y: top }); + const contentRect = this._view.contentDOM.getBoundingClientRect(); + let outside = 0; + if (top < contentRect.top) { + outside = -1; + } else if (top >= contentRect.bottom) { + outside = 1; + } + const safeOffset = offset === null ? + (outside < 0 ? 0 : this._view.state.doc.length) : + offset; + const position = this.posFromIndex(safeOffset); + const positionCoordinates = this._view.coordsAtPos(safeOffset); + position.sticky = "after"; + position.xRel = positionCoordinates ? left - positionCoordinates.left : 0; + if (outside) { + position.outside = outside; + } + return position; + }; + + CodeMirror6Adapter.prototype.defaultTextHeight = function () { + if (!this._view) { + return DEFAULT_LINE_HEIGHT; + } + const coordinates = this._view.coordsAtPos(0); + if (coordinates && coordinates.bottom > coordinates.top) { + return coordinates.bottom - coordinates.top; + } + return this._view.defaultLineHeight || DEFAULT_LINE_HEIGHT; + }; + + CodeMirror6Adapter.prototype.defaultCharWidth = function () { + if (!this._view) { + return DEFAULT_CHARACTER_WIDTH; + } + return this._view.defaultCharacterWidth || DEFAULT_CHARACTER_WIDTH; + }; + + CodeMirror6Adapter.prototype.heightAtLine = function (lineNumber, mode) { + if (!this._view) { + return 0; + } + if (mode === true) { + mode = "page"; + } else if (mode === false) { + mode = "local"; + } + mode = mode || "page"; + + if (typeof lineNumber !== "number") { + lineNumber = this.getLineNumber(lineNumber); + } + if (lineNumber === null || lineNumber === undefined) { + lineNumber = this.firstLine(); + } + const internalLineNumber = lineNumber - this._firstLine; + const beyondDocument = internalLineNumber >= this._view.state.doc.lines; + let blockTop; + if (beyondDocument) { + blockTop = this._view.contentHeight; + } else { + const line = this._view.state.doc.line( + _clamp( + internalLineNumber + 1, + 1, + this._view.state.doc.lines + ) + ); + blockTop = this._view.lineBlockAt(line.from).top; + } + + if (mode === "window") { + return this._view.documentTop + blockTop; + } + if (mode === "page") { + return this._view.documentTop + blockTop + window.scrollY; + } + return blockTop; + }; + + CodeMirror6Adapter.prototype.lineAtHeight = function (height, mode) { + if (!this._view) { + return this._firstLine; + } + if (mode === true) { + mode = "page"; + } else if (mode === false) { + mode = "local"; + } + mode = mode || "page"; + + let documentHeight = height; + if (mode === "window") { + documentHeight -= this._view.documentTop; + } else if (mode === "page") { + documentHeight -= this._view.documentTop + window.scrollY; + } + + const block = this._view.lineBlockAtHeight( + _clamp(documentHeight, 0, this._view.contentHeight) + ); + return this._view.state.doc.lineAt(block.from).number - 1 + + this._firstLine; + }; + + CodeMirror6Adapter.prototype.addOverlay = function (specification, options) { + const mode = specification && typeof specification.token === "function" ? + specification : + CodeMirror.getMode(this._options, specification); + if (mode.startState) { + throw new Error("Overlays may not be stateful."); + } + const overlayRecord = { + mode: mode, + modeSpec: specification, + opaque: options && options.opaque, + priority: options && options.priority || 0 + }; + const overlays = this.state.overlays; + let insertionIndex = 0; + while (insertionIndex < overlays.length && + overlays[insertionIndex].priority <= overlayRecord.priority) { + insertionIndex++; + } + overlays.splice(insertionIndex, 0, overlayRecord); + this._refreshLegacyHighlighting(); + }; + + CodeMirror6Adapter.prototype.removeOverlay = function (specification) { + const overlays = this.state.overlays; + const index = overlays.findIndex(function (record) { + const current = record.modeSpec; + return current === specification || + typeof specification === "string" && + current && current.name === specification; + }); + if (index !== -1) { + overlays.splice(index, 1); + this._refreshLegacyHighlighting(); + } + }; + + CodeMirror6Adapter.prototype._syncMarkerLines = function (marker) { + marker.lines.length = 0; + if (marker._cleared || marker._hidden || !this._view) { + return; + } + + const doc = this._view.state.doc; + const fromLine = doc.lineAt( + _clamp(marker._from, 0, doc.length) + ).number - 1 + this._firstLine; + const toLine = doc.lineAt( + _clamp(marker._to, 0, doc.length) + ).number - 1 + this._firstLine; + for (let lineNumber = fromLine; lineNumber <= toLine; lineNumber++) { + const lineHandle = this.getLineHandle(lineNumber); + if (lineHandle) { + marker.lines.push(lineHandle); + } + } + }; + + CodeMirror6Adapter.prototype._createMarker = function (type, from, to, options) { + const markerId = this._nextMarkerId++; + this.$mid = this._nextMarkerId; + const markerOptions = options && options.nodeType ? { + widget: options + } : Object.assign({}, options); + delete markerOptions._sharedInternal; + const marker = Object.assign({ + type: type, + _adapter: this, + id: markerId, + _id: markerId, + _from: this.indexFromPos(from), + _to: this.indexFromPos(to || from), + _cleared: false, + _hidden: false, + lines: [], + clear: function () { + if (this._cleared) { + return; + } + if (this.parent && !this._clearingShared) { + this.parent.clear(); + return; + } + const adapter = this._adapter; + const found = this.find(); + this._cleared = true; + this.explicitlyCleared = true; + this.lines.length = 0; + adapter._markers = adapter._markers.filter(function (candidate) { + return candidate !== marker; + }); + delete adapter.marks[this.id]; + adapter._refreshLegacyDecorations(); + if (found) { + const clearFrom = type === "bookmark" ? found : found.from; + const clearTo = type === "bookmark" ? found : found.to; + CodeMirror.signal(marker, "clear", clearFrom, clearTo); + } + adapter._emit("markerCleared", adapter, marker); + }, + find: function (side) { + const adapter = this._adapter; + if (this._cleared || this._hidden || !adapter._view) { + return undefined; + } + if (type === "bookmark") { + return adapter.posFromIndex(this._from); + } + if (side === -1) { + return adapter.posFromIndex(this._from); + } + if (side === 1) { + return adapter.posFromIndex(this._to); + } + return { + from: adapter.posFromIndex(this._from), + to: adapter.posFromIndex(this._to) + }; + }, + changed: function () { + const adapter = this._adapter; + adapter._refreshLegacyDecorations(); + if (adapter._view) { + adapter._view.requestMeasure(); + } + CodeMirror.signal(marker, "changed"); + adapter._emit("markerChanged", adapter, marker); + }, + on: function (eventName, listener) { + CodeMirror.on(marker, eventName, listener); + }, + off: function (eventName, listener) { + CodeMirror.off(marker, eventName, listener); + } + }, markerOptions); + marker.doc = this.doc; + const replacementNode = marker.replacedWith || + type === "bookmark" && marker.widget || + null; + if (replacementNode) { + marker.replacedWith = replacementNode; + marker.widgetNode = window.document.createElement("span"); + marker.widgetNode.className = "CodeMirror-widget"; + marker.widgetNode.setAttribute("role", "presentation"); + marker.widgetNode.appendChild(replacementNode); + if (!marker.handleMouseEvents) { + marker.widgetNode.setAttribute("cm-ignore-events", "true"); + } + if (marker.insertLeft) { + marker.widgetNode.insertLeft = true; + } + } + marker._widgetNode = marker.widgetNode || null; + if (type === "range" && marker.clearWhenEmpty === undefined) { + marker.clearWhenEmpty = true; + } + if (type === "range" && marker.replacedWith) { + marker.collapsed = true; + } + if (type === "range" && marker.collapsed) { + marker.atomic = true; + } + if (type === "range" && marker._from >= marker._to && + marker.clearWhenEmpty !== false) { + marker._hidden = true; + } + this._markers.push(marker); + this.marks[markerId] = marker; + this._syncMarkerLines(marker); + this._emit("markerAdded", this, marker); + if (marker.readOnly && !marker._hidden) { + this.clearHistory(); + } + this._refreshLegacyDecorations(); + return marker; + }; + + CodeMirror6Adapter.prototype._sharedMarkerRange = function (type, from, to) { + if (type === "bookmark") { + if (from.line < this.firstLine() || from.line > this.lastLine()) { + return null; + } + const position = this.clipPos(from); + return { + from: position, + to: position + }; + } + const docFrom = { + line: this.firstLine(), + ch: 0 + }; + const docTo = { + line: this.lastLine(), + ch: (this.getLine(this.lastLine()) || "").length + }; + if (CodeMirror.cmpPos(to, docFrom) < 0 || + CodeMirror.cmpPos(from, docTo) > 0) { + return null; + } + return { + from: CodeMirror.cmpPos(from, docFrom) < 0 ? docFrom : this.clipPos(from), + to: CodeMirror.cmpPos(to, docTo) > 0 ? docTo : this.clipPos(to) + }; + }; + + CodeMirror6Adapter.prototype._markerOptionsForLinkedDoc = function (marker) { + const optionNames = [ + "addToHistory", + "atomic", + "attributes", + "className", + "clearOnEnter", + "clearWhenEmpty", + "collapsed", + "css", + "endStyle", + "handleMouseEvents", + "inclusiveLeft", + "inclusiveRight", + "insertLeft", + "readOnly", + "startStyle", + "title" + ]; + const options = { + shared: false, + _sharedInternal: true + }; + optionNames.forEach(function (name) { + if (marker[name] !== undefined) { + options[name] = marker[name]; + } + }); + if (marker.replacedWith) { + if (marker.type === "bookmark") { + options.widget = marker.replacedWith.cloneNode(true); + } else { + options.replacedWith = marker.replacedWith.cloneNode(true); + } + } + return options; + }; + + CodeMirror6Adapter.prototype._createSharedMarker = function (type, from, to, options) { + const docs = [this.doc]; + this.doc.iterLinkedDocs(function (doc) { + docs.push(doc); + }); + const markers = []; + let primary = null; + docs.forEach(doc => { + if (!doc._adapter) { + return; + } + const range = doc._adapter._sharedMarkerRange(type, from, to); + if (!range) { + return; + } + const localOptions = Object.assign({}, options, { + shared: false, + _sharedInternal: true + }); + const marker = doc._adapter._createMarker( + type, + range.from, + range.to, + localOptions + ); + marker.shared = true; + markers.push(marker); + if (!doc._links.some(function (link) { + return link.isParent; + })) { + primary = marker; + } + }); + const sharedMarker = new CodeMirror.SharedTextMarker( + markers, + primary || markers[0] + ); + markers.forEach(function (marker) { + marker.parent = sharedMarker; + }); + return sharedMarker; + }; + + CodeMirror6Adapter.prototype._copySharedMarkersTo = function (targetDoc) { + const targetAdapter = targetDoc && targetDoc._adapter; + if (!targetAdapter) { + return; + } + const sharedMarkers = []; + this._markers.forEach(function (marker) { + if (marker.parent && sharedMarkers.indexOf(marker.parent) === -1) { + sharedMarkers.push(marker.parent); + } + }); + sharedMarkers.forEach(sharedMarker => { + if (sharedMarker._cleared || + sharedMarker.markers.some(function (marker) { + return marker._adapter === targetAdapter && !marker._cleared; + })) { + return; + } + const representative = sharedMarker.markers.find(function (marker) { + return marker && !marker._cleared && marker.find(); + }); + if (!representative) { + return; + } + const found = representative.find(); + const from = representative.type === "bookmark" ? found : found.from; + const to = representative.type === "bookmark" ? found : found.to; + const range = targetAdapter._sharedMarkerRange( + representative.type, + from, + to + ); + if (!range) { + return; + } + const marker = targetAdapter._createMarker( + representative.type, + range.from, + range.to, + this._markerOptionsForLinkedDoc(representative) + ); + marker.shared = true; + marker.parent = sharedMarker; + sharedMarker.markers.push(marker); + }); + }; + + CodeMirror6Adapter.prototype._partitionSharedMarkers = function (otherDoc) { + const docs = new Set(); + const visit = function (doc) { + if (docs.has(doc)) { + return; + } + docs.add(doc); + doc._links.forEach(function (link) { + visit(link.doc); + }); + }; + visit(this.doc); + visit(otherDoc); + + const sharedMarkers = []; + docs.forEach(function (doc) { + if (!doc._adapter) { + return; + } + doc._adapter._markers.forEach(function (marker) { + if (marker.parent && + sharedMarkers.indexOf(marker.parent) === -1) { + sharedMarkers.push(marker.parent); + } + }); + }); + sharedMarkers.forEach(function (sharedMarker) { + if (!sharedMarker.primary || !sharedMarker.primary.doc) { + return; + } + const primaryComponent = new Set(); + const visitPrimaryComponent = function (doc) { + if (primaryComponent.has(doc)) { + return; + } + primaryComponent.add(doc); + doc._links.forEach(function (link) { + visitPrimaryComponent(link.doc); + }); + }; + visitPrimaryComponent(sharedMarker.primary.doc); + + sharedMarker.markers = sharedMarker.markers.filter(function (marker) { + if (marker.doc && primaryComponent.has(marker.doc)) { + return true; + } + marker.parent = null; + return false; + }); + }); + }; + + CodeMirror6Adapter.prototype.markText = function (from, to, options) { + if (options && options.shared && !options._sharedInternal) { + return this._createSharedMarker("range", from, to, options); + } + return this._createMarker("range", from, to, options); + }; + + CodeMirror6Adapter.prototype.setBookmark = function (position, options) { + const markerOptions = { + replacedWith: options && ( + options.nodeType === null || options.nodeType === undefined ? + options.widget : + options + ), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, + shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents + }; + if (markerOptions.shared) { + return this._createSharedMarker( + "bookmark", + position, + position, + markerOptions + ); + } + return this._createMarker( + "bookmark", + position, + position, + markerOptions + ); + }; + + function _sortMarkersByFirstVisitedLine(markers, doc, fromIndex) { + return markers.map(function (marker, insertionIndex) { + const firstVisitedIndex = Math.max(marker._from, fromIndex); + return { + marker: marker, + insertionIndex: insertionIndex, + lineNumber: doc.lineAt( + _clamp(firstVisitedIndex, 0, doc.length) + ).number + }; + }).sort(function (left, right) { + return left.lineNumber - right.lineNumber || + left.insertionIndex - right.insertionIndex; + }).map(function (entry) { + return entry.marker; + }); + } + + CodeMirror6Adapter.prototype.getAllMarks = function () { + const markers = this._markers.filter(function (marker) { + return !marker._cleared && !marker._hidden; + }); + if (!this._view) { + return markers; + } + const sorted = _sortMarkersByFirstVisitedLine( + markers, + this._view.state.doc, + 0 + ); + return sorted; + }; + + CodeMirror6Adapter.prototype.findMarks = function (from, to, filter) { + const fromIndex = this.indexFromPos(from); + const toIndex = this.indexFromPos(to); + const markers = this._markers.filter(function (marker) { + if (marker._cleared || marker._hidden) { + return false; + } + if (marker.type === "bookmark") { + return marker._from > fromIndex && marker._from < toIndex; + } + return marker._to > fromIndex && marker._from < toIndex; + }); + const sorted = _sortMarkersByFirstVisitedLine( + markers, + this._view.state.doc, + fromIndex + ); + return sorted.filter(function (marker) { + return !filter || filter(marker); + }).map(function (marker) { + return marker.parent || marker; + }).filter(function (marker, index, allMarkers) { + return allMarkers.indexOf(marker) === index; + }); + }; + + CodeMirror6Adapter.prototype.findMarksAt = function (position) { + const index = this.indexFromPos(position); + return this._markers.filter(function (marker) { + if (marker._cleared || marker._hidden) { + return false; + } + if (marker.type === "bookmark") { + return marker._from === index; + } + return marker._from <= index && marker._to >= index; + }).map(function (marker) { + return marker.parent || marker; + }).filter(function (marker, markerIndex, markers) { + return markers.indexOf(marker) === markerIndex; + }); + }; + + CodeMirror6Adapter.prototype.addLineClass = function (line, where, className) { + const lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + if (!lineHandle) { + return null; + } + const currentClassName = this._lineClasses.filter(function (record) { + return record.lineHandle === lineHandle && record.where === where; + }).map(function (record) { + return record.className; + }).join(" "); + if (!_legacyClassPattern(className).test(currentClassName)) { + this._lineClasses = this._lineClasses.filter(function (record) { + return record.lineHandle !== lineHandle || record.where !== where; + }); + this._lineClasses.push({ + lineHandle: lineHandle, + where: where, + className: currentClassName ? + `${currentClassName} ${className}` : + className + }); + } + this._refreshLegacyDecorations(); + return lineHandle; + }; + + CodeMirror6Adapter.prototype.removeLineClass = function (line, where, className) { + const lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + const affectedKinds = new Set(this._lineClasses.filter(function (record) { + return record.lineHandle === lineHandle && + (!where || record.where === where); + }).map(function (record) { + return record.where; + })); + + affectedKinds.forEach(affectedWhere => { + const currentClassName = this._lineClasses.filter(function (record) { + return record.lineHandle === lineHandle && + record.where === affectedWhere; + }).map(function (record) { + return record.className; + }).join(" "); + let nextClassName = ""; + if (className !== null && className !== undefined) { + const match = currentClassName.match(_legacyClassPattern(className)); + if (!match) { + return; + } + const end = match.index + match[0].length; + nextClassName = ( + currentClassName.slice(0, match.index) + + (!match.index || end === currentClassName.length ? "" : " ") + + currentClassName.slice(end) + ).trim(); + } + this._lineClasses = this._lineClasses.filter(function (record) { + return record.lineHandle !== lineHandle || + record.where !== affectedWhere; + }); + if (nextClassName) { + this._lineClasses.push({ + lineHandle: lineHandle, + where: affectedWhere, + className: nextClassName + }); + } + }); + this._refreshLegacyDecorations(); + return lineHandle; + }; + + CodeMirror6Adapter.prototype.addLineWidget = function (line, node, options) { + const lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + if (!lineHandle) { + return null; + } + const widget = Object.assign({}, options || {}); + widget.doc = this.doc; + widget.node = node; + widget.line = lineHandle; + widget.on = function (eventName, listener) { + CodeMirror.on(this, eventName, listener); + }; + widget.off = function (eventName, listener) { + CodeMirror.off(this, eventName, listener); + }; + const record = { + widget: widget, + node: node, + options: Object.assign({}, options || {}), + renderedWrapper: null, + version: 0, + cleared: false + }; + widget.clear = function () { + if (record.cleared) { + return; + } + const adapter = widget.doc && widget.doc._adapter; + const lineNumber = adapter ? + adapter.getLineNumber(widget.line) : + null; + record.cleared = true; + if (adapter) { + adapter._lineWidgets = adapter._lineWidgets.filter(function (candidate) { + return candidate !== record; + }); + } + widget.line.widgets = widget.line.widgets.filter(function (candidate) { + return candidate !== widget; + }); + if (adapter) { + adapter._refreshLegacyDecorations(); + adapter._emit("lineWidgetCleared", adapter, widget, lineNumber); + } + }; + widget.changed = function () { + if (record.cleared) { + return; + } + const adapter = widget.doc && widget.doc._adapter; + widget.height = null; + if (!adapter) { + return; + } + adapter._applyLineWidgetLayout(record); + adapter._measureLineWidget(record); + if (adapter._view) { + adapter._view.requestMeasure(); + } + adapter._emit( + "lineWidgetChanged", + adapter, + widget, + adapter.getLineNumber(widget.line) + ); + }; + const insertAt = Number.isInteger(record.options.insertAt) ? + record.options.insertAt : + lineHandle.widgets.length; + lineHandle.widgets.splice( + _clamp(insertAt, 0, lineHandle.widgets.length), + 0, + widget + ); + this._lineWidgets.push(record); + this._refreshLegacyDecorations(); + if (widget.height === undefined) { + widget.height = 0; + } + this._emit("lineWidgetAdded", this, widget, this.getLineNumber(lineHandle)); + return widget; + }; + + CodeMirror6Adapter.prototype.removeLineWidget = function (widget) { + if (widget && typeof widget.clear === "function") { + widget.clear(); + } + }; + + CodeMirror6Adapter.prototype.setGutterMarker = function (line, gutterName, marker) { + const lineNumber = typeof line === "number" ? line : this.getLineNumber(line); + this._removeGutterMarkerRecords(lineNumber, gutterName); + const lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + if (!lineHandle) { + return null; + } + if (marker) { + this._gutterMarkers.push({ + lineHandle: lineHandle, + gutterName: gutterName, + marker: marker, + renderedNode: _nodeForGutterMarker(marker) + }); + } + this._scheduleGutterRefresh(); + return lineHandle; + }; + + CodeMirror6Adapter.prototype.clearGutter = function (gutterName) { + this._gutterMarkers = this._gutterMarkers.filter(function (record) { + return record.gutterName !== gutterName; + }); + this._scheduleGutterRefresh(); + }; + + CodeMirror6Adapter.prototype.lineInfo = function (line) { + const lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + const lineNumber = this.getLineNumber(lineHandle); + if (lineNumber === null || lineNumber === undefined) { + return null; + } + const gutterMarkers = {}; + this._gutterMarkers.forEach(record => { + if (this.getLineNumber(record.lineHandle) === lineNumber) { + gutterMarkers[record.gutterName] = record.marker; + } + }); + const classes = this._lineClasses.filter(function (record) { + return record.lineHandle === lineHandle; + }); + const activeLineOption = this.getOption("styleActiveLine"); + const isActiveLine = Boolean(activeLineOption) && + this._view.state.selection.ranges.some(range => { + const anchorLine = this._view.state.doc.lineAt( + range.anchor + ).number - 1 + this._firstLine; + const headLine = this._view.state.doc.lineAt( + range.head + ).number - 1 + this._firstLine; + const allowNonEmpty = typeof activeLineOption === "object" && + activeLineOption.nonEmpty; + if (allowNonEmpty ? anchorLine !== headLine : !range.empty) { + return false; + } + const visualStart = this.getLineHandleVisualStart(headLine); + return this.getLineNumber(visualStart) === lineNumber; + }); + const textClasses = classes.filter(function (record) { + return record.where === "text"; + }).map(function (record) { + return record.className; + }); + const backgroundClasses = classes.filter(function (record) { + return record.where === "background"; + }).map(function (record) { + return record.className; + }); + const wrapperClasses = classes.filter(function (record) { + return record.where === "wrap"; + }).map(function (record) { + return record.className; + }); + if (isActiveLine) { + backgroundClasses.push("CodeMirror-activeline-background"); + wrapperClasses.push("CodeMirror-activeline"); + } + return { + line: lineNumber, + handle: lineHandle, + text: this.getLine(lineNumber), + gutterMarkers: Object.keys(gutterMarkers).length ? + gutterMarkers : + undefined, + textClass: textClasses.join(" ") || undefined, + bgClass: backgroundClasses.join(" ") || undefined, + wrapClass: wrapperClasses.join(" ") || undefined, + widgets: lineHandle.widgets.length ? + lineHandle.widgets : + undefined + }; + }; + + CodeMirror6Adapter.prototype.getValue = function (separator) { + if (!this._view) { + return separator === false ? [""] : ""; + } + const value = this._view.state.doc.toString(); + if (separator === false) { + return _splitLines(value); + } + const lineSeparator = separator === undefined ? + this.lineSeparator() : + separator; + return lineSeparator === "\n" ? value : value.replace(/\n/g, lineSeparator); + }; + + CodeMirror6Adapter.prototype.setValue = function (text) { + const normalizedText = this.splitLines(String(text)).join("\n"); + this.operation(() => { + const startState = this._view.state; + const beforeChangeListeners = this._listeners.get("beforeChange"); + const documentBeforeChangeListeners = this.doc && + this.doc._handlers && + this.doc._handlers.beforeChange; + const isFullChange = !( + beforeChangeListeners && beforeChangeListeners.length || + documentBeforeChangeListeners && documentBeforeChangeListeners.length + ); + const change = this._createLegacyChange( + startState.doc, + 0, + startState.doc.length, + normalizedText, + "setValue" + ); + this._signalBeforeChange(change); + + const annotations = [ + this._originAnnotation.of(change.origin), + this._bypassReadOnlyAnnotation.of(true), + this._skipBeforeChangeAnnotation.of(true), + this._setValueSelectionResetAnnotation.of(true) + ]; + if (isFullChange) { + annotations.push(this._fullChangeAnnotation.of(true)); + } + const transactionSpec = { + selection: { + anchor: 0 + }, + annotations: annotations + }; + let syntheticChangeSpec; + if (!change._cancelled) { + const from = this.indexFromPos(change.from); + const to = this.indexFromPos(change.to); + const insert = change.text.join("\n"); + transactionSpec.changes = { + from: from, + to: to, + insert: insert + }; + syntheticChangeSpec = { + from: from, + to: to, + insert: insert, + origin: change.origin + }; + } + + if (syntheticChangeSpec && + (syntheticChangeSpec.from !== syntheticChangeSpec.to || + syntheticChangeSpec.insert)) { + annotations.push( + this._syntheticChangesAnnotation.of([syntheticChangeSpec]) + ); + } + this._view.dispatch(startState.update(transactionSpec)); + this.scrollTo(0, 0); + }); + }; + + CodeMirror6Adapter.prototype.replaceRange = function (text, from, to, origin) { + const fromIndex = this.indexFromPos(from); + const toIndex = this.indexFromPos(to || from); + const changes = { + from: fromIndex, + to: toIndex, + insert: String(text) + }; + const changeSet = this._view.state.changes(changes); + const currentSelection = this._view.state.selection; + const mappedChangeEnd = fromIndex + String(text).length; + function mapPosition(position) { + if (position < fromIndex) { + return position; + } + if (position <= toIndex) { + return mappedChangeEnd; + } + return changeSet.mapPos(position, 1); + } + const mappedSelection = CM6.EditorSelection.create( + currentSelection.ranges.map(function (range) { + return CM6.EditorSelection.range( + mapPosition(range.anchor), + mapPosition(range.head) + ); + }), + currentSelection.mainIndex + ); + this._view.dispatch({ + changes: changes, + selection: mappedSelection, + annotations: this._originAnnotation.of(origin) + }); + }; + + CodeMirror6Adapter.prototype.replaceSelection = function (text, select, origin) { + this.replaceSelections([text], select, origin || "+input"); + }; + + CodeMirror6Adapter.prototype.replaceSelections = function (text, select, origin) { + const texts = Array.isArray(text) ? text : this._view.state.selection.ranges.map(function () { + return text; + }); + const ranges = this._view.state.selection.ranges; + const changes = ranges.map(function (range, index) { + return { + from: range.from, + to: range.to, + insert: String(texts[index % texts.length]) + }; + }); + const preliminary = this._view.state.update({ + changes: changes + }); + const selections = ranges.map(function (range, index) { + const insertedText = String(texts[index % texts.length]); + const start = preliminary.changes.mapPos(range.from, -1); + const end = start + insertedText.length; + if (select === "around") { + return range.anchor > range.head ? + CM6.EditorSelection.range(end, start) : + CM6.EditorSelection.range(start, end); + } + if (select === "start") { + return CM6.EditorSelection.cursor(start); + } + return CM6.EditorSelection.cursor(end); + }); + this._view.dispatch({ + changes: changes, + selection: CM6.EditorSelection.create( + selections, + this._view.state.selection.mainIndex + ), + annotations: this._originAnnotation.of(origin) + }); + }; + + CodeMirror6Adapter.prototype.getRange = function (from, to, separator) { + if (!this._view) { + return separator === false ? [""] : ""; + } + const value = this._view.state.doc.sliceString( + this.indexFromPos(from), + this.indexFromPos(to) + ); + if (separator === false) { + return _splitLines(value); + } + const lineSeparator = separator === undefined ? + this.lineSeparator() : + separator; + return lineSeparator === "\n" ? value : value.replace(/\n/g, lineSeparator); + }; + + CodeMirror6Adapter.prototype.getLine = function (lineNumber) { + if (!this._view) { + return undefined; + } + if (lineNumber < this.firstLine() || lineNumber > this.lastLine()) { + return undefined; + } + return this._view.state.doc.line( + lineNumber - this._firstLine + 1 + ).text; + }; + + CodeMirror6Adapter.prototype.lineCount = function () { + return this._view ? this._view.state.doc.lines : 0; + }; + + CodeMirror6Adapter.prototype.firstLine = function () { + return this._firstLine; + }; + + CodeMirror6Adapter.prototype.lastLine = function () { + return this._firstLine + this.lineCount() - 1; + }; + + CodeMirror6Adapter.prototype.lineSeparator = function () { + return this.getOption("lineSeparator") || "\n"; + }; + + CodeMirror6Adapter.prototype.splitLines = function (text) { + const separator = this.getOption("lineSeparator"); + return separator ? + String(text).split(separator) : + CodeMirror.splitLines(String(text)); + }; + + CodeMirror6Adapter.prototype.indexFromPos = function (position) { + if (!position || !this._view) { + return 0; + } + const requestedLine = Number(position.line); + if (requestedLine < this._firstLine) { + return 0; + } + if (requestedLine > this.lastLine()) { + return this._view.state.doc.length; + } + + const lineNumber = Number.isFinite(requestedLine) ? + Math.floor(requestedLine) - this._firstLine : + 0; + const line = this._view.state.doc.line(lineNumber + 1); + if (position.ch === null || position.ch === undefined) { + return line.to; + } + + const requestedCharacter = Number(position.ch); + if (requestedCharacter === Infinity || requestedCharacter > line.length) { + return line.to; + } + if (!Number.isFinite(requestedCharacter) || requestedCharacter < 0) { + return line.from; + } + return line.from + Math.floor(requestedCharacter); + }; + + CodeMirror6Adapter.prototype.posFromIndex = function (index) { + if (!this._view) { + return { + line: this._firstLine, + ch: 0 + }; + } + return _positionFromOffset( + this._view.state.doc, + index, + this._firstLine + ); + }; + + CodeMirror6Adapter.prototype.clipPos = function (position) { + return this.posFromIndex(this.indexFromPos(position)); + }; + + CodeMirror6Adapter.prototype.getCursor = function (which) { + if (!this._view) { + return { + line: this._firstLine, + ch: 0 + }; + } + const range = this._view.state.selection.main; + let offset = range.head; + if (which === "anchor") { + offset = range.anchor; + } else if (which === "from" || which === "start") { + offset = range.from; + } else if (which === "to" || which === "end") { + offset = range.to; + } + return this.posFromIndex(offset); + }; + + CodeMirror6Adapter.prototype.setCursor = function (line, ch, options) { + if (typeof line === "object" && typeof ch === "object" && options === undefined) { + options = ch; + ch = null; + } + const position = typeof line === "object" ? line : { + line: line, + ch: ch || 0 + }; + this.setSelection(position, position, options); + }; + + CodeMirror6Adapter.prototype.listSelections = function () { + return this._view.state.selection.ranges.map(function (range) { + const selection = _selectionFromOffsets( + range, + this._view.state.doc, + this._firstLine + ); + Object.defineProperties(selection, { + from: { + value: function () { + return CodeMirror.cmpPos(this.anchor, this.head) <= 0 ? + this.anchor : + this.head; + } + }, + to: { + value: function () { + return CodeMirror.cmpPos(this.anchor, this.head) <= 0 ? + this.head : + this.anchor; + } + }, + empty: { + value: function () { + return CodeMirror.cmpPos(this.anchor, this.head) === 0; + } + } + }); + return selection; + }, this); + }; + + CodeMirror6Adapter.prototype.getSelections = function (separator) { + return this._view.state.selection.ranges.map(range => { + const value = this._view.state.doc.sliceString(range.from, range.to); + if (separator === false) { + return _splitLines(value); + } + const lineSeparator = separator === undefined ? + this.lineSeparator() : + separator; + return lineSeparator === "\n" ? + value : + value.replace(/\n/g, lineSeparator); + }); + }; + + CodeMirror6Adapter.prototype.setSelections = function (ranges, primary, options) { + const selectionRanges = ranges.map(range => { + const anchor = range.anchor || range.start; + const head = range.head || range.end || anchor; + return CM6.EditorSelection.range( + this.indexFromPos(anchor), + this.indexFromPos(head), + range.goalColumn + ); + }); + const mainIndex = primary === undefined ? selectionRanges.length - 1 : primary; + this._view.dispatch({ + selection: CM6.EditorSelection.create(selectionRanges, mainIndex), + annotations: [ + this._originAnnotation.of(options && options.origin), + this._selectionBiasAnnotation.of(options && options.bias) + ] + }); + if (!options || options.scroll !== false) { + this.scrollIntoView(this.getCursor()); + } + }; + + CodeMirror6Adapter.prototype.setSelection = function (anchor, head, options) { + if (head && head.line === undefined && head.ch === undefined && options === undefined) { + options = head; + head = null; + } + this.setSelections([{ + anchor: anchor, + head: head || anchor + }], 0, options); + }; + + CodeMirror6Adapter.prototype.addSelection = function (anchor, head, options) { + const ranges = this.listSelections(); + ranges.push({ + anchor: this.clipPos(anchor), + head: this.clipPos(head || anchor) + }); + this.setSelections(ranges, ranges.length - 1, options); + }; + + CodeMirror6Adapter.prototype.extendSelection = function (head, other, options) { + const currentRange = this.listSelections()[ + this._view.state.selection.mainIndex + ]; + let nextHead = head; + let anchor; + const extend = Boolean(this.state.shift || this.extend); + + if (extend) { + anchor = currentRange.anchor; + if (other) { + const headBeforeAnchor = CodeMirror.cmpPos(nextHead, anchor) < 0; + const otherBeforeAnchor = CodeMirror.cmpPos(other, anchor) < 0; + if (headBeforeAnchor !== otherBeforeAnchor) { + anchor = nextHead; + nextHead = other; + } else if (headBeforeAnchor !== (CodeMirror.cmpPos(nextHead, other) < 0)) { + nextHead = other; + } + } + } else { + anchor = other || nextHead; + } + + this.setSelection(anchor, nextHead, options); + }; + + CodeMirror6Adapter.prototype.extendSelections = function (heads, options) { + const currentSelection = this._view.state.selection; + const extend = Boolean(this.state.shift || this.extend); + const ranges = this.listSelections().map(function (range, index) { + const head = heads[index]; + return { + anchor: extend ? range.anchor : head, + head: head + }; + }); + this.setSelections(ranges, currentSelection.mainIndex, options); + }; + + CodeMirror6Adapter.prototype.extendSelectionsBy = function (mapper, options) { + const currentSelection = this._view.state.selection; + const currentRanges = this.listSelections(); + const extend = Boolean(this.state.shift || this.extend); + const nextRanges = currentRanges.map(function (range) { + const head = mapper(range); + return { + anchor: extend ? range.anchor : head, + head: head + }; + }); + + this.setSelections(nextRanges, currentSelection.mainIndex, options); + }; + + CodeMirror6Adapter.prototype.getSelection = function (separator) { + const range = this._view.state.selection.main; + const value = this._view.state.doc.sliceString(range.from, range.to); + if (separator === false) { + return _splitLines(value); + } + const lineSeparator = separator === undefined ? + this.lineSeparator() : + separator; + return lineSeparator === "\n" ? + value : + value.replace(/\n/g, lineSeparator); + }; + + CodeMirror6Adapter.prototype.somethingSelected = function () { + return this._view.state.selection.ranges.some(function (range) { + return !range.empty; + }); + }; + + CodeMirror6Adapter.prototype.getLastEditEnd = function () { + return this.posFromIndex(this.$lastChangeEndOffset); + }; + + CodeMirror6Adapter.prototype.releaseLineHandles = function () { + // CM5 used this internal hook to release temporary line handles after + // :global commands. Phoenix line handles are lightweight live + // metadata and may also be retained by extensions, so no release is + // required. + }; + + CodeMirror6Adapter.prototype.overWriteSelection = function (text) { + const doc = this._view.state.doc; + const selection = this._view.state.selection; + const ranges = selection.ranges.map(function (range) { + if (!range.empty || range.to >= doc.length || + doc.sliceString(range.to, range.to + 1) === "\n") { + return range; + } + return CM6.EditorSelection.range(range.from, range.to + 1); + }); + this._view.dispatch({ + selection: CM6.EditorSelection.create( + ranges, + selection.mainIndex + ) + }); + this.replaceSelection(text, "end", "+input"); + }; + + CodeMirror6Adapter.prototype.isInMultiSelectMode = function () { + return this._view.state.selection.ranges.length > 1; + }; + + CodeMirror6Adapter.prototype.virtualSelectionMode = function () { + return Boolean(this.virtualSelection); + }; + + CodeMirror6Adapter.prototype.forEachSelection = function (command) { + const originalSelection = this._view.state.selection; + this.virtualSelection = CM6.EditorSelection.create( + originalSelection.ranges.slice(), + originalSelection.mainIndex + ); + try { + for (let index = 0; + index < this.virtualSelection.ranges.length; + index++) { + const range = this.virtualSelection.ranges[index]; + if (!range) { + continue; + } + this._view.dispatch({ + selection: CM6.EditorSelection.create([range]) + }); + command(); + const updatedRanges = this.virtualSelection.ranges.slice(); + updatedRanges[index] = this._view.state.selection.main; + this.virtualSelection = CM6.EditorSelection.create( + updatedRanges, + Math.min( + originalSelection.mainIndex, + updatedRanges.length - 1 + ) + ); + } + } finally { + const finalSelection = this.virtualSelection; + this.virtualSelection = null; + if (finalSelection) { + this._view.dispatch({ + selection: finalSelection + }); + } + } + }; + + CodeMirror6Adapter.prototype.hardWrap = function (options) { + const configuration = options || {}; + const maximum = Number(configuration.column) || + Number(this.getOption("textwidth")) || + 80; + const allowMerge = configuration.allowMerge !== false; + let row = Math.min(configuration.from, configuration.to); + let endRow = Math.max(configuration.from, configuration.to); + + function findSpace(line, max, minimum) { + if (line.length < max) { + return; + } + const before = line.slice(0, max); + const after = line.slice(max); + const spaceAfter = /^(?:(\s+)|(\S+)(\s+))/.exec(after); + const spaceBefore = /(?:(\s+)|(\s+)(\S+))$/.exec(before); + let start = 0; + let end = 0; + if (spaceBefore && !spaceBefore[2]) { + start = max - spaceBefore[1].length; + end = max; + } + if (spaceAfter && !spaceAfter[2]) { + if (!start) { + start = max; + } + end = max + spaceAfter[1].length; + } + if (start) { + return { + start: start, + end: end + }; + } + if (spaceBefore && spaceBefore[2] && + spaceBefore.index > minimum) { + return { + start: spaceBefore.index, + end: spaceBefore.index + spaceBefore[2].length + }; + } + if (spaceAfter && spaceAfter[2]) { + start = max + spaceAfter[2].length; + return { + start: start, + end: start + spaceAfter[3].length + }; + } + } + + while (row <= endRow) { + const line = this.getLine(row) || ""; + if (line.length > maximum) { + const space = findSpace(line, maximum, 5); + if (space) { + const indentationMatch = /^\s*/.exec(line); + const indentation = indentationMatch ? + indentationMatch[0] : + ""; + this.replaceRange( + "\n" + indentation, + CodeMirror.Pos(row, space.start), + CodeMirror.Pos(row, space.end) + ); + } + endRow++; + } else if (allowMerge && /\S/.test(line) && row !== endRow) { + const nextLine = this.getLine(row + 1); + if (nextLine && /\S/.test(nextLine)) { + const trimmedLine = line.replace(/\s+$/, ""); + const trimmedNextLine = nextLine.replace(/^\s+/, ""); + const mergedLine = trimmedLine + " " + trimmedNextLine; + const space = findSpace(mergedLine, maximum, 5); + if (space && space.start > trimmedLine.length || + mergedLine.length < maximum) { + this.replaceRange( + " ", + CodeMirror.Pos(row, trimmedLine.length), + CodeMirror.Pos( + row + 1, + nextLine.length - trimmedNextLine.length + ) + ); + row--; + endRow--; + } else if (trimmedLine.length < line.length) { + this.replaceRange( + "", + CodeMirror.Pos(row, trimmedLine.length), + CodeMirror.Pos(row, line.length) + ); + } + } + } + row++; + } + return row; + }; + + CodeMirror6Adapter.prototype.setExtending = function (value) { + this.extend = value; + }; + + CodeMirror6Adapter.prototype.getExtending = function () { + return this.extend; + }; + + CodeMirror6Adapter.prototype.startOperation = function () { + if (this._operationDepth === 0) { + this._activeOperationId = this._nextOperationId++; + this.curOp = { + $d: 0, + cursorActivity: false, + isVimOp: false + }; + } + this._operationDepth++; + this.curOp.$d = this._operationDepth; + }; + + CodeMirror6Adapter.prototype.endOperation = function () { + if (this._operationDepth === 0) { + return; + } + this._operationDepth--; + if (this.curOp) { + this.curOp.$d = this._operationDepth; + this.curOp.cursorActivity = + this.curOp.cursorActivity || this._pendingCursorActivity; + } + if (this._operationDepth === 0) { + this._activeOperationId = null; + if (this._legacyDecorationsDirty) { + this._refreshLegacyDecorations(); + } + this.onBeforeEndOperation(); + } + }; + + CodeMirror6Adapter.prototype.operation = function (operation) { + this.startOperation(); + try { + return operation(); + } finally { + this.endOperation(); + } + }; + + CodeMirror6Adapter.prototype.onChange = function (update, legacyChanges) { + if (!update || !update.changes) { + return; + } + const curOp = this.curOp; + let changeIndex = 0; + update.changes.iterChanges(function ( + _fromA, + _toA, + fromB, + toB, + inserted + ) { + this.$lastChangeEndOffset = toB; + if (curOp) { + if (curOp.$changeStart === null || + curOp.$changeStart === undefined || + curOp.$changeStart > fromB) { + curOp.$changeStart = fromB; + } + const suppliedChange = legacyChanges && legacyChanges[changeIndex]; + const operationChange = suppliedChange ? + _copyLegacyChange(suppliedChange) : + { + text: inserted && typeof inserted.toJSON === "function" ? + inserted.toJSON() : + _splitLines(String(inserted || "")) + }; + if (!curOp.lastChange) { + curOp.change = operationChange; + curOp.lastChange = operationChange; + } else { + curOp.lastChange.next = operationChange; + curOp.lastChange = operationChange; + } + } + changeIndex++; + }.bind(this)); + if (curOp && !curOp.changeHandlers) { + const handlers = this._listeners.get("change"); + curOp.changeHandlers = handlers ? handlers.slice() : []; + } + }; + + CodeMirror6Adapter.prototype.onSelectionChange = function () { + this._pendingCursorActivity = true; + if (this.curOp) { + this.curOp.cursorActivity = true; + if (!this.curOp.cursorActivityHandlers) { + const handlers = this._listeners.get("cursorActivity"); + this.curOp.cursorActivityHandlers = handlers ? + handlers.slice() : + []; + } + } + }; + + CodeMirror6Adapter.prototype.onBeforeEndOperation = function () { + if (!this._operationDepth) { + const scrollIntoView = Boolean( + this.curOp && + this.curOp.isVimOp && + this.curOp.cursorActivity + ); + try { + this._flushOperationEvents(); + } finally { + this.curOp = null; + if (scrollIntoView && this._view && !this._destroyed) { + this.scrollIntoView(); + } + } + } + }; + + CodeMirror6Adapter.prototype.undo = function () { + let entry; + const moved = []; + while (this._historyDone.length > 1) { + const candidate = this._historyDone.pop(); + moved.push(candidate); + if (candidate.type === "change") { + entry = candidate; + break; + } + } + if (!entry) { + return; + } + moved.forEach(item => { + this._historyUndone.push(item); + }); + const previousGeneration = this._currentGeneration; + const previousVisibility = new Map(this._markers.map(function (marker) { + return [marker, marker._hidden]; + })); + let applied = false; + this._historyApplying = true; + this._currentGeneration = entry.generationBefore; + this._resetHistoryMergeState(); + try { + this.operation(() => { + const steps = entry.steps && entry.steps.length ? + entry.steps.slice().reverse() : + [{ + undoChanges: [{ + from: 0, + to: this._view.state.doc.length, + insert: entry.beforeText + }] + }]; + steps.forEach((step, index) => { + const selection = index === steps.length - 1 ? + entry.beforeSelection : + undefined; + this._view.dispatch(this._historyTransaction( + this._historyChangeSpecs(step.undoChanges), + selection, + "undo" + )); + }); + if (entry.steps && entry.steps.length || + this.getValue() === entry.beforeText) { + applied = true; + if (!entry.docId || entry.docId === this.doc.id) { + this._restoreMarkerSnapshot( + entry.markerBefore, + previousVisibility + ); + } + this.scrollIntoView(this.getCursor()); + } + }); + } finally { + this._historyApplying = false; + } + if (!applied) { + this._historyUndone.splice( + this._historyUndone.length - moved.length, + moved.length + ); + moved.slice().reverse().forEach(item => { + this._historyDone.push(item); + }); + this._currentGeneration = previousGeneration; + return; + } + }; + + CodeMirror6Adapter.prototype.redo = function () { + let entry; + const moved = []; + while (this._historyUndone.length) { + const candidate = this._historyUndone.pop(); + moved.push(candidate); + if (candidate.type === "change") { + entry = candidate; + break; + } + } + if (!entry) { + moved.slice().reverse().forEach(item => { + this._historyUndone.push(item); + }); + return; + } + const previousGeneration = this._currentGeneration; + const previousVisibility = new Map(this._markers.map(function (marker) { + return [marker, marker._hidden]; + })); + let applied = false; + this._historyApplying = true; + this._currentGeneration = entry.generationAfter; + this._resetHistoryMergeState(); + try { + this.operation(() => { + const steps = entry.steps && entry.steps.length ? + entry.steps : + [{ + redoChanges: [{ + from: 0, + to: this._view.state.doc.length, + insert: entry.afterText + }] + }]; + steps.forEach((step, index) => { + const selection = index === steps.length - 1 ? + entry.afterSelection : + undefined; + this._view.dispatch(this._historyTransaction( + this._historyChangeSpecs(step.redoChanges), + selection, + "redo" + )); + }); + if (entry.steps && entry.steps.length || + this.getValue() === entry.afterText) { + applied = true; + moved.slice(0, -1).forEach(item => { + this._historyDone.push(item); + }); + this._historyDone.push(entry); + while (this._historyUndone.length && + this._historyUndone[this._historyUndone.length - 1].type === "selection") { + const selectionEntry = this._historyUndone.pop(); + this._historyDone.push(selectionEntry); + if (!_sameSelection( + this._view.state.selection, + selectionEntry.afterSelection + )) { + this._view.dispatch({ + selection: selectionEntry.afterSelection, + annotations: this._addToHistoryAnnotation.of(false) + }); + } + } + if (!entry.docId || entry.docId === this.doc.id) { + this._restoreMarkerSnapshot( + entry.markerAfter, + previousVisibility + ); + } + this.scrollIntoView(this.getCursor()); + } + }); + } finally { + this._historyApplying = false; + } + if (!applied) { + moved.slice().reverse().forEach(item => { + this._historyUndone.push(item); + }); + this._currentGeneration = previousGeneration; + return; + } + }; + + CodeMirror6Adapter.prototype.undoSelection = function () { + const currentSelection = this._view.state.selection; + const hasUndoEvent = this._historyDone.some(function (entry) { + const selection = _historySelection(entry); + return entry.type === "change" || + selection && !_sameSelection(selection, currentSelection); + }); + if (!hasUndoEvent) { + return; + } + + while (this._historyDone.length) { + const entry = this._historyDone[this._historyDone.length - 1]; + if (entry.type === "change") { + this.undo(); + return; + } + + const selection = _historySelection(entry); + if (!_sameSelection(selection, currentSelection)) { + _pushSelectionHistoryEntry(this._historyUndone, entry); + this._historyApplying = true; + this._resetHistoryMergeState(); + try { + this._view.dispatch({ + selection: selection, + annotations: this._addToHistoryAnnotation.of(false) + }); + } finally { + this._historyApplying = false; + } + return; + } + + if (this._historyDone.length === 1) { + return; + } + this._historyDone.pop(); + _pushSelectionHistoryEntry(this._historyUndone, entry); + } + }; + + CodeMirror6Adapter.prototype.redoSelection = function () { + const currentSelection = this._view.state.selection; + const hasRedoEvent = this._historyUndone.some(function (entry) { + const selection = _historySelection(entry); + return entry.type === "change" || + selection && !_sameSelection(selection, currentSelection); + }); + if (!hasRedoEvent) { + return; + } + + while (this._historyUndone.length) { + const entry = this._historyUndone[this._historyUndone.length - 1]; + if (entry.type === "change") { + this.redo(); + return; + } + + const selection = _historySelection(entry); + if (!_sameSelection(selection, currentSelection)) { + _pushSelectionHistoryEntry(this._historyDone, entry); + this._historyApplying = true; + this._resetHistoryMergeState(); + try { + this._view.dispatch({ + selection: selection, + annotations: this._addToHistoryAnnotation.of(false) + }); + } finally { + this._historyApplying = false; + } + return; + } + + this._historyUndone.pop(); + _pushSelectionHistoryEntry(this._historyDone, entry); + } + }; + + CodeMirror6Adapter.prototype.getHistory = function () { + return { + done: _copyHistoryArray(this._historyDone, false), + undone: _copyHistoryArray(this._historyUndone, false) + }; + }; + + CodeMirror6Adapter.prototype.setHistory = function (history) { + this._historyDone = _copyHistoryArray( + history && history.done, + true + ) + .map(_prepareHistoryEntry); + this._historyUndone = _copyHistoryArray( + history && history.undone, + true + ) + .map(_prepareHistoryEntry); + const allChanges = this._historyDone.concat(this._historyUndone).filter(function (entry) { + return entry.type === "change"; + }); + const lastChange = this._historyDone.slice().reverse().find(function (entry) { + return entry.type === "change"; + }); + if (lastChange) { + this._currentGeneration = lastChange.generationAfter; + } + const maxGeneration = allChanges.reduce(function (maximum, entry) { + return Math.max( + maximum, + entry.generationBefore || 0, + entry.generationAfter || 0 + ); + }, this._currentGeneration); + this._nextGeneration = Math.max(this._nextGeneration, maxGeneration + 1); + this._resetHistoryMergeState(); + }; + + CodeMirror6Adapter.prototype.clearHistory = function () { + const selection = this._view ? this._view.state.selection : CM6.EditorSelection.single(0); + this._historyDone = [{ + type: "selection", + beforeSelection: selection, + afterSelection: selection, + generationBefore: this._currentGeneration, + generationAfter: this._currentGeneration + }]; + this._historyUndone = []; + this._resetHistoryMergeState(); + }; + + CodeMirror6Adapter.prototype.historySize = function () { + return { + undo: this._historyDone.filter(function (entry) { + return entry.type === "change"; + }).length, + redo: this._historyUndone.filter(function (entry) { + return entry.type === "change"; + }).length + }; + }; + + CodeMirror6Adapter.prototype.markClean = function () { + this._cleanGeneration = this.changeGeneration(true); + return this._cleanGeneration; + }; + + CodeMirror6Adapter.prototype.isClean = function (generation) { + return this._currentGeneration === ( + generation === undefined ? this._cleanGeneration : generation + ); + }; + + CodeMirror6Adapter.prototype.changeGeneration = function (closeEvent) { + if (closeEvent) { + this._resetHistoryMergeState(); + } + return this._currentGeneration; + }; + + CodeMirror6Adapter.prototype.getOption = function (name) { + return (this.options || this._options)[name]; + }; + + CodeMirror6Adapter.prototype.phrase = function (phraseText) { + const phrases = this.getOption("phrases"); + return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? + phrases[phraseText] : + phraseText; + }; + + CodeMirror6Adapter.prototype.setDirection = function (direction) { + this.setOption("direction", direction === "rtl" ? "rtl" : "ltr"); + }; + + CodeMirror6Adapter.prototype.setOption = function (name, value) { + const oldValue = this.getOption(name); + // Match CM5's option contract, including its intentional loose + // comparison and the special case that always reapplies "mode". + if (name !== "mode" && oldValue == value) { // eslint-disable-line eqeqeq + return; + } + if (name === "inputStyle") { + throw new Error( + "inputStyle can not be changed in a running editor" + ); + } + this._options[name] = value; + if (this.options && this.options !== this._options) { + this.options[name] = value; + } + if (CodeMirror.runOptionHandler) { + CodeMirror.runOptionHandler(this, name, value, oldValue); + } + this._syncDocumentMetadata(); + + let compartment; + let extension; + let updateDispatched = false; + const emitUpdate = LEGACY_UPDATE_OPTIONS.has(name); + switch (name) { + case "readOnly": + this._reconfigureMany([{ + compartment: this._readOnlyCompartment, + extension: CM6.EditorState.readOnly.of(Boolean(value)) + }, { + compartment: this._editableCompartment, + extension: CM6.EditorView.editable.of(value !== "nocursor") + }], emitUpdate); + updateDispatched = true; + if (value === "nocursor") { + this._contentElement.blur(); + this._setFocusState(false); + } + break; + case "spellcheck": + case "autocorrect": + case "autocapitalize": + compartment = this._contentAttributesCompartment; + extension = this._contentAttributesExtension(); + break; + case "placeholder": + compartment = this._placeholderCompartment; + extension = this._placeholderExtension(); + break; + case "lineNumbers": + this._refreshGutters(); + break; + case "lineWrapping": + compartment = this._lineWrappingCompartment; + extension = value ? CM6.EditorView.lineWrapping : []; + this._invalidateRenderedLines(); + break; + case "styleActiveLine": + compartment = this._activeLineCompartment; + extension = _activeLineExtension(value); + break; + case "autoCloseBrackets": + compartment = this._closeBracketsCompartment; + extension = _closeBracketsExtension(this, value); + break; + case "matchBrackets": + this.state.matchBrackets = value ? + typeof value === "object" ? value : {} : + null; + compartment = this._bracketMatchingCompartment; + extension = _bracketMatchingExtension(this, value); + break; + case "highlightSelectionMatches": + compartment = this._selectionMatchesCompartment; + extension = _selectionMatchExtension(this, value); + break; + case "cursorBlinkRate": + case "showCursorWhenSelecting": + compartment = this._drawSelectionCompartment; + extension = _drawSelectionExtension(this._options); + break; + case "tabSize": + this._invalidateLegacyModeStateCache(true); + this._reconfigureMany([{ + compartment: this._tabSizeCompartment, + extension: CM6.EditorState.tabSize.of(value || 4) + }, { + compartment: this._indentUnitCompartment, + extension: CM6.indentUnit.of(_indentUnitText(this._options)) + }], emitUpdate); + this._invalidateRenderedLines(); + updateDispatched = true; + break; + case "indentUnit": + this._invalidateLegacyModeStateCache(true); + compartment = this._indentUnitCompartment; + extension = CM6.indentUnit.of(_indentUnitText(this._options)); + break; + case "indentWithTabs": + compartment = this._indentUnitCompartment; + extension = CM6.indentUnit.of(_indentUnitText(this._options)); + break; + case "mode": + this._invalidateLegacyModeStateCache(true); + compartment = this._languageCompartment; + extension = _languageExtensionForMode(value, this._options); + this._refreshLegacyHighlighting(); + break; + case "autoCloseTags": + compartment = this._languageCompartment; + extension = _languageExtensionForMode(this.getOption("mode"), this._options); + break; + case "scrollPastEnd": + compartment = this._scrollPastEndCompartment; + extension = value ? CM6.scrollPastEnd() : []; + break; + case "scrollbarStyle": + this._applyScrollbarStyle(value); + break; + case "smartIndent": + compartment = this._smartIndentCompartment; + extension = value ? CM6.indentOnInput() : []; + break; + case "dragDrop": + compartment = this._dragDropCompartment; + extension = this._dragDropExtension(value); + break; + case "gutters": + this._refreshGutters(); + this._refreshScrollbarModel(); + break; + case "lineWiseCopyCut": + case "disableInput": + case "autofocus": + case "undoDepth": + break; + case "addModeClass": + this._refreshLegacyHighlighting(); + break; + case "rulers": + this._scheduleRulerRefresh(); + break; + case "firstLineNumber": + case "lineNumberFormatter": + this._refreshGutters(); + this._refreshScrollbarModel(); + break; + case "tabindex": + case "screenReaderLabel": + case "direction": + this._decorateDOM(); + break; + case "theme": + this._applyThemeClass(value); + break; + default: + break; + } + if (compartment) { + this._reconfigure(compartment, extension, emitUpdate); + updateDispatched = true; + } + if (emitUpdate && !updateDispatched) { + this._dispatchLegacyUpdate(); + } + this._decorateDOM(); + this._emit("optionChange", this._instance(), name); + }; + + CodeMirror6Adapter.prototype._dispatchLegacyUpdate = function () { + if (!this._view || this._destroyed) { + return; + } + this._view.dispatch({ + annotations: this._legacyUpdateAnnotation.of(true) + }); + }; + + CodeMirror6Adapter.prototype._reconfigure = function ( + compartment, + extension, + emitUpdate + ) { + if (!this._view || this._destroyed) { + return; + } + const transactionSpec = { + effects: compartment.reconfigure(extension) + }; + if (emitUpdate) { + transactionSpec.annotations = this._legacyUpdateAnnotation.of(true); + } + this._view.dispatch(transactionSpec); + this._decorateDOM(); + }; + + CodeMirror6Adapter.prototype._reconfigureMany = function ( + configurations, + emitUpdate + ) { + if (!this._view || this._destroyed) { + return; + } + const transactionSpec = { + effects: configurations.map(function (configuration) { + return configuration.compartment.reconfigure(configuration.extension); + }) + }; + if (emitUpdate) { + transactionSpec.annotations = this._legacyUpdateAnnotation.of(true); + } + this._view.dispatch(transactionSpec); + this._decorateDOM(); + }; + + CodeMirror6Adapter.prototype._applyThemeClass = function (theme) { + if (!this._view) { + return; + } + Array.from(this._view.dom.classList).forEach(className => { + if (className.indexOf("cm-s-") === 0) { + this._view.dom.classList.remove(className); + } + }); + String(theme || "default").split(/\s+/).filter(Boolean).forEach(themeName => { + this._view.dom.classList.add(`cm-s-${themeName}`); + }); + this._invalidateRenderedLines(); + this._scheduleRulerRefresh(); + }; + + CodeMirror6Adapter.prototype.getDoc = function () { + return this.doc; + }; + + CodeMirror6Adapter.prototype.getEditor = function () { + return this._detachedDoc ? null : this; + }; + + CodeMirror6Adapter.prototype._copyDocument = function (copyHistory) { + const copy = new CodeMirror.Doc( + this.getValue(), + this.getOption("mode"), + this.firstLine(), + this.getOption("lineSeparator"), + this.getOption("direction") + ); + copy.setSelections( + this.listSelections().map(function (selection) { + return { + anchor: _copyPosition(selection.anchor), + head: _copyPosition(selection.head) + }; + }), + this._view.state.selection.mainIndex, + {scroll: false} + ); + copy.setExtending(false); + copy._scrollLeft = this.doc && this.doc._scrollLeft || 0; + copy._scrollTop = this.doc && this.doc._scrollTop || 0; + if (copyHistory) { + copy.setHistory(this.getHistory()); + copy._adapter._currentGeneration = this._currentGeneration; + copy._adapter._nextGeneration = this._nextGeneration; + copy._adapter._cleanGeneration = this._cleanGeneration; + } else { + copy.clearHistory(); + } + return copy; + }; + + CodeMirror6Adapter.prototype.swapDoc = function (newDoc) { + if (!(newDoc instanceof CodeMirror.Doc)) { + throw new TypeError("swapDoc expects a CodeMirror.Doc."); + } + if (newDoc.getEditor()) { + throw new Error("This document is already in use."); + } + if (!newDoc._adapter || !newDoc._adapter._detachedDoc) { + throw new Error("The document has no detached CM6 state."); + } + + const oldDoc = this.doc; + const detachedAdapter = newDoc._adapter; + const oldState = this._takeDocumentState(); + const newState = detachedAdapter._takeDocumentState(); + + this.doc = newDoc; + newDoc._adapter = this; + newDoc.cm = this; + this._detachedDoc = false; + + detachedAdapter.doc = oldDoc; + oldDoc._adapter = detachedAdapter; + oldDoc.cm = null; + detachedAdapter._detachedDoc = true; + + this._restoreDocumentState(newState); + detachedAdapter._restoreDocumentState(oldState); + this._syncDocumentMetadata(); + detachedAdapter._syncDocumentMetadata(); + if (CodeMirror.registerInstance) { + CodeMirror.registerInstance(this, newDoc); + } + this._emit("swapDoc", this, oldDoc); + return oldDoc; + }; + + CodeMirror6Adapter.prototype.getMode = function () { + if (!this._legacyMode) { + this._legacyMode = CodeMirror.getMode( + this._options, + this.getOption("mode") + ); + } + return this._legacyMode; + }; + + CodeMirror6Adapter.prototype.getTokenAt = function (position, precise) { + if (!this._view) { + const mode = this.getMode(); + return { + start: 0, + end: 0, + string: "", + type: null, + state: CodeMirror.startState(mode) + }; + } + const clippedPosition = this.posFromIndex(this.indexFromPos(position)); + const mode = this.getMode(); + const state = _modeStateBefore(this, mode, clippedPosition.line, precise); + const line = this.getLine(clippedPosition.line) || ""; + const stream = new CodeMirror.StringStream( + line, + this.getOption("tabSize") || 4, + _legacyLineOracle(this, clippedPosition.line) + ); + let type; + + while (stream.pos < clippedPosition.ch && !stream.eol()) { + stream.start = stream.pos; + type = _readModeToken(mode, stream, state); + } + return { + start: stream.start, + end: stream.pos, + string: stream.current(), + type: type || null, + state: state + }; + }; + + CodeMirror6Adapter.prototype.getTokenTypeAt = function (position) { + const clippedPosition = this.posFromIndex(this.indexFromPos(position)); + const line = this.getLine(clippedPosition.line) || ""; + const tokenPosition = clippedPosition.ch === 0 && line.length ? + { + line: clippedPosition.line, + ch: 1 + } : + clippedPosition; + const type = this.state.overlays.length ? + _styleAtPosition( + _lineStylesWithOverlays(this, clippedPosition.line), + clippedPosition.ch + ) : + this.getTokenAt(tokenPosition).type; + const overlayIndex = type ? type.indexOf("overlay ") : -1; + if (overlayIndex < 0) { + return type; + } + return overlayIndex === 0 ? null : type.slice(0, overlayIndex - 1); + }; + + CodeMirror6Adapter.prototype.getLineTokens = function (lineNumber, precise) { + if (!this._view) { + return []; + } + const clippedLineNumber = _clamp( + Number(lineNumber) || 0, + 0, + this.lastLine() + ); + const mode = this.getMode(); + const state = _modeStateBefore(this, mode, clippedLineNumber, precise); + const text = this.getLine(clippedLineNumber) || ""; + const stream = new CodeMirror.StringStream( + text, + this.getOption("tabSize") || 4, + _legacyLineOracle(this, clippedLineNumber) + ); + const tokens = []; + + while (!stream.eol()) { + stream.start = stream.pos; + const type = _readModeToken(mode, stream, state); + tokens.push({ + start: stream.start, + end: stream.pos, + string: stream.current(), + type: type || null, + state: CodeMirror.copyState(mode, state) + }); + } + return tokens; + }; + + CodeMirror6Adapter.prototype.getModeAt = function (position) { + const token = this.getTokenAt(position, true); + return CodeMirror.innerMode(this.getMode(), token.state).mode; + }; + + CodeMirror6Adapter.prototype.getLineHandle = function (lineNumber) { + if (lineNumber < this.firstLine() || lineNumber > this.lastLine()) { + return null; + } + const line = this._view.state.doc.line( + lineNumber - this._firstLine + 1 + ); + let handle = Array.from(this._lineHandles).find(candidate => { + return !candidate._deleted && candidate._position === line.from; + }); + if (!handle) { + handle = { + _adapter: this, + _position: line.from, + _deleted: false, + parent: this.doc, + widgets: [], + lineNo: function () { + return handle._adapter.getLineNumber(handle); + } + }; + Object.defineProperty(handle, "text", { + configurable: true, + enumerable: true, + get: function () { + const currentLine = handle._adapter.getLineNumber(handle); + return currentLine === null ? null : handle._adapter.getLine(currentLine); + } + }); + Object.defineProperty(handle, "height", { + configurable: true, + enumerable: true, + get: function () { + const currentLine = handle._adapter.getLineNumber(handle); + if (currentLine === null) { + return 0; + } + const top = handle._adapter.heightAtLine(currentLine, "local"); + const bottom = handle._adapter.heightAtLine(currentLine + 1, "local"); + return Math.max(handle._adapter.defaultTextHeight(), bottom - top); + } + }); + this._lineHandles.add(handle); + } + return handle; + }; + + CodeMirror6Adapter.prototype.getLineHandleVisualStart = function (line) { + let lineHandle = typeof line === "number" ? this.getLineHandle(line) : line; + let lineNumber = this.getLineNumber(lineHandle); + if (lineNumber === null || lineNumber === undefined) { + return lineHandle; + } + + for (;;) { + const lineStart = this._view.state.doc.line( + lineNumber - this._firstLine + 1 + ).from; + let precedingMarker = null; + this._markers.forEach(function (marker) { + if (marker._cleared || marker._hidden || !marker.collapsed || + marker.type !== "range" || marker._from >= lineStart || + marker._to < lineStart) { + return; + } + if (!precedingMarker || marker._from < precedingMarker._from) { + precedingMarker = marker; + } + }); + if (!precedingMarker) { + return lineHandle; + } + + const precedingLine = this._view.state.doc.lineAt( + precedingMarker._from + ).number - 1 + this._firstLine; + if (precedingLine >= lineNumber) { + return lineHandle; + } + lineNumber = precedingLine; + lineHandle = this.getLineHandle(lineNumber); + } + }; + + CodeMirror6Adapter.prototype.getLineNumber = function (lineHandle) { + if (!lineHandle || lineHandle._adapter !== this || lineHandle._deleted || !this._view) { + return null; + } + return this._view.state.doc.lineAt( + _clamp(lineHandle._position, 0, this._view.state.doc.length) + ).number - 1 + this._firstLine; + }; + + CodeMirror6Adapter.prototype.eachLine = function (from, to, callback) { + if (typeof from === "function") { + callback = from; + from = this.firstLine(); + to = this.lastLine() + 1; + } else if (typeof to === "function") { + callback = to; + to = this.lastLine() + 1; + } + from = Math.max( + this.firstLine(), + from === undefined ? this.firstLine() : from + ); + to = Math.min( + this.lastLine() + 1, + to === undefined ? this.lastLine() + 1 : to + ); + for (let line = from; line < to; line++) { + if (callback(this.getLineHandle(line))) { + break; + } + } + }; + + CodeMirror6Adapter.prototype.findWordAt = function (position) { + const line = this.getLine(position.line) || ""; + let start = _clamp(position.ch, 0, line.length); + let end = start; + while (start > 0 && CodeMirror.isWordChar(line.charAt(start - 1))) { + start--; + } + while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) { + end++; + } + return { + anchor: { + line: position.line, + ch: start, + sticky: null + }, + head: { + line: position.line, + ch: end, + sticky: null + } + }; + }; + + CodeMirror6Adapter.prototype.execCommand = function (commandName) { + const nativeCommands = { + selectAll: CM6.selectAll, + insertTab: CM6.insertTab, + defaultTab: CM6.indentWithTab, + newlineAndIndent: CM6.splitLine, + splitSelectionByLine: function () { + return this.splitSelectionByLine(); + }.bind(this), + undo: function () { + return this.undo(); + }.bind(this), + redo: function () { + return this.redo(); + }.bind(this) + }; + const nativeCommand = nativeCommands[commandName]; + if (nativeCommand) { + return nativeCommand(this._view); + } + const command = CodeMirror.commands[commandName]; + if (command) { + return command(this._instance()); + } + }; + + CodeMirror6Adapter.prototype._moveHorizontalRange = function (range, direction, unit) { + if (!direction) { + return range.head; + } + + const forward = direction > 0; + const doc = this._view.state.doc; + let target = range.head; + let currentRange = range; + + for (let step = 0; step < Math.abs(direction); step++) { + let nextTarget; + if (unit === "word" || unit === "group") { + nextTarget = this._view.moveByGroup(currentRange, forward).head; + } else if (unit !== "codepoint" && unit !== "column") { + nextTarget = this._view.moveByChar(currentRange, forward).head; + } else { + const line = doc.lineAt(target); + if (forward) { + if (target < line.to) { + const first = doc.sliceString(target, target + 1).charCodeAt(0); + const length = first >= 0xD800 && first <= 0xDBFF && + target + 1 < line.to ? + 2 : + 1; + nextTarget = target + length; + } else { + nextTarget = unit === "column" || line.number >= doc.lines ? + target : + line.to + 1; + } + } else if (target > line.from) { + const last = doc.sliceString(target - 1, target).charCodeAt(0); + const length = last >= 0xDC00 && last <= 0xDFFF && + target - 1 > line.from ? + 2 : + 1; + nextTarget = target - length; + } else { + nextTarget = unit === "column" || line.number <= 1 ? + target : + line.from - 1; + } + } + + if (nextTarget === target) { + break; + } + target = nextTarget; + currentRange = CM6.EditorSelection.cursor(target); + } + + return target; + }; + + CodeMirror6Adapter.prototype.findPosH = function (from, amount, unit) { + const direction = amount < 0 ? -1 : 1; + let target = this.indexFromPos(this.clipPos(from)); + let hitSide = false; + + for (let index = 0; index < Math.abs(amount); index++) { + const nextTarget = this._moveHorizontalRange( + CM6.EditorSelection.cursor(target), + direction, + unit + ); + if (nextTarget === target) { + hitSide = true; + break; + } + target = nextTarget; + } + + const position = this.posFromIndex(target); + if (hitSide) { + position.hitSide = true; + } + return position; + }; + + CodeMirror6Adapter.prototype.findPosV = function (from, amount, unit, goalColumn) { + const forward = amount >= 0; + const pageHeight = Math.min( + this._view.dom.clientHeight || this._view.scrollDOM.clientHeight, + window.innerHeight || this._view.dom.ownerDocument.documentElement.clientHeight + ); + const distance = unit === "page" ? + Math.max(pageHeight - 0.5 * this.defaultTextHeight(), 3) : + undefined; + let range = CM6.EditorSelection.cursor( + this.indexFromPos(this.clipPos(from)), + 0, + undefined, + goalColumn + ); + let hitSide = false; + + for (let index = 0; index < Math.abs(amount); index++) { + const nextRange = this._view.moveVertically(range, forward, distance); + if (nextRange.head === range.head) { + hitSide = true; + break; + } + range = nextRange; + } + + const position = this.posFromIndex(range.head); + if (range.goalColumn !== null && range.goalColumn !== undefined) { + position.goalColumn = range.goalColumn; + } + if (hitSide) { + position.hitSide = true; + } + return position; + }; + + CodeMirror6Adapter.prototype.moveH = function (direction, unit) { + const currentSelection = this._view.state.selection; + const extend = Boolean(this.state.shift || this.extend); + const ranges = currentSelection.ranges.map(range => { + let target; + if (!extend && !range.empty) { + target = direction < 0 ? range.from : range.to; + } else { + target = this._moveHorizontalRange(range, direction, unit); + } + return extend ? + CM6.EditorSelection.range(range.anchor, target) : + CM6.EditorSelection.cursor(target); + }); + + this._view.dispatch({ + selection: CM6.EditorSelection.create(ranges, currentSelection.mainIndex), + annotations: this._originAnnotation.of("+move"), + scrollIntoView: true + }); + }; + + CodeMirror6Adapter.prototype.deleteH = function (direction, unit) { + const selection = this._view.state.selection; + const hasSelection = selection.ranges.some(function (range) { + return !range.empty; + }); + const changes = []; + + selection.ranges.forEach(range => { + if (hasSelection) { + if (range.empty) { + return; + } + changes.push({ + from: range.from, + to: range.to, + insert: "" + }); + return; + } + const target = this._moveHorizontalRange(range, direction, unit); + if (target !== range.head) { + changes.push({ + from: Math.min(range.head, target), + to: Math.max(range.head, target), + insert: "" + }); + } + }); + + if (!changes.length) { + return false; + } + this._view.dispatch({ + changes: changes, + annotations: this._originAnnotation.of("+delete") + }); + return true; + }; + + CodeMirror6Adapter.prototype.indentSelection = function (direction) { + const ranges = this.listSelections(); + let end = -1; + + this.operation(() => { + ranges.forEach((range, index) => { + if (!range.empty()) { + const from = range.from(); + const to = range.to(); + const start = Math.max(end, from.line); + end = Math.min( + this.lastLine(), + to.line - (to.ch ? 0 : 1) + ) + 1; + for (let line = start; line < end; line++) { + this.indentLine(line, direction); + } + + const newRanges = this.listSelections(); + if (from.ch === 0 && ranges.length === newRanges.length && + newRanges[index].from().ch > 0) { + const selectionRanges = newRanges.map(function (selection, selectionIndex) { + if (selectionIndex === index) { + return { + anchor: from, + head: selection.to() + }; + } + return { + anchor: selection.anchor, + head: selection.head + }; + }); + this.setSelections( + selectionRanges, + this._view.state.selection.mainIndex, + {scroll: false} + ); + } + } else if (range.head.line > end) { + this.indentLine(range.head.line, direction, true); + end = range.head.line; + } + }); + }); + }; + + CodeMirror6Adapter.prototype.splitSelectionByLine = function () { + const lineRanges = []; + + this.listSelections().forEach(range => { + const from = range.from(); + const to = range.to(); + for (let line = from.line; line <= to.line; line++) { + if (to.line > from.line && line === to.line && to.ch === 0) { + continue; + } + lineRanges.push({ + anchor: line === from.line ? from : { + line: line, + ch: 0 + }, + head: line === to.line ? to : { + line: line, + ch: (this.getLine(line) || "").length + } + }); + } + }); + + if (lineRanges.length) { + this.setSelections(lineRanges, 0); + } + }; + + CodeMirror6Adapter.prototype.toggleOverwrite = function (state) { + const nextState = state === undefined ? !this.state.overwrite : Boolean(state); + if (nextState !== this.state.overwrite) { + this.state.overwrite = nextState; + this._decorateDOM(); + this._emit("overwriteToggle", this, nextState); + } + return nextState; + }; + + CodeMirror6Adapter.prototype.addKeyMap = function (keyMap, bottom) { + if (bottom) { + this.state.keyMaps.push(keyMap); + } else { + this.state.keyMaps.unshift(keyMap); + } + }; + + CodeMirror6Adapter.prototype.removeKeyMap = function (keyMap) { + for (let index = 0; index < this.state.keyMaps.length; index++) { + const candidate = this.state.keyMaps[index]; + if (candidate === keyMap || candidate && candidate.name === keyMap) { + this.state.keyMaps.splice(index, 1); + return true; + } + } + return false; + }; + + CodeMirror6Adapter.prototype.indentLine = function (lineNumber, direction, aggressive) { + const line = this.getLine(lineNumber); + if (line === undefined) { + return; + } + + let indentationDirection = direction; + if (typeof indentationDirection !== "string" && + typeof indentationDirection !== "number") { + if (indentationDirection === null || indentationDirection === undefined) { + indentationDirection = this.getOption("smartIndent") ? + "smart" : + "prev"; + } else { + indentationDirection = indentationDirection ? "add" : "subtract"; + } + } + + const mode = this.getMode(); + let state; + if (indentationDirection === "smart") { + if (!mode.indent) { + indentationDirection = "prev"; + } else { + state = this.getStateBefore(lineNumber); + } + } + + const tabSize = this.getOption("tabSize") || 4; + const currentIndentation = CodeMirror.countColumn(line, null, tabSize); + const currentIndentationString = line.match(/^\s*/)[0]; + let indentation; + + if (!aggressive && !/\S/.test(line)) { + indentation = 0; + indentationDirection = "not"; + } else if (indentationDirection === "smart") { + indentation = mode.indent( + state, + line.slice(currentIndentationString.length), + line + ); + if (indentation === CodeMirror.Pass || indentation > 150) { + if (!aggressive) { + return; + } + indentationDirection = "prev"; + } + } + + if (indentationDirection === "prev") { + indentation = lineNumber > this.firstLine() ? + CodeMirror.countColumn(this.getLine(lineNumber - 1), null, tabSize) : + 0; + } else if (indentationDirection === "add") { + indentation = currentIndentation + (this.getOption("indentUnit") || 4); + } else if (indentationDirection === "subtract") { + indentation = currentIndentation - (this.getOption("indentUnit") || 4); + } else if (typeof indentationDirection === "number") { + indentation = currentIndentation + indentationDirection; + } + indentation = Math.max(0, indentation); + + let indentationString = ""; + let indentationColumn = 0; + if (this.getOption("indentWithTabs")) { + const tabCount = Math.floor(indentation / tabSize); + indentationString = "\t".repeat(tabCount); + indentationColumn = tabCount * tabSize; + } + if (indentationColumn < indentation) { + indentationString += " ".repeat(indentation - indentationColumn); + } + + if (indentationString !== currentIndentationString) { + this.replaceRange( + indentationString, + { line: lineNumber, ch: 0 }, + { line: lineNumber, ch: currentIndentationString.length }, + "+input" + ); + return true; + } + + const currentSelection = this.listSelections(); + const rangeIndex = currentSelection.findIndex(function (range) { + return range.head.line === lineNumber && + range.head.ch < currentIndentationString.length; + }); + if (rangeIndex !== -1) { + const ranges = currentSelection.map(function (range, index) { + if (index === rangeIndex) { + const position = { + line: lineNumber, + ch: currentIndentationString.length + }; + return { + anchor: position, + head: position + }; + } + return { + anchor: range.anchor, + head: range.head + }; + }); + this.setSelections( + ranges, + this._view.state.selection.mainIndex, + {scroll: false} + ); + } + }; + + CodeMirror6Adapter.prototype.triggerElectric = function (inserted) { + if (!this.getOption("electricChars") || !this.getOption("smartIndent")) { + return; + } + + this.operation(() => { + const ranges = this.listSelections(); + for (let index = ranges.length - 1; index >= 0; index--) { + const head = ranges[index].head; + if (head.ch > 100 || + index > 0 && ranges[index - 1].head.line === head.line) { + continue; + } + + const mode = this.getModeAt(head); + let shouldIndent = false; + if (mode.electricChars) { + for (let charIndex = 0; charIndex < mode.electricChars.length; charIndex++) { + if (String(inserted).indexOf(mode.electricChars.charAt(charIndex)) !== -1) { + shouldIndent = true; + break; + } + } + } else if (mode.electricInput) { + const line = this.getLine(head.line) || ""; + shouldIndent = mode.electricInput.test(line.slice(0, head.ch)); + } + + if (shouldIndent && this.indentLine(head.line, "smart")) { + this._emit("electricInput", this._instance(), head.line); + } + } + }); + }; + + CodeMirror6Adapter.prototype.toggleComment = function () { + return CM6.toggleComment(this._view); + }; + + CodeMirror6Adapter.prototype.moveV = function (amount, unit) { + const currentSelection = this._view.state.selection; + const currentRanges = this.listSelections(); + const extend = Boolean(this.state.shift || this.extend); + const collapse = !extend && currentRanges.some(function (range) { + return !range.empty(); + }); + const ranges = currentRanges.map(range => { + let target; + if (collapse) { + target = amount < 0 ? range.from() : range.to(); + } else { + target = this.findPosV( + range.head, + amount, + unit, + range.goalColumn + ); + } + return { + anchor: extend ? range.anchor : target, + head: target, + goalColumn: target.goalColumn + }; + }); + this.setSelections(ranges, currentSelection.mainIndex, { + origin: "+move" + }); + }; + + CodeMirror6Adapter.prototype.getHelpers = function (position, type) { + if (CodeMirror.getHelpers) { + return CodeMirror.getHelpers(this, position, type); + } + const registry = CodeMirror.helpers && CodeMirror.helpers[type]; + if (!registry) { + return []; + } + const mode = this.getModeAt(position); + const result = []; + [mode.helperType, mode.name].filter(Boolean).forEach(function (name) { + if (registry[name] && result.indexOf(registry[name]) === -1) { + result.push(registry[name]); + } + }); + (registry._global || []).forEach(function (helper) { + if (helper.pred(mode, this) && result.indexOf(helper.val) === -1) { + result.push(helper.val); + } + }, this); + return result; + }; + + CodeMirror6Adapter.prototype.getHelper = function (position, type) { + return this.getHelpers(position, type)[0]; + }; + + CodeMirror6Adapter.prototype.getSearchCursor = function (query, start, options) { + const adapter = this; + const caseFold = typeof options === "boolean" ? options : + Boolean(options && options.caseFold); + const expression = query instanceof RegExp ? query : null; + const allowMultiline = !options || typeof options !== "object" || + options.multiline !== false; + const sourceQuery = expression ? null : _splitLines(String(query)).join("\n"); + const normalize = typeof String.prototype.normalize === "function"; + const foldText = caseFold ? + function (text) { + const normalized = normalize ? text.normalize("NFD") : text; + return normalized.toLowerCase(); + } : + function (text) { + return normalize ? text.normalize("NFD") : text; + }; + const queryLines = expression ? null : foldText(sourceQuery).split("\n"); + let cachedSourceDoc = null; + let cachedSource = ""; + + function documentSource() { + const doc = adapter._view.state.doc; + if (doc !== cachedSourceDoc) { + cachedSourceDoc = doc; + cachedSource = doc.toString(); + } + return cachedSource; + } + + function clipPosition(position) { + const doc = adapter._view.state.doc; + const requestedLine = Number(position && position.line); + const lineNumber = Number.isFinite(requestedLine) ? + Math.floor(requestedLine) : + adapter.firstLine(); + if (lineNumber < adapter.firstLine()) { + return { + line: adapter.firstLine(), + ch: 0 + }; + } + if (lineNumber > adapter.lastLine()) { + return adapter.posFromIndex(doc.length); + } + + const line = doc.line( + lineNumber - adapter._firstLine + 1 + ); + let character = position && position.ch; + if (character === null || character === undefined) { + character = line.length; + } else { + character = Number(character); + if (!Number.isFinite(character)) { + character = 0; + } + } + return { + line: lineNumber, + ch: _clamp(Math.floor(character), 0, line.length) + }; + } + + function positionIndex(position) { + return adapter.indexFromPos(position); + } + + function adjustFoldedOffset(original, folded, offset) { + if (original.length === folded.length) { + return offset; + } + + let minimum = 0; + let maximum = offset + Math.max(0, original.length - folded.length); + while (minimum < maximum) { + const middle = Math.floor((minimum + maximum) / 2); + const length = foldText(original.slice(0, middle)).length; + if (length === offset) { + return middle; + } + if (length > offset) { + maximum = middle; + } else { + minimum = middle + 1; + } + } + return minimum; + } + + function addRegexpFlags(regexp, requiredFlags) { + let flags = regexp.flags || ""; + requiredFlags.split("").forEach(function (flag) { + if (flags.indexOf(flag) === -1) { + flags += flag; + } + }); + return flags; + } + + function lastRegexpMatch(text, regexp, endOffset) { + let match = null; + let from = 0; + while (from <= text.length) { + regexp.lastIndex = from; + const candidate = regexp.exec(text); + if (!candidate) { + break; + } + const candidateEnd = candidate.index + candidate[0].length; + if (candidateEnd > endOffset) { + break; + } + if (!match || + candidateEnd > match.index + match[0].length) { + match = candidate; + } + from = Math.max(from + 1, candidate.index + 1); + } + return match; + } + + function findString(reverse, headPosition) { + if (!sourceQuery.length) { + return null; + } + + if (reverse) { + const firstCandidateLine = + adapter.firstLine() + queryLines.length - 1; + let character = headPosition.ch; + for (let lineNumber = headPosition.line; + lineNumber >= firstCandidateLine; + lineNumber--, character = null) { + let original = adapter.getLine(lineNumber) || ""; + if (character !== null) { + original = original.slice(0, character); + } + const folded = foldText(original); + if (queryLines.length === 1) { + const found = folded.lastIndexOf(queryLines[0]); + if (found === -1) { + continue; + } + return { + from: positionIndex({ + line: lineNumber, + ch: adjustFoldedOffset( + original, + folded, + found + ) + }), + to: positionIndex({ + line: lineNumber, + ch: adjustFoldedOffset( + original, + folded, + found + queryLines[0].length + ) + }) + }; + } + + const lastQueryLine = queryLines[queryLines.length - 1]; + if (folded.slice(0, lastQueryLine.length) !== + lastQueryLine) { + continue; + } + const startLine = lineNumber - queryLines.length + 1; + let matches = true; + for (let index = 1; + index < queryLines.length - 1; + index++) { + if (foldText(adapter.getLine(startLine + index) || "") !== + queryLines[index]) { + matches = false; + break; + } + } + if (!matches) { + continue; + } + const firstOriginal = adapter.getLine(startLine) || ""; + const firstFolded = foldText(firstOriginal); + const firstQueryLine = queryLines[0]; + const firstMatch = firstFolded.length - + firstQueryLine.length; + if (firstMatch < 0 || + firstFolded.slice(firstMatch) !== firstQueryLine) { + continue; + } + return { + from: positionIndex({ + line: startLine, + ch: adjustFoldedOffset( + firstOriginal, + firstFolded, + firstMatch + ) + }), + to: positionIndex({ + line: lineNumber, + ch: adjustFoldedOffset( + original, + folded, + lastQueryLine.length + ) + }) + }; + } + return null; + } + + const lastCandidateLine = + adapter.lastLine() + 1 - queryLines.length; + let character = headPosition.ch; + for (let lineNumber = headPosition.line; + lineNumber <= lastCandidateLine; + lineNumber++, character = 0) { + const fullLine = adapter.getLine(lineNumber) || ""; + const original = fullLine.slice(character); + const folded = foldText(original); + if (queryLines.length === 1) { + const found = folded.indexOf(queryLines[0]); + if (found === -1) { + continue; + } + return { + from: positionIndex({ + line: lineNumber, + ch: character + adjustFoldedOffset( + original, + folded, + found + ) + }), + to: positionIndex({ + line: lineNumber, + ch: character + adjustFoldedOffset( + original, + folded, + found + queryLines[0].length + ) + }) + }; + } + + const firstQueryLine = queryLines[0]; + const firstMatch = folded.length - firstQueryLine.length; + if (firstMatch < 0 || + folded.slice(firstMatch) !== firstQueryLine) { + continue; + } + let matches = true; + for (let index = 1; + index < queryLines.length - 1; + index++) { + if (foldText(adapter.getLine(lineNumber + index) || "") !== + queryLines[index]) { + matches = false; + break; + } + } + if (!matches) { + continue; + } + const lastLineNumber = lineNumber + queryLines.length - 1; + const lastOriginal = adapter.getLine(lastLineNumber) || ""; + const lastFolded = foldText(lastOriginal); + const lastQueryLine = queryLines[queryLines.length - 1]; + if (lastFolded.slice(0, lastQueryLine.length) !== + lastQueryLine) { + continue; + } + return { + from: positionIndex({ + line: lineNumber, + ch: character + adjustFoldedOffset( + original, + folded, + firstMatch + ) + }), + to: positionIndex({ + line: lastLineNumber, + ch: adjustFoldedOffset( + lastOriginal, + lastFolded, + lastQueryLine.length + ) + }) + }; + } + return null; + } + + function findRegexpAcrossDocument(reverse, headOffset) { + const source = documentSource(); + const regexp = new RegExp( + expression.source, + addRegexpFlags(expression, "gm") + ); + const match = reverse ? + lastRegexpMatch(source, regexp, headOffset) : + (function () { + regexp.lastIndex = headOffset; + return regexp.exec(source); + }()); + if (!match) { + return null; + } + return { + from: match.index, + to: match.index + match[0].length, + match: match + }; + } + + function findRegexpByLine(reverse, headOffset) { + const doc = adapter._view.state.doc; + const regexp = new RegExp( + expression.source, + addRegexpFlags(expression, "g") + ); + let line = doc.lineAt(headOffset); + let character = headOffset - line.from; + + while (line) { + let match; + if (reverse) { + match = lastRegexpMatch(line.text, regexp, character); + } else { + regexp.lastIndex = character; + match = regexp.exec(line.text); + } + if (match) { + return { + from: line.from + match.index, + to: line.from + match.index + match[0].length, + match: match + }; + } + if (reverse) { + if (line.number === 1) { + break; + } + line = doc.line(line.number - 1); + character = line.length; + } else { + if (line.number === doc.lines) { + break; + } + line = doc.line(line.number + 1); + character = 0; + } + } + return null; + } + + const initialPosition = clipPosition(start || { + line: adapter.firstLine(), + ch: 0 + }); + const cursor = { + atOccurrence: false, + afterEmptyMatch: false, + doc: adapter, + pos: { + from: initialPosition, + to: initialPosition + }, + find: function (reverse) { + const doc = adapter._view.state.doc; + const headPosition = clipPosition( + reverse ? cursor.pos.from : cursor.pos.to + ); + let headOffset = positionIndex(headPosition); + if (cursor.afterEmptyMatch && cursor.atOccurrence) { + headOffset += reverse ? -1 : 1; + if (headOffset < 0 || headOffset > doc.length) { + const end = reverse ? { + line: adapter.firstLine(), + ch: 0 + } : { + line: adapter.lastLine() + 1, + ch: 0 + }; + cursor.pos = { + from: end, + to: end + }; + cursor.atOccurrence = false; + cursor.afterEmptyMatch = false; + return false; + } + } + + const result = expression ? + (allowMultiline ? + findRegexpAcrossDocument(Boolean(reverse), headOffset) : + findRegexpByLine(Boolean(reverse), headOffset)) : + findString(Boolean(reverse), headPosition); + cursor.afterEmptyMatch = Boolean( + result && result.from === result.to + ); + if (!result) { + const end = reverse ? { + line: adapter.firstLine(), + ch: 0 + } : { + line: adapter.lastLine() + 1, + ch: 0 + }; + cursor.pos = { + from: end, + to: end + }; + cursor.atOccurrence = false; + return false; + } + + cursor.pos = { + from: adapter.posFromIndex(result.from), + to: adapter.posFromIndex(result.to) + }; + if (result.match) { + cursor.pos.match = result.match; + } + cursor.atOccurrence = true; + return result.match || true; + }, + findNext: function () { + return cursor.find(false); + }, + findPrevious: function () { + return cursor.find(true); + }, + from: function () { + return cursor.atOccurrence ? cursor.pos.from : undefined; + }, + to: function () { + return cursor.atOccurrence ? cursor.pos.to : undefined; + }, + replace: function (replacement, origin) { + if (!cursor.atOccurrence) { + return; + } + const replacementText = String(replacement); + const from = cursor.pos.from; + adapter.replaceRange( + replacementText, + from, + cursor.pos.to, + origin + ); + cursor.pos.to = adapter.posFromIndex( + adapter.indexFromPos(from) + replacementText.length + ); + } + }; + return cursor; + }; + + CodeMirror6Adapter.prototype.scrollCursorIntoView = function () { + this.scrollIntoView(this.getCursor()); + }; + + CodeMirror6Adapter.prototype.getStateAfter = function (lineNumber, precise) { + const targetLine = lineNumber === null || lineNumber === undefined ? + this.lastLine() : + _clamp(Number(lineNumber) || 0, 0, this.lastLine()); + const mode = this.getMode(); + return _modeStateBefore(this, mode, targetLine + 1, precise); + }; + + CodeMirror6Adapter.prototype.getStateBefore = function (lineNumber, precise) { + const mode = this.getMode(); + return _modeStateBefore(this, mode, lineNumber, precise); + }; + + CodeMirror6Adapter.prototype.destroy = function () { + if (this._destroyed) { + return; + } + const activeKeyMap = CodeMirror.getKeyMap && + CodeMirror.getKeyMap(this.getOption("keyMap")); + if (activeKeyMap && typeof activeKeyMap.detach === "function") { + activeKeyMap.detach.call(activeKeyMap, this, null); + } + Array.from(this._searchAnnotations).forEach(function (annotation) { + annotation.clear(); + }); + Array.from(this._scrollbarAnnotations).forEach(function (annotation) { + annotation.clear(); + }); + this._destroyed = true; + if (this._keySequenceTimer) { + clearTimeout(this._keySequenceTimer); + this._keySequenceTimer = null; + } + if (this._rulerElement) { + this._rulerElement.remove(); + this._rulerElement = null; + } + if (this._legacyDOM) { + this._legacyDOM.sizer.remove(); + this._legacyDOM.verticalScrollbar.remove(); + this._legacyDOM = null; + } + this._clearScrollbarModel(); + this._lineWidgets.forEach(function (record) { + record.renderedWrapper = null; + }); + this._lineHandles.forEach(function (handle) { + handle.parent = null; + }); + if (this._view) { + this._view.scrollDOM.removeEventListener("scroll", this._scrollHandler); + this._view.destroy(); + } + this._listeners.clear(); + if (CodeMirror.unregisterInstance) { + CodeMirror.unregisterInstance(this); + } + this._markers.length = 0; + this._lineHandles.clear(); + this._overlays.length = 0; + this._gutterMarkers.length = 0; + this._lineClasses.length = 0; + this._lineWidgets.length = 0; + this.marks = Object.create(null); + this.virtualSelection = null; + this.curOp = null; + this.cm6 = null; + this._view = null; + }; + + if (CodeMirror.defineExtension) { + CodeMirror.defineExtension( + "annotateScrollbar", + CodeMirror6Adapter.prototype.annotateScrollbar + ); + CodeMirror.defineExtension( + "showMatchesOnScrollbar", + CodeMirror6Adapter.prototype.showMatchesOnScrollbar + ); + } + + if (CodeMirror.registerEditorConstructor) { + CodeMirror.registerEditorConstructor(CodeMirror6Adapter); + } + + exports.CodeMirror6Adapter = CodeMirror6Adapter; +}); diff --git a/src/editor/CodeMirrorCompat.js b/src/editor/CodeMirrorCompat.js new file mode 100644 index 0000000000..a0f26ffadc --- /dev/null +++ b/src/editor/CodeMirrorCompat.js @@ -0,0 +1,5978 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + +/** + * Static legacy CodeMirror compatibility APIs used by Phoenix and its extensions. + * + * This module provides the utility functions and mutable registries that + * historically lived on the CodeMirror constructor. Calling the exported + * function creates an editor backed by the native CodeMirror 6 adapter. + */ +define(function (require, exports, module) { + + const CM6 = require("thirdparty/CodeMirror6/codemirror6"), + LegacyModeMeta = require("editor/CodeMirrorLegacyModeMeta"), + LegacyModesCompat = require("editor/CodeMirrorLegacyModesCompat"); + let editorConstructor = null; + + /** + * Preserve the legacy callable CodeMirror constructor contract while + * creating a native CodeMirror 6-backed editor. + * + * @param {Element|function(Element)} place + * @param {Object=} options + * @return {Object} + */ + function _resolveEditorConstructor() { + if (!editorConstructor) { + // Keep this module out of CodeMirror6Adapter's static dependency + // graph. RequireJS scans literal require() calls before executing + // the factory, which otherwise gives the adapter a partially + // initialized compatibility facade during the cycle. + const adapterModule = require(["editor", "CodeMirror6Adapter"].join("/")); + editorConstructor = adapterModule.CodeMirror6Adapter; + } + return editorConstructor; + } + + function CodeMirrorCompat(place, options) { + const suppliedOptions = options || {}; + const suppliedDoc = suppliedOptions.value instanceof CompatDoc ? + suppliedOptions.value : + null; + if (suppliedDoc && suppliedDoc.getEditor()) { + throw new Error("This document is already in use."); + } + + const placeFunction = typeof place === "function" ? place : null; + const parent = placeFunction || !place ? + window.document.createElement("div") : + place; + const editorOptions = Object.assign({}, suppliedOptions); + if (suppliedDoc) { + editorOptions.value = suppliedDoc.getValue(); + editorOptions.mode = suppliedDoc._modeOption; + editorOptions.lineSeparator = suppliedDoc._lineSeparator; + editorOptions.direction = suppliedDoc._direction; + editorOptions._compatDoc = suppliedDoc; + editorOptions._compatDocSource = suppliedDoc._adapter; + editorOptions._firstLine = suppliedDoc.firstLine(); + } + const EditorConstructor = _resolveEditorConstructor(); + const instance = new EditorConstructor(parent, editorOptions); + if (placeFunction) { + placeFunction(instance.getWrapperElement()); + } + return instance; + } + + const NO_HANDLERS = []; + const NON_ASCII_SINGLE_CASE_WORD_CHAR = + /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + + const Pass = { + toString: function () { + return "CodeMirror.Pass"; + } + }; + const Init = { + toString: function () { + return "CodeMirror.Init"; + } + }; + + const defaults = {}; + const optionHandlers = {}; + const extensions = {}; + const docExtensions = {}; + const helpers = {}; + const commands = {}; + const keyMap = {}; + const inputStyles = {}; + const scrollbarModel = {}; + const modes = {}; + const mimeModes = {}; + const modeExtensions = {}; + const builtInModeFactories = {}; + const registeredInstances = new Map(); + const installedLegacyCompatibilityModules = new Set(); + const initHooks = []; + const BRACKET_INFO = { + "(": {matching: ")", direction: 1}, + ")": {matching: "(", direction: -1}, + "[": {matching: "]", direction: 1}, + "]": {matching: "[", direction: -1}, + "{": {matching: "}", direction: 1}, + "}": {matching: "{", direction: -1}, + "<": {matching: ">", direction: 1}, + ">": {matching: "<", direction: -1} + }; + const DEFAULT_BRACKET_REGEX = /[()[\]{}]/; + let nextDocId = 0; + const bundledModes = Object.assign({}, CM6.legacyModeParsers || {}, { + erlang: CM6.erlang, + pascal: CM6.pascal, + scheme: CM6.scheme + }); + const bundledModeModules = CM6.legacyModeModules || {}; + const bundledModeMIMEs = CM6.legacyModeMIMEs || {}; + + const DOC_DELEGATE_METHODS = [ + "addLineClass", + "addLineWidget", + "addSelection", + "changeGeneration", + "clearGutter", + "clearHistory", + "clipPos", + "eachLine", + "extendSelection", + "extendSelections", + "extendSelectionsBy", + "findMarks", + "findMarksAt", + "findWordAt", + "getAllMarks", + "getCursor", + "getExtending", + "getHelper", + "getHelpers", + "getHistory", + "getLine", + "getLineHandle", + "getLineHandleVisualStart", + "getLineNumber", + "getLineTokens", + "getMode", + "getModeAt", + "getRange", + "getSearchCursor", + "getSelection", + "getSelections", + "getStateAfter", + "getStateBefore", + "getTokenAt", + "getTokenTypeAt", + "getValue", + "historySize", + "indexFromPos", + "isClean", + "lastLine", + "lineCount", + "lineInfo", + "lineSeparator", + "listSelections", + "markClean", + "markText", + "posFromIndex", + "redo", + "redoSelection", + "removeLineClass", + "removeLineWidget", + "replaceRange", + "replaceSelection", + "replaceSelections", + "setBookmark", + "setCursor", + "setDirection", + "setExtending", + "setGutterMarker", + "setHistory", + "setSelection", + "setSelections", + "setValue", + "somethingSelected", + "splitLines", + "undo", + "undoSelection" + ]; + + function _initializeDoc(doc, adapter, options) { + const settings = options || {}; + doc.id = ++nextDocId; + doc._adapter = adapter || null; + doc.cm = settings.editor || null; + doc._links = []; + doc._modeOption = settings.mode; + doc._lineSeparator = settings.lineSeparator; + doc._direction = settings.direction === "rtl" ? "rtl" : "ltr"; + doc._scrollLeft = Number(settings.scrollLeft) || 0; + doc._scrollTop = Number(settings.scrollTop) || 0; + doc._handlers = {}; + Object.keys(docExtensions).forEach(function (name) { + doc[name] = docExtensions[name]; + }); + return doc; + } + + /** + * CM5-compatible document identity backed entirely by a CM6 adapter. + * Detached documents keep a CM6 EditorState in an off-DOM EditorView so + * all text, selection, history, marker, and token APIs use the same engine + * as attached editors. + * + * @constructor + * @param {string|Array=} text + * @param {*=} mode + * @param {number=} firstLine + * @param {string=} lineSeparator + * @param {string=} direction + */ + function CompatDoc(text, mode, firstLine, lineSeparator, direction) { + if (!(this instanceof CompatDoc)) { + return new CompatDoc(text, mode, firstLine, lineSeparator, direction); + } + + _initializeDoc(this, null, { + mode: mode, + lineSeparator: lineSeparator, + direction: direction + }); + const holder = window.document.createElement("div"); + const EditorConstructor = _resolveEditorConstructor(); + const initialText = Array.isArray(text) ? + text.join(lineSeparator || "\n") : + String(text || ""); + this._adapter = new EditorConstructor(holder, { + value: initialText, + mode: mode, + lineSeparator: lineSeparator, + direction: direction, + _compatDoc: this, + _detachedDoc: true, + _firstLine: firstLine === null || firstLine === undefined ? + 0 : + firstLine + }); + } + + DOC_DELEGATE_METHODS.forEach(function (methodName) { + CompatDoc.prototype[methodName] = function () { + if (!this._adapter || typeof this._adapter[methodName] !== "function") { + throw new Error(`Document method ${methodName} is unavailable.`); + } + return this._adapter[methodName].apply(this._adapter, arguments); + }; + }); + + CompatDoc.prototype.firstLine = function () { + return this._adapter ? this._adapter.firstLine() : 0; + }; + + CompatDoc.prototype.getEditor = function () { + return this.cm; + }; + + Object.defineProperty(CompatDoc.prototype, "history", { + configurable: true, + enumerable: true, + get: function () { + return this._adapter ? this._adapter.history : undefined; + } + }); + + CompatDoc.prototype.on = function (eventName, listener) { + on(this, eventName, listener); + }; + + CompatDoc.prototype.off = function (eventName, listener) { + off(this, eventName, listener); + }; + + CompatDoc.prototype.copy = function (copyHistory) { + if (!this._adapter || + typeof this._adapter._copyDocument !== "function") { + throw new Error("Document copying is unavailable."); + } + return this._adapter._copyDocument(Boolean(copyHistory)); + }; + + CompatDoc.prototype.linkedDoc = function (options) { + if (!this._adapter || + typeof this._adapter._createLinkedDocument !== "function") { + throw new Error("Linked documents are unavailable."); + } + return this._adapter._createLinkedDocument(options || {}); + }; + + CompatDoc.prototype.unlinkDoc = function (other) { + const otherDoc = other && typeof other.getDoc === "function" ? + other.getDoc() : + other; + if (!(otherDoc instanceof CompatDoc)) { + return; + } + if (this._adapter && + typeof this._adapter._unlinkDocument === "function") { + this._adapter._unlinkDocument(otherDoc); + } + }; + + CompatDoc.prototype.iterLinkedDocs = function (callback) { + const visited = new Set([this]); + const visit = function (doc, sharedHistory) { + doc._links.forEach(function (link) { + if (visited.has(link.doc)) { + return; + } + visited.add(link.doc); + const sharesHistory = sharedHistory && Boolean(link.sharedHist); + callback(link.doc, sharesHistory); + visit(link.doc, sharesHistory); + }); + }; + visit(this, true); + }; + + function createDocumentForAdapter(adapter, options) { + return _initializeDoc(Object.create(CompatDoc.prototype), adapter, options); + } + + function Pos(line, ch, sticky) { + if (!(this instanceof Pos)) { + return new Pos(line, ch, sticky); + } + this.line = line; + this.ch = ch; + this.sticky = sticky === undefined ? null : sticky; + } + + function cmpPos(left, right) { + return left.line - right.line || left.ch - right.ch; + } + + function splitLines(string) { + return String(string).split(/\r\n?|\n/); + } + + function defineLegacyInstanceCheck(constructor, predicate) { + if (typeof Symbol !== "undefined" && Symbol.hasInstance) { + Object.defineProperty(constructor, Symbol.hasInstance, { + configurable: true, + value: predicate + }); + } + return constructor; + } + + const Line = defineLegacyInstanceCheck(function Line() {}, function (value) { + return Boolean( + value && + value._adapter && + Number.isFinite(value._position) && + typeof value.lineNo === "function" + ); + }); + const TextMarker = defineLegacyInstanceCheck( + function TextMarker() {}, + function (value) { + return Boolean( + value && + value._adapter && + (value.type === "range" || value.type === "bookmark") && + typeof value.clear === "function" && + typeof value.find === "function" + ); + } + ); + const LineWidget = defineLegacyInstanceCheck( + function LineWidget() {}, + function (value) { + return Boolean( + value && + value.doc && + value.node && + value.line && + typeof value.clear === "function" && + typeof value.changed === "function" + ); + } + ); + + function SharedTextMarker(markers, primary) { + this.markers = markers || []; + this.primary = primary || this.markers[0] || null; + this._handlers = {}; + this._cleared = false; + } + + SharedTextMarker.prototype.clear = function () { + if (this._cleared) { + return; + } + const found = this.find(); + this._cleared = true; + this.markers.forEach(function (marker) { + if (marker && typeof marker.clear === "function") { + marker._clearingShared = true; + marker.clear(); + marker._clearingShared = false; + } + }); + if (found) { + const from = found.from || found; + const to = found.to || found; + signal(this, "clear", from, to); + } + }; + + SharedTextMarker.prototype.find = function (side, lineObj) { + return this.primary && typeof this.primary.find === "function" ? + this.primary.find(side, lineObj) : + undefined; + }; + + SharedTextMarker.prototype.on = function (eventName, listener) { + on(this, eventName, listener); + }; + + SharedTextMarker.prototype.off = function (eventName, listener) { + off(this, eventName, listener); + }; + + SharedTextMarker.prototype.changed = function () { + this.markers.forEach(function (marker) { + if (marker && typeof marker.changed === "function") { + marker.changed(); + } + }); + signal(this, "changed"); + }; + + function defineInitHook(hook) { + return initHooks.push(hook); + } + + function findColumn(string, goal, tabSize) { + let position = 0; + let column = 0; + const configuredTabSize = tabSize || 4; + + while (true) { + let nextTab = string.indexOf("\t", position); + if (nextTab === -1) { + nextTab = string.length; + } + const skipped = nextTab - position; + if (nextTab === string.length || column + skipped >= goal) { + return position + Math.min(skipped, goal - column); + } + column += skipped; + column += configuredTabSize - column % configuredTabSize; + position = nextTab + 1; + if (column >= goal) { + return position; + } + } + } + + function wheelEventPixels(event) { + let x = Number(event.deltaX); + let y = Number(event.deltaY); + + if (!Number.isFinite(x)) { + x = event.wheelDeltaX === undefined ? 0 : -event.wheelDeltaX; + } + if (!Number.isFinite(y)) { + if (event.wheelDeltaY !== undefined) { + y = -event.wheelDeltaY; + } else if (event.wheelDelta !== undefined) { + y = -event.wheelDelta; + } else { + y = Number(event.detail) || 0; + } + } + + if (event.deltaMode === 1) { + x *= 16; + y *= 16; + } else if (event.deltaMode === 2) { + x *= window.innerWidth || 1; + y *= window.innerHeight || 1; + } + + return {x: x, y: y}; + } + + function ePreventDefault(event) { + if (event.preventDefault) { + event.preventDefault(); + } else { + event.returnValue = false; + } + } + + function eStopPropagation(event) { + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + } + + function eStop(event) { + ePreventDefault(event); + eStopPropagation(event); + } + + function addClass(node, classNames) { + String(classNames || "").split(/\s+/).filter(Boolean).forEach(function (className) { + if (node.classList) { + node.classList.add(className); + } else if (!new RegExp("(^|\\s)" + className + "(?:$|\\s)").test(node.className)) { + node.className += (node.className ? " " : "") + className; + } + }); + } + + function rmClass(node, classNames) { + String(classNames || "").split(/\s+/).filter(Boolean).forEach(function (className) { + if (node.classList) { + node.classList.remove(className); + } else { + node.className = node.className + .split(/\s+/) + .filter(function (candidate) { + return candidate && candidate !== className; + }) + .join(" "); + } + }); + } + + function contains(parent, child) { + let current = child && child.nodeType === 3 ? child.parentNode : child; + if (!parent || !current) { + return false; + } + if (parent.contains && parent.contains(current)) { + return true; + } + while (current) { + if (current === parent) { + return true; + } + current = current.nodeType === 11 && current.host ? + current.host : + current.parentNode; + } + return false; + } + + /** + * Minimal CM5 input-style facade over the CM6 content DOM. These + * constructors remain public because some extensions inspect or subclass + * CodeMirror.inputStyles, but input and composition are always owned by + * the CM6 EditorView. + * + * @constructor + * @param {!Object} editor + * @param {boolean} supportsTouch + */ + function CompatInputStyle(editor, supportsTouch) { + this.cm = editor; + this._supportsTouch = supportsTouch; + } + + CompatInputStyle.prototype.init = function () {}; + CompatInputStyle.prototype.prepareSelection = function () { + return null; + }; + CompatInputStyle.prototype.showSelection = function () {}; + CompatInputStyle.prototype.showPrimarySelection = function () {}; + CompatInputStyle.prototype.reset = function () {}; + CompatInputStyle.prototype.resetPosition = function () {}; + CompatInputStyle.prototype.receivedFocus = function () {}; + CompatInputStyle.prototype.selectionChanged = function () {}; + CompatInputStyle.prototype.pollSelection = function () {}; + CompatInputStyle.prototype.pollContent = function () {}; + CompatInputStyle.prototype.ensurePolled = function () {}; + CompatInputStyle.prototype.forceCompositionEnd = function () {}; + CompatInputStyle.prototype.readFromDOMSoon = function () {}; + CompatInputStyle.prototype.updateFromDOM = function () {}; + CompatInputStyle.prototype.onKeyPress = function () {}; + CompatInputStyle.prototype.onContextMenu = function () {}; + CompatInputStyle.prototype.readOnlyChanged = function () {}; + CompatInputStyle.prototype.setUneditable = function (node) { + if (node && node.setAttribute) { + node.setAttribute("contenteditable", "false"); + } + }; + CompatInputStyle.prototype.getField = function () { + return this.cm && typeof this.cm.getInputField === "function" ? + this.cm.getInputField() : + null; + }; + CompatInputStyle.prototype.getSelection = function () { + const field = this.getField(); + const ownerDocument = field && field.ownerDocument; + return ownerDocument && typeof ownerDocument.getSelection === "function" ? + ownerDocument.getSelection() : + null; + }; + CompatInputStyle.prototype.focus = function () { + if (this.cm && typeof this.cm.focus === "function") { + this.cm.focus(); + } + }; + CompatInputStyle.prototype.blur = function () { + const field = this.getField(); + if (field && typeof field.blur === "function") { + field.blur(); + } + }; + CompatInputStyle.prototype.supportsTouch = function () { + return this._supportsTouch; + }; + CompatInputStyle.prototype.screenReaderLabelChanged = function (label) { + const field = this.getField(); + if (!field) { + return; + } + if (label) { + field.setAttribute("aria-label", label); + } else { + field.removeAttribute("aria-label"); + } + }; + + function TextareaInputStyle(editor) { + CompatInputStyle.call(this, editor, false); + } + TextareaInputStyle.prototype = Object.create(CompatInputStyle.prototype); + TextareaInputStyle.prototype.constructor = TextareaInputStyle; + + function ContentEditableInputStyle(editor) { + CompatInputStyle.call(this, editor, true); + } + ContentEditableInputStyle.prototype = Object.create(CompatInputStyle.prototype); + ContentEditableInputStyle.prototype.constructor = ContentEditableInputStyle; + + function NativeScrollbarModel(_place, _scroll, editor) { + this.cm = editor || null; + } + NativeScrollbarModel.prototype.update = function () { + return {right: 0, bottom: 0}; + }; + NativeScrollbarModel.prototype.setScrollLeft = function (position) { + if (this.cm && typeof this.cm.scrollTo === "function") { + this.cm.scrollTo(position, null); + } + }; + NativeScrollbarModel.prototype.setScrollTop = function (position) { + if (this.cm && typeof this.cm.scrollTo === "function") { + this.cm.scrollTo(null, position); + } + }; + NativeScrollbarModel.prototype.clear = function () {}; + + function NullScrollbarModel() {} + NullScrollbarModel.prototype.update = function () { + return {right: 0, bottom: 0}; + }; + NullScrollbarModel.prototype.setScrollLeft = function () {}; + NullScrollbarModel.prototype.setScrollTop = function () {}; + NullScrollbarModel.prototype.clear = function () {}; + + inputStyles.textarea = TextareaInputStyle; + inputStyles.contenteditable = ContentEditableInputStyle; + scrollbarModel.native = NativeScrollbarModel; + scrollbarModel.null = NullScrollbarModel; + + function changeEnd(change) { + if (!change.text) { + return change.to; + } + + const text = typeof change.text === "string" ? splitLines(change.text) : change.text; + return Pos( + change.from.line + text.length - 1, + text[text.length - 1].length + (text.length === 1 ? change.from.ch : 0) + ); + } + + function _findTagNodeAt(tree, offset) { + const candidates = [ + tree.resolveInner(offset, 1), + tree.resolveInner(offset, -1) + ]; + + for (let index = 0; index < candidates.length; index++) { + let node = candidates[index]; + while (node) { + if ((node.name === "OpenTag" || + node.name === "CloseTag" || + node.name === "SelfClosingTag" || + node.name === "MismatchedCloseTag") && + node.from <= offset && offset < node.to) { + return node; + } + node = node.parent; + } + } + return null; + } + + function _tagInfo(editor, node) { + if (!node) { + return null; + } + const tagNameNode = node.getChild("TagName"); + if (!tagNameNode) { + return null; + } + return { + tag: editor._view.state.doc.sliceString(tagNameNode.from, tagNameNode.to), + from: editor.posFromIndex(node.from), + to: editor.posFromIndex(node.to) + }; + } + + function _tagNodeInRange(editor, node, range) { + if (!node || !range) { + return Boolean(node); + } + const line = editor.posFromIndex(node.from).line; + return line >= Math.max(0, range.from) && + line < Math.min(editor.lineCount(), range.to); + } + + /** + * Finds the opening and closing tags associated with the tag at a position. + * This preserves the CodeMirror 5 xml-fold result shape while using the + * active CodeMirror 6 syntax tree. + * @param {!Object} editor CodeMirror-compatible editor instance + * @param {{line:number, ch:number}} position + * @param {{from:number, to:number}=} range Optional line range + * @return {?{open:?Object, close:?Object, at:string}} + */ + function findMatchingTag(editor, position, range) { + if (!editor || !editor._view || !editor._view.state || + typeof editor.indexFromPos !== "function" || + typeof editor.posFromIndex !== "function") { + return; + } + + const state = editor._view.state; + const offset = editor.indexFromPos(position); + const tagNode = _findTagNodeAt(CM6.syntaxTree(state), offset); + if (!tagNode) { + return; + } + + const here = _tagInfo(editor, tagNode); + if (!here) { + return; + } + if (tagNode.name === "SelfClosingTag") { + return { + open: here, + close: null, + at: "open" + }; + } + + let element = tagNode.parent; + while (element && element.name !== "Element") { + element = element.parent; + } + + let openNode = null; + let closeNode = null; + if (element) { + openNode = element.getChild("OpenTag"); + closeNode = element.getChild("CloseTag"); + } + + if (tagNode.name === "OpenTag") { + openNode = tagNode; + } else { + closeNode = tagNode; + } + + const open = _tagNodeInRange(editor, openNode, range) ? + _tagInfo(editor, openNode) : + null; + const close = _tagNodeInRange(editor, closeNode, range) ? + _tagInfo(editor, closeNode) : + null; + + if (open && close && open.tag.toLowerCase() !== close.tag.toLowerCase()) { + if (tagNode === openNode) { + return { + open: open, + close: null, + at: "open" + }; + } + return { + open: null, + close: close, + at: "close" + }; + } + + return { + open: open, + close: close, + at: tagNode === openNode ? "open" : "close" + }; + } + + function _getBracketRegex(config) { + const configuredRegex = config && config.bracketRegex; + return configuredRegex && typeof configuredRegex.test === "function" ? + configuredRegex : + DEFAULT_BRACKET_REGEX; + } + + function _matchesBracket(regex, character) { + regex.lastIndex = 0; + return regex.test(character); + } + + function _getStateLine(editor, lineNumber) { + if (!editor || !editor._view || !editor._view.state || + typeof editor.firstLine !== "function") { + return null; + } + + const stateDoc = editor._view.state.doc; + const stateLineNumber = lineNumber - editor.firstLine() + 1; + if (stateLineNumber < 1 || stateLineNumber > stateDoc.lines) { + return null; + } + return stateDoc.line(stateLineNumber).text; + } + + function _sameBracketStyle(editor, lineNumber, character, style) { + if (style === undefined) { + return true; + } + const tokenStyle = editor.getTokenTypeAt( + Pos(lineNumber, character + 1) + ); + return (tokenStyle || "") === (style || ""); + } + + /** + * Scans through the CM6 document state for the next bracket in the + * requested direction, preserving the CodeMirror 5 result contract. + * @param {!Object} editor CodeMirror-compatible editor instance + * @param {{line:number, ch:number}} position + * @param {number} direction Either 1 or -1 + * @param {string|null|undefined} style Token style to constrain the scan + * @param {Object=} config + * @return {{pos: !Pos, ch: string}|boolean|null} + */ + function scanForBracket(editor, position, direction, style, config) { + if (!position || !editor || !editor._view || !editor._view.state || + typeof editor.getTokenTypeAt !== "function" || + typeof editor.firstLine !== "function" || + typeof editor.lastLine !== "function") { + return null; + } + + const scanDirection = direction > 0 ? 1 : -1; + const maxScanLineLength = config && config.maxScanLineLength || 10000; + const maxScanLines = config && config.maxScanLines || 1000; + const bracketRegex = _getBracketRegex(config); + const firstLine = editor.firstLine(); + const lastLine = editor.lastLine(); + if (position.line < firstLine || position.line > lastLine) { + return null; + } + + const lineBoundary = scanDirection > 0 ? + Math.min(position.line + maxScanLines, lastLine + 1) : + Math.max(firstLine - 1, position.line - maxScanLines); + let depth = 0; + let lineNumber = position.line; + + for (; lineNumber !== lineBoundary; lineNumber += scanDirection) { + const lineText = _getStateLine(editor, lineNumber); + if (lineText === null || lineText.length > maxScanLineLength) { + continue; + } + + let character = scanDirection > 0 ? 0 : lineText.length - 1; + const lineEnd = scanDirection > 0 ? lineText.length : -1; + if (lineNumber === position.line) { + character = position.ch - (scanDirection < 0 ? 1 : 0); + } + character = scanDirection > 0 ? + Math.max(0, Math.min(character, lineText.length)) : + Math.max(-1, Math.min(character, lineText.length - 1)); + + for (; character !== lineEnd; character += scanDirection) { + const bracket = lineText.charAt(character); + if (!_matchesBracket(bracketRegex, bracket) || + !_sameBracketStyle( + editor, + lineNumber, + character, + style + )) { + continue; + } + + const bracketInfo = BRACKET_INFO[bracket]; + if (bracketInfo && + bracketInfo.direction === scanDirection) { + depth++; + } else if (depth === 0) { + return { + pos: Pos(lineNumber, character), + ch: bracket + }; + } else { + depth--; + } + } + } + + const scannedToDocumentEdge = lineNumber - scanDirection === ( + scanDirection > 0 ? lastLine : firstLine + ); + return scannedToDocumentEdge ? false : null; + } + + /** + * Finds the bracket adjacent to a cursor and its partner using the CM6 + * state-backed scanner, returning the historical CM5 result shape. + * @param {!Object} editor CodeMirror-compatible editor instance + * @param {{line:number, ch:number}} position + * @param {Object=} config + * @return {?{from:!Pos, to:(!Pos|boolean), match:boolean, forward:boolean}} + */ + function findMatchingBracket(editor, position, config) { + if (!position || !editor || + typeof editor.getWrapperElement !== "function") { + return null; + } + + const lineText = _getStateLine(editor, position.line); + if (lineText === null) { + return null; + } + + const cursorCharacter = Number.isFinite(Number(position.ch)) ? + Number(position.ch) : + 0; + const bracketRegex = _getBracketRegex(config); + let bracketCharacter = cursorCharacter - 1; + let afterCursor = config && config.afterCursor; + if (afterCursor === null || afterCursor === undefined) { + afterCursor = /(^| )cm-fat-cursor($| )/.test( + editor.getWrapperElement().className + ); + } + + let bracket = lineText.charAt(bracketCharacter); + if (afterCursor || bracketCharacter < 0 || + !_matchesBracket(bracketRegex, bracket) || + !BRACKET_INFO[bracket]) { + bracketCharacter++; + bracket = lineText.charAt(bracketCharacter); + if (!_matchesBracket(bracketRegex, bracket) || + !BRACKET_INFO[bracket]) { + return null; + } + } + + const bracketInfo = BRACKET_INFO[bracket]; + const direction = bracketInfo.direction; + if (config && config.strict && + (direction > 0) !== (bracketCharacter === cursorCharacter)) { + return null; + } + + const style = editor.getTokenTypeAt( + Pos(position.line, bracketCharacter + 1) + ); + const found = scanForBracket( + editor, + Pos( + position.line, + bracketCharacter + (direction > 0 ? 1 : 0) + ), + direction, + style, + config + ); + if (found === null || found === undefined) { + return null; + } + + return { + from: Pos(position.line, bracketCharacter), + to: found && found.pos, + match: Boolean(found && found.ch === bracketInfo.matching), + forward: direction > 0 + }; + } + + function _configuredBracketOptions(editor, config) { + if (config) { + return config; + } + const option = editor && typeof editor.getOption === "function" ? + editor.getOption("matchBrackets") : + null; + return option && typeof option === "object" ? option : {}; + } + + /** + * Highlights matching brackets through the adapter's CM6-backed marker + * layer. With autoclear disabled, returns a cleanup function. + * @param {!Object} editor CodeMirror-compatible editor instance + * @param {boolean=} autoclear + * @param {Object=} config + * @return {function()|undefined} + */ + function matchBrackets(editor, autoclear, config) { + if (!editor || typeof editor.listSelections !== "function" || + typeof editor.markText !== "function") { + return; + } + + const bracketOptions = _configuredBracketOptions(editor, config); + const maxHighlightLineLength = + bracketOptions.maxHighlightLineLength || 1000; + const highlightNonMatching = + bracketOptions.highlightNonMatching !== false; + const markers = []; + + editor.listSelections().forEach(function (selection) { + if (!selection.empty()) { + return; + } + const match = findMatchingBracket( + editor, + selection.head, + bracketOptions + ); + if (!match || !match.match && !highlightNonMatching) { + return; + } + + const fromLine = _getStateLine(editor, match.from.line); + if (fromLine !== null && + fromLine.length <= maxHighlightLineLength) { + const className = match.match ? + "CodeMirror-matchingbracket" : + "CodeMirror-nonmatchingbracket"; + markers.push(editor.markText( + match.from, + Pos(match.from.line, match.from.ch + 1), + {className: className} + )); + if (match.to) { + const toLine = _getStateLine(editor, match.to.line); + if (toLine !== null && + toLine.length <= maxHighlightLineLength) { + markers.push(editor.markText( + match.to, + Pos(match.to.line, match.to.ch + 1), + {className: className} + )); + } + } + } + }); + + if (!markers.length) { + return; + } + + const clear = function () { + const clearMarkers = function () { + markers.forEach(function (marker) { + marker.clear(); + }); + }; + if (typeof editor.operation === "function") { + editor.operation(clearMarkers); + } else { + clearMarkers(); + } + }; + + if (autoclear) { + window.setTimeout(clear, 800); + return; + } + return clear; + } + + function countColumn(string, end, tabSize, startIndex, startValue) { + if (end === null || end === undefined) { + end = string.search(/[^\s\u00a0]/); + if (end === -1) { + end = string.length; + } + } + + const effectiveTabSize = tabSize || 4; + let index = startIndex || 0; + let column = startValue || 0; + + for (;;) { + const nextTab = string.indexOf("\t", index); + if (nextTab < 0 || nextTab >= end) { + return column + end - index; + } + column += nextTab - index; + column += effectiveTabSize - column % effectiveTabSize; + index = nextTab + 1; + } + } + + function isWordChar(character) { + return /\w/.test(character) || + character > "\x80" && + (character.toUpperCase() !== character.toLowerCase() || + NON_ASCII_SINGLE_CASE_WORD_CHAR.test(character)); + } + + function on(emitter, type, handler) { + if (emitter.isCodeMirror6 && typeof emitter.on === "function") { + emitter.on(type, handler); + } else if (emitter.addEventListener) { + emitter.addEventListener(type, handler, false); + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, handler); + } else { + const handlers = emitter._handlers || (emitter._handlers = {}); + handlers[type] = (handlers[type] || NO_HANDLERS).concat(handler); + } + } + + function off(emitter, type, handler) { + if (emitter.isCodeMirror6 && typeof emitter.off === "function") { + emitter.off(type, handler); + } else if (emitter.removeEventListener) { + emitter.removeEventListener(type, handler, false); + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, handler); + } else { + const handlers = emitter._handlers; + const listeners = handlers && handlers[type]; + if (!listeners) { + return; + } + + const index = listeners.indexOf(handler); + if (index > -1) { + handlers[type] = listeners.slice(0, index).concat(listeners.slice(index + 1)); + } + } + } + + function signal(emitter, type) { + const args = Array.prototype.slice.call(arguments, 2); + if (emitter.isCodeMirror6 && typeof emitter._emit === "function") { + emitter._emit.apply(emitter, [type].concat(args)); + return; + } + + const handlers = emitter._handlers && emitter._handlers[type] || NO_HANDLERS; + if (!handlers.length) { + return; + } + + handlers.forEach(function (handler) { + handler.apply(null, args); + }); + } + + function defineOption(name, defaultValue, handler, notOnInit) { + defaults[name] = defaultValue; + + const previous = optionHandlers[name]; + if (handler) { + optionHandlers[name] = { + defaultValue: defaultValue, + handler: handler, + notOnInit: Boolean(notOnInit) + }; + } else if (previous) { + previous.defaultValue = defaultValue; + } else { + optionHandlers[name] = { + defaultValue: defaultValue, + handler: null, + notOnInit: Boolean(notOnInit) + }; + } + } + + function runOptionHandler(instance, name, value, oldValue) { + const definition = optionHandlers[name]; + if (!definition || !definition.handler || + definition.notOnInit && oldValue === Init) { + return; + } + return definition.handler(instance, value, oldValue); + } + + function initOptions(instance, suppliedOptions) { + const options = Object.assign({}, defaults, suppliedOptions || {}); + instance.options = options; + + Object.keys(optionHandlers).forEach(function (name) { + runOptionHandler(instance, name, options[name], Init); + }); + if (typeof options.finishInit === "function") { + options.finishInit(instance); + } + initHooks.forEach(function (hook) { + hook(instance); + }); + + return options; + } + + function fromTextArea(textArea, suppliedOptions) { + const options = Object.assign({}, suppliedOptions || {}); + const previousDisplay = textArea.style.display; + let codeMirror; + let realSubmit; + let wrappedSubmit; + + options.value = textArea.value; + if (!options.tabindex && textArea.tabIndex) { + options.tabindex = textArea.tabIndex; + } + if (!options.placeholder && textArea.placeholder) { + options.placeholder = textArea.placeholder; + } + if (options.autofocus === null || options.autofocus === undefined) { + const root = textArea.getRootNode ? textArea.getRootNode() : textArea.ownerDocument; + const activeElement = root.activeElement; + options.autofocus = activeElement === textArea || + textArea.hasAttribute("autofocus") && + activeElement === textArea.ownerDocument.body; + } + + function save() { + textArea.value = codeMirror.getValue(); + } + + if (textArea.form) { + textArea.form.addEventListener("submit", save); + if (!options.leaveSubmitMethodAlone) { + const form = textArea.form; + realSubmit = form.submit; + try { + wrappedSubmit = function () { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + form.submit = wrappedSubmit; + } catch (error) { + realSubmit = null; + } + } + } + + options.finishInit = function (instance) { + codeMirror = instance; + instance.save = save; + instance.getTextArea = function () { + return textArea; + }; + instance.toTextArea = function () { + if (!codeMirror) { + return; + } + save(); + const wrapper = instance.getWrapperElement(); + instance.destroy(); + if (wrapper && wrapper.parentNode) { + wrapper.parentNode.removeChild(wrapper); + } + textArea.style.display = previousDisplay; + if (textArea.form) { + textArea.form.removeEventListener("submit", save); + if (realSubmit && textArea.form.submit === wrappedSubmit) { + textArea.form.submit = realSubmit; + } + } + codeMirror = null; + }; + }; + + textArea.style.display = "none"; + codeMirror = CodeMirrorCompat(function (node) { + textArea.parentNode.insertBefore(node, textArea.nextSibling); + }, options); + return codeMirror; + } + + function defineExtension(name, extension) { + extensions[name] = extension; + CodeMirrorCompat.prototype[name] = extension; + registeredInstances.forEach(function (_doc, instance) { + instance[name] = extension; + }); + } + + function defineDocExtension(name, extension) { + docExtensions[name] = extension; + CompatDoc.prototype[name] = extension; + registeredInstances.forEach(function (doc) { + if (doc) { + doc[name] = extension; + } + }); + } + + function installExtensions(instance, doc) { + if (instance) { + Object.keys(extensions).forEach(function (name) { + instance[name] = extensions[name]; + }); + } + if (doc) { + Object.keys(docExtensions).forEach(function (name) { + doc[name] = docExtensions[name]; + }); + } + } + + function registerInstance(instance, doc) { + const targetDoc = doc || + (typeof instance.getDoc === "function" ? instance.getDoc() : null); + registeredInstances.set(instance, targetDoc); + installExtensions(instance, targetDoc); + return instance; + } + + function unregisterInstance(instance) { + registeredInstances.delete(instance); + } + + function registerEditorConstructor(constructor) { + editorConstructor = constructor; + } + + function registerHelper(type, name, value) { + if (!helpers[type]) { + helpers[type] = {_global: []}; + CodeMirrorCompat[type] = helpers[type]; + } + helpers[type][name] = value; + } + + function registerGlobalHelper(type, name, predicate, value) { + registerHelper(type, name, value); + helpers[type]._global.push({ + pred: predicate, + val: value + }); + } + + function getHelpers(editor, position, type) { + const result = []; + const registry = helpers[type]; + if (!registry) { + return result; + } + const mode = editor.getModeAt(position); + + if (typeof mode[type] === "string") { + if (registry[mode[type]]) { + result.push(registry[mode[type]]); + } + } else if (Array.isArray(mode[type])) { + mode[type].forEach(function (name) { + if (registry[name]) { + result.push(registry[name]); + } + }); + } else if (mode.helperType && registry[mode.helperType]) { + result.push(registry[mode.helperType]); + } else if (registry[mode.name]) { + result.push(registry[mode.name]); + } + + registry._global.forEach(function (globalHelper) { + if (globalHelper.pred(mode, editor) && + result.indexOf(globalHelper.val) === -1) { + result.push(globalHelper.val); + } + }); + return result; + } + + function _nativeKeyMapCommand(bindings, key, shifted) { + const binding = (bindings || []).find(function (candidate) { + return candidate.key === key; + }); + return binding && (shifted ? binding.shift : binding.run); + } + + function _runNativeViewCommand(editor, command) { + if (!editor || !editor._view || typeof command !== "function") { + return false; + } + return Boolean(command(editor._view)); + } + + function _legacyHintText(completion) { + if (typeof completion === "string") { + return completion; + } + if (!completion) { + return ""; + } + if (completion.text !== null && completion.text !== undefined) { + return String(completion.text); + } + return completion.displayText !== null && + completion.displayText !== undefined ? + String(completion.displayText) : + ""; + } + + function _requestLegacyHints(hint, editor, options) { + return new Promise(function (resolve, reject) { + let completed = false; + const finish = function (result) { + if (!completed) { + completed = true; + resolve(result || null); + } + }; + + try { + if (hint.async) { + hint(editor, finish, options); + return; + } + + const result = hint(editor, options); + if (result && typeof result.then === "function") { + result.then(finish, reject); + } else { + finish(result); + } + } catch (error) { + reject(error); + } + }); + } + + function _applicableHintHelpers(editor, candidates) { + if (!editor.somethingSelected()) { + return candidates; + } + return candidates.filter(function (candidate) { + return candidate.supportsSelection; + }); + } + + function _resolveAutoHint(editor, position) { + const hintHelpers = editor.getHelpers(position, "hint"); + const hintWords = editor.getHelper(position, "hintWords"); + + if (hintHelpers.length) { + const resolved = function (currentEditor, callback, options) { + const candidates = _applicableHintHelpers( + currentEditor, + hintHelpers + ); + const tryHint = function (index) { + if (index >= candidates.length) { + callback(null); + return; + } + _requestLegacyHints( + candidates[index], + currentEditor, + options + ).then(function (result) { + if (result && result.list && result.list.length) { + callback(result); + } else { + tryHint(index + 1); + } + }).catch(function () { + tryHint(index + 1); + }); + }; + tryHint(0); + }; + resolved.async = true; + resolved.supportsSelection = true; + return resolved; + } + if (hintWords) { + return function (currentEditor) { + return CodeMirrorCompat.hint.fromList(currentEditor, { + words: hintWords + }); + }; + } + if (CodeMirrorCompat.hint.anyword) { + return function (currentEditor, options) { + return CodeMirrorCompat.hint.anyword(currentEditor, options); + }; + } + return function () {}; + } + + function _parseLegacyHintOptions(editor, suppliedOptions) { + const options = Object.assign({ + hint: CodeMirrorCompat.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null + }, editor.getOption("hintOptions") || {}, suppliedOptions || {}); + + if (options.hint && typeof options.hint.resolve === "function") { + options.hint = options.hint.resolve( + editor, + editor.getCursor("start") + ); + } + return options; + } + + function _closeLegacyHint(editor, completion, closeNative) { + if (!completion || editor.state.completionActive !== completion) { + return; + } + if (closeNative !== false) { + _runNativeViewCommand( + editor, + _nativeKeyMapCommand(CM6.completionKeymap, "Escape") + ); + } + if (completion.keyMap) { + editor.removeKeyMap(completion.keyMap); + } + if (completion.blurHandler) { + editor.off("blur", completion.blurHandler); + } + editor.state.completionActive = null; + if (completion.opened && completion.data) { + signal(completion.data, "close"); + } + signal(editor, "endCompletion", editor); + } + + function _applyLegacyHint(editor, completion, data, item, from, to) { + const itemOptions = typeof item === "object" && item ? item : {}; + const itemFrom = itemOptions.from || data.from || + editor.posFromIndex(from); + const itemTo = itemOptions.to || data.to || + editor.posFromIndex(to); + + if (typeof itemOptions.hint === "function") { + itemOptions.hint(editor, data, itemOptions); + } else { + editor.replaceRange( + _legacyHintText(item), + itemFrom, + itemTo, + "complete" + ); + } + signal(data, "pick", item); + _closeLegacyHint(editor, completion, false); + } + + function _convertLegacyHintResult(editor, completion, data, context) { + if (!data || !Array.isArray(data.list) || !data.list.length) { + return null; + } + + const from = data.from ? + editor.indexFromPos(editor.clipPos(data.from)) : + context.pos; + const to = data.to ? + editor.indexFromPos(editor.clipPos(data.to)) : + context.pos; + const selectedHint = Math.max( + 0, + Math.min(Number(data.selectedHint) || 0, data.list.length - 1) + ); + const converted = data.list.map(function (item, index) { + const itemOptions = typeof item === "object" && item ? item : {}; + const text = _legacyHintText(item); + const completionItem = { + label: text || " ", + apply: function (_view, _selected, completionFrom, completionTo) { + _applyLegacyHint( + editor, + completion, + data, + item, + completionFrom, + completionTo + ); + }, + _legacyClassName: itemOptions.className || "", + _legacyData: data, + _legacyItem: item + }; + + if (itemOptions.displayText !== null && + itemOptions.displayText !== undefined) { + completionItem.displayLabel = String(itemOptions.displayText); + } + if (itemOptions.detail !== null && itemOptions.detail !== undefined) { + completionItem.detail = String(itemOptions.detail); + } + if (itemOptions.type) { + completionItem.type = itemOptions.type; + } + if (index === selectedHint) { + completionItem.boost = 1000000; + } + return completionItem; + }); + + return { + filter: false, + from: Math.min(from, context.pos), + options: converted, + to: Math.max(from, to) + }; + } + + function _legacyHintKeyMap(editor, completion) { + const moveDown = _nativeKeyMapCommand( + CM6.completionKeymap, + "ArrowDown" + ); + const moveUp = _nativeKeyMapCommand( + CM6.completionKeymap, + "ArrowUp" + ); + const pageDown = _nativeKeyMapCommand( + CM6.completionKeymap, + "PageDown" + ); + const pageUp = _nativeKeyMapCommand( + CM6.completionKeymap, + "PageUp" + ); + const accept = _nativeKeyMapCommand( + CM6.completionKeymap, + "Enter" + ); + const run = function (command) { + return _runNativeViewCommand(editor, command) ? + true : + Pass; + }; + + return { + Up: function () { + return run(moveUp); + }, + Down: function () { + return run(moveDown); + }, + PageUp: function () { + return run(pageUp); + }, + PageDown: function () { + return run(pageDown); + }, + Enter: function () { + return run(accept); + }, + Tab: function () { + return run(accept); + }, + Esc: function () { + completion.close(); + return true; + } + }; + } + + function _openLegacyHint(editor, suppliedOptions) { + const options = _parseLegacyHintOptions(editor, suppliedOptions); + const hint = options.hint; + const selections = editor.listSelections(); + + if (typeof hint !== "function" || selections.length > 1) { + return; + } + if (editor.somethingSelected()) { + if (!hint.supportsSelection) { + return; + } + for (let index = 0; index < selections.length; index++) { + if (selections[index].head.line !== + selections[index].anchor.line) { + return; + } + } + } + + if (editor.state.completionActive) { + editor.state.completionActive.close(); + } + + const completion = { + blurHandler: null, + data: null, + keyMap: null, + opened: false, + active: function () { + return editor.state.completionActive === completion; + }, + close: function () { + _closeLegacyHint(editor, completion, true); + } + }; + editor.state.completionActive = completion; + signal(editor, "startCompletion", editor); + + completion.promise = _requestLegacyHints( + hint, + editor, + options + ).then(function (initialData) { + if (!completion.active()) { + return; + } + if (!initialData || !Array.isArray(initialData.list) || + !initialData.list.length) { + _closeLegacyHint(editor, completion, false); + return; + } + + completion.data = initialData; + if (options.completeSingle && initialData.list.length === 1) { + const cursorOffset = editor.indexFromPos(editor.getCursor()); + _applyLegacyHint( + editor, + completion, + initialData, + initialData.list[0], + cursorOffset, + cursorOffset + ); + return; + } + + let firstResult = initialData; + const source = function (context) { + if (firstResult) { + const result = firstResult; + firstResult = null; + return _convertLegacyHintResult( + editor, + completion, + result, + context + ); + } + return _requestLegacyHints( + hint, + editor, + options + ).then(function (nextData) { + if (!completion.active()) { + return null; + } + if (completion.data) { + signal(completion.data, "update"); + } + completion.data = nextData; + if (!nextData || !Array.isArray(nextData.list) || + !nextData.list.length) { + window.setTimeout(function () { + _closeLegacyHint(editor, completion, false); + }, 0); + return null; + } + signal(nextData, "shown"); + return _convertLegacyHintResult( + editor, + completion, + nextData, + context + ); + }); + }; + const autocomplete = CM6.autocompletion({ + activateOnTyping: false, + closeOnBlur: options.closeOnUnfocus !== false, + defaultKeymap: true, + filterStrict: false, + optionClass: function (item) { + return item._legacyClassName || ""; + }, + override: [source], + selectOnOpen: true + }); + + if (!editor.state.legacyHintCompartment) { + editor.state.legacyHintCompartment = new CM6.Compartment(); + editor._view.dispatch({ + effects: CM6.StateEffect.appendConfig.of( + editor.state.legacyHintCompartment.of(autocomplete) + ) + }); + } else { + editor._view.dispatch({ + effects: editor.state.legacyHintCompartment.reconfigure( + autocomplete + ) + }); + } + + completion.opened = true; + completion.keyMap = _legacyHintKeyMap(editor, completion); + editor.addKeyMap(completion.keyMap); + if (options.closeOnUnfocus !== false) { + completion.blurHandler = function () { + completion.close(); + }; + editor.on("blur", completion.blurHandler); + } + signal(initialData, "shown"); + + const startCompletion = _nativeKeyMapCommand( + CM6.completionKeymap, + "Ctrl-Space" + ); + if (!_runNativeViewCommand(editor, startCompletion)) { + _closeLegacyHint(editor, completion, false); + } + }).catch(function (error) { + completion.error = error; + _closeLegacyHint(editor, completion, true); + }); + + return completion; + } + + function _installLegacyHintCompatibility() { + if (installedLegacyCompatibilityModules.has("hint")) { + return true; + } + if (!CM6.autocompletion || + !_nativeKeyMapCommand(CM6.completionKeymap, "Ctrl-Space")) { + return false; + } + + registerHelper("hint", "fromList", function (editor, options) { + const cursor = editor.getCursor(); + const token = editor.getTokenAt(cursor); + const tokenText = token.string || ""; + const endsWithWord = tokenText && + /\w/.test(tokenText.charAt(tokenText.length - 1)); + const term = endsWithWord ? tokenText : ""; + const from = endsWithWord ? + Pos(cursor.line, token.start) : + Pos(cursor.line, cursor.ch); + const to = Pos(cursor.line, cursor.ch); + const words = options && options.words || []; + const list = words.filter(function (word) { + return String(word).slice(0, term.length) === term; + }); + return list.length ? { + from: from, + list: list, + to: to + } : undefined; + }); + + registerHelper("hint", "anyword", function (editor, options) { + const word = options && options.word || /[\w$]+/; + const range = options && options.range || 500; + const cursor = editor.getCursor(); + const currentLine = editor.getLine(cursor.line); + let start = cursor.ch; + while (start) { + word.lastIndex = 0; + if (!word.test(currentLine.charAt(start - 1))) { + break; + } + start--; + } + const currentWord = start !== cursor.ch && + currentLine.slice(start, cursor.ch); + const list = options && options.list ? + options.list.slice() : + []; + const seen = {}; + list.forEach(function (item) { + seen[_legacyHintText(item)] = true; + }); + const flags = [ + word.ignoreCase ? "i" : "", + word.multiline ? "m" : "", + word.unicode ? "u" : "" + ].join(""); + const expression = new RegExp(word.source, "g" + flags); + + for (let direction = -1; direction <= 1; direction += 2) { + let line = cursor.line; + const endLine = Math.min( + Math.max(line + direction * range, editor.firstLine()), + editor.lastLine() + ) + direction; + for (; line !== endLine; line += direction) { + const text = editor.getLine(line); + expression.lastIndex = 0; + let match; + while ((match = expression.exec(text))) { + if (line === cursor.line && + match[0] === currentWord) { + continue; + } + if ((!currentWord || + match[0].indexOf(currentWord) === 0) && + !seen[match[0]]) { + seen[match[0]] = true; + list.push(match[0]); + } + if (!match[0].length) { + expression.lastIndex++; + } + } + } + } + return { + from: Pos(cursor.line, start), + list: list, + to: Pos(cursor.line, cursor.ch) + }; + }); + + registerHelper("hint", "auto", { + resolve: _resolveAutoHint + }); + defineOption("hintOptions", null); + defineExtension("showHint", function (options) { + return _openLegacyHint(this, options); + }); + CodeMirrorCompat.showHint = function (editor, getHints, options) { + if (!getHints) { + return editor.showHint(options); + } + if (options && options.async) { + getHints.async = true; + } + return editor.showHint(Object.assign({}, options || {}, { + hint: getHints + })); + }; + commands.autocomplete = function (editor) { + return editor.showHint(); + }; + + installedLegacyCompatibilityModules.add("hint"); + return true; + } + + function _installLegacySearchCompatibility() { + if (installedLegacyCompatibilityModules.has("search")) { + return true; + } + + const openSearch = _nativeKeyMapCommand( + CM6.searchKeymap, + "Mod-f" + ); + const findNext = _nativeKeyMapCommand( + CM6.searchKeymap, + "Mod-g" + ); + const findPrevious = _nativeKeyMapCommand( + CM6.searchKeymap, + "Mod-g", + true + ); + const closeSearch = _nativeKeyMapCommand( + CM6.searchKeymap, + "Escape" + ); + const goToLine = _nativeKeyMapCommand( + CM6.searchKeymap, + "Mod-Alt-g" + ); + if (!openSearch || !findNext || !findPrevious || + !closeSearch || !goToLine) { + return false; + } + + const command = function (nativeCommand) { + return function (editor) { + return _runNativeViewCommand(editor, nativeCommand); + }; + }; + commands.find = command(openSearch); + commands.findPersistent = commands.find; + commands.findNext = command(findNext); + commands.findPersistentNext = commands.findNext; + commands.findPrev = command(findPrevious); + commands.findPersistentPrev = commands.findPrev; + commands.clearSearch = command(closeSearch); + commands.replace = commands.find; + commands.replaceAll = commands.find; + commands.jumpToLine = command(goToLine); + + installedLegacyCompatibilityModules.add("search"); + return true; + } + + function installLegacyCompatibility(modulePath) { + switch (String(modulePath || "") + .replace(/[?#].*$/, "") + .replace(/\.js$/, "")) { + case "addon/hint/show-hint": + case "addon/hint/anyword-hint": + return _installLegacyHintCompatibility(); + case "addon/search/jump-to-line": + case "addon/search/search": + return _installLegacySearchCompatibility(); + default: + return false; + } + } + + function defineMode(name, modeFactory) { + if (arguments.length > 2) { + modeFactory.dependencies = Array.prototype.slice.call(arguments, 2); + } + if (!defaults.mode && name !== "null") { + defaults.mode = name; + } + modes[name] = modeFactory; + } + + function defineMIME(mime, specification) { + mimeModes[mime] = specification; + } + + function resolveMode(specification) { + let resolved = specification; + + if (typeof resolved === "string" && + Object.prototype.hasOwnProperty.call(mimeModes, resolved)) { + resolved = mimeModes[resolved]; + } else if (resolved && typeof resolved.name === "string" && + Object.prototype.hasOwnProperty.call(mimeModes, resolved.name)) { + let mimeSpec = mimeModes[resolved.name]; + if (typeof mimeSpec === "string") { + mimeSpec = {name: mimeSpec}; + } + resolved = Object.assign(Object.create(mimeSpec), resolved); + resolved.name = mimeSpec.name; + } else if (typeof resolved === "string" && + /^[\w-]+\/[\w-]+\+xml$/.test(resolved)) { + return resolveMode("application/xml"); + } else if (typeof resolved === "string" && + /^[\w-]+\/[\w-]+\+json$/.test(resolved)) { + return resolveMode("application/json"); + } + + if (typeof resolved === "string") { + return {name: resolved}; + } + return resolved || {name: "null"}; + } + + function getMode(options, specification) { + const resolved = resolveMode(specification); + let modeFactory = modes[resolved.name]; + + if (!modeFactory && loadMode(resolved.name)) { + modeFactory = modes[resolved.name]; + } + + if (!modeFactory) { + if (resolved.name === "null") { + return createNullMode(); + } + return getMode(options, "text/plain"); + } + + const mode = modeFactory(options || {}, resolved); + const registeredExtensions = modeExtensions[resolved.name]; + if (registeredExtensions) { + Object.keys(registeredExtensions).forEach(function (property) { + if (Object.prototype.hasOwnProperty.call(mode, property)) { + mode["_" + property] = mode[property]; + } + mode[property] = registeredExtensions[property]; + }); + } + + mode.name = resolved.name; + if (resolved.helperType) { + mode.helperType = resolved.helperType; + } + if (resolved.modeProps) { + Object.assign(mode, resolved.modeProps); + } + return mode; + } + + function hasMode(specification) { + return Boolean(modes[resolveMode(specification).name]); + } + + function isModeOverridden(specification) { + const modeName = resolveMode(specification).name; + return Object.prototype.hasOwnProperty.call( + builtInModeFactories, + modeName + ) && modes[modeName] !== builtInModeFactories[modeName]; + } + + function loadMode(modeName) { + const requestedModes = bundledModeModules[modeName] || [modeName]; + let loaded = true; + + requestedModes.forEach(function (requestedMode) { + if (Object.prototype.hasOwnProperty.call(modes, requestedMode)) { + return; + } + + const parser = bundledModes[requestedMode]; + if (parser) { + defineMode(requestedMode, parserFactory(parser)); + return; + } + loaded = false; + }); + + return loaded; + } + + function extendMode(modeName, properties) { + if (!modeExtensions[modeName]) { + modeExtensions[modeName] = {}; + } + Object.assign(modeExtensions[modeName], properties); + } + + function copyState(mode, state) { + if (state === true) { + return state; + } + if (mode.copyState) { + return mode.copyState(state); + } + + const copiedState = {}; + Object.keys(state || {}).forEach(function (name) { + const value = state[name]; + copiedState[name] = Array.isArray(value) ? value.slice() : value; + }); + return copiedState; + } + + function startState(mode, argument1, argument2) { + return mode.startState ? mode.startState(argument1, argument2) : true; + } + + function innerMode(mode, state) { + let currentMode = mode; + let currentState = state; + let result; + + while (currentMode.innerMode) { + result = currentMode.innerMode(currentState); + if (!result || result.mode === currentMode) { + break; + } + currentMode = result.mode; + currentState = result.state; + } + + return result || { + mode: currentMode, + state: currentState + }; + } + + class StringStream extends CM6.StringStream { + constructor(string, tabSize, lineOracle) { + super(string, tabSize || 8, 2); + this.lineStart = 0; + this.lineOracle = lineOracle; + } + + sol() { + return this.pos === this.lineStart; + } + + column() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn( + this.string, + this.start, + this.tabSize, + this.lastColumnPos, + this.lastColumnValue + ); + this.lastColumnPos = this.start; + } + + return this.lastColumnValue - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + } + + indentation() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + } + + hideFirstChars(count, callback) { + this.lineStart += count; + try { + return callback(); + } finally { + this.lineStart -= count; + } + } + + lookAhead(lineCount) { + return this.lineOracle && this.lineOracle.lookAhead(lineCount); + } + + baseToken() { + return this.lineOracle && this.lineOracle.baseToken(this.pos); + } + + match(pattern, consume, caseInsensitive) { + if (typeof pattern === "string") { + const normalize = function (value) { + return caseInsensitive ? value.toLowerCase() : value; + }; + const candidate = this.string.substr(this.pos, pattern.length); + if (normalize(candidate) === normalize(pattern)) { + if (consume !== false) { + this.pos += pattern.length; + } + return true; + } + return; + } + + const match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) { + return null; + } + if (match && consume !== false) { + this.pos += match[0].length; + } + return match; + } + } + + function normalizeKeyName(nameToNormalize) { + const parts = nameToNormalize.split(/-(?!$)/); + let name = parts[parts.length - 1]; + let alt = false; + let ctrl = false; + let shift = false; + let cmd = false; + + for (let index = 0; index < parts.length - 1; index++) { + const modifier = parts[index]; + if (/^(cmd|meta|m)$/i.test(modifier)) { + cmd = true; + } else if (/^a(lt)?$/i.test(modifier)) { + alt = true; + } else if (/^(c|ctrl|control)$/i.test(modifier)) { + ctrl = true; + } else if (/^s(hift)?$/i.test(modifier)) { + shift = true; + } else { + throw new Error("Unrecognized modifier name: " + modifier); + } + } + + if (alt) { + name = "Alt-" + name; + } + if (ctrl) { + name = "Ctrl-" + name; + } + if (cmd) { + name = "Cmd-" + name; + } + if (shift) { + name = "Shift-" + name; + } + return name; + } + + function normalizeKeyMap(map) { + const normalized = {}; + + Object.keys(map).forEach(function (mapKeyName) { + const value = map[mapKeyName]; + if (/^(name|fallthrough|(de|at)tach)$/.test(mapKeyName)) { + return; + } + if (value === "...") { + delete map[mapKeyName]; + return; + } + + const keys = mapKeyName.split(" ").map(normalizeKeyName); + keys.forEach(function (_key, index) { + const name = keys.slice(0, index + 1).join(" "); + const binding = index === keys.length - 1 ? value : "..."; + if (normalized[name] && normalized[name] !== binding) { + throw new Error("Inconsistent bindings for " + name); + } + normalized[name] = binding; + }); + delete map[mapKeyName]; + }); + + Object.assign(map, normalized); + return map; + } + + function getKeyMap(map) { + return typeof map === "string" ? keyMap[map] : map; + } + + function lookupKey(key, map, handle, context) { + const resolvedMap = getKeyMap(map); + if (!resolvedMap) { + return; + } + + const binding = typeof resolvedMap.call === "function" ? + resolvedMap.call(key, context) : + resolvedMap[key]; + + if (binding === false) { + return "nothing"; + } + if (binding === "...") { + return "multi"; + } + if (binding !== null && binding !== undefined && handle(binding)) { + return "handled"; + } + + if (resolvedMap.fallthrough) { + const fallthrough = Array.isArray(resolvedMap.fallthrough) ? + resolvedMap.fallthrough : + [resolvedMap.fallthrough]; + for (let index = 0; index < fallthrough.length; index++) { + const result = lookupKey(key, fallthrough[index], handle, context); + if (result) { + return result; + } + } + } + } + + const KEY_NAMES = { + 3: "Pause", + 8: "Backspace", + 9: "Tab", + 13: "Enter", + 16: "Shift", + 17: "Ctrl", + 18: "Alt", + 19: "Pause", + 20: "CapsLock", + 27: "Esc", + 32: "Space", + 33: "PageUp", + 34: "PageDown", + 35: "End", + 36: "Home", + 37: "Left", + 38: "Up", + 39: "Right", + 40: "Down", + 44: "PrintScrn", + 45: "Insert", + 46: "Delete", + 59: ";", + 61: "=", + 91: "Mod", + 92: "Mod", + 93: "Mod", + 106: "*", + 107: "=", + 109: "-", + 110: ".", + 111: "/", + 145: "ScrollLock", + 173: "-", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "'", + 224: "Mod", + 63232: "Up", + 63233: "Down", + 63234: "Left", + 63235: "Right", + 63272: "Delete", + 63273: "Home", + 63275: "End", + 63276: "PageUp", + 63277: "PageDown", + 63302: "Insert" + }; + + for (let digit = 0; digit < 10; digit++) { + KEY_NAMES[digit + 48] = String(digit); + KEY_NAMES[digit + 96] = String(digit); + } + for (let letter = 65; letter <= 90; letter++) { + KEY_NAMES[letter] = String.fromCharCode(letter); + } + for (let functionKey = 1; functionKey <= 12; functionKey++) { + KEY_NAMES[functionKey + 111] = "F" + functionKey; + KEY_NAMES[functionKey + 63235] = "F" + functionKey; + } + + function keyName(event, noShift) { + if (event.altGraphKey) { + return false; + } + + let name = KEY_NAMES[event.keyCode || event.which]; + if (!name && event.key) { + const aliases = { + " ": "Space", + ArrowDown: "Down", + ArrowLeft: "Left", + ArrowRight: "Right", + ArrowUp: "Up", + Control: "Ctrl", + Escape: "Esc", + Meta: "Mod", + OS: "Mod", + PrintScreen: "PrintScrn" + }; + name = aliases[event.key] || event.key; + if (name.length === 1 && /[a-z]/i.test(name)) { + name = name.toUpperCase(); + } + } + if (!name || name === "Unidentified" || name === "Dead") { + return false; + } + + const baseName = name; + if (event.altKey && baseName !== "Alt") { + name = "Alt-" + name; + } + if (event.ctrlKey && baseName !== "Ctrl") { + name = "Ctrl-" + name; + } + if (event.metaKey && baseName !== "Mod") { + name = "Cmd-" + name; + } + if (!noShift && event.shiftKey && baseName !== "Shift") { + name = "Shift-" + name; + } + return name; + } + + function isModifierKey(value) { + const name = typeof value === "string" ? value : keyName(value, true); + return name === "Ctrl" || name === "Alt" || name === "Shift" || name === "Mod"; + } + + const LEGACY_STREAM_STYLE_MAP = { + attributeName: "attribute", + character: "string-2", + heading: "header", + invalid: "error", + modifier: "qualifier", + propertyName: "property", + "string.special": "string-2", + tagName: "tag", + typeName: "type", + variableName: "variable", + "variableName.constant": "variable-3", + "variableName.definition": "def", + "variableName.function": "variable callee", + "variableName.local": "variable-2", + "variableName.special": "variable-2", + "variableName.standard": "builtin" + }; + + function translateStreamStyle(style, styleMap) { + if (!style || typeof style !== "string") { + return style; + } + + return style.split(/\s+/).map(function (part) { + return styleMap[part] || part; + }).join(" "); + } + + function legacyCloseBrackets(closeBrackets) { + const closingBracket = { + "(": ")", + "[": "]", + "{": "}", + "'": "'", + "\"": "\"", + "`": "`" + }; + const brackets = closeBrackets && closeBrackets.brackets; + if (!Array.isArray(brackets)) { + return; + } + return brackets.map(function (open) { + return open + (closingBracket[open] || open); + }).join(""); + } + + function cloneParser(parser, config, additionalStyleMap) { + const modeConfig = config || {}; + const configuredIndentUnit = Number(modeConfig.indentUnit); + const indentUnit = Number.isFinite(configuredIndentUnit) ? + configuredIndentUnit : + 2; + const styleMap = Object.assign( + {}, + LEGACY_STREAM_STYLE_MAP, + additionalStyleMap || {} + ); + const clonedParser = Object.assign({}, parser); + + if (parser.startState) { + clonedParser.startState = function () { + return parser.startState(indentUnit); + }; + } + + clonedParser.token = function (stream, state) { + stream.indentUnit = indentUnit; + return translateStreamStyle(parser.token(stream, state), styleMap); + }; + + if (parser.blankLine) { + clonedParser.blankLine = function (state) { + return parser.blankLine(state, indentUnit); + }; + } + + if (parser.indent) { + clonedParser.indent = function (state, textAfter) { + const indentation = parser.indent(state, textAfter, { + unit: indentUnit + }); + return indentation === null || indentation === undefined ? + Pass : + indentation; + }; + } + + const languageData = parser.languageData || {}; + const commentTokens = languageData.commentTokens; + if (commentTokens) { + if (Object.prototype.hasOwnProperty.call(commentTokens, "line")) { + clonedParser.lineComment = commentTokens.line; + } + if (commentTokens.block) { + clonedParser.blockCommentStart = commentTokens.block.open; + clonedParser.blockCommentEnd = commentTokens.block.close; + } + } + if (languageData.indentOnInput) { + clonedParser.electricInput = languageData.indentOnInput; + } + const closeBrackets = legacyCloseBrackets(languageData.closeBrackets); + if (closeBrackets) { + clonedParser.closeBrackets = closeBrackets; + } + + if (parser.startState) { + const initialState = parser.startState(indentUnit); + const baseTokenizer = initialState && initialState.tokenize; + if (baseTokenizer && Object.prototype.hasOwnProperty.call(initialState, "lastType")) { + clonedParser.expressionAllowed = function (stream, state, backUp) { + return state.tokenize === baseTokenizer && + /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}(,;:]|=>)$/ + .test(state.lastType) || + state.lastType === "quasi" && + /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))); + }; + clonedParser.skipExpression = function (state) { + const stream = new StringStream("true", modeConfig.tabSize || 4); + clonedParser.token(stream, state); + }; + } + } + + return clonedParser; + } + + function parserFactory(parser, additionalStyleMap) { + return function (config) { + return cloneParser(parser, config, additionalStyleMap); + }; + } + + function createNullMode() { + return { + token: function (stream) { + stream.skipToEnd(); + return null; + } + }; + } + + function createMarkdownMode(config, parserConfig) { + const modeConfig = parserConfig || {}; + const githubFlavored = Boolean(modeConfig.githubFlavored); + const highlightFormatting = Boolean(modeConfig.highlightFormatting); + const taskLists = modeConfig.taskLists === undefined ? + githubFlavored : + Boolean(modeConfig.taskLists); + const strikethrough = modeConfig.strikethrough === undefined ? + githubFlavored : + Boolean(modeConfig.strikethrough); + const emoji = modeConfig.emoji === undefined ? + githubFlavored : + Boolean(modeConfig.emoji); + const fencedCodeBlockHighlighting = + modeConfig.fencedCodeBlockHighlighting !== false; + const xmlEnabled = modeConfig.xml !== false; + const htmlMode = xmlEnabled ? getMode(config, "text/html") : null; + const fencedModeAliases = { + bash: "text/x-sh", + c: "text/x-csrc", + "c++": "text/x-c++src", + cpp: "text/x-c++src", + cs: "text/x-csharp", + csharp: "text/x-csharp", + html: "text/html", + js: "javascript", + javascript: "javascript", + json: "application/json", + jsx: "text/jsx", + kt: "text/x-kotlin", + kotlin: "text/x-kotlin", + less: "text/x-less", + md: "markdown", + mysql: "text/x-mysql", + php: "application/x-httpd-php-open", + py: "python", + rb: "ruby", + scss: "text/x-scss", + sh: "text/x-sh", + sql: "text/x-sql", + ts: "application/typescript", + tsx: "text/typescript-jsx", + typescript: "application/typescript", + xml: "application/xml", + yml: "text/x-yaml" + }; + let mode; + + function withFormatting(type, formattingType) { + if (!highlightFormatting || !formattingType) { + return type; + } + return [ + type, + "formatting", + "formatting-" + formattingType + ].filter(Boolean).join(" "); + } + + function contextualType(state, type) { + return [ + type, + state.header ? "header header-" + state.header : "", + state.quote ? "quote" : "", + state.list ? "variable-2" : "" + ].filter(Boolean).join(" ") || null; + } + + function resetLineState(state) { + state.header = 0; + state.quote = 0; + state.list = false; + state.taskList = false; + } + + function fencedMode(languageName) { + if (!fencedCodeBlockHighlighting) { + return null; + } + const requestedMode = String( + languageName || + modeConfig.fencedCodeBlockDefaultMode || + "text/plain" + ).toLowerCase(); + const resolvedMode = getMode( + config, + fencedModeAliases[requestedMode] || requestedMode + ); + return resolvedMode.name === "null" ? null : resolvedMode; + } + + function closeFencePattern(state) { + const escapedCharacter = state.fenceCharacter === "`" ? "`" : "~"; + return new RegExp( + "^ {0,3}" + escapedCharacter + "{" + state.fenceLength + ",}\\s*$" + ); + } + + function startHTML(state) { + state.htmlState = startState(htmlMode); + state.htmlActive = true; + } + + function htmlBlockIsComplete(state) { + const inner = innerMode(htmlMode, state.htmlState); + return inner.mode && inner.mode.name === "xml" && + inner.state && + inner.state.tagStart === null && + !inner.state.context && + inner.state.tokenize && + inner.state.tokenize.isInText; + } + + function tokenHTML(stream, state) { + const style = htmlMode.token(stream, state.htmlState); + if (htmlBlockIsComplete(state)) { + state.htmlActive = false; + } + return style; + } + + mode = { + startState: function () { + return { + fencedCode: false, + fenceCharacter: null, + fenceLength: 0, + localMode: null, + localState: null, + htmlActive: false, + htmlState: null, + header: 0, + quote: 0, + list: false, + taskList: false + }; + }, + + copyState: function (state) { + const copiedState = Object.assign({}, state); + copiedState.localState = state.localMode && state.localState ? + copyState(state.localMode, state.localState) : + null; + copiedState.htmlState = htmlMode && state.htmlState ? + copyState(htmlMode, state.htmlState) : + null; + return copiedState; + }, + + blankLine: function (state) { + resetLineState(state); + if (state.localMode && state.localMode.blankLine) { + state.localMode.blankLine(state.localState); + } + if (state.htmlActive && htmlMode && htmlMode.blankLine) { + htmlMode.blankLine(state.htmlState); + if (htmlBlockIsComplete(state)) { + state.htmlActive = false; + } + } + return null; + }, + + token: function (stream, state) { + if (stream.sol()) { + resetLineState(state); + + if (state.fencedCode) { + if (stream.match(closeFencePattern(state))) { + state.fencedCode = false; + state.fenceCharacter = null; + state.fenceLength = 0; + state.localMode = null; + state.localState = null; + return withFormatting("comment", "code-block"); + } + if (state.localMode) { + return state.localMode.token(stream, state.localState); + } + stream.skipToEnd(); + return withFormatting("comment", "code-block"); + } + + if (state.htmlActive) { + return tokenHTML(stream, state); + } + + const openingFence = stream.match( + /^ {0,3}(`{3,}|~{3,})[ \t]*([\w/+#-]*)[^\n`]*$/ + ); + if (openingFence) { + state.fencedCode = true; + state.fenceCharacter = openingFence[1].charAt(0); + state.fenceLength = openingFence[1].length; + state.localMode = fencedMode(openingFence[2]); + state.localState = state.localMode ? + startState(state.localMode) : + null; + return withFormatting("comment", "code-block"); + } + + const atxHeader = stream.match(/^ {0,3}(#{1,6})(?:\s+|$)/); + if (atxHeader) { + state.header = atxHeader[1].length; + stream.skipToEnd(); + return withFormatting( + "header header-" + state.header, + "header" + ); + } + + const setextHeader = stream.match(/^ {0,3}(=+|-{2,})\s*$/); + if (setextHeader) { + state.header = setextHeader[1].charAt(0) === "=" ? 1 : 2; + return withFormatting( + "header header-" + state.header, + "header" + ); + } + + if (stream.match(/^ {0,3}(?:[*_-]\s*){3,}$/)) { + return "hr"; + } + + if (stream.match(/^(?: {4}|\t)/)) { + stream.skipToEnd(); + return "comment"; + } + + if (stream.match(/^ {0,3}> ?/)) { + state.quote = 1; + return withFormatting("quote", "quote"); + } + + if (stream.match(/^(\s*)(?:[*+-]|\d+[.)])\s+/)) { + state.list = true; + state.taskList = taskLists && + Boolean(stream.match(/^\[(?:x| )\](?=\s)/i, false)); + return withFormatting("variable-2", "list"); + } + } + + if (state.fencedCode) { + if (state.localMode) { + return state.localMode.token(stream, state.localState); + } + stream.skipToEnd(); + return withFormatting("comment", "code-block"); + } + + if (state.htmlActive) { + return tokenHTML(stream, state); + } + + if (state.taskList) { + const task = stream.match(/^\[(x| )\]/i); + state.taskList = false; + if (task) { + return withFormatting( + task[1] === " " ? "meta" : "property", + "task" + ); + } + } + + if (stream.eatSpace()) { + return contextualType(state, null); + } + + if (stream.match(/^`+[^`]*`+/)) { + return contextualType(state, "comment"); + } + if (strikethrough && stream.match(/^~~(?:[^~]|~(?!~))+~~/)) { + return contextualType(state, "strikethrough"); + } + if (stream.match(/^(?:\*\*|__)(?=\S)/)) { + stream.match(/^.*?(?:\*\*|__)/); + return contextualType(state, "strong"); + } + if (stream.match(/^(?:\*|_)(?=\S)/)) { + stream.match(/^.*?(?:\*|_)/); + return contextualType(state, "em"); + } + if (stream.match(/^!\[[^\]]*\](?:\([^)]+\)|\[[^\]]*\])/)) { + return contextualType(state, "image"); + } + if (stream.match(/^\[[^\]]*\](?:\([^)]+\)|\[[^\]]*\])/)) { + return contextualType(state, "link"); + } + if (stream.match(/^<(?:(?:https?:\/\/|mailto:)[^>]+|[^>]+@[^>]+)>/i)) { + return contextualType(state, "link"); + } + if (githubFlavored && + (stream.sol() || + /\s/.test(stream.string.charAt(stream.pos - 1)))) { + if (stream.match( + /^(?:[a-zA-Z0-9_-]+\/)?(?:[a-zA-Z0-9_-]+@)?(?=.{0,6}\d)[a-f0-9]{7,40}\b/i + ) || stream.match( + /^(?:[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9_-]*#[0-9]+\b/ + )) { + return contextualType(state, "link"); + } + } + if (githubFlavored && + stream.match(/^(?:(?:https?:\/\/|www\.)[^\s<>()]+)/i)) { + return contextualType(state, "link"); + } + if (emoji && + stream.match(/^:(?:[a-z_\d+][a-z_\d+-]*|-[a-z_\d+][a-z_\d+-]*):/i)) { + return contextualType(state, "builtin"); + } + if (htmlMode && stream.match( + /^<(?:!--|\?|!\[CDATA\[|\/?[A-Za-z][A-Za-z0-9-]*(?:\s|\/?>))/, + false + )) { + startHTML(state); + return tokenHTML(stream, state); + } + if (stream.match(/^\\./)) { + return contextualType(state, null); + } + + if (!stream.eatWhile(/[^\s`*_![\\<~:]/)) { + stream.next(); + } + return contextualType(state, null); + }, + + indent: function (state, textAfter, line) { + if (state.localMode && state.localMode.indent) { + return state.localMode.indent(state.localState, textAfter, line); + } + if (state.htmlActive && htmlMode && htmlMode.indent) { + return htmlMode.indent(state.htmlState, textAfter, line); + } + return Pass; + }, + + innerMode: function (state) { + if (state.localMode && state.localState) { + return { + mode: state.localMode, + state: state.localState + }; + } + if (state.htmlActive && htmlMode && state.htmlState) { + return { + mode: htmlMode, + state: state.htmlState + }; + } + return { + mode: mode, + state: state + }; + }, + + blockCommentStart: "", + closeBrackets: "()[]{}''\"\"``", + fold: "markdown", + helperType: "markdown" + }; + return mode; + } + + function patternIndex(string, pattern, from, returnEnd) { + if (typeof pattern === "string") { + const found = string.indexOf(pattern, from); + return returnEnd && found > -1 ? found + pattern.length : found; + } + + pattern.lastIndex = 0; + const match = pattern.exec(from ? string.slice(from) : string); + return match ? match.index + from + (returnEnd ? match[0].length : 0) : -1; + } + + /** + * Combines a base stream mode with an overlay stream mode. This is a + * compatibility implementation of the historical CodeMirror overlay + * contract; both parsers operate on the CM6-backed document stream. + */ + function overlayMode(base, overlay, combine) { + return { + startState: function () { + return { + base: startState(base), + overlay: startState(overlay), + basePos: 0, + baseCur: null, + overlayPos: 0, + overlayCur: null, + streamSeen: null + }; + }, + + copyState: function (state) { + return { + base: copyState(base, state.base), + overlay: copyState(overlay, state.overlay), + basePos: state.basePos, + baseCur: null, + overlayPos: state.overlayPos, + overlayCur: null, + streamSeen: null + }; + }, + + token: function (stream, state) { + if (stream !== state.streamSeen || + Math.min(state.basePos, state.overlayPos) < stream.start) { + state.streamSeen = stream; + state.basePos = stream.start; + state.overlayPos = stream.start; + } + + if (stream.start === state.basePos) { + state.baseCur = base.token(stream, state.base); + state.basePos = stream.pos; + } + if (stream.start === state.overlayPos) { + stream.pos = stream.start; + state.overlayCur = overlay.token(stream, state.overlay); + state.overlayPos = stream.pos; + } + stream.pos = Math.min(state.basePos, state.overlayPos); + + if (state.overlayCur === null || + state.overlayCur === undefined) { + return state.baseCur; + } + const combineTokens = state.overlay && + state.overlay.combineTokens; + if (state.baseCur !== null && state.baseCur !== undefined && + (combineTokens || + combine && combineTokens == null)) { // eslint-disable-line eqeqeq + return state.baseCur + " " + state.overlayCur; + } + return state.overlayCur; + }, + + indent: base.indent && function (state, textAfter, line) { + return base.indent(state.base, textAfter, line); + }, + + electricChars: base.electricChars, + + innerMode: function (state) { + return { + state: state.base, + mode: base + }; + }, + + blankLine: function (state) { + const baseToken = base.blankLine ? + base.blankLine(state.base) : + undefined; + const overlayToken = overlay.blankLine ? + overlay.blankLine(state.overlay) : + undefined; + if (overlayToken === null || overlayToken === undefined) { + return baseToken; + } + return combine && baseToken !== null && + baseToken !== undefined ? + baseToken + " " + overlayToken : + overlayToken; + } + }; + } + + function multiplexingMode(outerMode) { + const innerModes = Array.prototype.slice.call(arguments, 1); + + return { + startState: function () { + return { + outer: startState(outerMode), + innerActive: null, + inner: null, + startingInner: false + }; + }, + + copyState: function (state) { + return { + outer: copyState(outerMode, state.outer), + innerActive: state.innerActive, + inner: state.innerActive ? + copyState(state.innerActive.mode, state.inner) : + null, + startingInner: state.startingInner + }; + }, + + token: function (stream, state) { + if (!state.innerActive) { + let cutOff = Infinity; + const originalContent = stream.string; + + for (let index = 0; index < innerModes.length; index++) { + const candidateMode = innerModes[index]; + const found = patternIndex( + originalContent, + candidateMode.open, + stream.pos + ); + if (found === stream.pos) { + if (!candidateMode.parseDelimiters) { + stream.match(candidateMode.open); + } + state.startingInner = Boolean(candidateMode.parseDelimiters); + state.innerActive = candidateMode; + + let outerIndent = 0; + if (outerMode.indent) { + const candidate = outerMode.indent(state.outer, "", ""); + if (candidate !== Pass) { + outerIndent = candidate; + } + } + state.inner = startState(candidateMode.mode, outerIndent); + if (candidateMode.parseDelimiters) { + let token = candidateMode.mode.token( + stream, + state.inner + ); + if (stream.pos > stream.start) { + state.startingInner = false; + } + if (candidateMode.innerStyle) { + token = token ? + token + " " + candidateMode.innerStyle : + candidateMode.innerStyle; + } + return token; + } + return candidateMode.delimStyle && + candidateMode.delimStyle + " " + + candidateMode.delimStyle + "-open"; + } + if (found !== -1 && found < cutOff) { + cutOff = found; + } + } + + if (cutOff !== Infinity) { + stream.string = originalContent.slice(0, cutOff); + } + const outerToken = outerMode.token(stream, state.outer); + stream.string = originalContent; + return outerToken; + } + + const active = state.innerActive; + const originalContent = stream.string; + if (!active.close && stream.sol()) { + state.innerActive = null; + state.inner = null; + return this.token(stream, state); + } + + const found = active.close && !state.startingInner ? + patternIndex( + originalContent, + active.close, + stream.pos, + active.parseDelimiters + ) : + -1; + + if (found === stream.pos && !active.parseDelimiters) { + stream.match(active.close); + state.innerActive = null; + state.inner = null; + return active.delimStyle && + active.delimStyle + " " + active.delimStyle + "-close"; + } + + if (found > -1) { + stream.string = originalContent.slice(0, found); + } + let token = active.mode.token(stream, state.inner); + stream.string = originalContent; + if (found === -1 && stream.pos > stream.start) { + state.startingInner = false; + } + if (found === stream.pos && active.parseDelimiters) { + state.innerActive = null; + state.inner = null; + } + if (active.innerStyle) { + token = token ? token + " " + active.innerStyle : active.innerStyle; + } + return token; + }, + + indent: function (state, textAfter, line) { + const activeMode = state.innerActive ? state.innerActive.mode : outerMode; + if (!activeMode.indent) { + return Pass; + } + return activeMode.indent( + state.innerActive ? state.inner : state.outer, + textAfter, + line + ); + }, + + blankLine: function (state) { + const activeMode = state.innerActive ? state.innerActive.mode : outerMode; + if (activeMode.blankLine) { + activeMode.blankLine(state.innerActive ? state.inner : state.outer); + } + + if (!state.innerActive) { + innerModes.forEach(function (candidateMode) { + if (candidateMode.open === "\n") { + state.innerActive = candidateMode; + state.inner = startState( + candidateMode.mode, + activeMode.indent ? activeMode.indent(state.outer, "", "") : 0 + ); + } + }); + } else if (state.innerActive.close === "\n") { + state.innerActive = null; + state.inner = null; + } + }, + + electricChars: outerMode.electricChars, + + innerMode: function (state) { + return state.inner ? { + state: state.inner, + mode: state.innerActive.mode + } : { + state: state.outer, + mode: outerMode + }; + } + }; + } + + function regexFromValue(value, anchored) { + if (!value) { + return /(?:)/; + } + + let flags = ""; + let source = value; + if (value instanceof RegExp) { + if (value.ignoreCase) { + flags += "i"; + } + if (value.unicode) { + flags += "u"; + } + source = value.source; + } + + return new RegExp((anchored === false ? "" : "^") + "(?:" + source + ")", flags); + } + + function tokenValue(value) { + if (!value) { + return null; + } + if (typeof value === "function") { + return value; + } + if (typeof value === "string") { + return value.replace(/\./g, " "); + } + return value.map(function (token) { + return token && token.replace(/\./g, " "); + }); + } + + function valuesEqual(left, right) { + if (left === right) { + return true; + } + if (!left || typeof left !== "object" || !right || typeof right !== "object") { + return false; + } + + const leftProperties = Object.keys(left); + const rightProperties = Object.keys(right); + if (leftProperties.length !== rightProperties.length) { + return false; + } + return leftProperties.every(function (property) { + return Object.prototype.hasOwnProperty.call(right, property) && + valuesEqual(left[property], right[property]); + }); + } + + function simpleMode(config, states) { + if (!Object.prototype.hasOwnProperty.call(states, "start")) { + throw new Error("Undefined state start in simple mode"); + } + + const compiledStates = {}; + const metadata = states.meta || {}; + let hasIndentation = false; + + Object.keys(states).forEach(function (stateName) { + if (stateName === "meta") { + return; + } + compiledStates[stateName] = states[stateName].map(function (data) { + const nextState = data.next || data.push; + if (nextState && !Object.prototype.hasOwnProperty.call(states, nextState)) { + throw new Error("Undefined state " + nextState + " in simple mode"); + } + if (data.indent || data.dedent) { + hasIndentation = true; + } + return { + data: data, + regex: regexFromValue(data.regex), + token: tokenValue(data.token) + }; + }); + }); + + const mode = { + startState: function () { + return { + state: "start", + pending: null, + local: null, + localState: null, + indent: hasIndentation ? [] : null + }; + }, + + copyState: function (state) { + const copiedState = { + state: state.state, + pending: state.pending && state.pending.slice(), + local: state.local, + localState: state.localState && state.local ? + copyState(state.local.mode, state.localState) : + null, + indent: state.indent && state.indent.slice(), + stack: state.stack && state.stack.slice() + }; + + for (let persistent = state.persistentStates; + persistent; + persistent = persistent.next) { + copiedState.persistentStates = { + mode: persistent.mode, + spec: persistent.spec, + state: persistent.state === state.localState ? + copiedState.localState : + copyState(persistent.mode, persistent.state), + next: copiedState.persistentStates + }; + } + return copiedState; + }, + + token: function (stream, state) { + if (state.pending) { + const pending = state.pending.shift(); + if (!state.pending.length) { + state.pending = null; + } + stream.pos += pending.text.length; + return pending.token; + } + + if (state.local) { + if (state.local.end && stream.match(state.local.end)) { + const endToken = state.local.endToken || null; + state.local = null; + state.localState = null; + return endToken; + } + + const localToken = state.local.mode.token(stream, state.localState); + const endMatch = state.local.endScan && + state.local.endScan.exec(stream.current()); + if (endMatch) { + stream.pos = stream.start + endMatch.index; + } + return localToken; + } + + const rules = compiledStates[state.state]; + for (let index = 0; index < rules.length; index++) { + const rule = rules[index]; + const matches = (!rule.data.sol || stream.sol()) && + stream.match(rule.regex); + if (!matches) { + continue; + } + + if (rule.data.next) { + state.state = rule.data.next; + } else if (rule.data.push) { + (state.stack || (state.stack = [])).push(state.state); + state.state = rule.data.push; + } else if (rule.data.pop && state.stack && state.stack.length) { + state.state = state.stack.pop(); + } + if (rule.data.mode) { + let persistent; + if (rule.data.mode.persistent) { + for (let candidate = state.persistentStates; + candidate && !persistent; + candidate = candidate.next) { + if (rule.data.mode.spec ? + valuesEqual(rule.data.mode.spec, candidate.spec) : + rule.data.mode.mode === candidate.mode) { + persistent = candidate; + } + } + } + + const localMode = persistent ? + persistent.mode : + rule.data.mode.mode || + getMode(config, rule.data.mode.spec); + const localState = persistent ? + persistent.state : + startState(localMode); + if (rule.data.mode.persistent && !persistent) { + state.persistentStates = { + mode: localMode, + spec: rule.data.mode.spec, + state: localState, + next: state.persistentStates + }; + } + state.localState = localState; + state.local = { + mode: localMode, + end: rule.data.mode.end && + regexFromValue(rule.data.mode.end), + endScan: rule.data.mode.end && + rule.data.mode.forceEnd !== false && + regexFromValue(rule.data.mode.end, false), + endToken: rule.token && Array.isArray(rule.token) ? + rule.token[rule.token.length - 1] : + rule.token + }; + } + if (rule.data.indent) { + state.indent.push(stream.indentation() + config.indentUnit); + } + if (rule.data.dedent) { + state.indent.pop(); + } + + const token = typeof rule.token === "function" ? + rule.token(matches) : + rule.token; + if (matches.length > 2 && rule.token && + typeof rule.token !== "string") { + for (let group = 2; group < matches.length; group++) { + if (matches[group]) { + (state.pending || (state.pending = [])).push({ + text: matches[group], + token: rule.token[group - 1] + }); + } + } + stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); + return token[0]; + } + return Array.isArray(token) ? token[0] : token; + } + + stream.next(); + return null; + }, + + innerMode: function (state) { + return state.local && { + mode: state.local.mode, + state: state.localState + }; + }, + + indent: function (state, textAfter, line) { + if (state.local && state.local.mode.indent) { + return state.local.mode.indent(state.localState, textAfter, line); + } + if (state.indent === null || state.local || + metadata.dontIndentStates && + metadata.dontIndentStates.indexOf(state.state) !== -1) { + return Pass; + } + + let indentationIndex = state.indent.length - 1; + let rules = compiledStates[state.state]; + let remainingText = textAfter; + let shouldContinue = true; + while (shouldContinue) { + shouldContinue = false; + for (let index = 0; index < rules.length; index++) { + const rule = rules[index]; + if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { + const match = rule.regex.exec(remainingText); + if (match && match[0]) { + indentationIndex--; + if (rule.data.next || rule.data.push) { + rules = compiledStates[rule.data.next || rule.data.push]; + } + remainingText = remainingText.slice(match[0].length); + shouldContinue = true; + break; + } + } + } + } + return indentationIndex < 0 ? 0 : state.indent[indentationIndex]; + } + }; + + Object.assign(mode, metadata); + return mode; + } + + function defineSimpleMode(name, states) { + defineMode(name, function (config) { + return simpleMode(config, states); + }); + } + + function JSXContext(state, mode, depth, previous) { + this.state = state; + this.mode = mode; + this.depth = depth; + this.prev = previous; + } + + function copyJSXContext(context) { + return new JSXContext( + copyState(context.mode, context.state), + context.mode, + context.depth, + context.prev && copyJSXContext(context.prev) + ); + } + + function createJSXMode(config, modeConfig) { + const xmlMode = getMode(config, { + name: "xml", + allowMissing: true, + multilineTagIndentPastTag: false, + allowMissingTagName: true + }); + const jsMode = getMode( + config, + modeConfig && modeConfig.base || "javascript" + ); + const indentUnit = Number(config && config.indentUnit) || 2; + + function flatXMLIndent(state) { + const tagName = state.tagName; + state.tagName = null; + const result = xmlMode.indent ? xmlMode.indent(state, "", "") : 0; + state.tagName = tagName; + return result === Pass ? 0 : result; + } + + function token(stream, state) { + if (state.context.mode === xmlMode) { + return xmlToken(stream, state, state.context); + } + return jsToken(stream, state, state.context); + } + + function xmlToken(stream, state, context) { + if (context.depth === 2) { + if (stream.match(/^.*?\*\//)) { + context.depth = 1; + } else { + stream.skipToEnd(); + } + return "comment"; + } + + if (stream.peek() === "{") { + if (xmlMode.skipAttribute) { + xmlMode.skipAttribute(context.state); + } + + let indentation = flatXMLIndent(context.state); + let xmlContext = context.state.context; + if (xmlContext && stream.match(/^[^>]*>\s*$/, false)) { + while (xmlContext.prev && !xmlContext.startOfLine) { + xmlContext = xmlContext.prev; + } + if (xmlContext.startOfLine) { + indentation -= indentUnit; + } else if (context.prev.state.lexical) { + indentation = context.prev.state.lexical.indented; + } + } else if (context.depth === 1) { + indentation += indentUnit; + } + + state.context = new JSXContext( + startState(jsMode, indentation), + jsMode, + 0, + state.context + ); + return null; + } + + if (context.depth === 1) { + if (stream.peek() === "<") { + if (xmlMode.skipAttribute) { + xmlMode.skipAttribute(context.state); + } + state.context = new JSXContext( + startState(xmlMode, flatXMLIndent(context.state)), + xmlMode, + 0, + state.context + ); + return null; + } + if (stream.match("//")) { + stream.skipToEnd(); + return "comment"; + } + if (stream.match("/*")) { + context.depth = 2; + return token(stream, state); + } + } + + const style = xmlMode.token(stream, context.state); + const current = stream.current(); + let openingBrace; + if (/\btag\b/.test(style || "")) { + if (/>$/.test(current)) { + if (context.state.context) { + context.depth = 0; + } else { + state.context = state.context.prev; + } + } else if (/^ -1) { + stream.backUp(current.length - openingBrace); + } + return style; + } + + function jsToken(stream, state, context) { + if (stream.peek() === "<" && + !stream.match(/^<([^<>]|<[^>]*>)+,\s*>/, false) && + jsMode.expressionAllowed && + jsMode.expressionAllowed(stream, context.state)) { + const indentation = jsMode.indent ? + jsMode.indent(context.state, "", "") : + 0; + state.context = new JSXContext( + startState(xmlMode, indentation === Pass ? 0 : indentation), + xmlMode, + 0, + state.context + ); + if (jsMode.skipExpression) { + jsMode.skipExpression(context.state); + } + return null; + } + + const style = jsMode.token(stream, context.state); + if (!style && context.depth !== null && context.depth !== undefined) { + const current = stream.current(); + if (current === "{") { + context.depth++; + } else if (current === "}" && --context.depth === 0) { + state.context = state.context.prev; + } + } + return style; + } + + return { + startState: function () { + return { + context: new JSXContext(startState(jsMode), jsMode) + }; + }, + + copyState: function (state) { + return { + context: copyJSXContext(state.context) + }; + }, + + token: token, + + indent: function (state, textAfter, line) { + const currentMode = state.context.mode; + return currentMode.indent ? + currentMode.indent(state.context.state, textAfter, line) : + Pass; + }, + + innerMode: function (state) { + return state.context; + } + }; + } + + const DEFAULT_HTML_MIXED_TAGS = { + script: [ + ["lang", /(javascript|babel)/i, "javascript"], + [ + "type", + /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, + "javascript" + ], + ["type", /./, "text/plain"], + [null, null, "javascript"] + ], + style: [ + ["lang", /^css$/i, "css"], + ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"], + ["type", /./, "text/plain"], + [null, null, "css"] + ] + }; + const VUE_HTML_MIXED_TAGS = { + script: [ + ["lang", /coffee(script)?/i, "coffeescript"], + [ + "type", + /^(?:text|application)\/(?:x-)?coffee(?:script)?$/i, + "coffeescript" + ], + ["lang", /^(?:ts|typescript)$/i, { + name: "javascript", + typescript: true + }], + [ + "type", + /^(?:text|application)\/typescript$/i, + { + name: "javascript", + typescript: true + } + ], + ["lang", /^babel$/i, "javascript"], + ["type", /^text\/babel$/i, "javascript"], + ["type", /^text\/ecmascript-\d+$/i, "javascript"] + ], + style: [ + ["lang", /^stylus$/i, "stylus"], + ["lang", /^sass$/i, "sass"], + ["lang", /^less$/i, "text/x-less"], + ["lang", /^scss$/i, "text/x-scss"], + ["type", /^(text\/)?(x-)?styl(us)?$/i, "stylus"], + ["type", /^text\/sass/i, "sass"], + ["type", /^(text\/)?(x-)?scss$/i, "text/x-scss"], + ["type", /^(text\/)?(x-)?less$/i, "text/x-less"] + ], + template: [ + ["lang", /^vue-template$/i, "vue"], + ["lang", /^pug$/i, "pug"], + ["lang", /^handlebars$/i, "handlebars"], + ["type", /^(text\/)?(x-)?pug$/i, "pug"], + ["type", /^text\/x-handlebars-template$/i, "handlebars"], + [null, null, "vue-template"] + ] + }; + const htmlAttributeRegexpCache = {}; + + function getHTMLAttributeValue(text, attribute) { + let expression = htmlAttributeRegexpCache[attribute]; + if (!expression) { + expression = new RegExp( + "\\s+" + attribute + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*" + ); + htmlAttributeRegexpCache[attribute] = expression; + } + const match = text.match(expression); + return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""; + } + + function addHTMLMixedTags(source, destination) { + Object.keys(source).forEach(function (tagName) { + const target = destination[tagName] || (destination[tagName] = []); + for (let index = source[tagName].length - 1; index >= 0; index--) { + target.unshift(source[tagName][index]); + } + }); + } + + function findHTMLMixedMode(tagInfo, tagText) { + for (let index = 0; index < tagInfo.length; index++) { + const specification = tagInfo[index]; + if (!specification[0]) { + return specification[2]; + } + specification[1].lastIndex = 0; + if (specification[1].test( + getHTMLAttributeValue(tagText, specification[0]) + )) { + return specification[2]; + } + } + } + + function htmlClosingTagRegexp(tagName, anchored) { + return new RegExp( + (anchored ? "^" : "") + "<\\/\\s*" + tagName + "\\s*>", + "i" + ); + } + + function maybeBackUpBeforeClosingTag(stream, expression, style) { + const current = stream.current(); + const close = current.search(expression); + if (close > -1) { + stream.backUp(current.length - close); + } else if (/<\/?$/.test(current)) { + stream.backUp(current.length); + if (!stream.match(expression, false)) { + stream.match(current); + } + } + return style; + } + + function createHTMLMixedMode(config, parserConfig) { + const htmlMode = getMode(config, { + name: "xml", + htmlMode: true, + multilineTagIndentFactor: parserConfig && + parserConfig.multilineTagIndentFactor, + multilineTagIndentPastTag: parserConfig && + parserConfig.multilineTagIndentPastTag, + allowMissingTagName: parserConfig && + parserConfig.allowMissingTagName + }); + const tags = {}; + const scriptTypes = parserConfig && parserConfig.scriptTypes || []; + addHTMLMixedTags(DEFAULT_HTML_MIXED_TAGS, tags); + if (parserConfig && parserConfig.tags) { + addHTMLMixedTags(parserConfig.tags, tags); + } + for (let index = scriptTypes.length - 1; index >= 0; index--) { + tags.script.unshift([ + "type", + scriptTypes[index].matches, + scriptTypes[index].mode + ]); + } + + function htmlToken(stream, state) { + const style = htmlMode.token(stream, state.htmlState); + const current = stream.current(); + const tagName = state.htmlState.tagName && + String(state.htmlState.tagName).toLowerCase(); + + if (/\btag\b/.test(style || "") && tagName && + !/[<>\s/]/.test(current) && + Object.prototype.hasOwnProperty.call(tags, tagName)) { + state.inTag = tagName + " "; + } else if (state.inTag && /\btag\b/.test(style || "") && + />$/.test(current)) { + const inTag = /^(\S+) (.*)/.exec(state.inTag); + state.inTag = null; + const modeSpec = current === ">" && + findHTMLMixedMode(tags[inTag[1]], inTag[2]); + state.localMode = getMode(config, modeSpec); + state.localState = startState(state.localMode); + state.token = localToken; + state.endTagAnchored = htmlClosingTagRegexp(inTag[1], true); + state.endTag = htmlClosingTagRegexp(inTag[1], false); + } else if (state.inTag) { + state.inTag += current; + if (stream.eol()) { + state.inTag += " "; + } + } + return style; + } + + function localToken(stream, state) { + if (stream.match(state.endTagAnchored, false)) { + state.token = htmlToken; + state.localMode = null; + state.localState = null; + state.endTag = null; + state.endTagAnchored = null; + return null; + } + return maybeBackUpBeforeClosingTag( + stream, + state.endTag, + state.localMode.token(stream, state.localState) + ); + } + + return { + startState: function () { + return { + token: htmlToken, + inTag: null, + localMode: null, + localState: null, + endTag: null, + endTagAnchored: null, + htmlState: startState(htmlMode) + }; + }, + + copyState: function (state) { + return { + token: state.token, + inTag: state.inTag, + localMode: state.localMode, + localState: state.localState ? + copyState(state.localMode, state.localState) : + null, + endTag: state.endTag, + endTagAnchored: state.endTagAnchored, + htmlState: copyState(htmlMode, state.htmlState) + }; + }, + + token: function (stream, state) { + return state.token(stream, state); + }, + + indent: function (state, textAfter, line) { + if (state.localMode && !/^\s*<\//.test(textAfter) && + state.localMode.indent) { + return state.localMode.indent(state.localState, textAfter, line); + } + return htmlMode.indent ? + htmlMode.indent(state.htmlState, textAfter, line) : + Pass; + }, + + innerMode: function (state) { + return { + state: state.localState || state.htmlState, + mode: state.localMode || htmlMode + }; + } + }; + } + + function createVueTemplateMode(config, parserConfig) { + const mustacheOverlay = { + token: function (stream) { + if (stream.match(/^\{\{.*?\}\}/)) { + return "meta mustache"; + } + while (stream.next() && !stream.match("{{", false)) { + // Continue to the next interpolation marker. + } + return null; + } + }; + return overlayMode( + getMode(config, parserConfig.backdrop || "text/html"), + mustacheOverlay + ); + } + + function createHTMLEmbeddedMode(config, parserConfig) { + const modeConfig = parserConfig || {}; + const closeComment = modeConfig.closeComment || "--%>"; + return multiplexingMode( + getMode(config, "htmlmixed"), + { + open: modeConfig.openComment || "<%--", + close: closeComment, + delimStyle: "comment", + mode: { + token: function (stream) { + stream.skipTo(closeComment) || stream.skipToEnd(); + return "comment"; + } + } + }, + { + open: modeConfig.open || modeConfig.scriptStartRegex || "<%", + close: modeConfig.close || modeConfig.scriptEndRegex || "%>", + mode: getMode(config, modeConfig.scriptingModeSpec) + } + ); + } + + function wordSet(words) { + const result = {}; + words.split(/\s+/).filter(Boolean).forEach(function (word) { + result[word] = true; + }); + return result; + } + + const SQL_DIALECT_KEYWORD_STRINGS = Object.freeze({ + sql: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit begin" + ), + mssql: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit begin " + + "trigger proc view index for add constraint key primary foreign " + + "collate clustered nonclustered declare exec go if use holdlock " + + "nolock nowait paglock readcommitted readcommittedlock readpast " + + "readuncommitted repeatableread rowlock serializable snapshot " + + "tablock tablockx updlock with" + ), + mysql: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit accessible " + + "action add after algorithm all analyze asensitive at authors " + + "auto_increment autocommit avg avg_row_length before binary binlog " + + "both btree cache call cascade cascaded case catalog_name chain " + + "change changed character check checkpoint checksum class_origin " + + "client_statistics close coalesce code collate collation " + + "collations column columns comment commit committed completion " + + "concurrent condition connection consistent constraint contains " + + "continue contributors convert cross current current_date " + + "current_time current_timestamp current_user cursor data database " + + "databases day_hour day_microsecond day_minute day_second " + + "deallocate dec declare default delay_key_write delayed delimiter " + + "des_key_file describe deterministic dev_pop dev_samp deviance " + + "diagnostics directory disable discard distinctrow div dual " + + "dumpfile each elseif enable enclosed end ends engine engines enum " + + "errors escape escaped even event events every execute exists exit " + + "explain extended fast fetch field fields first flush for force " + + "foreign found_rows full fulltext function general get global " + + "grant grants group_concat handler hash help high_priority hosts " + + "hour_microsecond hour_minute hour_second if ignore " + + "ignore_server_ids import index index_statistics infile inner " + + "innodb inout insensitive insert_method install interval invoker " + + "isolation iterate key keys kill language last leading leave left " + + "level linear lines list load local localtime localtimestamp lock " + + "logs low_priority master master_heartbeat_period " + + "master_ssl_verify_server_cert masters match max max_rows " + + "maxvalue message_text middleint migrate min min_rows " + + "minute_microsecond minute_second mod mode modifies modify mutex " + + "mysql_errno natural next no no_write_to_binlog offline offset one " + + "online open optimize option optionally out outer outfile " + + "pack_keys parser partition partitions password phase plugin " + + "plugins prepare preserve prev primary privileges procedure " + + "processlist profile profiles purge query quick range read " + + "read_write reads real rebuild recover references regexp relaylog " + + "release remove rename reorganize repair repeatable replace " + + "require resignal restrict resume return returns revoke right " + + "rlike rollback rollup row row_format rtree savepoint schedule " + + "schema schema_name schemas second_microsecond security sensitive " + + "separator serializable server session share show signal slave " + + "slow smallint snapshot soname spatial specific sql sql_big_result " + + "sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache " + + "sql_small_result sqlexception sqlstate sqlwarning ssl start " + + "starting starts status std stddev stddev_pop stddev_samp storage " + + "straight_join subclass_origin sum suspend table_name " + + "table_statistics tables tablespace temporary terminated to " + + "trailing transaction trigger triggers truncate uncommitted undo " + + "uninstall unique unlock upgrade usage use use_frm user " + + "user_resources user_statistics using utc_date utc_time " + + "utc_timestamp value variables varying view views warnings when " + + "while with work write xa xor year_month zerofill begin do then " + + "else loop repeat" + ), + mariadb: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit accessible " + + "action add after algorithm all always analyze asensitive at " + + "authors auto_increment autocommit avg avg_row_length before " + + "binary binlog both btree cache call cascade cascaded case " + + "catalog_name chain change changed character check checkpoint " + + "checksum class_origin client_statistics close coalesce code " + + "collate collation collations column columns comment commit " + + "committed completion concurrent condition connection consistent " + + "constraint contains continue contributors convert cross current " + + "current_date current_time current_timestamp current_user cursor " + + "data database databases day_hour day_microsecond day_minute " + + "day_second deallocate dec declare default delay_key_write delayed " + + "delimiter des_key_file describe deterministic dev_pop dev_samp " + + "deviance diagnostics directory disable discard distinctrow div " + + "dual dumpfile each elseif enable enclosed end ends engine engines " + + "enum errors escape escaped even event events every execute exists " + + "exit explain extended fast fetch field fields first flush for " + + "force foreign found_rows full fulltext function general generated " + + "get global grant grants group_concat handler hard hash help " + + "high_priority hosts hour_microsecond hour_minute hour_second if " + + "ignore ignore_server_ids import index index_statistics infile " + + "inner innodb inout insensitive insert_method install interval " + + "invoker isolation iterate key keys kill language last leading " + + "leave left level linear lines list load local localtime " + + "localtimestamp lock logs low_priority master " + + "master_heartbeat_period master_ssl_verify_server_cert masters " + + "match max max_rows maxvalue message_text middleint migrate min " + + "min_rows minute_microsecond minute_second mod mode modifies " + + "modify mutex mysql_errno natural next no no_write_to_binlog " + + "offline offset one online open optimize option optionally out " + + "outer outfile pack_keys parser partition partitions password " + + "persistent phase plugin plugins prepare preserve prev primary " + + "privileges procedure processlist profile profiles purge query " + + "quick range read read_write reads real rebuild recover references " + + "regexp relaylog release remove rename reorganize repair " + + "repeatable replace require resignal restrict resume return " + + "returns revoke right rlike rollback rollup row row_format rtree " + + "savepoint schedule schema schema_name schemas second_microsecond " + + "security sensitive separator serializable server session share " + + "show shutdown signal slave slow smallint snapshot soft soname " + + "spatial specific sql sql_big_result sql_buffer_result sql_cache " + + "sql_calc_found_rows sql_no_cache sql_small_result sqlexception " + + "sqlstate sqlwarning ssl start starting starts status std stddev " + + "stddev_pop stddev_samp storage straight_join subclass_origin sum " + + "suspend table_name table_statistics tables tablespace temporary " + + "terminated to trailing transaction trigger triggers truncate " + + "uncommitted undo uninstall unique unlock upgrade usage use " + + "use_frm user user_resources user_statistics using utc_date " + + "utc_time utc_timestamp value variables varying view views " + + "virtual warnings when while with work write xa xor year_month " + + "zerofill begin do then else loop repeat" + ), + sqlite: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit abort " + + "action add after all analyze attach autoincrement before begin " + + "cascade case cast check collate column commit conflict constraint " + + "cross current_date current_time current_timestamp database default " + + "deferrable deferred detach each else end escape except exclusive " + + "exists explain fail for foreign full glob if ignore immediate " + + "index indexed initially inner instead intersect isnull key left " + + "match natural no notnull null of offset outer plan pragma primary " + + "query raise recursive references regexp reindex release rename " + + "replace restrict right rollback row savepoint temp temporary then " + + "to transaction trigger unique using vacuum view virtual when with " + + "without" + ), + cassandra: ( + "add all allow alter and any apply as asc authorize batch begin by " + + "clustering columnfamily compact consistency count create custom " + + "delete desc distinct drop each_quorum exists filtering from grant " + + "if in index insert into key keyspace keyspaces level limit " + + "local_one local_quorum modify nan norecursive nosuperuser not of " + + "on one order password permission permissions primary quorum " + + "rename revoke schema select set storage superuser table three to " + + "token truncate ttl two type unlogged update use user users using " + + "values where with writetime" + ), + plsql: ( + "abort accept access add all alter and any array arraylen as asc " + + "assert assign at attributes audit authorization avg base_table " + + "begin between binary_integer body boolean by case cast char " + + "char_base check close cluster clusters colauth column comment " + + "commit compress connect connected constant constraint crash " + + "create current currval cursor data_base database date dba " + + "deallocate debugoff debugon decimal declare default definition " + + "delay delete desc digits dispose distinct do drop else elseif " + + "elsif enable end entry escape exception exception_init exchange " + + "exclusive exists exit external fast fetch file for force form " + + "from function generic goto grant group having identified if " + + "immediate in increment index indexes indicator initial initrans " + + "insert interface intersect into is key level library like limited " + + "local lock log logging long loop master maxextents maxtrans " + + "member minextents minus mislabel mode modify multiset new next no " + + "noaudit nocompress nologging noparallel not nowait number_base " + + "object of off offline on online only open option or order out " + + "package parallel partition pctfree pctincrease pctused " + + "pls_integer positive positiven pragma primary prior private " + + "privileges procedure public raise range raw read rebuild record " + + "ref references refresh release rename replace resource restrict " + + "return returning returns reverse revoke rollback row rowid " + + "rowlabel rownum rows run savepoint schema segment select separate " + + "session set share snapshot some space split sql start statement " + + "storage subtype successful synonym tabauth table tables " + + "tablespace task terminate then to trigger truncate type union " + + "unique unlimited unrecoverable unusable update use using validate " + + "value values variable view views when whenever where while with " + + "work" + ), + hive: ( + "select alter $elem$ $key$ $value$ add after all analyze and " + + "archive as asc before between binary both bucket buckets by " + + "cascade case cast change cluster clustered clusterstatus " + + "collection column columns comment compute concatenate continue " + + "create cross cursor data database databases dbproperties deferred " + + "delete delimited desc describe directory disable distinct " + + "distribute drop else enable end escaped exclusive exists explain " + + "export extended external fetch fields fileformat first format " + + "formatted from full function functions grant group having " + + "hold_ddltime idxproperties if import in index indexes inpath " + + "inputdriver inputformat insert intersect into is items join keys " + + "lateral left like limit lines load local location lock locks " + + "mapjoin materialized minus msck no_drop nocompress not of offline " + + "on option or order out outer outputdriver outputformat overwrite " + + "partition partitioned partitions percent plus preserve procedure " + + "purge range rcfile read readonly reads rebuild recordreader " + + "recordwriter recover reduce regexp rename repair replace restrict " + + "revoke right rlike row schema schemas semi sequencefile serde " + + "serdeproperties set shared show show_database sort sorted ssl " + + "statistics stored streamtable table tables tablesample " + + "tblproperties temporary terminated textfile then tmp to touch " + + "transform trigger unarchive undo union uniquejoin unlock update " + + "use using utc utc_tmestamp view when where while with admin " + + "authorization char compact compactions conf cube current " + + "current_date current_timestamp day decimal defined dependency " + + "directories elem_type exchange file following for grouping hour " + + "ignore inner interval jar less logical macro minute month more " + + "none noscan over owner partialscan preceding pretty principals " + + "protection reload rewrite role roles rollup rows second server " + + "sets skewed transactions truncate unbounded unset uri user values " + + "window year" + ), + pgsql: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit a abort " + + "abs absent absolute access according action ada add admin after " + + "aggregate alias all allocate also always analyse analyze any are " + + "array array_agg array_max_cardinality asensitive assert assertion " + + "assignment asymmetric at atomic attach attribute attributes " + + "authorization avg backward base64 before begin begin_frame " + + "begin_partition bernoulli bigint binary bit bit_length blob " + + "blocked bom boolean both breadth c cache call called cardinality " + + "cascade cascaded case cast catalog catalog_name ceil ceiling " + + "chain char char_length character character_length " + + "character_set_catalog character_set_name character_set_schema " + + "characteristics characters check checkpoint class class_origin " + + "clob close cluster coalesce cobol collate collation " + + "collation_catalog collation_name collation_schema collect column " + + "column_name columns command_function command_function_code " + + "comment comments commit committed concurrently condition " + + "condition_number configuration conflict connect connection " + + "connection_name constant constraint constraint_catalog " + + "constraint_name constraint_schema constraints constructor " + + "contains content continue control conversion convert copy corr " + + "corresponding cost covar_pop covar_samp cross csv cube cume_dist " + + "current current_catalog current_date " + + "current_default_transform_group current_path current_role " + + "current_row current_schema current_time current_timestamp " + + "current_transform_group_for_type current_user cursor cursor_name " + + "cycle data database datalink datatype date " + + "datetime_interval_code datetime_interval_precision day db " + + "deallocate debug dec decimal declare default defaults deferrable " + + "deferred defined definer degree delimiter delimiters dense_rank " + + "depends depth deref derived describe descriptor detach detail " + + "deterministic diagnostics dictionary disable discard disconnect " + + "dispatch dlnewcopy dlpreviouscopy dlurlcomplete " + + "dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly " + + "dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain " + + "double dump dynamic dynamic_function dynamic_function_code each " + + "element else elseif elsif empty enable encoding encrypted end " + + "end_frame end_partition endexec enforced enum equals errcode " + + "error escape event every except exception exclude excluding " + + "exclusive exec execute exists exit exp explain expression " + + "extension external extract false family fetch file filter final " + + "first first_value flag float floor following for force foreach " + + "foreign fortran forward found frame_row free freeze fs full " + + "function functions fusion g general generated get global go goto " + + "grant granted greatest grouping groups handler header hex " + + "hierarchy hint hold hour id identity if ignore ilike immediate " + + "immediately immutable implementation implicit import include " + + "including increment indent index indexes indicator info inherit " + + "inherits initially inline inner inout input insensitive instance " + + "instantiable instead int integer integrity intersect intersection " + + "interval invoker isnull isolation k key key_member key_type label " + + "lag language large last last_value lateral lead leading leakproof " + + "least left length level library like_regex link listen ln load " + + "local localtime localtimestamp location locator lock locked log " + + "logged loop lower m map mapping match matched materialized max " + + "max_cardinality maxvalue member merge message message_length " + + "message_octet_length message_text method min minute minvalue mod " + + "mode modifies module month more move multiset mumps name names " + + "namespace national natural nchar nclob nesting new next nfc nfd " + + "nfkc nfkd nil no none normalize normalized nothing notice notify " + + "notnull nowait nth_value ntile null nullable nullif nulls number " + + "numeric object occurrences_regex octet_length octets of off " + + "offset oids old only open operator option options ordering " + + "ordinality others out outer output over overlaps overlay " + + "overriding owned owner p pad parallel parameter parameter_mode " + + "parameter_name parameter_ordinal_position " + + "parameter_specific_catalog parameter_specific_name " + + "parameter_specific_schema parser partial partition pascal " + + "passing passthrough password path percent percent_rank " + + "percentile_cont percentile_disc perform period permission " + + "pg_context pg_datatype_name pg_exception_context " + + "pg_exception_detail pg_exception_hint placing plans pli policy " + + "portion position position_regex power precedes preceding " + + "precision prepare prepared preserve primary print_strict_params " + + "prior privileges procedural procedure procedures program public " + + "publication query quote raise range rank read reads real reassign " + + "recheck recovery recursive ref references referencing refresh " + + "regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope " + + "regr_sxx regr_sxy regr_syy reindex relative release rename " + + "repeatable replace replica requiring reset respect restart " + + "restore restrict result result_oid return returned_cardinality " + + "returned_length returned_octet_length returned_sqlstate " + + "returning returns reverse revoke right role rollback rollup " + + "routine routine_catalog routine_name routine_schema routines row " + + "row_count row_number rows rowtype rule savepoint scale schema " + + "schema_name schemas scope scope_catalog scope_name scope_schema " + + "scroll search second section security selective self sensitive " + + "sequence sequences serializable server server_name session " + + "session_user setof sets share show similar simple size skip slice " + + "smallint snapshot some source space specific specific_name " + + "specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning " + + "sqrt stable stacked standalone start state statement static " + + "statistics stddev_pop stddev_samp stdin stdout storage strict " + + "strip structure style subclass_origin submultiset subscription " + + "substring substring_regex succeeds sum symmetric sysid system " + + "system_time system_user t table_name tables tablesample " + + "tablespace temp template temporary text then ties time timestamp " + + "timezone_hour timezone_minute to token top_level_count trailing " + + "transaction transaction_active transactions_committed " + + "transactions_rolled_back transform transforms translate " + + "translate_regex translation treat trigger trigger_catalog " + + "trigger_name trigger_schema trim trim_array true truncate trusted " + + "type types uescape unbounded uncommitted under unencrypted unique " + + "unknown unlink unlisten unlogged unnamed unnest until untyped " + + "upper uri usage use_column use_variable user " + + "user_defined_type_catalog user_defined_type_code " + + "user_defined_type_name user_defined_type_schema using vacuum " + + "valid validate validator value value_of var_pop var_samp " + + "varbinary varchar variable_conflict variadic varying verbose " + + "version versioning view views volatile warning when whenever " + + "while whitespace width_bucket window with within without work " + + "wrapper write xml xmlagg xmlattributes xmlbinary xmlcast " + + "xmlcomment xmlconcat xmldeclaration xmldocument xmlelement " + + "xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi " + + "xmlquery xmlroot xmlschema xmlserialize xmltable xmltext " + + "xmlvalidate year yes zone" + ), + gql: ( + "ancestor and asc by contains desc descendant distinct from group " + + "has in is limit offset on order select superset where" + ), + gpsql: ( + "abort absolute access action active add admin after aggregate all " + + "also alter always analyse analyze and any array as asc assertion " + + "assignment asymmetric at authorization backward before begin " + + "between bigint binary bit boolean both by cache called cascade " + + "cascaded case cast chain char character characteristics check " + + "checkpoint class close cluster coalesce codegen collate column " + + "comment commit committed concurrency concurrently configuration " + + "connection constraint constraints contains content continue " + + "conversion copy cost cpu_rate_limit create createdb " + + "createexttable createrole createuser cross csv cube current " + + "current_catalog current_date current_role current_schema " + + "current_time current_timestamp current_user cursor cycle data " + + "database day deallocate dec decimal declare decode default " + + "defaults deferrable deferred definer delete delimiter delimiters " + + "deny desc dictionary disable discard distinct distributed do " + + "document domain double drop dxl each else enable encoding " + + "encrypted end enum errors escape every except exchange exclude " + + "excluding exclusive execute exists explain extension external " + + "extract false family fetch fields filespace fill filter first " + + "float following for force foreign format forward freeze from full " + + "function global grant granted greatest group group_id grouping " + + "handler hash having header hold host hour identity if ignore " + + "ilike immediate immutable implicit in including inclusive " + + "increment index indexes inherit inherits initially inline inner " + + "inout input insensitive insert instead int integer intersect " + + "interval into invoker is isnull isolation join key language large " + + "last leading least left level like limit list listen load local " + + "localtime localtimestamp location lock log login mapping master " + + "match maxvalue median merge minute minvalue missing mode modifies " + + "modify month move name names national natural nchar new newline " + + "next no nocreatedb nocreateexttable nocreaterole nocreateuser " + + "noinherit nologin none noovercommit nosuperuser not nothing notify " + + "notnull nowait null nullif nulls numeric object of off offset oids " + + "old on only operator option options or order ordered others out " + + "outer over overcommit overlaps overlay owned owner parser partial " + + "partition partitions passing password percent percentile_cont " + + "percentile_disc placing plans position preceding precision " + + "prepare prepared preserve primary prior privileges procedural " + + "procedure protocol queue quote randomly range read readable reads " + + "real reassign recheck recursive ref references reindex reject " + + "relative release rename repeatable replace replica reset resource " + + "restart restrict returning returns revoke right role rollback " + + "rollup rootpartition row rows rule savepoint scatter schema scroll " + + "search second security segment select sequence serializable " + + "session session_user set setof sets share show similar simple " + + "smallint some split sql stable standalone start statement " + + "statistics stdin stdout storage strict strip subpartition " + + "subpartitions substring superuser symmetric sysid system table " + + "tablespace temp template temporary text then threshold ties time " + + "timestamp to trailing transaction treat trigger trim true " + + "truncate trusted type unbounded uncommitted unencrypted union " + + "unique unknown unlisten until update user using vacuum valid " + + "validation validator value values varchar variadic varying " + + "verbose version view volatile web when where whitespace window " + + "with within without work writable write xml xmlattributes " + + "xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot " + + "xmlserialize year yes zone" + ), + sparksql: ( + "add after all alter analyze and anti archive array as asc at " + + "between bucket buckets by cache cascade case cast change clear " + + "cluster clustered codegen collection column columns comment " + + "commit compact compactions compute concatenate cost create cross " + + "cube current current_date current_timestamp database databases " + + "data dbproperties defined delete delimited deny desc describe dfs " + + "directories distinct distribute drop else end escaped except " + + "exchange exists explain export extended external false fields " + + "fileformat first following for format formatted from full " + + "function functions global grant group grouping having if ignore " + + "import in index indexes inner inpath inputformat insert intersect " + + "interval into is items join keys last lateral lazy left like " + + "limit lines list load local location lock locks logical macro map " + + "minus msck natural no not null nulls of on optimize option options " + + "or order out outer outputformat over overwrite partition " + + "partitioned partitions percent preceding principals purge range " + + "recordreader recordwriter recover reduce refresh regexp rename " + + "repair replace reset restrict revoke right rlike role roles " + + "rollback rollup row rows schema schemas select semi separated " + + "serde serdeproperties set sets show skewed sort sorted start " + + "statistics stored stratify struct table tables tablesample " + + "tblproperties temp temporary terminated then to touch transaction " + + "transactions transform true truncate unarchive unbounded uncache " + + "union unlock unset use using values view when where window with" + ), + esper: ( + "alter and as asc between by count create delete desc distinct " + + "drop from group having in insert into is join like not on or " + + "order select set table union update values where limit after all " + + "at avedev avg case cast coalesce current_timestamp day days " + + "define else end escape events every exists false first full hour " + + "hours inner instanceof irstream istream last lastweekday left max " + + "match_recognize matches median measures metadatasql min minute " + + "minutes msec millisecond milliseconds null offset outer output " + + "partition pattern prev prior regexp retain-union " + + "retain-intersection right rstream sec second seconds some snapshot " + + "sql stddev sum then true unidirectional until variable weekday " + + "when window" + ) + }); + + function createPHPStreamParser() { + const keywords = wordSet( + "abstract and array as break callable case catch class clone const continue " + + "declare default do else elseif enddeclare endfor endforeach endif endswitch " + + "endwhile enum extends final finally fn for foreach from function global goto " + + "if implements include include_once instanceof insteadof interface iterable " + + "match namespace never new object or parent print private protected public " + + "readonly require require_once return self static string switch throw trait " + + "try unset use var while xor yield" + ); + const atoms = wordSet("true false null TRUE FALSE NULL"); + const builtins = wordSet( + "count define defined die echo empty eval exit isset list print_r strlen " + + "var_dump var_export" + ); + + function phpString(closing, escapes) { + return function (stream, state) { + if (escapes !== false && stream.match("${", false) || + stream.match("{$", false)) { + state.tokenize = null; + return "string"; + } + + if (escapes !== false && + stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) { + if (stream.match("[", false)) { + state.tokenize = matchSequence([ + [["[", null]], + [ + [/\d[\w.]*/, "number"], + [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"], + [/[\w$]+/, "variable"] + ], + [["]", null]] + ], closing, escapes); + } else if (stream.match(/^->\w/, false)) { + state.tokenize = matchSequence([ + [["->", null]], + [[/\w+/, "variable"]] + ], closing, escapes); + } + return "variable-2"; + } + + let escaped = false; + while (!stream.eol() && + (escaped || escapes === false || + !stream.match("{$", false) && + !stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false))) { + if (!escaped && stream.match(closing)) { + state.tokenize = null; + state.tokStack.pop(); + state.tokStack.pop(); + break; + } + escaped = stream.next() === "\\" && !escaped; + } + return "string"; + }; + } + + function matchSequence(sequence, closing, escapes) { + if (!sequence.length) { + return phpString(closing, escapes); + } + return function (stream, state) { + const patterns = sequence[0]; + for (let index = 0; index < patterns.length; index++) { + if (stream.match(patterns[index][0])) { + state.tokenize = matchSequence( + sequence.slice(1), + closing, + escapes + ); + return patterns[index][1]; + } + } + state.tokenize = phpString(closing, escapes); + return "string"; + }; + } + + return CM6.makeLegacyCLike({ + name: "clike", + keywords: keywords, + blockKeywords: wordSet( + "catch do else elseif finally for foreach if switch try while" + ), + defKeywords: wordSet( + "class enum function interface namespace trait" + ), + atoms: atoms, + builtin: builtins, + multiLineStrings: true, + namespaceSeparator: "\\", + hooks: { + "$": function (stream) { + stream.eatWhile(/[\w$_]/); + return "variable-2"; + }, + "<": function (stream, state) { + const markerPrefix = stream.match(/^<<\s*/); + if (!markerPrefix) { + return false; + } + + const quote = stream.eat(/['"]/); + stream.eatWhile(/[\w.]/); + const delimiter = stream.current().slice( + markerPrefix[0].length + (quote ? 2 : 1) + ); + if (quote) { + stream.eat(quote); + } + if (!delimiter) { + return false; + } + + (state.tokStack || (state.tokStack = [])).push(delimiter, 0); + state.tokenize = phpString(delimiter, quote !== "'"); + return "string"; + }, + "#": function (stream) { + while (!stream.eol() && !stream.match("?>", false)) { + stream.next(); + } + return "comment"; + }, + "/": function (stream) { + if (!stream.eat("/")) { + return false; + } + while (!stream.eol() && !stream.match("?>", false)) { + stream.next(); + } + return "comment"; + }, + "\"": function (_stream, state) { + (state.tokStack || (state.tokStack = [])).push("\"", 0); + state.tokenize = phpString("\""); + return "string"; + }, + "{": function (_stream, state) { + if (state.tokStack && state.tokStack.length) { + state.tokStack[state.tokStack.length - 1]++; + } + return false; + }, + "}": function (_stream, state) { + if (state.tokStack && state.tokStack.length && + !--state.tokStack[state.tokStack.length - 1]) { + state.tokenize = phpString( + state.tokStack[state.tokStack.length - 2] + ); + } + return false; + } + }, + languageData: { + commentTokens: { + line: "//", + block: { + open: "/*", + close: "*/" + } + }, + closeBrackets: { + brackets: ["(", "[", "{", "'", "\""] + } + } + }); + } + + function createPHPMode(config, parserConfig) { + const htmlMode = getMode( + config, + parserConfig && parserConfig.htmlMode || "text/html" + ); + const phpMode = getMode(config, { + name: "clike", + helperType: "php", + variant: "php" + }); + const openPHP = /<\?(?:php\b|=)?/i; + + function enterPHP(state) { + state.currentMode = phpMode; + if (!state.phpState) { + let indentation = 0; + if (htmlMode.indent) { + indentation = htmlMode.indent(state.htmlState, "", ""); + if (indentation === Pass) { + indentation = 0; + } + } + state.phpState = startState(phpMode, indentation); + } + state.currentState = state.phpState; + } + + function token(stream, state) { + const isPHP = state.currentMode === phpMode; + if (stream.sol() && state.pending && + state.pending !== "\"" && state.pending !== "'") { + state.pending = null; + } + + if (!isPHP) { + if (stream.match(openPHP)) { + enterPHP(state); + return "meta"; + } + + let style; + if (state.pending === "\"" || state.pending === "'") { + while (!stream.eol() && stream.next() !== state.pending) { + // Continue through the remainder of the HTML string. + } + style = "string"; + } else if (state.pending && stream.pos < state.pending.end) { + stream.pos = state.pending.end; + style = state.pending.style; + } else { + style = htmlMode.token(stream, state.currentState); + } + + state.pending = null; + const current = stream.current(); + const openingIndex = current.search(openPHP); + if (openingIndex !== -1) { + const closingQuote = style === "string" && + current.match(/['"]$/); + if (closingQuote && !/\?>/.test(current)) { + state.pending = closingQuote[0]; + } else { + state.pending = { + end: stream.pos, + style: style + }; + } + stream.backUp(current.length - openingIndex); + } + return style; + } + + if (state.phpState.tokenize === null && stream.match("?>")) { + state.currentMode = htmlMode; + state.currentState = state.htmlState; + if (!state.phpState.context || !state.phpState.context.prev) { + state.phpState = null; + } + return "meta"; + } + return phpMode.token(stream, state.currentState); + } + + return { + startState: function () { + const htmlState = startState(htmlMode); + const startOpen = Boolean(parserConfig && parserConfig.startOpen); + const phpState = startOpen ? startState(phpMode) : null; + return { + htmlState: htmlState, + phpState: phpState, + currentMode: startOpen ? phpMode : htmlMode, + currentState: startOpen ? phpState : htmlState, + pending: null + }; + }, + + copyState: function (state) { + const htmlState = copyState(htmlMode, state.htmlState); + const phpState = state.phpState ? + copyState(phpMode, state.phpState) : + null; + return { + htmlState: htmlState, + phpState: phpState, + currentMode: state.currentMode, + currentState: state.currentMode === phpMode ? + phpState : + htmlState, + pending: state.pending && typeof state.pending === "object" ? + Object.assign({}, state.pending) : + state.pending + }; + }, + + token: token, + + indent: function (state, textAfter, line) { + if (state.currentMode === phpMode && /^\s*\?>/.test(textAfter) || + state.currentMode !== phpMode && /^\s*<\//.test(textAfter)) { + return htmlMode.indent ? + htmlMode.indent(state.htmlState, textAfter, line) : + Pass; + } + return state.currentMode.indent ? + state.currentMode.indent(state.currentState, textAfter, line) : + Pass; + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + + innerMode: function (state) { + return { + state: state.currentState, + mode: state.currentMode + }; + } + }; + } + + function defineCoreOptions() { + const coreDefaults = { + value: "", + mode: null, + indentUnit: 2, + indentWithTabs: false, + smartIndent: true, + tabSize: 4, + lineSeparator: null, + specialChars: new RegExp( + "[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u200b\\u200e\\u200f" + + "\\u2028\\u2029\\u202d\\u202e\\u2066\\u2067\\u2069\\ufeff\\ufff9-\\ufffc]", + "g" + ), + specialCharPlaceholder: null, + electricChars: true, + inputStyle: "contenteditable", + spellcheck: false, + autocorrect: false, + autocapitalize: false, + placeholder: "", + rtlMoveVisually: true, + wholeLineUpdateBefore: true, + theme: "default", + keyMap: "default", + extraKeys: null, + continueComments: null, + autoCloseBrackets: false, + autoCloseTags: false, + matchBrackets: false, + matchTags: false, + highlightSelectionMatches: false, + styleActiveLine: false, + styleSelectedText: false, + configureMouse: null, + lineWrapping: false, + gutters: [], + fixedGutter: true, + coverGutterNextToScrollbar: false, + scrollbarStyle: "native", + scrollButtonHeight: 0, + scrollPastEnd: false, + rulers: false, + lineNumbers: false, + firstLineNumber: 1, + lineNumberFormatter: function (integer) { + return integer; + }, + showCursorWhenSelecting: false, + resetSelectionOnContextMenu: true, + lineWiseCopyCut: true, + pasteLinesPerSelection: true, + selectionsMayTouch: false, + readOnly: false, + screenReaderLabel: null, + disableInput: false, + dragDrop: true, + allowDropFileTypes: null, + cursorBlinkRate: 530, + cursorScrollMargin: 0, + cursorHeight: 1, + singleCursorHeightPerLine: true, + workTime: 100, + workDelay: 100, + flattenSpans: true, + addModeClass: false, + pollInterval: 100, + undoDepth: 200, + historyEventDelay: 1250, + viewportMargin: 10, + maxHighlightLength: 10000, + moveInputWithCursor: true, + tabindex: null, + autofocus: null, + direction: "ltr", + phrases: null + }; + + Object.keys(coreDefaults).forEach(function (name) { + defineOption(name, coreDefaults[name]); + }); + defineOption("keyMap", coreDefaults.keyMap, function (editor, value, oldValue) { + const next = getKeyMap(value); + const previous = oldValue !== Init ? getKeyMap(oldValue) : null; + if (previous && typeof previous.detach === "function") { + previous.detach.call(previous, editor, next || null); + } + if (next && typeof next.attach === "function") { + next.attach.call(next, editor, previous || null); + } + }); + } + + function defineCoreCommands() { + function rangeStart(range) { + if (typeof range.from === "function") { + return range.from(); + } + return cmpPos(range.anchor, range.head) <= 0 ? range.anchor : range.head; + } + + function rangeEnd(range) { + if (typeof range.to === "function") { + return range.to(); + } + return cmpPos(range.anchor, range.head) <= 0 ? range.head : range.anchor; + } + + function rangeIsEmpty(range) { + return typeof range.empty === "function" ? + range.empty() : + cmpPos(range.anchor, range.head) === 0; + } + + function runOperation(editor, operation) { + return typeof editor.operation === "function" ? + editor.operation(operation) : + operation(); + } + + function runNativeCommand(editor, command, fallback) { + if (editor._view && typeof command === "function") { + return command(editor._view); + } + return fallback(); + } + + function runNativeMotion(editor, cursorCommand, selectCommand, fallback) { + const command = editor.state && editor.state.shift ? + selectCommand : + cursorCommand; + return runNativeCommand(editor, command, fallback); + } + + function extendSelection(editor, target, options) { + if (typeof editor.extendSelection === "function") { + return editor.extendSelection(target, undefined, options); + } + return editor.setSelection(editor.getCursor("anchor"), target, options); + } + + function extendSelections(editor, mapper, options) { + if (typeof editor.extendSelectionsBy === "function") { + return editor.extendSelectionsBy(mapper, options); + } + return extendSelection(editor, mapper({ + anchor: editor.getCursor("anchor"), + head: editor.getCursor("head") + }), options); + } + + function lineStart(editor, position, smart) { + const text = editor.getLine(position.line) || ""; + if (!smart) { + return Pos(position.line, 0); + } + const firstNonWhitespace = text.search(/\S/); + const indentationEnd = firstNonWhitespace < 0 ? text.length : firstNonWhitespace; + return Pos(position.line, position.ch > 0 && position.ch <= indentationEnd ? + 0 : + indentationEnd); + } + + function lineEnd(editor, position) { + return Pos(position.line, (editor.getLine(position.line) || "").length); + } + + function replaceComputedRanges(editor, computeRange, origin) { + const ranges = editor.listSelections().map(function (range) { + return computeRange(range); + }).sort(function (left, right) { + return cmpPos(right.from, left.from); + }); + + return runOperation(editor, function () { + ranges.forEach(function (range) { + editor.replaceRange("", range.from, range.to, origin); + }); + }); + } + + commands.selectAll = function (editor) { + return runNativeCommand(editor, CM6.selectAll, function () { + const lastLine = editor.lastLine(); + return editor.setSelection( + Pos(editor.firstLine(), 0), + Pos(lastLine, editor.getLine(lastLine).length) + ); + }); + }; + commands.singleSelection = function (editor) { + return runNativeCommand(editor, CM6.simplifySelection, function () { + return editor.setSelection( + editor.getCursor("anchor"), + editor.getCursor("head"), + {scroll: false} + ); + }); + }; + commands.undo = function (editor) { + return editor.undo(); + }; + commands.redo = function (editor) { + return editor.redo(); + }; + commands.undoSelection = function (editor) { + return typeof editor.undoSelection === "function" ? + editor.undoSelection() : + editor.undo(); + }; + commands.redoSelection = function (editor) { + return typeof editor.redoSelection === "function" ? + editor.redoSelection() : + editor.redo(); + }; + commands.goDocStart = function (editor) { + return runNativeMotion( + editor, + CM6.cursorDocStart, + CM6.selectDocStart, + function () { + return extendSelection(editor, Pos(editor.firstLine(), 0)); + } + ); + }; + commands.goDocEnd = function (editor) { + return runNativeMotion( + editor, + CM6.cursorDocEnd, + CM6.selectDocEnd, + function () { + const lastLine = editor.lastLine(); + return extendSelection( + editor, + Pos(lastLine, editor.getLine(lastLine).length) + ); + } + ); + }; + commands.goLineStart = function (editor) { + return runNativeMotion( + editor, + CM6.cursorLineStart, + CM6.selectLineStart, + function () { + return extendSelections(editor, function (range) { + return lineStart(editor, range.head, false); + }, { + origin: "+move", + bias: 1 + }); + } + ); + }; + commands.goLineStartSmart = function (editor) { + return runNativeMotion( + editor, + CM6.cursorLineStart, + CM6.selectLineStart, + function () { + return extendSelections(editor, function (range) { + return lineStart(editor, range.head, true); + }, { + origin: "+move", + bias: 1 + }); + } + ); + }; + commands.goLineEnd = function (editor) { + return runNativeMotion( + editor, + CM6.cursorLineEnd, + CM6.selectLineEnd, + function () { + return extendSelections(editor, function (range) { + return lineEnd(editor, range.head); + }, { + origin: "+move", + bias: -1 + }); + } + ); + }; + commands.goLineLeft = commands.goLineStart; + commands.goLineLeftSmart = commands.goLineStartSmart; + commands.goLineRight = commands.goLineEnd; + commands.killLine = function (editor) { + return runNativeCommand(editor, CM6.deleteToLineEnd, function () { + return replaceComputedRanges(editor, function (range) { + const from = rangeStart(range); + const to = rangeEnd(range); + if (!rangeIsEmpty(range)) { + return {from: from, to: to}; + } + + const lineLength = (editor.getLine(to.line) || "").length; + return { + from: to, + to: to.ch === lineLength && to.line < editor.lastLine() ? + Pos(to.line + 1, 0) : + Pos(to.line, lineLength) + }; + }, "+delete"); + }); + }; + commands.deleteLine = function (editor) { + return runNativeCommand(editor, CM6.deleteLine, function () { + return replaceComputedRanges(editor, function (range) { + const from = rangeStart(range); + const to = rangeEnd(range); + const lastLine = editor.lastLine(); + return { + from: Pos(from.line, 0), + to: to.line < lastLine ? + Pos(to.line + 1, 0) : + Pos(to.line, (editor.getLine(to.line) || "").length) + }; + }, "+delete"); + }); + }; + commands.delLineLeft = function (editor) { + return runNativeCommand(editor, CM6.deleteLineBoundaryBackward, function () { + return replaceComputedRanges(editor, function (range) { + const from = rangeStart(range); + return { + from: Pos(from.line, 0), + to: from + }; + }, "+delete"); + }); + }; + commands.delWrappedLineLeft = commands.delLineLeft; + commands.delWrappedLineRight = function (editor) { + return runNativeCommand(editor, CM6.deleteLineBoundaryForward, function () { + return replaceComputedRanges(editor, function (range) { + const from = rangeStart(range); + return { + from: from, + to: lineEnd(editor, from) + }; + }, "+delete"); + }); + }; + commands.goLineUp = function (editor) { + return runNativeMotion( + editor, + CM6.cursorLineUp, + CM6.selectLineUp, + function () { + return editor.moveV(-1, "line"); + } + ); + }; + commands.goLineDown = function (editor) { + return runNativeMotion( + editor, + CM6.cursorLineDown, + CM6.selectLineDown, + function () { + return editor.moveV(1, "line"); + } + ); + }; + commands.goPageUp = function (editor) { + return runNativeMotion( + editor, + CM6.cursorPageUp, + CM6.selectPageUp, + function () { + return editor.moveV(-1, "page"); + } + ); + }; + commands.goPageDown = function (editor) { + return runNativeMotion( + editor, + CM6.cursorPageDown, + CM6.selectPageDown, + function () { + return editor.moveV(1, "page"); + } + ); + }; + commands.goCharLeft = function (editor) { + return runNativeMotion( + editor, + CM6.cursorCharLeft, + CM6.selectCharLeft, + function () { + return editor.moveH(-1, "char"); + } + ); + }; + commands.goCharRight = function (editor) { + return runNativeMotion( + editor, + CM6.cursorCharRight, + CM6.selectCharRight, + function () { + return editor.moveH(1, "char"); + } + ); + }; + commands.goColumnLeft = function (editor) { + return commands.goCharLeft(editor); + }; + commands.goColumnRight = function (editor) { + return commands.goCharRight(editor); + }; + commands.goWordLeft = function (editor) { + return runNativeMotion( + editor, + CM6.cursorGroupLeft, + CM6.selectGroupLeft, + function () { + return editor.moveH(-1, "word"); + } + ); + }; + commands.goWordRight = function (editor) { + return runNativeMotion( + editor, + CM6.cursorGroupRight, + CM6.selectGroupRight, + function () { + return editor.moveH(1, "word"); + } + ); + }; + commands.goGroupLeft = commands.goWordLeft; + commands.goGroupRight = commands.goWordRight; + commands.delCharBefore = function (editor) { + return runNativeCommand(editor, CM6.deleteCharBackward, function () { + return editor.deleteH(-1, "codepoint"); + }); + }; + commands.delCharAfter = function (editor) { + return runNativeCommand(editor, CM6.deleteCharForward, function () { + return editor.deleteH(1, "char"); + }); + }; + commands.delWordBefore = function (editor) { + return runNativeCommand(editor, CM6.deleteGroupBackward, function () { + return editor.deleteH(-1, "word"); + }); + }; + commands.delWordAfter = function (editor) { + return runNativeCommand(editor, CM6.deleteGroupForward, function () { + return editor.deleteH(1, "word"); + }); + }; + commands.delGroupBefore = commands.delWordBefore; + commands.delGroupAfter = commands.delWordAfter; + commands.indentAuto = function (editor) { + return editor.indentSelection("smart"); + }; + commands.indentMore = function (editor) { + return editor.indentSelection("add"); + }; + commands.indentLess = function (editor) { + return editor.indentSelection("subtract"); + }; + commands.insertTab = function (editor) { + return runNativeCommand(editor, CM6.insertTab, function () { + return editor.replaceSelection("\t"); + }); + }; + commands.insertSoftTab = function (editor) { + const tabSize = editor.getOption("tabSize"); + const spaces = editor.listSelections().map(function (range) { + const position = range.from(); + const column = countColumn(editor.getLine(position.line), position.ch, tabSize); + return " ".repeat(tabSize - column % tabSize); + }); + return editor.replaceSelections(spaces); + }; + commands.defaultTab = function (editor) { + if (editor.somethingSelected()) { + return editor.indentSelection("add"); + } + return commands.insertTab(editor); + }; + commands.newlineAndIndent = function (editor) { + return runNativeCommand(editor, CM6.insertNewlineAndIndent, function () { + return runOperation(editor, function () { + const selections = editor.listSelections(); + editor.replaceSelections( + selections.map(function () { + return "\n"; + }), + "end", + "+input" + ); + editor.listSelections().forEach(function (selection) { + editor.indentLine(rangeStart(selection).line, null, true); + }); + }); + }); + }; + commands.openLine = function (editor) { + return editor.replaceSelection("\n", "start"); + }; + commands.transposeChars = function (editor) { + if (editor._view && typeof CM6.transposeChars === "function") { + return CM6.transposeChars(editor._view); + } + const cursor = editor.getCursor(); + const line = cursor.line; + let character = cursor.ch; + const text = editor.getLine(line) || ""; + + if (!text || editor.somethingSelected()) { + return; + } + if (character === text.length) { + character--; + } + if (character > 0) { + editor.replaceRange( + text.charAt(character) + text.charAt(character - 1), + Pos(line, character - 1), + Pos(line, character + 1), + "+transpose" + ); + editor.setCursor(Pos(line, character + 1)); + return; + } + if (line > editor.firstLine()) { + const previous = editor.getLine(line - 1) || ""; + if (previous) { + editor.replaceRange( + text.charAt(0) + "\n" + previous.charAt(previous.length - 1), + Pos(line - 1, previous.length - 1), + Pos(line, 1), + "+transpose" + ); + editor.setCursor(Pos(line, 1)); + } + } + }; + commands.toggleOverwrite = function (editor) { + return editor.toggleOverwrite(); + }; + } + + function defineCoreKeyMaps() { + keyMap.basic = { + Left: "goCharLeft", + Right: "goCharRight", + Up: "goLineUp", + Down: "goLineDown", + End: "goLineEnd", + Home: "goLineStartSmart", + PageUp: "goPageUp", + PageDown: "goPageDown", + Delete: "delCharAfter", + Backspace: "delCharBefore", + "Shift-Backspace": "delCharBefore", + Tab: "defaultTab", + "Shift-Tab": "indentAuto", + Enter: "newlineAndIndent", + Insert: "toggleOverwrite", + Esc: "singleSelection" + }; + keyMap.pcDefault = { + "Ctrl-A": "selectAll", + "Ctrl-D": "deleteLine", + "Ctrl-Z": "undo", + "Shift-Ctrl-Z": "redo", + "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", + "Ctrl-End": "goDocEnd", + "Ctrl-Up": "goLineUp", + "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", + "Ctrl-Right": "goGroupRight", + "Alt-Left": "goLineStart", + "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", + "Ctrl-Delete": "delGroupAfter", + "Ctrl-S": "save", + "Ctrl-F": "find", + "Ctrl-G": "findNext", + "Shift-Ctrl-G": "findPrev", + "Shift-Ctrl-F": "replace", + "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", + "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", + "Shift-Ctrl-U": "redoSelection", + "Alt-U": "redoSelection", + fallthrough: "basic" + }; + keyMap.emacsy = { + "Ctrl-F": "goCharRight", + "Ctrl-B": "goCharLeft", + "Ctrl-P": "goLineUp", + "Ctrl-N": "goLineDown", + "Ctrl-A": "goLineStart", + "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", + "Shift-Ctrl-V": "goPageUp", + "Ctrl-D": "delCharAfter", + "Ctrl-H": "delCharBefore", + "Alt-Backspace": "delWordBefore", + "Ctrl-K": "killLine", + "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", + "Cmd-D": "deleteLine", + "Cmd-Z": "undo", + "Shift-Cmd-Z": "redo", + "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", + "Cmd-Up": "goDocStart", + "Cmd-End": "goDocEnd", + "Cmd-Down": "goDocEnd", + "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", + "Cmd-Left": "goLineLeft", + "Cmd-Right": "goLineRight", + "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", + "Alt-Delete": "delGroupAfter", + "Cmd-S": "save", + "Cmd-F": "find", + "Cmd-G": "findNext", + "Shift-Cmd-G": "findPrev", + "Cmd-Alt-F": "replace", + "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", + "Cmd-]": "indentMore", + "Cmd-Backspace": "delWrappedLineLeft", + "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", + "Shift-Cmd-U": "redoSelection", + "Ctrl-Up": "goDocStart", + "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + + const isMac = typeof navigator !== "undefined" && /Mac/.test(navigator.platform); + keyMap.default = isMac ? keyMap.macDefault : keyMap.pcDefault; + } + + function defineBuiltInModes() { + defineMode("null", createNullMode); + defineMIME("text/plain", "null"); + + defineMode("javascript", function (config, parserConfig) { + let parser; + if (parserConfig.jsonld) { + parser = CM6.legacyJSONLD; + } else if (parserConfig.json) { + parser = CM6.legacyJSON; + } else if (parserConfig.typescript) { + parser = CM6.legacyTypeScript; + } else { + parser = CM6.legacyJavaScript; + } + const mode = cloneParser(parser, config); + mode.fold = "brace"; + mode.helperType = parserConfig.json || parserConfig.jsonld ? + "json" : + "javascript"; + mode.jsonldMode = Boolean(parserConfig.jsonld); + mode.jsonMode = Boolean(parserConfig.json || parserConfig.jsonld); + return mode; + }); + defineMode("jsx", createJSXMode, "xml", "javascript"); + defineMIME("text/javascript", "javascript"); + defineMIME("application/javascript", "javascript"); + defineMIME("application/json", {name: "javascript", json: true}); + defineMIME("application/ld+json", {name: "javascript", jsonld: true}); + defineMIME("application/typescript", {name: "javascript", typescript: true}); + defineMIME("text/typescript", {name: "javascript", typescript: true}); + defineMIME("text/jsx", "jsx"); + defineMIME("text/typescript-jsx", { + name: "jsx", + base: { + name: "javascript", + typescript: true + } + }); + + defineMode("css", function (config, parserConfig) { + let parser; + if (parserConfig.variant === "scss") { + parser = CM6.legacySCSS; + } else if (parserConfig.variant === "less") { + parser = CM6.legacyLess; + } else { + parser = CM6.legacyCSS; + } + const mode = cloneParser(parser, config, { + variableName: "variable-2" + }); + const token = mode.token; + mode.token = function (stream, state) { + const style = token(stream, state); + if (style === "error" && state.state === "maybeprop" && + state.context && state.context.type === "block") { + return "property error"; + } + return style; + }; + mode.electricChars = "}"; + mode.fold = "brace"; + return mode; + }); + defineMIME("text/css", "css"); + defineMIME("text/x-scss", {name: "css", variant: "scss", helperType: "scss"}); + defineMIME("text/x-less", {name: "css", variant: "less", helperType: "less"}); + + defineMode("xml", function (config, parserConfig) { + const mode = cloneParser( + parserConfig.htmlMode ? CM6.legacyHTML : CM6.legacyXML, + config, + { + angleBracket: "tag bracket", + invalid: "tag error" + } + ); + const token = mode.token; + mode.token = function (stream, state) { + const wasInClosingTag = state._legacyClosingTag; + const style = token(stream, state); + const current = stream.current(); + + if (current === "$/.test(current)) { + state._legacyClosingTag = false; + } + + if (wasInClosingTag && style === "error") { + return /\/?>$/.test(current) ? + "tag bracket error" : + "tag error"; + } + return style; + }; + mode.helperType = parserConfig.htmlMode ? "html" : "xml"; + mode.configuration = parserConfig.htmlMode ? "html" : "xml"; + return mode; + }); + defineMIME("application/xml", "xml"); + defineMIME("text/xml", "xml"); + + defineMode("htmlmixed", createHTMLMixedMode, "xml", "javascript", "css"); + defineMode("vue-template", createVueTemplateMode, "htmlmixed"); + defineMode("vue", function (config) { + return getMode(config, { + name: "htmlmixed", + tags: VUE_HTML_MIXED_TAGS + }); + }, "htmlmixed", "xml", "javascript", "coffeescript", "css", + "sass", "stylus", "pug", "handlebars"); + defineMode("htmlembedded", createHTMLEmbeddedMode, "htmlmixed"); + defineMode("php", createPHPMode, "htmlmixed", "clike"); + defineMIME("text/html", "htmlmixed"); + defineMIME("script/x-vue", "vue"); + defineMIME("text/x-vue", "vue"); + defineMIME("application/x-ejs", { + name: "htmlembedded", + scriptingModeSpec: "javascript" + }); + defineMIME("application/x-erb", { + name: "htmlembedded", + scriptingModeSpec: "ruby" + }); + defineMIME("application/x-httpd-php", "php"); + defineMIME("application/x-httpd-php-open", { + name: "php", + startOpen: true + }); + defineMIME("text/x-php", { + name: "clike", + helperType: "php", + variant: "php" + }); + + const parsers = { + clojure: CM6.clojure, + coffeescript: CM6.coffeeScript, + dart: CM6.dart, + diff: CM6.diff, + go: CM6.go, + groovy: CM6.groovy, + haskell: CM6.haskell, + haxe: CM6.haxe, + lua: CM6.lua, + perl: CM6.perl, + properties: CM6.properties, + pug: CM6.pug, + python: CM6.python, + ruby: CM6.ruby, + rust: CM6.rust, + sass: CM6.sass, + shell: CM6.shell, + stex: CM6.stex, + stylus: CM6.stylus, + swift: CM6.swift, + toml: CM6.toml, + turtle: CM6.turtle, + vb: CM6.vb, + vbscript: CM6.vbScript, + yaml: CM6.yaml + }; + Object.keys(parsers).forEach(function (name) { + defineMode(name, parserFactory(parsers[name])); + }); + + defineMode("clike", function (config, parserConfig) { + if (parserConfig.variant === "php") { + return cloneParser(createPHPStreamParser(), config); + } + const parser = { + c: CM6.c, + cpp: CM6.cpp, + csharp: CM6.csharp, + dart: CM6.dart, + java: CM6.java, + kotlin: CM6.kotlin, + objectiveC: CM6.objectiveC, + scala: CM6.scala + }[parserConfig.variant] || CM6.c; + return cloneParser(parser, config); + }); + + const sqlParsers = { + cassandra: bundledModes.cassandra, + esper: bundledModes.esper, + gql: bundledModes.gql, + gpsql: bundledModes.gpsql, + hive: bundledModes.hive, + mariadb: bundledModes.mariadb, + mssql: bundledModes.mssql, + mysql: bundledModes.mysql, + pgsql: bundledModes.pgsql, + plsql: bundledModes.plsql, + sparksql: bundledModes.sparksql, + sql: bundledModes.sql, + sqlite: bundledModes.sqlite + }; + defineMode("sql", function (config, parserConfig) { + const parser = sqlParsers[parserConfig.variant] || sqlParsers.sql; + const mode = cloneParser(parser, config); + mode.config = parserConfig; + return mode; + }); + + [ + ["text/x-csrc", {name: "clike", variant: "c"}], + ["text/x-c++src", {name: "clike", variant: "cpp"}], + ["text/x-csharp", {name: "clike", variant: "csharp"}], + ["text/x-java", {name: "clike", variant: "java"}], + ["text/x-kotlin", {name: "clike", variant: "kotlin"}], + ["text/x-objectivec", {name: "clike", variant: "objectiveC"}], + ["text/x-scala", {name: "clike", variant: "scala"}], + ["application/dart", "dart"], + ["text/x-properties", "properties"], + ["text/x-rustsrc", "rust"], + ["text/x-sh", "shell"], + ["text/x-sql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.sql), + name: "sql", + variant: "sql" + }], + ["text/x-mssql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.mssql), + name: "sql", + variant: "mssql" + }], + ["text/x-mysql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.mysql), + name: "sql", + variant: "mysql" + }], + ["text/x-mariadb", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.mariadb), + name: "sql", + variant: "mariadb" + }], + ["text/x-sqlite", { + identifierQuote: "\"", + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.sqlite), + name: "sql", + variant: "sqlite" + }], + ["text/x-cassandra", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.cassandra), + name: "sql", + variant: "cassandra" + }], + ["text/x-plsql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.plsql), + name: "sql", + variant: "plsql" + }], + ["text/x-hive", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.hive), + name: "sql", + variant: "hive" + }], + ["text/x-pgsql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.pgsql), + name: "sql", + variant: "pgsql" + }], + ["text/x-gql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.gql), + name: "sql", + variant: "gql" + }], + ["text/x-gpsql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.gpsql), + name: "sql", + variant: "gpsql" + }], + ["text/x-sparksql", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.sparksql), + name: "sql", + variant: "sparksql" + }], + ["text/x-esper", { + keywords: wordSet(SQL_DIALECT_KEYWORD_STRINGS.esper), + name: "sql", + variant: "esper" + }], + ["text/x-stex", "stex"], + ["text/x-styl", "stylus"], + ["text/x-toml", "toml"], + ["text/x-vb", "vb"], + ["text/x-yaml", "yaml"] + ].forEach(function (entry) { + defineMIME(entry[0], entry[1]); + }); + + defineMode("markdown", createMarkdownMode); + defineMode("gfm", function (config, parserConfig) { + return createMarkdownMode( + config, + Object.assign({}, parserConfig, {githubFlavored: true}) + ); + }); + defineMIME("text/markdown", "markdown"); + defineMIME("text/x-markdown", "markdown"); + defineMIME("text/x-gfm", "gfm"); + + defineSimpleMode("handlebars", { + start: [ + {regex: /\{\{\{/, push: "raw", token: "tag"}, + {regex: /\{\{!--/, push: "dashComment", token: "comment"}, + {regex: /\{\{!/, push: "comment", token: "comment"}, + {regex: /\{\{/, push: "expression", token: "tag"} + ], + raw: [ + {regex: /\}\}\}/, pop: true, token: "tag"} + ], + expression: [ + {regex: /\}\}/, pop: true, token: "tag"}, + {regex: /"(?:[^\\"]|\\.)*"?/, token: "string"}, + {regex: /'(?:[^\\']|\\.)*'?/, token: "string"}, + {regex: />|[#/]([A-Za-z_]\w*)/, token: "keyword"}, + {regex: /(?:else|this)\b/, token: "keyword"}, + {regex: /\d+/i, token: "number"}, + {regex: /=|~|@|true|false/, token: "atom"}, + {regex: /(?:\.\.\/)*(?:[A-Za-z_][\w.]*)+/, token: "variable-2"} + ], + dashComment: [ + {regex: /--\}\}/, pop: true, token: "comment"}, + {regex: /./, token: "comment"} + ], + comment: [ + {regex: /\}\}/, pop: true, token: "comment"}, + {regex: /./, token: "comment"} + ], + meta: { + blockCommentStart: "{{--", + blockCommentEnd: "--}}" + } + }); + defineMode("htmlhandlebars", function (config) { + return multiplexingMode( + getMode(config, "text/html"), + { + open: "{{", + close: /\}\}\}?/, + mode: getMode(config, "handlebars"), + parseDelimiters: true + } + ); + }); + defineMIME("text/x-handlebars-template", "htmlhandlebars"); + + Object.keys(bundledModeMIMEs).forEach(function (mime) { + if (!Object.prototype.hasOwnProperty.call(mimeModes, mime)) { + defineMIME(mime, bundledModeMIMEs[mime]); + } + }); + } + + defineCoreOptions(); + defineCoreCommands(); + defineCoreKeyMaps(); + + Object.assign(CodeMirrorCompat, { + Doc: CompatDoc, + Init: Init, + Line: Line, + LineWidget: LineWidget, + Pass: Pass, + SharedTextMarker: SharedTextMarker, + StringStream: StringStream, + TextMarker: TextMarker, + addClass: addClass, + changeEnd: changeEnd, + cmpPos: cmpPos, + commands: commands, + contains: contains, + copyState: copyState, + countColumn: countColumn, + createDocumentForAdapter: createDocumentForAdapter, + defaults: defaults, + defineDocExtension: defineDocExtension, + defineExtension: defineExtension, + defineInitHook: defineInitHook, + defineMIME: defineMIME, + defineMode: defineMode, + defineOption: defineOption, + defineSimpleMode: defineSimpleMode, + docExtensions: docExtensions, + e_preventDefault: ePreventDefault, + e_stop: eStop, + e_stopPropagation: eStopPropagation, + extendMode: extendMode, + extensions: extensions, + findColumn: findColumn, + findMatchingBracket: findMatchingBracket, + findMatchingTag: findMatchingTag, + fromTextArea: fromTextArea, + getHelpers: getHelpers, + getKeyMap: getKeyMap, + getMode: getMode, + hasMode: hasMode, + helpers: helpers, + initOptions: initOptions, + inputStyles: inputStyles, + installExtensions: installExtensions, + installLegacyCompatibility: installLegacyCompatibility, + innerMode: innerMode, + isMac: typeof navigator !== "undefined" && /Mac/.test(navigator.platform), + isModeOverridden: isModeOverridden, + isModifierKey: isModifierKey, + isWordChar: isWordChar, + keyMap: keyMap, + keyNames: KEY_NAMES, + keyName: keyName, + loadMode: loadMode, + lookupKey: lookupKey, + mimeModes: mimeModes, + modeExtensions: modeExtensions, + modes: modes, + multiplexingMode: multiplexingMode, + overlayMode: overlayMode, + normalizeKeyMap: normalizeKeyMap, + off: off, + on: on, + optionHandlers: optionHandlers, + Pos: Pos, + prototype: extensions, + registerGlobalHelper: registerGlobalHelper, + registerHelper: registerHelper, + registerEditorConstructor: registerEditorConstructor, + registerInstance: registerInstance, + resolveMode: resolveMode, + rmClass: rmClass, + runOptionHandler: runOptionHandler, + scanForBracket: scanForBracket, + scrollbarModel: scrollbarModel, + signal: signal, + simpleMode: simpleMode, + splitLines: splitLines, + startState: startState, + matchBrackets: matchBrackets, + unregisterInstance: unregisterInstance, + backend: "codemirror6", + isCodeMirror6: true, + version: "5.65.16", + wheelEventPixels: wheelEventPixels + }); + + LegacyModeMeta.install(CodeMirrorCompat); + + defineExtension("matchBrackets", function () { + return matchBrackets(this, true); + }); + defineExtension("findMatchingBracket", function (position, config, oldConfig) { + let bracketConfig = config; + if (oldConfig || typeof bracketConfig === "boolean") { + if (!oldConfig) { + bracketConfig = bracketConfig ? {strict: true} : null; + } else { + oldConfig.strict = bracketConfig; + bracketConfig = oldConfig; + } + } + return findMatchingBracket(this, position, bracketConfig); + }); + defineExtension("scanForBracket", function (position, direction, style, config) { + return scanForBracket(this, position, direction, style, config); + }); + defineExtension("linkedDoc", function (options) { + return this.getDoc().linkedDoc(options); + }); + defineExtension("unlinkDoc", function (other) { + return this.getDoc().unlinkDoc(other); + }); + defineExtension("iterLinkedDocs", function (callback) { + return this.getDoc().iterLinkedDocs(callback); + }); + + defineLegacyInstanceCheck(CodeMirrorCompat, function (value) { + return Boolean(value && value.isCodeMirror6 && !value._detachedDoc); + }); + + defineBuiltInModes(); + LegacyModesCompat.install(CodeMirrorCompat); + Object.keys(modes).forEach(function (modeName) { + builtInModeFactories[modeName] = modes[modeName]; + }); + + module.exports = CodeMirrorCompat; +}); diff --git a/src/editor/CodeMirrorLegacyAddons.js b/src/editor/CodeMirrorLegacyAddons.js new file mode 100644 index 0000000000..d17d83aed8 --- /dev/null +++ b/src/editor/CodeMirrorLegacyAddons.js @@ -0,0 +1,1800 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5 compatibility implementations. + * + * Compatibility behavior in this file is based in part on CodeMirror 5 + * addons. CodeMirror is distributed under the following MIT license: + * See thirdparty/licences/codemirror5-derived.markdown. + * + * Copyright (C) 2017 by Marijn Haverbeke and others + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +/** + * CM6-backed implementations of high-value CodeMirror 5 addons. + * + * The installers take the compatibility facade as an argument so this module + * can be loaded before the facade without introducing an AMD dependency + * cycle. Each installer is safe to call repeatedly. + */ +define(function (require, exports, module) { + + const installedAddons = new WeakMap(); + const NON_WHITESPACE = /[^\s\u00a0]/; + const HTML_VOID_TAGS = new Set([ + "area", + "base", + "br", + "col", + "command", + "embed", + "hr", + "img", + "input", + "keygen", + "link", + "meta", + "param", + "source", + "track", + "wbr" + ]); + const TAG_PATTERN = /||<\?[\s\S]*?\?>|]*>|<\/?\s*([A-Za-z_\u00c0-\uffff][\w:.\-\u00b7-\uffff]*)(?:\s+(?:"[^"]*"|'[^']*'|[^'">])*)?\s*\/?>/g; + const ADDON_PATHS = { + "addon/comment/comment": "comment", + "addon/comment/continuecomment": "continueComments", + "addon/edit/closetag": "closeTag", + "addon/edit/matchtags": "matchTags", + "addon/edit/trailingspace": "trailingSpace", + "addon/fold/brace-fold": "braceFold", + "addon/fold/comment-fold": "commentFold", + "addon/fold/markdown-fold": "markdownFold", + "addon/fold/xml-fold": "tagHelpers", + "addon/runmode/runmode": "runMode", + "addon/search/searchcursor": "selectMatches", + "addon/selection/mark-selection": "styleSelectedText" + }; + + function _installationSet(CodeMirror) { + let installed = installedAddons.get(CodeMirror); + if (!installed) { + installed = new Set(); + installedAddons.set(CodeMirror, installed); + } + return installed; + } + + function _installOnce(CodeMirror, name, installer) { + if (!CodeMirror || typeof CodeMirror.defineExtension !== "function") { + return false; + } + const installed = _installationSet(CodeMirror); + if (installed.has(name)) { + return true; + } + installer(); + installed.add(name); + return true; + } + + function _firstNonWhitespace(text) { + const index = String(text).search(NON_WHITESPACE); + return index === -1 ? String(text).length : index; + } + + function _hasNonWhitespace(text) { + return NON_WHITESPACE.test(String(text)); + } + + function _commentMode(editor, position) { + const outerMode = editor.getMode(); + if (outerMode && (outerMode.useInnerComments === false || !outerMode.innerMode)) { + return outerMode; + } + return editor.getModeAt(position); + } + + function _lineCommentToken(mode, options) { + const configured = options && options.lineComment; + const token = configured || mode && mode.lineComment; + return Array.isArray(token) ? token[0] : token; + } + + function _blockCommentTokens(mode, options) { + return { + open: options && options.blockCommentStart || + mode && mode.blockCommentStart, + close: options && options.blockCommentEnd || + mode && mode.blockCommentEnd, + lead: options && options.blockCommentLead !== undefined ? + options.blockCommentLead : + mode && mode.blockCommentLead + }; + } + + function _selectedLineEnd(editor, from, to) { + const includesEndLine = from.line === to.line || to.ch !== 0; + return Math.min( + includesEndLine ? to.line : to.line - 1, + editor.lastLine() + ); + } + + function _lineComment(editor, from, to, suppliedOptions, CodeMirror) { + const options = suppliedOptions || {}; + const mode = _commentMode(editor, from); + const lineToken = _lineCommentToken(mode, options); + if (!lineToken) { + const blockTokens = _blockCommentTokens(mode, options); + if (blockTokens.open && blockTokens.close) { + _blockComment( + editor, + from, + to, + Object.assign({}, options, {fullLines: true}), + CodeMirror + ); + } + return; + } + + const endLine = _selectedLineEnd(editor, from, to); + if (endLine < from.line) { + return; + } + const padding = options.padding === undefined ? " " : options.padding; + const commentBlankLines = Boolean(options.commentBlankLines) || + from.line === to.line; + let commonIndent = ""; + + if (options.indent) { + let shortestIndent = Infinity; + for (let line = from.line; line <= endLine; line++) { + const text = editor.getLine(line) || ""; + const indentLength = _firstNonWhitespace(text); + if (indentLength < shortestIndent) { + shortestIndent = indentLength; + commonIndent = text.slice(0, indentLength); + } + } + } + + editor.operation(function () { + for (let line = from.line; line <= endLine; line++) { + const text = editor.getLine(line) || ""; + if (!commentBlankLines && !_hasNonWhitespace(text)) { + continue; + } + if (!options.indent) { + editor.replaceRange( + lineToken + padding, + CodeMirror.Pos(line, 0), + null, + "+comment" + ); + continue; + } + + const lineIndentLength = _firstNonWhitespace(text); + const cut = text.slice(0, commonIndent.length) === commonIndent ? + commonIndent.length : + lineIndentLength; + editor.replaceRange( + commonIndent + lineToken + padding, + CodeMirror.Pos(line, 0), + CodeMirror.Pos(line, cut), + "+comment" + ); + } + }); + } + + function _blockComment(editor, from, to, suppliedOptions, CodeMirror) { + const options = suppliedOptions || {}; + const mode = _commentMode(editor, from); + const tokens = _blockCommentTokens(mode, options); + if (!tokens.open || !tokens.close) { + if (_lineCommentToken(mode, options) && options.fullLines !== false) { + _lineComment(editor, from, to, options, CodeMirror); + } + return; + } + + let endLine = Math.min(to.line, editor.lastLine()); + if (endLine !== from.line && to.ch === 0 && + _hasNonWhitespace(editor.getLine(endLine) || "")) { + endLine--; + } + if (endLine < from.line) { + return; + } + + const padding = options.padding === undefined ? " " : options.padding; + editor.operation(function () { + if (options.fullLines !== false) { + const lastLineHasText = _hasNonWhitespace( + editor.getLine(endLine) || "" + ); + editor.replaceRange( + padding + tokens.close, + CodeMirror.Pos(endLine), + null, + "+comment" + ); + editor.replaceRange( + tokens.open + padding, + CodeMirror.Pos(from.line, 0), + null, + "+comment" + ); + if (tokens.lead !== null && tokens.lead !== undefined) { + for (let line = from.line + 1; line <= endLine; line++) { + if (line !== endLine || lastLineHasText) { + editor.replaceRange( + tokens.lead + padding, + CodeMirror.Pos(line, 0), + null, + "+comment" + ); + } + } + } + return; + } + + const selectionEndsAtTo = CodeMirror.cmpPos( + editor.getCursor("to"), + to + ) === 0; + const emptySelection = !editor.somethingSelected(); + editor.replaceRange(tokens.close, to, null, "+comment"); + if (selectionEndsAtTo) { + editor.setSelection( + emptySelection ? to : editor.getCursor("from"), + to + ); + } + editor.replaceRange(tokens.open, from, null, "+comment"); + }); + } + + function _lineUncomment(editor, startLine, endLine, lineToken, padding, CodeMirror) { + const removals = []; + for (let line = startLine; line <= endLine; line++) { + const text = editor.getLine(line) || ""; + const commentIndex = text.indexOf(lineToken); + if (commentIndex === -1) { + if (_hasNonWhitespace(text)) { + return false; + } + continue; + } + if (_hasNonWhitespace(text.slice(0, commentIndex))) { + return false; + } + let end = commentIndex + lineToken.length; + if (padding && text.slice(end, end + padding.length) === padding) { + end += padding.length; + } + removals.push({ + from: CodeMirror.Pos(line, commentIndex), + to: CodeMirror.Pos(line, end) + }); + } + if (!removals.length) { + return false; + } + + editor.operation(function () { + removals.forEach(function (removal) { + editor.replaceRange( + "", + removal.from, + removal.to, + "+comment" + ); + }); + }); + return true; + } + + function _blockUncomment( + editor, + from, + to, + startLine, + endLine, + tokens, + padding, + CodeMirror + ) { + if (!tokens.open || !tokens.close) { + return false; + } + const firstText = editor.getLine(startLine) || ""; + const lastText = editor.getLine(endLine) || ""; + const openingIndex = firstText.indexOf(tokens.open); + const closingIndex = lastText.indexOf( + tokens.close, + startLine === endLine ? openingIndex + tokens.open.length : 0 + ); + if (openingIndex === -1 || closingIndex === -1) { + return false; + } + + const previousOpening = firstText.lastIndexOf(tokens.open, from.ch); + if (previousOpening !== -1 && previousOpening !== openingIndex) { + const closeBeforeSelection = firstText.indexOf( + tokens.close, + previousOpening + tokens.open.length + ); + if (closeBeforeSelection !== -1 && + closeBeforeSelection + tokens.close.length !== from.ch) { + return false; + } + } + + editor.operation(function () { + let closeStart = closingIndex; + if (padding && lastText.slice( + closingIndex - padding.length, + closingIndex + ) === padding) { + closeStart -= padding.length; + } + editor.replaceRange( + "", + CodeMirror.Pos(endLine, closeStart), + CodeMirror.Pos(endLine, closingIndex + tokens.close.length), + "+comment" + ); + + let openEnd = openingIndex + tokens.open.length; + if (padding && firstText.slice(openEnd, openEnd + padding.length) === padding) { + openEnd += padding.length; + } + editor.replaceRange( + "", + CodeMirror.Pos(startLine, openingIndex), + CodeMirror.Pos(startLine, openEnd), + "+comment" + ); + + if (tokens.lead !== null && tokens.lead !== undefined) { + for (let line = startLine + 1; line <= endLine; line++) { + const text = editor.getLine(line) || ""; + const leadIndex = text.indexOf(tokens.lead); + if (leadIndex === -1 || + _hasNonWhitespace(text.slice(0, leadIndex))) { + continue; + } + let leadEnd = leadIndex + tokens.lead.length; + if (padding && + text.slice(leadEnd, leadEnd + padding.length) === padding) { + leadEnd += padding.length; + } + editor.replaceRange( + "", + CodeMirror.Pos(line, leadIndex), + CodeMirror.Pos(line, leadEnd), + "+comment" + ); + } + } + }); + return true; + } + + function _uncomment(editor, from, to, suppliedOptions, CodeMirror) { + const options = suppliedOptions || {}; + const mode = _commentMode(editor, from); + const startLine = Math.min(from.line, editor.lastLine()); + const endLine = Math.max( + startLine, + _selectedLineEnd(editor, from, to) + ); + const padding = options.padding === undefined ? " " : options.padding; + const lineToken = _lineCommentToken(mode, options); + + if (lineToken && _lineUncomment( + editor, + startLine, + endLine, + lineToken, + padding, + CodeMirror + )) { + return true; + } + + return _blockUncomment( + editor, + from, + to, + startLine, + endLine, + _blockCommentTokens(mode, options), + padding, + CodeMirror + ); + } + + function installComment(CodeMirror) { + return _installOnce(CodeMirror, "comment", function () { + CodeMirror.defineExtension("lineComment", function (from, to, options) { + return _lineComment(this, from, to, options, CodeMirror); + }); + CodeMirror.defineExtension("blockComment", function (from, to, options) { + return _blockComment(this, from, to, options, CodeMirror); + }); + CodeMirror.defineExtension("uncomment", function (from, to, options) { + return _uncomment(this, from, to, options, CodeMirror); + }); + CodeMirror.defineExtension("toggleComment", function (options) { + const editor = this; + const selections = editor.listSelections(); + let operation = null; + let earliestLine = Infinity; + + for (let index = selections.length - 1; index >= 0; index--) { + const selection = selections[index]; + const from = selection.from(); + let to = selection.to(); + if (from.line >= earliestLine) { + continue; + } + if (to.line >= earliestLine) { + to = CodeMirror.Pos(earliestLine, 0); + } + earliestLine = from.line; + if (operation === null) { + operation = editor.uncomment(from, to, options) ? + "uncomment" : + "comment"; + } else if (operation === "uncomment") { + editor.uncomment(from, to, options); + } + if (operation === "comment") { + editor.lineComment(from, to, options); + } + } + }); + CodeMirror.commands.toggleComment = function (editor) { + return editor.toggleComment(); + }; + }); + } + + function installSelectMatches(CodeMirror) { + return _installOnce(CodeMirror, "selectMatches", function () { + CodeMirror.defineExtension("selectMatches", function (query, caseFold) { + const selectionStart = this.getCursor("from"); + const selectionEnd = this.getCursor("to"); + const cursor = this.getSearchCursor( + query, + selectionStart, + caseFold + ); + const ranges = []; + while (cursor.findNext()) { + if (CodeMirror.cmpPos(cursor.to(), selectionEnd) > 0) { + break; + } + ranges.push({ + anchor: cursor.from(), + head: cursor.to() + }); + } + if (ranges.length) { + this.setSelections(ranges, 0); + } + }); + }); + } + + function _clipPosition(CodeMirror, editor, position) { + if (typeof editor.clipPos === "function") { + return editor.clipPos(position); + } + const line = Math.max( + editor.firstLine(), + Math.min(position.line, editor.lastLine()) + ); + return CodeMirror.Pos( + line, + Math.max(0, Math.min(position.ch || 0, editor.getLine(line).length)) + ); + } + + function _bracketFolding(CodeMirror, pairs) { + return function (editor, start) { + const line = start.line; + const lineText = editor.getLine(line); + + function findOpening(pair) { + let tokenType; + let at = start.ch; + let pass = 0; + + while (true) { + const found = at <= 0 ? + -1 : + lineText.lastIndexOf(pair[0], at - 1); + if (found === -1) { + if (pass === 1) { + break; + } + pass = 1; + at = lineText.length; + continue; + } + if (pass === 1 && found < start.ch) { + break; + } + tokenType = editor.getTokenTypeAt( + CodeMirror.Pos(line, found + 1) + ); + if (!/^(comment|string)/.test(tokenType || "")) { + return { + ch: found + 1, + pair: pair, + tokenType: tokenType + }; + } + at = found - 1; + } + } + + function findRange(opening) { + let count = 1; + let end; + let endCh; + + outer: + for (let lineNumber = line; + lineNumber <= editor.lastLine(); + lineNumber++) { + const text = editor.getLine(lineNumber); + let position = lineNumber === line ? opening.ch : 0; + + while (true) { + let nextOpen = text.indexOf( + opening.pair[0], + position + ); + let nextClose = text.indexOf( + opening.pair[1], + position + ); + if (nextOpen < 0) { + nextOpen = text.length; + } + if (nextClose < 0) { + nextClose = text.length; + } + position = Math.min(nextOpen, nextClose); + if (position === text.length) { + break; + } + if (editor.getTokenTypeAt( + CodeMirror.Pos(lineNumber, position + 1) + ) === opening.tokenType) { + if (position === nextOpen) { + count++; + } else if (!--count) { + end = lineNumber; + endCh = position; + break outer; + } + } + position++; + } + } + + if (end === undefined || line === end) { + return null; + } + return { + from: CodeMirror.Pos(line, opening.ch), + to: CodeMirror.Pos(end, endCh) + }; + } + + const openings = []; + pairs.forEach(function (pair) { + const opening = findOpening(pair); + if (opening) { + openings.push(opening); + } + }); + openings.sort(function (left, right) { + return left.ch - right.ch; + }); + + for (let index = 0; index < openings.length; index++) { + const range = findRange(openings[index]); + if (range) { + return range; + } + } + return null; + }; + } + + function _hasImport(CodeMirror, editor, line) { + if (line < editor.firstLine() || line > editor.lastLine()) { + return null; + } + let start = editor.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) { + start = editor.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + } + if (start.type !== "keyword" || start.string !== "import") { + return null; + } + for (let lineNumber = line; + lineNumber <= Math.min(editor.lastLine(), line + 10); + lineNumber++) { + const semicolon = editor.getLine(lineNumber).indexOf(";"); + if (semicolon !== -1) { + return { + end: CodeMirror.Pos(lineNumber, semicolon), + startCh: start.end + }; + } + } + return null; + } + + function _importFold(CodeMirror, editor, start) { + const startLine = start.line; + const first = _hasImport(CodeMirror, editor, startLine); + const previous = _hasImport(CodeMirror, editor, startLine - 2); + if (!first || + _hasImport(CodeMirror, editor, startLine - 1) || + previous && previous.end.line === startLine - 1) { + return null; + } + + let end = first.end; + while (true) { + const next = _hasImport(CodeMirror, editor, end.line + 1); + if (!next) { + break; + } + end = next.end; + } + return { + from: _clipPosition( + CodeMirror, + editor, + CodeMirror.Pos(startLine, first.startCh + 1) + ), + to: end + }; + } + + function _hasInclude(CodeMirror, editor, line) { + if (line < editor.firstLine() || line > editor.lastLine()) { + return null; + } + let start = editor.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) { + start = editor.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + } + if (start.type === "meta" && + start.string.slice(0, 8) === "#include") { + return start.start + 8; + } + return null; + } + + function _includeFold(CodeMirror, editor, start) { + const startLine = start.line; + const first = _hasInclude(CodeMirror, editor, startLine); + if (first === null || + _hasInclude(CodeMirror, editor, startLine - 1) !== null) { + return null; + } + + let end = startLine; + while (_hasInclude(CodeMirror, editor, end + 1) !== null) { + end++; + } + return { + from: CodeMirror.Pos(startLine, first + 1), + to: _clipPosition(CodeMirror, editor, CodeMirror.Pos(end)) + }; + } + + function installBraceFold(CodeMirror) { + return _installOnce(CodeMirror, "braceFold", function () { + const fold = CodeMirror.helpers.fold || {}; + if (typeof fold.brace !== "function") { + CodeMirror.registerHelper("fold", "brace", _bracketFolding( + CodeMirror, + [ + ["{", "}"], + ["[", "]"] + ] + )); + } + if (typeof fold["brace-paren"] !== "function") { + CodeMirror.registerHelper( + "fold", + "brace-paren", + _bracketFolding( + CodeMirror, + [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ] + ) + ); + } + if (typeof fold.import !== "function") { + CodeMirror.registerHelper("fold", "import", function (editor, start) { + return _importFold(CodeMirror, editor, start); + }); + } + if (typeof fold.include !== "function") { + CodeMirror.registerHelper("fold", "include", function (editor, start) { + return _includeFold(CodeMirror, editor, start); + }); + } + }); + } + + function _commentFold(CodeMirror, editor, start) { + const mode = editor.getModeAt(start); + const startToken = mode.blockCommentStart; + const endToken = mode.blockCommentEnd; + if (!startToken || !endToken) { + return; + } + + const line = start.line; + const lineText = editor.getLine(line); + let startCh; + let at = start.ch; + let pass = 0; + + while (true) { + const found = at <= 0 ? + -1 : + lineText.lastIndexOf(startToken, at - 1); + if (found === -1) { + if (pass === 1) { + return; + } + pass = 1; + at = lineText.length; + continue; + } + if (pass === 1 && found < start.ch) { + return; + } + if (/comment/.test( + editor.getTokenTypeAt( + CodeMirror.Pos(line, found + 1) + ) || "" + ) && (found === 0 || + lineText.slice(found - endToken.length, found) === + endToken || + !/comment/.test( + editor.getTokenTypeAt( + CodeMirror.Pos(line, found) + ) || "" + ))) { + startCh = found + startToken.length; + break; + } + at = found - 1; + } + + let depth = 1; + let end; + let endCh; + outer: + for (let lineNumber = line; + lineNumber <= editor.lastLine(); + lineNumber++) { + const text = editor.getLine(lineNumber); + let position = lineNumber === line ? startCh : 0; + while (true) { + let nextOpen = text.indexOf(startToken, position); + let nextClose = text.indexOf(endToken, position); + if (nextOpen < 0) { + nextOpen = text.length; + } + if (nextClose < 0) { + nextClose = text.length; + } + position = Math.min(nextOpen, nextClose); + if (position === text.length) { + break; + } + if (position === nextOpen) { + depth++; + } else if (!--depth) { + end = lineNumber; + endCh = position; + break outer; + } + position++; + } + } + + if (end === undefined || + line === end && endCh === startCh) { + return; + } + return { + from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh) + }; + } + + function installCommentFold(CodeMirror) { + return _installOnce(CodeMirror, "commentFold", function () { + const fold = CodeMirror.helpers.fold || {}; + if (typeof fold.comment === "function") { + return; + } + CodeMirror.registerGlobalHelper( + "fold", + "comment", + function (mode) { + return mode.blockCommentStart && mode.blockCommentEnd; + }, + function (editor, start) { + return _commentFold(CodeMirror, editor, start); + } + ); + }); + } + + function _markdownFold(CodeMirror, editor, start) { + const maxDepth = 100; + + function isHeader(lineNumber) { + const tokenType = editor.getTokenTypeAt( + CodeMirror.Pos(lineNumber, 0) + ); + return tokenType && /\bheader\b/.test(tokenType); + } + + function headerLevel(lineNumber, line, nextLine) { + let match = line && line.match(/^#+/); + if (match && isHeader(lineNumber)) { + return match[0].length; + } + match = nextLine && nextLine.match(/^[=-]+\s*$/); + if (match && isHeader(lineNumber + 1)) { + return nextLine[0] === "=" ? 1 : 2; + } + return maxDepth; + } + + const firstLine = editor.getLine(start.line); + let nextLine = editor.getLine(start.line + 1); + const level = headerLevel(start.line, firstLine, nextLine); + if (level === maxDepth) { + return; + } + + const lastLine = editor.lastLine(); + let end = start.line; + let nextNextLine = editor.getLine(end + 2); + while (end < lastLine) { + if (headerLevel(end + 1, nextLine, nextNextLine) <= level) { + break; + } + end++; + nextLine = nextNextLine; + nextNextLine = editor.getLine(end + 2); + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, editor.getLine(end).length) + }; + } + + function installMarkdownFold(CodeMirror) { + return _installOnce(CodeMirror, "markdownFold", function () { + const fold = CodeMirror.helpers.fold || {}; + if (typeof fold.markdown !== "function") { + CodeMirror.registerHelper( + "fold", + "markdown", + function (editor, start) { + return _markdownFold(CodeMirror, editor, start); + } + ); + } + }); + } + + function _runMode(CodeMirror, source, modeSpec, suppliedCallback, options) { + const mode = CodeMirror.getMode(CodeMirror.defaults, modeSpec); + const tabSize = options && options.tabSize || + CodeMirror.defaults.tabSize; + let callback = suppliedCallback; + + if (callback && typeof callback.appendChild === "function") { + const node = callback; + const ownerDocument = node.ownerDocument || window.document; + let column = 0; + node.textContent = ""; + callback = function (text, style) { + if (text === "\n") { + node.appendChild(ownerDocument.createTextNode(text)); + column = 0; + return; + } + + let content = ""; + let position = 0; + while (true) { + const tabIndex = text.indexOf("\t", position); + if (tabIndex === -1) { + content += text.slice(position); + column += text.length - position; + break; + } + column += tabIndex - position; + content += text.slice(position, tabIndex); + const size = tabSize - column % tabSize; + column += size; + content += " ".repeat(size); + position = tabIndex + 1; + } + + if (style) { + const span = node.appendChild( + ownerDocument.createElement("span") + ); + span.className = "cm-" + + style.replace(/ +/g, " cm-"); + span.appendChild( + ownerDocument.createTextNode(content) + ); + } else { + node.appendChild(ownerDocument.createTextNode(content)); + } + }; + } + + const lines = CodeMirror.splitLines(source); + const state = options && options.state || + CodeMirror.startState(mode); + for (let lineNumber = 0; + lineNumber < lines.length; + lineNumber++) { + if (lineNumber) { + callback("\n"); + } + const stream = new CodeMirror.StringStream( + lines[lineNumber], + null, + { + lookAhead: function (lineOffset) { + return lines[lineNumber + lineOffset]; + }, + baseToken: function () {} + } + ); + if (!stream.string && mode.blankLine) { + mode.blankLine(state); + } + while (!stream.eol()) { + const style = mode.token(stream, state); + if (stream.pos <= stream.start) { + stream.next(); + } + callback( + stream.current(), + style, + lineNumber, + stream.start, + state, + mode + ); + stream.start = stream.pos; + } + } + } + + function installRunMode(CodeMirror) { + return _installOnce(CodeMirror, "runMode", function () { + if (typeof CodeMirror.runMode !== "function") { + CodeMirror.runMode = function ( + source, + modeSpec, + callback, + options + ) { + return _runMode( + CodeMirror, + source, + modeSpec, + callback, + options + ); + }; + } + }); + } + + function installTrailingSpace(CodeMirror) { + return _installOnce(CodeMirror, "trailingSpace", function () { + if (Object.prototype.hasOwnProperty.call( + CodeMirror.optionHandlers, + "showTrailingSpace" + )) { + return; + } + CodeMirror.defineOption( + "showTrailingSpace", + false, + function (editor, value, oldValue) { + const previousValue = oldValue === CodeMirror.Init ? + false : + oldValue; + if (previousValue && !value) { + editor.removeOverlay("trailingspace"); + } else if (!previousValue && value) { + editor.addOverlay({ + name: "trailingspace", + token: function (stream) { + const length = stream.string.length; + let index = length; + while (index && + /\s/.test( + stream.string.charAt(index - 1) + )) { + index--; + } + if (index > stream.pos) { + stream.pos = index; + return null; + } + stream.pos = length; + return "trailingspace"; + } + }); + } + } + ); + }); + } + + function _tagRecords(editor, fromIndex, toIndex) { + const text = editor.getValue(); + const start = Math.max(0, fromIndex || 0); + const end = Math.min( + text.length, + toIndex === undefined ? text.length : toIndex + ); + const expression = new RegExp(TAG_PATTERN.source, "g"); + const records = []; + let match; + + while ((match = expression.exec(text))) { + if (match.index >= end) { + break; + } + if (!match[1] || expression.lastIndex <= start) { + continue; + } + const nameOffset = match.index + match[0].indexOf(match[1]); + const tokenType = typeof editor.getTokenTypeAt === "function" ? + editor.getTokenTypeAt(editor.posFromIndex(nameOffset + 1)) : + null; + if (tokenType && !/(^|\s)tag(\s|$)/.test(tokenType)) { + continue; + } + + const closing = /^<\s*\//.test(match[0]); + const selfClosing = /\/\s*>$/.test(match[0]); + records.push({ + tag: match[1], + key: match[1].toLowerCase(), + opening: !closing, + closing: closing, + selfClosing: selfClosing, + fromIndex: match.index, + toIndex: expression.lastIndex, + from: editor.posFromIndex(match.index), + to: editor.posFromIndex(expression.lastIndex) + }); + } + return records; + } + + function _pairTags(records) { + const stack = []; + const pairs = new Map(); + records.forEach(function (record) { + if (record.selfClosing) { + return; + } + if (record.opening) { + stack.push(record); + return; + } + let matchIndex = stack.length - 1; + while (matchIndex >= 0 && stack[matchIndex].key !== record.key) { + matchIndex--; + } + if (matchIndex < 0) { + return; + } + const opening = stack[matchIndex]; + pairs.set(opening, record); + pairs.set(record, opening); + stack.length = matchIndex; + }); + return pairs; + } + + function _findTagAt(records, offset) { + return records.find(function (record) { + return record.fromIndex <= offset && offset <= record.toIndex; + }) || null; + } + + function _findMatchingTagFallback(editor, position, range, CodeMirror) { + const firstLine = range ? Math.max(editor.firstLine(), range.from) : + editor.firstLine(); + const lastLine = range ? Math.min(editor.lastLine() + 1, range.to) : + editor.lastLine() + 1; + const start = editor.indexFromPos(CodeMirror.Pos(firstLine, 0)); + const end = lastLine > editor.lastLine() ? + editor.getValue().length : + editor.indexFromPos(CodeMirror.Pos(lastLine, 0)); + const records = _tagRecords(editor, start, end); + const current = _findTagAt(records, editor.indexFromPos(position)); + if (!current) { + return; + } + if (current.selfClosing) { + return { + open: current, + close: null, + at: "open" + }; + } + const matching = _pairTags(records).get(current) || null; + return { + open: current.opening ? current : matching, + close: current.closing ? current : matching, + at: current.opening ? "open" : "close" + }; + } + + function _findEnclosingTag(editor, position, range, CodeMirror) { + const firstLine = range ? Math.max(editor.firstLine(), range.from) : + editor.firstLine(); + const lastLine = range ? Math.min(editor.lastLine() + 1, range.to) : + editor.lastLine() + 1; + const start = editor.indexFromPos(CodeMirror.Pos(firstLine, 0)); + const end = lastLine > editor.lastLine() ? + editor.getValue().length : + editor.indexFromPos(CodeMirror.Pos(lastLine, 0)); + const offset = editor.indexFromPos(position); + const records = _tagRecords(editor, start, end); + const pairs = _pairTags(records); + let enclosing = null; + + records.forEach(function (record) { + if (!record.opening || record.selfClosing) { + return; + } + const close = pairs.get(record); + if (!close || record.toIndex > offset || close.fromIndex < offset) { + return; + } + if (range && (record.from.line < range.from || + close.to.line >= range.to)) { + return; + } + if (!enclosing || record.fromIndex > enclosing.open.fromIndex) { + enclosing = { + open: record, + close: close + }; + } + }); + return enclosing || undefined; + } + + function _scanForClosingTag(editor, position, tagName, endLine, CodeMirror) { + const start = editor.indexFromPos(position); + const end = endLine === undefined || endLine > editor.lastLine() ? + editor.getValue().length : + editor.indexFromPos(CodeMirror.Pos(endLine, 0)); + const records = _tagRecords(editor, start, end).filter(function (record) { + return record.fromIndex >= start; + }); + const stack = []; + const wanted = tagName && String(tagName).toLowerCase(); + + for (let index = 0; index < records.length; index++) { + const record = records[index]; + if (record.selfClosing) { + continue; + } + if (record.opening) { + stack.push(record.key); + continue; + } + let matchIndex = stack.length - 1; + while (matchIndex >= 0 && stack[matchIndex] !== record.key) { + matchIndex--; + } + if (matchIndex >= 0) { + stack.length = matchIndex; + continue; + } + if (!wanted || wanted === record.key) { + return record; + } + } + return undefined; + } + + function installTagHelpers(CodeMirror) { + return _installOnce(CodeMirror, "tagHelpers", function () { + if (typeof CodeMirror.findMatchingTag !== "function") { + CodeMirror.findMatchingTag = function (editor, position, range) { + return _findMatchingTagFallback( + editor, + position, + range, + CodeMirror + ); + }; + } + CodeMirror.findEnclosingTag = function (editor, position, range, tagName) { + let enclosing = _findEnclosingTag(editor, position, range, CodeMirror); + while (enclosing && tagName && + enclosing.open.tag.toLowerCase() !== String(tagName).toLowerCase()) { + const outerPosition = enclosing.open.fromIndex > 0 ? + editor.posFromIndex(enclosing.open.fromIndex - 1) : + enclosing.open.from; + enclosing = _findEnclosingTag( + editor, + outerPosition, + range, + CodeMirror + ); + } + return enclosing; + }; + CodeMirror.scanForClosingTag = function (editor, position, name, endLine) { + return _scanForClosingTag( + editor, + position, + name, + endLine, + CodeMirror + ); + }; + CodeMirror.registerHelper("fold", "xml", function (editor, start) { + const startIndex = editor.indexFromPos( + CodeMirror.Pos(start.line, 0) + ); + const records = _tagRecords( + editor, + startIndex, + editor.getValue().length + ); + const opening = records.find(function (record) { + return record.opening && !record.selfClosing && + record.from.line === start.line; + }); + if (!opening) { + return; + } + const closing = _pairTags(records).get(opening); + if (!closing || + CodeMirror.cmpPos(opening.to, closing.from) >= 0) { + return; + } + return { + from: opening.to, + to: closing.from + }; + }); + }); + } + + function _matchingTagAtCursor(CodeMirror, editor) { + const cursor = editor.getCursor(); + let match = CodeMirror.findMatchingTag(editor, cursor, editor.getViewport()); + if (!match && cursor.ch > 0) { + match = CodeMirror.findMatchingTag( + editor, + CodeMirror.Pos(cursor.line, cursor.ch - 1), + editor.getViewport() + ); + } + return match; + } + + function _clearTagMatches(editor) { + ["tagHit", "tagOther"].forEach(function (property) { + if (editor.state[property]) { + editor.state[property].clear(); + } + editor.state[property] = null; + }); + } + + function _updateTagMatches(CodeMirror, editor) { + editor.state.failedTagMatch = false; + editor.operation(function () { + _clearTagMatches(editor); + if (editor.somethingSelected()) { + return; + } + const match = _matchingTagAtCursor(CodeMirror, editor); + if (!match) { + return; + } + + if (editor.state.matchBothTags) { + const current = match.at === "open" ? match.open : match.close; + if (current) { + editor.state.tagHit = editor.markText( + current.from, + current.to, + {className: "CodeMirror-matchingtag"} + ); + } + } + const other = match.at === "close" ? match.open : match.close; + if (other) { + editor.state.tagOther = editor.markText( + other.from, + other.to, + {className: "CodeMirror-matchingtag"} + ); + } else { + editor.state.failedTagMatch = true; + } + }); + } + + function installMatchTags(CodeMirror) { + installTagHelpers(CodeMirror); + return _installOnce(CodeMirror, "matchTags", function () { + const update = function (editor) { + _updateTagMatches(CodeMirror, editor); + }; + const updateFailedMatch = function (editor) { + if (editor.state.failedTagMatch) { + _updateTagMatches(CodeMirror, editor); + } + }; + CodeMirror.defineOption("matchTags", false, function (editor, value, oldValue) { + if (oldValue && oldValue !== CodeMirror.Init) { + editor.off("cursorActivity", update); + editor.off("viewportChange", updateFailedMatch); + _clearTagMatches(editor); + } + if (!value) { + return; + } + editor.state.matchBothTags = typeof value === "object" && + Boolean(value.bothTags); + editor.on("cursorActivity", update); + editor.on("viewportChange", updateFailedMatch); + _updateTagMatches(CodeMirror, editor); + }); + CodeMirror.commands.toMatchingTag = function (editor) { + const match = _matchingTagAtCursor(CodeMirror, editor); + if (!match) { + return; + } + const other = match.at === "close" ? match.open : match.close; + if (other) { + editor.extendSelection(other.to, other.from); + } + }; + }); + } + + function _isHTMLMode(editor, position) { + const mode = editor.getModeAt(position) || {}; + const outerMode = editor.getMode() || {}; + return mode.configuration === "html" || + mode.helperType === "html" || + outerMode.name === "htmlmixed" || + outerMode.helperType === "html"; + } + + function _unclosedTagAt(editor, position) { + const end = editor.indexFromPos(position); + const htmlMode = _isHTMLMode(editor, position); + const records = _tagRecords(editor, 0, end).filter(function (record) { + return record.toIndex <= end; + }); + const stack = []; + records.forEach(function (record) { + if (record.selfClosing || + htmlMode && HTML_VOID_TAGS.has(record.key)) { + return; + } + if (record.opening) { + stack.push(record); + return; + } + let matchIndex = stack.length - 1; + while (matchIndex >= 0 && stack[matchIndex].key !== record.key) { + matchIndex--; + } + if (matchIndex >= 0) { + stack.length = matchIndex; + } + }); + return stack.length ? stack[stack.length - 1] : null; + } + + function installCloseTag(CodeMirror) { + installTagHelpers(CodeMirror); + return _installOnce(CodeMirror, "closeTag", function () { + CodeMirror.commands.closeTag = function (editor) { + if (editor.getOption("disableInput")) { + return CodeMirror.Pass; + } + const selections = editor.listSelections(); + const replacements = []; + for (let index = 0; index < selections.length; index++) { + const selection = selections[index]; + if (!selection.empty()) { + return CodeMirror.Pass; + } + const tag = _unclosedTagAt(editor, selection.head); + if (!tag) { + return CodeMirror.Pass; + } + const existingClose = CodeMirror.scanForClosingTag( + editor, + selection.head, + tag.tag, + Math.min(editor.lastLine() + 1, selection.head.line + 500) + ); + if (existingClose && existingClose.key === tag.key) { + return CodeMirror.Pass; + } + replacements.push(``); + } + editor.replaceSelections(replacements, "end", "+insert"); + return true; + }; + }); + } + + function _nextNonWhitespace(text, start) { + const suffix = String(text).slice(start); + const index = suffix.search(NON_WHITESPACE); + return index === -1 ? -1 : start + index; + } + + function _blockCommentContinuation(mode) { + if (mode && mode.blockCommentContinue !== undefined) { + return mode.blockCommentContinue; + } + if (mode && mode.blockCommentStart === "/*" && + mode.blockCommentEnd === "*/") { + return " * "; + } + return null; + } + + function _continuedCommentText(editor, position) { + const tokenType = editor.getTokenTypeAt(position) || ""; + if (!/(^|\s)comment(\s|$)/.test(tokenType)) { + return null; + } + + const mode = _commentMode(editor, position) || {}; + const text = editor.getLine(position.line) || ""; + const lineToken = _lineCommentToken(mode); + const blockStart = mode.blockCommentStart; + const blockEnd = mode.blockCommentEnd; + const blockContinue = _blockCommentContinuation(mode); + + if (blockStart && blockEnd && blockContinue) { + const closingBeforeCursor = text.lastIndexOf( + blockEnd, + Math.max(0, position.ch - blockEnd.length) + ); + const lineCommentIndex = lineToken ? + text.lastIndexOf(lineToken, Math.max(0, position.ch - 1)) : + -1; + if (!(closingBeforeCursor !== -1 && + closingBeforeCursor + blockEnd.length === position.ch) && + lineCommentIndex === -1) { + const openingBeforeCursor = text.lastIndexOf( + blockStart, + Math.max(0, position.ch - blockStart.length) + ); + if (openingBeforeCursor > closingBeforeCursor) { + const leading = text.slice(0, openingBeforeCursor); + const indent = _hasNonWhitespace(leading) ? + " ".repeat(openingBeforeCursor) : + leading; + return "\n" + indent + blockContinue; + } + + const leader = blockContinue.replace(/\s+$/, ""); + const leaderIndex = leader ? text.indexOf(leader) : -1; + if (leaderIndex !== -1 && + leaderIndex <= position.ch && + !_hasNonWhitespace(text.slice(0, leaderIndex))) { + return "\n" + text.slice(0, leaderIndex) + blockContinue; + } + } + } + + if (!lineToken) { + return null; + } + const lineCommentIndex = text.indexOf(lineToken); + if (lineCommentIndex === -1 || + _hasNonWhitespace(text.slice(0, lineCommentIndex))) { + return null; + } + if (position.ch === 0 && lineCommentIndex === 0) { + return "\n"; + } + + const option = editor.getOption("continueComments"); + if (option && typeof option === "object" && + option.continueLineComment === false) { + return null; + } + const nextLine = editor.getLine(position.line + 1) || ""; + const nextCommentIndex = nextLine.indexOf(lineToken); + const hasTextAfterCursor = _nextNonWhitespace(text, position.ch) !== -1; + const nextLineContinues = nextCommentIndex !== -1 && + !_hasNonWhitespace(nextLine.slice(0, nextCommentIndex)); + if (!hasTextAfterCursor && !nextLineContinues) { + return null; + } + + const trailingWhitespace = text.slice( + lineCommentIndex + lineToken.length + ).match(/^\s*/); + return "\n" + text.slice(0, lineCommentIndex) + lineToken + + (trailingWhitespace ? trailingWhitespace[0] : ""); + } + + function _continueComment(CodeMirror, editor) { + if (editor.getOption("disableInput")) { + return CodeMirror.Pass; + } + const selections = editor.listSelections(); + const inserts = []; + for (let index = 0; index < selections.length; index++) { + const selection = selections[index]; + const insertion = _continuedCommentText(editor, selection.head); + if (insertion === null) { + return CodeMirror.Pass; + } + inserts.push(insertion); + } + editor.replaceSelections(inserts, "end", "+insert"); + return true; + } + + function installContinueComments(CodeMirror) { + return _installOnce(CodeMirror, "continueComments", function () { + const continueCommand = function (editor) { + return _continueComment(CodeMirror, editor); + }; + CodeMirror.commands.continueComment = continueCommand; + CodeMirror.defineOption( + "continueComments", + null, + function (editor, value, oldValue) { + if (oldValue && oldValue !== CodeMirror.Init) { + editor.removeKeyMap("continueComment"); + } + if (!value) { + return; + } + let key = "Enter"; + if (typeof value === "string") { + key = value; + } else if (typeof value === "object" && value.key) { + key = value.key; + } + const keyMap = { + name: "continueComment" + }; + keyMap[key] = continueCommand; + editor.addKeyMap(keyMap); + } + ); + }); + } + + function _clearSelectedTextMarks(editor) { + const marks = editor.state.markedSelection || []; + marks.forEach(function (marker) { + marker.clear(); + }); + marks.length = 0; + } + + function _refreshSelectedTextMarks(editor) { + _clearSelectedTextMarks(editor); + const marks = editor.state.markedSelection; + const className = editor.state.markedSelectionStyle; + editor.listSelections().forEach(function (selection) { + const from = selection.from(); + const to = selection.to(); + if (from.line === to.line && from.ch === to.ch) { + return; + } + marks.push(editor.markText(from, to, { + className: className + })); + }); + } + + function installStyleSelectedText(CodeMirror) { + return _installOnce(CodeMirror, "styleSelectedText", function () { + const refresh = function (editor) { + if (editor.state.markedSelection) { + editor.operation(function () { + _refreshSelectedTextMarks(editor); + }); + } + }; + CodeMirror.defineOption( + "styleSelectedText", + false, + function (editor, value, oldValue) { + const wasEnabled = Boolean( + oldValue && oldValue !== CodeMirror.Init + ); + if (wasEnabled) { + editor.off("cursorActivity", refresh); + _clearSelectedTextMarks(editor); + } + if (!value) { + editor.state.markedSelection = null; + editor.state.markedSelectionStyle = null; + return; + } + + editor.state.markedSelection = []; + editor.state.markedSelectionStyle = + typeof value === "string" ? + value : + "CodeMirror-selectedtext"; + _refreshSelectedTextMarks(editor); + editor.on("cursorActivity", refresh); + } + ); + }); + } + + function installAll(CodeMirror) { + installComment(CodeMirror); + installSelectMatches(CodeMirror); + installBraceFold(CodeMirror); + installCommentFold(CodeMirror); + installMarkdownFold(CodeMirror); + installTagHelpers(CodeMirror); + installMatchTags(CodeMirror); + installCloseTag(CodeMirror); + installContinueComments(CodeMirror); + installStyleSelectedText(CodeMirror); + installRunMode(CodeMirror); + installTrailingSpace(CodeMirror); + return true; + } + + function _addonKey(moduleName) { + const normalized = String(moduleName || "") + .replace(/[?#].*$/, "") + .replace(/\.js$/, ""); + const addonIndex = normalized.indexOf("addon/"); + return addonIndex === -1 ? normalized : normalized.slice(addonIndex); + } + + function install(CodeMirror, moduleName) { + if (!moduleName) { + return installAll(CodeMirror); + } + switch (ADDON_PATHS[_addonKey(moduleName)]) { + case "comment": + return installComment(CodeMirror); + case "continueComments": + return installContinueComments(CodeMirror); + case "closeTag": + return installCloseTag(CodeMirror); + case "matchTags": + return installMatchTags(CodeMirror); + case "trailingSpace": + return installTrailingSpace(CodeMirror); + case "braceFold": + return installBraceFold(CodeMirror); + case "commentFold": + return installCommentFold(CodeMirror); + case "markdownFold": + return installMarkdownFold(CodeMirror); + case "tagHelpers": + return installTagHelpers(CodeMirror); + case "runMode": + return installRunMode(CodeMirror); + case "selectMatches": + return installSelectMatches(CodeMirror); + case "styleSelectedText": + return installStyleSelectedText(CodeMirror); + default: + return false; + } + } + + exports.install = install; + exports.installAll = installAll; + exports.installBraceFold = installBraceFold; + exports.installCloseTag = installCloseTag; + exports.installComment = installComment; + exports.installCommentFold = installCommentFold; + exports.installContinueComments = installContinueComments; + exports.installMarkdownFold = installMarkdownFold; + exports.installMatchTags = installMatchTags; + exports.installRunMode = installRunMode; + exports.installSelectMatches = installSelectMatches; + exports.installStyleSelectedText = installStyleSelectedText; + exports.installTagHelpers = installTagHelpers; + exports.installTrailingSpace = installTrailingSpace; + exports.isSupported = function (moduleName) { + return Boolean(ADDON_PATHS[_addonKey(moduleName)]); + }; +}); diff --git a/src/editor/CodeMirrorLegacyExtendedAddons.js b/src/editor/CodeMirrorLegacyExtendedAddons.js new file mode 100644 index 0000000000..6aa013b4d7 --- /dev/null +++ b/src/editor/CodeMirrorLegacyExtendedAddons.js @@ -0,0 +1,7827 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. See thirdparty/licences/codemirror5-derived.markdown. */ + +/*eslint no-invalid-this: 0*/ + +/** + * CM6-backed compatibility for the remaining CodeMirror 5 addon surface. + * + * These installers intentionally receive the CodeMirror compatibility facade + * instead of importing it. That keeps historical module resolution + * idempotent and avoids a dependency cycle with CodeMirrorCompat. + */ +define(function (require, exports, module) { + + const LegacyAddons = require("editor/CodeMirrorLegacyAddons"); + const installedAddons = new WeakMap(); + const LINT_GUTTER_ID = "CodeMirror-lint-markers"; + const ADDON_PATHS = Object.freeze({ + "addon/dialog/dialog": "dialog", + "addon/display/autorefresh": "autoRefresh", + "addon/display/fullscreen": "fullScreen", + "addon/display/panel": "panel", + "addon/edit/continuelist": "continueList", + "addon/fold/foldcode": "foldCode", + "addon/fold/foldgutter": "foldGutter", + "addon/fold/indent-fold": "indentFold", + "addon/hint/css-hint": "cssHint", + "addon/hint/html-hint": "htmlHint", + "addon/hint/javascript-hint": "javascriptHint", + "addon/hint/sql-hint": "sqlHint", + "addon/hint/xml-hint": "xmlHint", + "addon/lint/coffeescript-lint": "coffeeLint", + "addon/lint/css-lint": "cssLint", + "addon/lint/html-lint": "htmlLint", + "addon/lint/javascript-lint": "javascriptLint", + "addon/lint/json-lint": "jsonLint", + "addon/lint/lint": "lint", + "addon/lint/yaml-lint": "yamlLint", + "addon/merge/merge": "merge", + "addon/mode/loadmode": "loadMode", + "addon/mode/multiplex_test": "multiplexTest", + "addon/runmode/colorize": "colorize", + "addon/runmode/runmode-standalone": "runMode", + "addon/runmode/runmode.node": "runMode", + "addon/scroll/simplescrollbars": "simpleScrollbars", + "addon/selection/selection-pointer": "selectionPointer", + "addon/tern/tern": "tern", + "addon/tern/worker": "ternWorker", + "addon/wrap/hardwrap": "hardWrap", + "keymap/emacs": "emacs" + }); + const supportedPaths = Object.freeze(Object.keys(ADDON_PATHS).sort()); + + function _installationSet(CodeMirror) { + let installed = installedAddons.get(CodeMirror); + if (!installed) { + installed = new Set(); + installedAddons.set(CodeMirror, installed); + } + return installed; + } + + function _installOnce(CodeMirror, name, installer) { + if (!CodeMirror || typeof CodeMirror.defineExtension !== "function") { + return false; + } + const installed = _installationSet(CodeMirror); + if (installed.has(name)) { + return true; + } + installer(); + installed.add(name); + return true; + } + + function _normalizePath(moduleName) { + const normalized = String(moduleName || "") + .replace(/[?#].*$/, "") + .replace(/\.js$/, "") + .replace(/^\/+/, ""); + const addonIndex = normalized.indexOf("addon/"); + const keymapIndex = normalized.indexOf("keymap/"); + let start = -1; + if (addonIndex !== -1) { + start = addonIndex; + } + if (keymapIndex !== -1 && (start === -1 || keymapIndex < start)) { + start = keymapIndex; + } + return start === -1 ? normalized : normalized.slice(start); + } + + function _documentFor(editor) { + const wrapper = editor && editor.getWrapperElement && + editor.getWrapperElement(); + return wrapper && wrapper.ownerDocument || document; + } + + function _removeNode(node) { + if (node && node.parentNode) { + node.parentNode.removeChild(node); + } + } + + function _eventKeyCode(event) { + if (event.keyCode) { + return event.keyCode; + } + if (event.key === "Escape") { + return 27; + } + if (event.key === "Enter") { + return 13; + } + return 0; + } + + function _dialogNode(editor, template, bottom) { + const ownerDocument = _documentFor(editor); + const wrapper = editor.getWrapperElement(); + const dialog = ownerDocument.createElement("div"); + dialog.className = bottom ? + "CodeMirror-dialog CodeMirror-dialog-bottom" : + "CodeMirror-dialog CodeMirror-dialog-top"; + if (typeof template === "string") { + dialog.innerHTML = template; + } else if (template) { + dialog.appendChild(template); + } + wrapper.appendChild(dialog); + CodeMirrorSafeAddClass(wrapper, "dialog-opened"); + return dialog; + } + + function CodeMirrorSafeAddClass(node, className) { + if (!node) { + return; + } + if (node.classList) { + node.classList.add(className); + } else if (!new RegExp(`(^|\\s)${className}(?:$|\\s)`).test(node.className)) { + node.className += (node.className ? " " : "") + className; + } + } + + function CodeMirrorSafeRemoveClass(node, className) { + if (!node) { + return; + } + if (node.classList) { + node.classList.remove(className); + } else { + node.className = String(node.className || "") + .split(/\s+/) + .filter(function (candidate) { + return candidate && candidate !== className; + }) + .join(" "); + } + } + + function _closeNotification(editor, close) { + if (editor.state.currentNotificationClose) { + editor.state.currentNotificationClose(); + } + editor.state.currentNotificationClose = close || null; + } + + function installDialog(CodeMirror) { + return _installOnce(CodeMirror, "dialog", function () { + CodeMirror.defineExtension( + "openDialog", + function (template, callback, suppliedOptions) { + const options = suppliedOptions || {}; + const editor = this; + _closeNotification(editor, null); + const dialog = _dialogNode(editor, template, options.bottom); + const input = dialog.getElementsByTagName("input")[0]; + const button = dialog.getElementsByTagName("button")[0]; + let closed = false; + + const close = function (newValue) { + if (typeof newValue === "string" && input) { + input.value = newValue; + return; + } + if (closed) { + return; + } + closed = true; + CodeMirrorSafeRemoveClass( + editor.getWrapperElement(), + "dialog-opened" + ); + _removeNode(dialog); + editor.focus(); + if (typeof options.onClose === "function") { + options.onClose(dialog); + } + }; + + if (input) { + if (options.value !== undefined) { + input.value = options.value; + if (options.selectValueOnOpen !== false && + typeof input.select === "function") { + input.select(); + } + } + if (typeof options.onInput === "function") { + CodeMirror.on(input, "input", function (event) { + options.onInput(event, input.value, close); + }); + } + if (typeof options.onKeyUp === "function") { + CodeMirror.on(input, "keyup", function (event) { + options.onKeyUp(event, input.value, close); + }); + } + CodeMirror.on(input, "keydown", function (event) { + if (typeof options.onKeyDown === "function" && + options.onKeyDown( + event, + input.value, + close + )) { + return; + } + const keyCode = _eventKeyCode(event); + if (keyCode === 27 || + options.closeOnEnter !== false && + keyCode === 13) { + input.blur(); + CodeMirror.e_stop(event); + close(); + } + if (keyCode === 13 && + typeof callback === "function") { + callback(input.value, event); + } + }); + if (options.closeOnBlur !== false) { + CodeMirror.on(dialog, "focusout", function (event) { + if (event.relatedTarget !== null) { + close(); + } + }); + } + input.focus(); + } else if (button) { + CodeMirror.on(button, "click", function () { + close(); + editor.focus(); + }); + if (options.closeOnBlur !== false) { + CodeMirror.on(button, "blur", close); + } + button.focus(); + } + return close; + } + ); + + CodeMirror.defineExtension( + "openConfirm", + function (template, callbacks, options) { + const editor = this; + _closeNotification(editor, null); + const dialog = _dialogNode( + editor, + template, + options && options.bottom + ); + const buttons = dialog.getElementsByTagName("button"); + let closed = false; + let blurring = 1; + const close = function () { + if (closed) { + return; + } + closed = true; + CodeMirrorSafeRemoveClass( + editor.getWrapperElement(), + "dialog-opened" + ); + _removeNode(dialog); + editor.focus(); + }; + + Array.prototype.forEach.call(buttons, function (button, index) { + CodeMirror.on(button, "click", function (event) { + CodeMirror.e_preventDefault(event); + close(); + if (callbacks && callbacks[index]) { + callbacks[index](editor); + } + }); + CodeMirror.on(button, "blur", function () { + blurring--; + window.setTimeout(function () { + if (blurring <= 0) { + close(); + } + }, 200); + }); + CodeMirror.on(button, "focus", function () { + blurring++; + }); + }); + if (buttons[0]) { + buttons[0].focus(); + } + return close; + } + ); + + CodeMirror.defineExtension( + "openNotification", + function (template, options) { + const editor = this; + let closed = false; + let timer = null; + _closeNotification(editor, null); + const dialog = _dialogNode( + editor, + template, + options && options.bottom + ); + const close = function () { + if (closed) { + return; + } + closed = true; + window.clearTimeout(timer); + CodeMirrorSafeRemoveClass( + editor.getWrapperElement(), + "dialog-opened" + ); + _removeNode(dialog); + if (editor.state.currentNotificationClose === close) { + editor.state.currentNotificationClose = null; + } + }; + editor.state.currentNotificationClose = close; + CodeMirror.on(dialog, "click", function (event) { + CodeMirror.e_preventDefault(event); + close(); + }); + const duration = options && + options.duration !== undefined ? + options.duration : + 5000; + if (duration) { + timer = window.setTimeout(close, duration); + } + return close; + } + ); + }); + } + + function _stopAutoRefresh(CodeMirror, state) { + if (!state) { + return; + } + window.clearTimeout(state.timeout); + CodeMirror.off(window, "mouseup", state.hurry); + CodeMirror.off(window, "keyup", state.hurry); + } + + function _startAutoRefresh(CodeMirror, editor, state) { + const check = function () { + const wrapper = editor.getWrapperElement(); + if (!editor.state.autoRefresh || editor.state.autoRefresh !== state) { + return; + } + if (wrapper.offsetHeight) { + _stopAutoRefresh(CodeMirror, state); + editor.state.autoRefresh = null; + if (state.height !== wrapper.clientHeight) { + editor.refresh(); + } + } else { + state.timeout = window.setTimeout(check, state.delay); + } + }; + state.hurry = function () { + window.clearTimeout(state.timeout); + state.timeout = window.setTimeout(check, 50); + }; + state.timeout = window.setTimeout(check, state.delay); + CodeMirror.on(window, "mouseup", state.hurry); + CodeMirror.on(window, "keyup", state.hurry); + } + + function installAutoRefresh(CodeMirror) { + return _installOnce(CodeMirror, "autoRefresh", function () { + CodeMirror.defineOption("autoRefresh", false, function (editor, value) { + if (editor.state.autoRefresh) { + _stopAutoRefresh(CodeMirror, editor.state.autoRefresh); + } + editor.state.autoRefresh = null; + const wrapper = editor.getWrapperElement(); + if (value && wrapper && wrapper.offsetHeight === 0) { + const state = { + delay: typeof value === "object" && value.delay || 250, + height: wrapper.clientHeight, + hurry: null, + timeout: null + }; + editor.state.autoRefresh = state; + _startAutoRefresh(CodeMirror, editor, state); + } + }); + }); + } + + function installFullScreen(CodeMirror) { + return _installOnce(CodeMirror, "fullScreen", function () { + CodeMirror.defineOption( + "fullScreen", + false, + function (editor, value, oldValue) { + const wasEnabled = oldValue !== CodeMirror.Init && + Boolean(oldValue); + if (wasEnabled === Boolean(value)) { + return; + } + const wrapper = editor.getWrapperElement(); + const ownerDocument = _documentFor(editor); + if (value) { + editor.state.fullScreenRestore = { + documentOverflow: + ownerDocument.documentElement.style.overflow, + height: wrapper.style.height, + scrollLeft: window.pageXOffset, + scrollTop: window.pageYOffset, + width: wrapper.style.width + }; + wrapper.style.width = ""; + wrapper.style.height = "auto"; + CodeMirror.addClass(wrapper, "CodeMirror-fullscreen"); + ownerDocument.documentElement.style.overflow = "hidden"; + } else { + const restore = editor.state.fullScreenRestore || {}; + CodeMirror.rmClass(wrapper, "CodeMirror-fullscreen"); + ownerDocument.documentElement.style.overflow = + restore.documentOverflow || ""; + wrapper.style.width = restore.width || ""; + wrapper.style.height = restore.height || ""; + if (typeof window.scrollTo === "function" && + Number.isFinite(restore.scrollLeft) && + Number.isFinite(restore.scrollTop)) { + window.scrollTo( + restore.scrollLeft, + restore.scrollTop + ); + } + editor.state.fullScreenRestore = null; + } + editor.refresh(); + } + ); + }); + } + + function _panelIsAboveEditor(editor, node) { + const editorWrapper = editor.getWrapperElement(); + for (let sibling = node.nextSibling; sibling; sibling = sibling.nextSibling) { + if (sibling === editorWrapper) { + return true; + } + } + return false; + } + + function _removePanelWrapper(editor) { + const info = editor.state.panels; + if (!info) { + return; + } + const wrapper = editor.getWrapperElement(); + const focused = editor.hasFocus(); + const scroll = editor.getScrollInfo(); + if (info.wrapper.parentNode) { + info.wrapper.parentNode.replaceChild(wrapper, info.wrapper); + } + editor.state.panels = null; + editor.setSize = info.originalSetSize; + wrapper.style.height = info.originalHeight; + editor.scrollTo(scroll.left, scroll.top); + editor.setSize(); + if (focused) { + editor.focus(); + } + } + + function _initializePanels(editor) { + const wrapper = editor.getWrapperElement(); + const ownerDocument = _documentFor(editor); + const panelWrapper = ownerDocument.createElement("div"); + panelWrapper.className = "CodeMirror-panels"; + const focused = editor.hasFocus(); + const scroll = editor.getScrollInfo(); + const computedStyle = window.getComputedStyle ? + window.getComputedStyle(wrapper) : + wrapper.currentStyle; + const computedHeight = computedStyle && + parseFloat(computedStyle.height); + const info = { + explicitHeight: Number.isFinite(computedHeight) ? + computedHeight : + null, + originalHeight: wrapper.style.height, + originalSetSize: editor.setSize, + panels: [], + wrapper: panelWrapper + }; + editor.state.panels = info; + if (wrapper.parentNode) { + wrapper.parentNode.insertBefore(panelWrapper, wrapper); + } + panelWrapper.appendChild(wrapper); + editor.setSize = function (width, height) { + let editorHeight = height; + if (height !== null && height !== undefined) { + let numericHeight = height; + if (typeof height !== "number") { + panelWrapper.style.height = height; + numericHeight = panelWrapper.offsetHeight; + } + if (Number.isFinite(numericHeight)) { + const panelHeight = info.panels.reduce( + function (total, panel) { + return total + + panel.node.getBoundingClientRect().height; + }, + 0 + ); + editorHeight = Math.max(0, numericHeight - panelHeight); + info.explicitHeight = numericHeight; + } + } else if (info.explicitHeight !== null) { + const panelHeight = info.panels.reduce( + function (total, panel) { + return total + + panel.node.getBoundingClientRect().height; + }, + 0 + ); + editorHeight = Math.max(0, info.explicitHeight - panelHeight); + } + return info.originalSetSize.call(editor, width, editorHeight); + }; + editor.scrollTo(scroll.left, scroll.top); + if (focused) { + editor.focus(); + } + return info; + } + + function Panel(editor, node, options, height) { + this.cm = editor; + this.node = node; + this.options = options; + this.height = height; + this.cleared = false; + } + + Panel.prototype.clear = function (skipRemove) { + if (this.cleared) { + return; + } + this.cleared = true; + const info = this.cm.state.panels; + if (!info) { + _removeNode(this.node); + return; + } + const index = info.panels.indexOf(this); + if (index !== -1) { + info.panels.splice(index, 1); + } + if (this.options.stable && _panelIsAboveEditor(this.cm, this.node)) { + this.cm.scrollTo( + null, + this.cm.getScrollInfo().top - this.height + ); + } + _removeNode(this.node); + if (!info.panels.length && !skipRemove) { + _removePanelWrapper(this.cm); + } else { + this.cm.setSize(); + } + }; + + Panel.prototype.changed = function () { + this.height = this.node.getBoundingClientRect().height; + this.cm.setSize(); + }; + + function installPanel(CodeMirror) { + return _installOnce(CodeMirror, "panel", function () { + CodeMirror.defineExtension("addPanel", function (node, suppliedOptions) { + const options = suppliedOptions || {}; + const info = this.state.panels || _initializePanels(this); + const wrapper = info.wrapper; + const editorWrapper = this.getWrapperElement(); + const before = options.before instanceof Panel && + !options.before.cleared ? + options.before : + null; + const after = options.after instanceof Panel && + !options.after.cleared ? + options.after : + null; + const replace = options.replace instanceof Panel && + !options.replace.cleared ? + options.replace : + null; + + if (after) { + wrapper.insertBefore(node, after.node.nextSibling); + } else if (before) { + wrapper.insertBefore(node, before.node); + } else if (replace) { + wrapper.insertBefore(node, replace.node); + replace.clear(true); + } else if (options.position === "bottom") { + wrapper.appendChild(node); + } else if (options.position === "before-bottom") { + wrapper.insertBefore(node, editorWrapper.nextSibling); + } else if (options.position === "after-top") { + wrapper.insertBefore(node, editorWrapper); + } else { + wrapper.insertBefore(node, wrapper.firstChild); + } + + const height = options.height || + node.getBoundingClientRect().height || + node.offsetHeight || + 0; + const panel = new Panel(this, node, options, height); + info.panels.push(panel); + this.setSize(); + if (options.stable && _panelIsAboveEditor(this, node)) { + this.scrollTo(null, this.getScrollInfo().top + height); + } + return panel; + }); + }); + } + + const LIST_PATTERN = + /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/i; + const EMPTY_LIST_PATTERN = + /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/i; + const UNORDERED_LIST_PATTERN = /[*+-]\s/; + + function _incrementMarkdownListNumbers(editor, position) { + const startLine = position.line; + const startItem = LIST_PATTERN.exec(editor.getLine(startLine) || ""); + if (!startItem || !startItem[3]) { + return; + } + const startIndent = startItem[1]; + let lookAhead = 0; + let skipped = 0; + while (startLine + lookAhead < editor.lastLine()) { + lookAhead++; + const lineNumber = startLine + lookAhead; + const line = editor.getLine(lineNumber) || ""; + const nextItem = LIST_PATTERN.exec(line); + if (!nextItem) { + break; + } + const nextIndent = nextItem[1]; + const nextNumber = parseInt(nextItem[3], 10); + if (startIndent === nextIndent && Number.isFinite(nextNumber)) { + const expected = + parseInt(startItem[3], 10) + lookAhead - skipped; + let itemNumber = nextNumber; + if (expected === nextNumber) { + itemNumber++; + } else if (expected > nextNumber) { + itemNumber = expected + 1; + } + editor.replaceRange( + line.replace( + LIST_PATTERN, + nextIndent + itemNumber + nextItem[4] + nextItem[5] + ), + CodeMirrorPosition(editor, lineNumber, 0), + CodeMirrorPosition(editor, lineNumber, line.length) + ); + } else { + if (startIndent.length > nextIndent.length || + startIndent.length < nextIndent.length && + lookAhead === 1) { + return; + } + skipped++; + } + } + } + + function CodeMirrorPosition(editor, line, ch) { + const facade = editor && editor.constructor && + editor.constructor.Pos; + if (typeof facade === "function") { + return facade(line, ch); + } + return {line: line, ch: ch === undefined ? null : ch}; + } + + function installContinueList(CodeMirror) { + return _installOnce(CodeMirror, "continueList", function () { + CodeMirror.commands.newlineAndIndentContinueMarkdownList = + function (editor) { + if (editor.getOption("disableInput")) { + return CodeMirror.Pass; + } + const ranges = editor.listSelections(); + const replacements = []; + for (let index = 0; index < ranges.length; index++) { + const position = ranges[index].head; + const endOfLineState = editor.getStateAfter( + position.line + ); + const inner = CodeMirror.innerMode( + editor.getMode(), + endOfLineState + ); + const mode = inner && inner.mode; + if (!mode || mode.name !== "markdown" && + mode.name !== "gfm" && + mode.helperType !== "markdown") { + editor.execCommand("newlineAndIndent"); + return; + } + const modeState = inner.state || {}; + const inList = modeState.list !== false; + const inQuote = modeState.quote !== 0; + const line = editor.getLine(position.line) || ""; + const match = LIST_PATTERN.exec(line); + const cursorBeforeBullet = + /^\s*$/.test(line.slice(0, position.ch)); + if (!ranges[index].empty() || + !inList && !inQuote || + !match || + cursorBeforeBullet) { + editor.execCommand("newlineAndIndent"); + return; + } + if (EMPTY_LIST_PATTERN.test(line)) { + const endOfQuote = + inQuote && />\s*$/.test(line); + const endOfList = !/>\s*$/.test(line); + if (endOfQuote || endOfList) { + editor.replaceRange( + "", + CodeMirror.Pos(position.line, 0), + CodeMirror.Pos( + position.line, + position.ch + 1 + ) + ); + } + replacements[index] = "\n"; + } else { + const indent = match[1]; + const after = match[5]; + const numbered = + !UNORDERED_LIST_PATTERN.test(match[2]) && + match[2].indexOf(">") === -1; + const bullet = numbered ? + `${parseInt(match[3], 10) + 1}${match[4]}` : + match[2].replace(/x/i, " "); + replacements[index] = + `\n${indent}${bullet}${after}`; + if (numbered) { + _incrementMarkdownListNumbers( + editor, + position + ); + } + } + } + editor.replaceSelections(replacements); + }; + }); + } + + function _foldOption(editor, options, name, defaults) { + if (options && options[name] !== undefined) { + return options[name]; + } + const editorOptions = editor.getOption("foldOptions"); + if (editorOptions && editorOptions[name] !== undefined) { + return editorOptions[name]; + } + return defaults[name]; + } + + function installFoldCode(CodeMirror) { + return _installOnce(CodeMirror, "foldCode", function () { + const defaults = { + rangeFinder: null, + widget: "\u2194", + minFoldSize: 0, + scanUp: false, + clearOnEnter: true + }; + + if (!CodeMirror.fold || + typeof CodeMirror.fold.combine !== "function") { + CodeMirror.registerHelper("fold", "combine", function () { + const finders = Array.prototype.slice.call(arguments); + return function (editor, start) { + for (let index = 0; index < finders.length; index++) { + const found = finders[index](editor, start); + if (found) { + return found; + } + } + }; + }); + } + + if (!CodeMirror.fold || + typeof CodeMirror.fold.auto !== "function") { + CodeMirror.registerHelper("fold", "auto", function (editor, start) { + const helpers = editor.getHelpers(start, "fold"); + for (let index = 0; index < helpers.length; index++) { + if (helpers[index] === CodeMirror.fold.auto) { + continue; + } + const found = helpers[index](editor, start); + if (found) { + return found; + } + } + }); + } + defaults.rangeFinder = CodeMirror.fold.auto; + if (!CodeMirror.optionHandlers.foldOptions) { + CodeMirror.defineOption("foldOptions", null); + } + + const makeWidget = function (editor, options, range) { + let widget = _foldOption( + editor, + options, + "widget", + defaults + ); + if (typeof widget === "function") { + widget = widget(range.from, range.to); + } + if (typeof widget === "string") { + const ownerDocument = _documentFor(editor); + const element = ownerDocument.createElement("span"); + element.className = "CodeMirror-foldmarker"; + element.appendChild( + ownerDocument.createTextNode(widget) + ); + return element; + } + return widget && widget.cloneNode ? + widget.cloneNode(true) : + widget; + }; + + const doFold = function (editor, suppliedPosition, options, force) { + let position = typeof suppliedPosition === "number" ? + CodeMirror.Pos(suppliedPosition, 0) : + suppliedPosition || editor.getCursor(); + const finder = typeof options === "function" ? + options : + _foldOption( + editor, + options, + "rangeFinder", + defaults + ); + const minSize = _foldOption( + editor, + options, + "minFoldSize", + defaults + ); + if (typeof finder !== "function") { + return; + } + + const getRange = function (allowFolded) { + const range = finder(editor, position); + if (!range || + range.to.line - range.from.line < minSize) { + return null; + } + if (force === "fold") { + return range; + } + const marks = editor.findMarksAt(range.from); + for (let index = 0; index < marks.length; index++) { + if (marks[index].__isFold) { + if (!allowFolded) { + return null; + } + range.cleared = true; + marks[index].clear(); + } + } + return range; + }; + + let range = getRange(true); + if (_foldOption(editor, options, "scanUp", defaults)) { + while (!range && position.line > editor.firstLine()) { + position = CodeMirror.Pos(position.line - 1, 0); + range = getRange(false); + } + } + if (!range || range.cleared || force === "unfold") { + return; + } + + const widget = makeWidget(editor, options, range); + let marker; + if (widget) { + CodeMirror.on(widget, "mousedown", function (event) { + marker.clear(); + CodeMirror.e_preventDefault(event); + }); + } + marker = editor.markText(range.from, range.to, { + replacedWith: widget, + clearOnEnter: _foldOption( + editor, + options, + "clearOnEnter", + defaults + ), + __isFold: true + }); + marker.on("clear", function (from, to) { + CodeMirror.signal(editor, "unfold", editor, from, to); + }); + CodeMirror.signal( + editor, + "fold", + editor, + range.from, + range.to + ); + return marker; + }; + + if (typeof CodeMirror.newFoldFunction !== "function") { + CodeMirror.newFoldFunction = function (rangeFinder, widget) { + return function (editor, position) { + return editor.foldCode(position, { + rangeFinder: rangeFinder, + widget: widget + }); + }; + }; + } + if (typeof CodeMirror.prototype.foldCode !== "function") { + CodeMirror.defineExtension( + "foldCode", + function (position, options, force) { + return doFold(this, position, options, force); + } + ); + } + if (typeof CodeMirror.prototype.isFolded !== "function") { + CodeMirror.defineExtension("isFolded", function (position) { + return this.findMarksAt(position).some(function (marker) { + return Boolean(marker.__isFold); + }); + }); + } + if (typeof CodeMirror.prototype.foldOption !== "function") { + CodeMirror.defineExtension("foldOption", function (options, name) { + return _foldOption(this, options, name, defaults); + }); + } + if (typeof CodeMirror.commands.toggleFold !== "function") { + CodeMirror.commands.toggleFold = function (editor) { + return editor.foldCode(editor.getCursor()); + }; + } + if (typeof CodeMirror.commands.fold !== "function") { + CodeMirror.commands.fold = function (editor) { + return editor.foldCode( + editor.getCursor(), + null, + "fold" + ); + }; + } + if (typeof CodeMirror.commands.unfold !== "function") { + CodeMirror.commands.unfold = function (editor) { + return editor.foldCode( + editor.getCursor(), + {scanUp: false}, + "unfold" + ); + }; + } + if (typeof CodeMirror.commands.foldAll !== "function") { + CodeMirror.commands.foldAll = function (editor) { + return editor.operation(function () { + for (let line = editor.firstLine(); + line <= editor.lastLine(); + line++) { + editor.foldCode( + CodeMirror.Pos(line, 0), + {scanUp: false}, + "fold" + ); + } + }); + }; + } + if (typeof CodeMirror.commands.unfoldAll !== "function") { + CodeMirror.commands.unfoldAll = function (editor) { + return editor.operation(function () { + editor.getAllMarks().forEach(function (marker) { + if (marker.__isFold) { + marker.clear(); + } + }); + }); + }; + } + }); + } + + function _indentationForFold(CodeMirror, editor, lineNumber) { + const text = editor.getLine(lineNumber); + if (text === undefined) { + return -1; + } + const firstContent = text.search(/\S/); + if (firstContent === -1 || + /\bcomment\b/.test( + editor.getTokenTypeAt( + CodeMirror.Pos(lineNumber, firstContent + 1) + ) || "" + )) { + return -1; + } + return CodeMirror.countColumn( + text, + null, + editor.getOption("tabSize") + ); + } + + function installIndentFold(CodeMirror) { + return _installOnce(CodeMirror, "indentFold", function () { + if (CodeMirror.fold && + typeof CodeMirror.fold.indent === "function") { + return; + } + CodeMirror.registerHelper( + "fold", + "indent", + function (editor, start) { + const baseIndent = _indentationForFold( + CodeMirror, + editor, + start.line + ); + if (baseIndent < 0) { + return; + } + let lastLineInFold = null; + for (let line = start.line + 1; + line <= editor.lastLine(); + line++) { + const indentation = _indentationForFold( + CodeMirror, + editor, + line + ); + if (indentation === -1) { + continue; + } + if (indentation > baseIndent) { + lastLineInFold = line; + } else { + break; + } + } + if (lastLineInFold !== null) { + return { + from: CodeMirror.Pos( + start.line, + (editor.getLine(start.line) || "").length + ), + to: CodeMirror.Pos( + lastLineInFold, + (editor.getLine(lastLineInFold) || "").length + ) + }; + } + } + ); + }); + } + + function _foldGutterMarker(editor, specification) { + const ownerDocument = _documentFor(editor); + if (typeof specification === "string") { + const marker = ownerDocument.createElement("div"); + marker.className = + `${specification} CodeMirror-guttermarker-subtle`; + return marker; + } + return specification && specification.cloneNode ? + specification.cloneNode(true) : + specification; + } + + function _findFoldOnLine(CodeMirror, editor, line) { + const from = CodeMirror.Pos(line, 0); + const nextLine = Math.min(line + 1, editor.lastLine()); + const to = CodeMirror.Pos( + nextLine, + nextLine === line ? + (editor.getLine(line) || "").length : + 0 + ); + const marks = editor.findMarks(from, to); + for (let index = 0; index < marks.length; index++) { + if (marks[index].__isFold) { + const start = marks[index].find(-1); + if (start && start.line === line) { + return marks[index]; + } + } + } + } + + function _refreshFoldGutter(CodeMirror, editor) { + const state = editor.state.foldGutter; + if (!state) { + return; + } + const options = state.options; + const viewport = editor.getViewport(); + const from = Math.max(editor.firstLine(), viewport.from); + const to = Math.min(editor.lastLine() + 1, viewport.to); + const finder = editor.foldOption(options, "rangeFinder"); + const minimum = editor.foldOption(options, "minFoldSize"); + editor.operation(function () { + for (let line = from; line < to; line++) { + const folded = _findFoldOnLine(CodeMirror, editor, line); + let marker = null; + if (folded) { + marker = _foldGutterMarker( + editor, + options.indicatorFolded + ); + } else if (typeof finder === "function") { + const range = finder(editor, CodeMirror.Pos(line, 0)); + if (range && + range.to.line - range.from.line >= minimum) { + marker = _foldGutterMarker( + editor, + options.indicatorOpen + ); + } + } + editor.setGutterMarker(line, options.gutter, marker); + } + }); + state.from = from; + state.to = to; + } + + function _clearFoldGutter(editor) { + const state = editor.state.foldGutter; + if (!state) { + return; + } + window.clearTimeout(state.timeout); + editor.clearGutter(state.options.gutter); + Object.keys(state.listeners).forEach(function (eventName) { + editor.off(eventName, state.listeners[eventName]); + }); + editor.state.foldGutter = null; + } + + function installFoldGutter(CodeMirror) { + installFoldCode(CodeMirror); + return _installOnce(CodeMirror, "foldGutter", function () { + if (CodeMirror.optionHandlers.foldGutter) { + return; + } + CodeMirror.defineOption( + "foldGutter", + false, + function (editor, value, oldValue) { + if (oldValue && oldValue !== CodeMirror.Init) { + _clearFoldGutter(editor); + } + if (!value) { + return; + } + const options = Object.assign({ + gutter: "CodeMirror-foldgutter", + indicatorFolded: + "CodeMirror-foldgutter-folded", + indicatorOpen: "CodeMirror-foldgutter-open" + }, value === true ? {} : value); + const schedule = function (delay) { + const state = editor.state.foldGutter; + if (!state) { + return; + } + window.clearTimeout(state.timeout); + state.timeout = window.setTimeout(function () { + _refreshFoldGutter(CodeMirror, editor); + }, delay); + }; + const listeners = { + gutterClick: function (_editor, line, gutter) { + if (gutter !== options.gutter) { + return; + } + const folded = _findFoldOnLine( + CodeMirror, + editor, + line + ); + if (folded) { + folded.clear(); + } else { + editor.foldCode( + CodeMirror.Pos(line, 0), + options + ); + } + }, + changes: function () { + schedule(options.foldOnChangeTimeSpan || 600); + }, + viewportChange: function () { + schedule(options.updateViewportTimeSpan || 400); + }, + fold: function () { + _refreshFoldGutter(CodeMirror, editor); + }, + unfold: function () { + _refreshFoldGutter(CodeMirror, editor); + }, + swapDoc: function () { + schedule(0); + }, + optionChange: function (_editor, option) { + if (option === "mode") { + schedule(0); + } + } + }; + editor.state.foldGutter = { + from: 0, + listeners: listeners, + options: options, + timeout: null, + to: 0 + }; + Object.keys(listeners).forEach(function (eventName) { + editor.on(eventName, listeners[eventName]); + }); + _refreshFoldGutter(CodeMirror, editor); + } + ); + }); + } + + function _findParagraph(editor, position, options) { + const startExpression = options.paragraphStart || + editor.getHelper(position, "paragraphStart"); + const endExpression = options.paragraphEnd || + editor.getHelper(position, "paragraphEnd"); + let start = position.line; + let end = position.line + 1; + for (; start > editor.firstLine(); start--) { + const line = editor.getLine(start) || ""; + if (startExpression && startExpression.test(line)) { + break; + } + if (!/\S/.test(line)) { + start++; + break; + } + } + for (; end <= editor.lastLine(); end++) { + const line = editor.getLine(end) || ""; + if (endExpression && endExpression.test(line)) { + end++; + break; + } + if (!/\S/.test(line)) { + break; + } + } + return {from: start, to: end}; + } + + function _findWrapPoint(text, column, wrapOn, trimTrailing, forceBreak) { + let at = column; + while (at < text.length && text.charAt(at) === " ") { + at++; + } + for (; at > 0; at--) { + wrapOn.lastIndex = 0; + if (wrapOn.test(text.slice(at - 1, at + 1))) { + break; + } + } + if (!forceBreak && at <= text.match(/^[ \t]*/)[0].length) { + for (at = column + 1; at < text.length - 1; at++) { + wrapOn.lastIndex = 0; + if (wrapOn.test(text.slice(at - 1, at + 1))) { + break; + } + } + } + let first = true; + while (true) { + let end = at; + if (trimTrailing) { + while (text.charAt(end - 1) === " ") { + end--; + } + } + if (end === 0 && first) { + at = column; + first = false; + } else { + return {from: end, to: at}; + } + } + } + + function _wrapRange(CodeMirror, editor, from, to, suppliedOptions) { + const options = suppliedOptions || {}; + const clippedFrom = editor.clipPos(from); + const clippedTo = editor.clipPos(to); + let column = options.column || 80; + const wrapOn = options.wrapOn || /\s\S|-[^.\d]/; + const forceBreak = options.forceBreak !== false; + const trimTrailing = options.killTrailingSpace !== false; + const lines = editor.getRange(clippedFrom, clippedTo, false); + if (!lines.length) { + return null; + } + const leadingSpace = lines[0].match(/^[ \t]*/)[0]; + if (leadingSpace.length >= column) { + column = leadingSpace.length + 1; + } + + const changes = []; + let currentLine = ""; + let currentLineNumber = clippedFrom.line; + lines.forEach(function (originalText, lineIndex) { + let text = originalText; + const oldLength = currentLine.length; + let insertedSpace = 0; + wrapOn.lastIndex = 0; + if (currentLine && text && + !wrapOn.test( + currentLine.charAt(currentLine.length - 1) + + text.charAt(0) + )) { + currentLine += " "; + insertedSpace = 1; + } + let trimmed = ""; + if (lineIndex) { + trimmed = text.match(/^\s*/)[0]; + text = text.slice(trimmed.length); + } + currentLine += text; + if (lineIndex) { + const firstBreak = currentLine.length > column && + leadingSpace === trimmed && + _findWrapPoint( + currentLine, + column, + wrapOn, + trimTrailing, + forceBreak + ); + if (!firstBreak || + firstBreak.from !== oldLength || + firstBreak.to !== oldLength + insertedSpace) { + changes.push({ + text: insertedSpace ? " " : "", + from: CodeMirror.Pos(currentLineNumber, oldLength), + to: CodeMirror.Pos( + currentLineNumber + 1, + trimmed.length + ) + }); + } else { + currentLine = leadingSpace + text; + currentLineNumber++; + } + } + while (currentLine.length > column) { + const breakPoint = _findWrapPoint( + currentLine, + column, + wrapOn, + trimTrailing, + forceBreak + ); + if (breakPoint.from !== breakPoint.to || + forceBreak && + leadingSpace !== + currentLine.slice(0, breakPoint.to)) { + changes.push({ + text: `\n${leadingSpace}`, + from: CodeMirror.Pos( + currentLineNumber, + breakPoint.from + ), + to: CodeMirror.Pos( + currentLineNumber, + breakPoint.to + ) + }); + currentLine = + leadingSpace + currentLine.slice(breakPoint.to); + currentLineNumber++; + } else { + break; + } + } + }); + + if (!changes.length) { + return null; + } + editor.operation(function () { + changes.forEach(function (change) { + if (change.text || + CodeMirror.cmpPos(change.from, change.to)) { + editor.replaceRange( + change.text, + change.from, + change.to + ); + } + }); + }); + return { + from: changes[0].from, + to: CodeMirror.changeEnd(changes[changes.length - 1]) + }; + } + + function installHardWrap(CodeMirror) { + return _installOnce(CodeMirror, "hardWrap", function () { + CodeMirror.defineExtension( + "wrapRange", + function (from, to, options) { + return _wrapRange( + CodeMirror, + this, + from, + to, + options + ); + } + ); + CodeMirror.defineExtension( + "wrapParagraph", + function (position, suppliedOptions) { + const options = suppliedOptions || {}; + const cursor = position || this.getCursor(); + const paragraph = _findParagraph( + this, + cursor, + options + ); + return _wrapRange( + CodeMirror, + this, + CodeMirror.Pos(paragraph.from, 0), + CodeMirror.Pos(paragraph.to - 1), + options + ); + } + ); + CodeMirror.defineExtension( + "wrapParagraphsInRange", + function (from, to, suppliedOptions) { + const editor = this; + const options = suppliedOptions || {}; + const paragraphs = []; + for (let line = from.line; line <= to.line;) { + const paragraph = _findParagraph( + editor, + CodeMirror.Pos(line, 0), + options + ); + paragraphs.push(paragraph); + line = Math.max(line + 1, paragraph.to); + } + let changed = null; + editor.operation(function () { + for (let index = paragraphs.length - 1; + index >= 0; + index--) { + changed = _wrapRange( + CodeMirror, + editor, + CodeMirror.Pos( + paragraphs[index].from, + 0 + ), + CodeMirror.Pos( + paragraphs[index].to - 1 + ), + options + ) || changed; + } + }); + return changed; + } + ); + CodeMirror.commands.wrapLines = function (editor) { + return editor.operation(function () { + const ranges = editor.listSelections(); + let previousLine = editor.lastLine() + 1; + for (let index = ranges.length - 1; + index >= 0; + index--) { + const range = ranges[index]; + let span; + if (range.empty()) { + const paragraph = _findParagraph( + editor, + range.head, + {} + ); + span = { + from: CodeMirror.Pos(paragraph.from, 0), + to: CodeMirror.Pos(paragraph.to - 1) + }; + } else { + span = { + from: range.from(), + to: range.to() + }; + } + if (span.to.line >= previousLine) { + continue; + } + previousLine = span.from.line; + _wrapRange( + CodeMirror, + editor, + span.from, + span.to, + {} + ); + } + }); + }; + }); + } + + const CSS_PSEUDO_CLASSES = [ + "active", "after", "before", "checked", "default", "disabled", + "empty", "enabled", "first-child", "first-letter", "first-line", + "first-of-type", "focus", "hover", "in-range", "indeterminate", + "invalid", "lang", "last-child", "last-of-type", "link", "not", + "nth-child", "nth-last-child", "nth-last-of-type", "nth-of-type", + "only-of-type", "only-child", "optional", "out-of-range", + "placeholder", "read-only", "read-write", "required", "root", + "selection", "target", "valid", "visited" + ]; + const CSS_PROPERTIES = [ + "align-content", "align-items", "align-self", "animation", + "appearance", "aspect-ratio", "backdrop-filter", "background", + "background-color", "background-image", "background-position", + "background-repeat", "background-size", "border", "border-color", + "border-radius", "border-style", "border-width", "bottom", + "box-shadow", "box-sizing", "color", "column-count", "content", + "cursor", "display", "filter", "flex", "flex-basis", + "flex-direction", "flex-flow", "flex-grow", "flex-shrink", + "flex-wrap", "float", "font", "font-family", "font-size", + "font-style", "font-weight", "gap", "grid", "grid-area", + "grid-auto-columns", "grid-auto-flow", "grid-auto-rows", + "grid-column", "grid-row", "grid-template", + "grid-template-columns", "grid-template-rows", "height", + "inset", "justify-content", "left", "letter-spacing", + "line-height", "list-style", "margin", "margin-bottom", + "margin-left", "margin-right", "margin-top", "max-height", + "max-width", "min-height", "min-width", "object-fit", "opacity", + "order", "outline", "overflow", "overflow-x", "overflow-y", + "padding", "padding-bottom", "padding-left", "padding-right", + "padding-top", "pointer-events", "position", "right", + "table-layout", "text-align", "text-decoration", "text-overflow", + "text-transform", "top", "transform", "transform-origin", + "transition", "user-select", "vertical-align", "visibility", + "white-space", "width", "word-break", "word-wrap", "z-index" + ]; + const CSS_VALUES = [ + "absolute", "auto", "baseline", "block", "bold", "border-box", + "both", "bottom", "center", "column", "contain", "contents", + "cover", "currentcolor", "dashed", "default", "ease", "fixed", + "flex", "grid", "hidden", "inherit", "initial", "inline", + "inline-block", "inline-flex", "inline-grid", "left", "none", + "normal", "nowrap", "relative", "repeat", "right", "row", + "scroll", "solid", "space-around", "space-between", + "space-evenly", "sticky", "stretch", "top", "transparent", + "unset", "visible", "wrap" + ]; + const CSS_COLORS = [ + "aliceblue", "aqua", "black", "blue", "currentcolor", "fuchsia", + "gray", "green", "lime", "maroon", "navy", "olive", "orange", + "purple", "red", "silver", "teal", "transparent", "white", + "yellow" + ]; + const CSS_MEDIA_TYPES = [ + "all", "aural", "braille", "handheld", "print", "projection", + "screen", "tty", "tv", "embossed" + ]; + const CSS_MEDIA_FEATURES = [ + "width", "min-width", "max-width", "height", "min-height", + "max-height", "device-width", "min-device-width", "max-device-width", + "device-height", "min-device-height", "max-device-height", + "aspect-ratio", "min-aspect-ratio", "max-aspect-ratio", + "device-aspect-ratio", "min-device-aspect-ratio", + "max-device-aspect-ratio", "color", "min-color", "max-color", + "color-index", "min-color-index", "max-color-index", "monochrome", + "min-monochrome", "max-monochrome", "resolution", "min-resolution", + "max-resolution", "scan", "grid", "orientation", + "device-pixel-ratio", "min-device-pixel-ratio", + "max-device-pixel-ratio", "pointer", "any-pointer", "hover", + "any-hover", "prefers-color-scheme", "dynamic-range", + "video-dynamic-range" + ]; + const cssHintDataCache = new WeakMap(); + + function _cssCandidateStyle(CodeMirror, mode, prefix, candidate) { + const state = CodeMirror.startState(mode); + const stream = new CodeMirror.StringStream( + prefix + candidate, + 4 + ); + let style = null; + while (!stream.eol()) { + stream.start = stream.pos; + style = mode.token(stream, state); + if (stream.pos <= stream.start) { + stream.next(); + } + } + return style; + } + + function _cssHintData(CodeMirror) { + let cached = cssHintDataCache.get(CodeMirror); + if (cached) { + return cached; + } + + const mode = CodeMirror.getMode({indentUnit: 2}, "text/css"); + const autocomplete = mode && mode.languageData && + mode.languageData.autocomplete; + if (!Array.isArray(autocomplete)) { + cached = { + colors: CSS_COLORS, + properties: CSS_PROPERTIES, + values: CSS_VALUES + }; + cssHintDataCache.set(CodeMirror, cached); + return cached; + } + + const allWords = Array.from(new Set(autocomplete.map(function (word) { + return String(word).toLowerCase(); + }))); + cached = { + colors: allWords.filter(function (candidate) { + return _cssCandidateStyle( + CodeMirror, + mode, + ".CodeMirror-hint { color: ", + candidate + ) === "keyword"; + }), + properties: allWords.filter(function (candidate) { + return _cssCandidateStyle( + CodeMirror, + mode, + ".CodeMirror-hint { ", + candidate + ) === "property"; + }), + values: allWords.filter(function (candidate) { + return _cssCandidateStyle( + CodeMirror, + mode, + ".CodeMirror-hint { color: ", + candidate + ) === "atom"; + }) + }; + cssHintDataCache.set(CodeMirror, cached); + return cached; + } + + function installCSSHint(CodeMirror) { + return _installOnce(CodeMirror, "cssHint", function () { + CodeMirror.registerHelper("hint", "css", function (editor) { + const cursor = editor.getCursor(); + const token = editor.getTokenAt(cursor); + const inner = CodeMirror.innerMode( + editor.getMode(), + token.state + ); + if (!inner.mode || inner.mode.name !== "css") { + return; + } + if (token.type === "keyword" && + "!important".indexOf(token.string) === 0) { + return { + from: CodeMirror.Pos(cursor.line, token.start), + list: ["!important"], + to: CodeMirror.Pos(cursor.line, token.end) + }; + } + + let start = token.start; + let end = cursor.ch; + let word = token.string.slice(0, end - start); + if (/[^\w$_-]/.test(word)) { + word = ""; + start = end; + } + const stateName = inner.state && inner.state.state; + const hintData = _cssHintData(CodeMirror); + const list = []; + const add = function (values) { + values.forEach(function (name) { + if (!word || name.lastIndexOf(word, 0) === 0) { + list.push(name); + } + }); + }; + + if (stateName === "pseudo" || + /\bvariable-3\b/.test(token.type || "")) { + add(CSS_PSEUDO_CLASSES); + } else if (stateName === "block" || + stateName === "maybeprop") { + add(hintData.properties); + } else if (stateName === "prop" || + stateName === "parens" || + stateName === "at" || + stateName === "params") { + add(hintData.values); + add(hintData.colors); + } else if (stateName === "media" || + stateName === "media_parens" || + stateName === "atBlock" || + stateName === "atBlock_parens") { + add(CSS_MEDIA_TYPES); + add(CSS_MEDIA_FEATURES); + } + + if (list.length) { + return { + from: CodeMirror.Pos(cursor.line, start), + list: list, + to: CodeMirror.Pos(cursor.line, end) + }; + } + }); + }); + } + + const HTML_LANGUAGE_CODES = ( + "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs " + + "br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee " + + "fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga " + + "ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj " + + "la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb " + + "nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn " + + "ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta " + + "te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa " + + "cy wo fy xh yi yo za zu" + ).split(" "); + + function _createHTMLSchema() { + const targets = ["_blank", "_self", "_top", "_parent"]; + const charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"]; + const methods = ["get", "post", "put", "delete"]; + const encodings = [ + "application/x-www-form-urlencoded", + "multipart/form-data", + "text/plain" + ]; + const media = [ + "all", "screen", "print", "embossed", "braille", "handheld", + "print", "projection", "screen", "tty", "tv", "speech", + "3d-glasses", "resolution [>][<][=] [X]", + "device-aspect-ratio: X/Y", "orientation:portrait", + "orientation:landscape", "device-height: [X]", + "device-width: [X]" + ]; + const simple = {attrs: {}}; + const schema = { + a: { + attrs: { + href: null, + ping: null, + type: null, + media: media, + target: targets, + hreflang: HTML_LANGUAGE_CODES + } + }, + abbr: simple, + acronym: simple, + address: simple, + applet: simple, + area: { + attrs: { + alt: null, + coords: null, + href: null, + target: null, + ping: null, + media: media, + hreflang: HTML_LANGUAGE_CODES, + type: null, + shape: ["default", "rect", "circle", "poly"] + } + }, + article: simple, + aside: simple, + audio: { + attrs: { + src: null, + mediagroup: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["none", "metadata", "auto"], + autoplay: ["", "autoplay"], + loop: ["", "loop"], + controls: ["", "controls"] + } + }, + b: simple, + base: {attrs: {href: null, target: targets}}, + basefont: simple, + bdi: simple, + bdo: simple, + big: simple, + blockquote: {attrs: {cite: null}}, + body: simple, + br: simple, + button: { + attrs: { + form: null, + formaction: null, + name: null, + value: null, + autofocus: ["", "autofocus"], + disabled: ["", "autofocus"], + formenctype: encodings, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + type: ["submit", "reset", "button"] + } + }, + canvas: {attrs: {width: null, height: null}}, + caption: simple, + center: simple, + cite: simple, + code: simple, + col: {attrs: {span: null}}, + colgroup: {attrs: {span: null}}, + command: { + attrs: { + type: ["command", "checkbox", "radio"], + label: null, + icon: null, + radiogroup: null, + command: null, + title: null, + disabled: ["", "disabled"], + checked: ["", "checked"] + } + }, + data: {attrs: {value: null}}, + datagrid: { + attrs: { + disabled: ["", "disabled"], + multiple: ["", "multiple"] + } + }, + datalist: {attrs: {data: null}}, + dd: simple, + del: {attrs: {cite: null, datetime: null}}, + details: {attrs: {open: ["", "open"]}}, + dfn: simple, + dir: simple, + div: simple, + dialog: {attrs: {open: null}}, + dl: simple, + dt: simple, + em: simple, + embed: { + attrs: { + src: null, + type: null, + width: null, + height: null + } + }, + eventsource: {attrs: {src: null}}, + fieldset: { + attrs: { + disabled: ["", "disabled"], + form: null, + name: null + } + }, + figcaption: simple, + figure: simple, + font: simple, + footer: simple, + form: { + attrs: { + action: null, + name: null, + "accept-charset": charsets, + autocomplete: ["on", "off"], + enctype: encodings, + method: methods, + novalidate: ["", "novalidate"], + target: targets + } + }, + frame: simple, + frameset: simple, + h1: simple, + h2: simple, + h3: simple, + h4: simple, + h5: simple, + h6: simple, + head: { + attrs: {}, + children: [ + "title", "base", "link", "style", "meta", "script", + "noscript", "command" + ] + }, + header: simple, + hgroup: simple, + hr: simple, + html: { + attrs: {manifest: null}, + children: ["head", "body"] + }, + i: simple, + iframe: { + attrs: { + src: null, + srcdoc: null, + name: null, + width: null, + height: null, + sandbox: [ + "allow-top-navigation", + "allow-same-origin", + "allow-forms", + "allow-scripts" + ], + seamless: ["", "seamless"] + } + }, + img: { + attrs: { + alt: null, + src: null, + ismap: null, + usemap: null, + width: null, + height: null, + crossorigin: ["anonymous", "use-credentials"] + } + }, + input: { + attrs: { + alt: null, + dirname: null, + form: null, + formaction: null, + height: null, + list: null, + max: null, + maxlength: null, + min: null, + name: null, + pattern: null, + placeholder: null, + size: null, + src: null, + step: null, + value: null, + width: null, + accept: ["audio/*", "video/*", "image/*"], + autocomplete: ["on", "off"], + autofocus: ["", "autofocus"], + checked: ["", "checked"], + disabled: ["", "disabled"], + formenctype: encodings, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + multiple: ["", "multiple"], + readonly: ["", "readonly"], + required: ["", "required"], + type: [ + "hidden", "text", "search", "tel", "url", "email", + "password", "datetime", "date", "month", "week", + "time", "datetime-local", "number", "range", "color", + "checkbox", "radio", "file", "submit", "image", + "reset", "button" + ] + } + }, + ins: {attrs: {cite: null, datetime: null}}, + kbd: simple, + keygen: { + attrs: { + challenge: null, + form: null, + name: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + keytype: ["RSA"] + } + }, + label: {attrs: {"for": null, form: null}}, + legend: simple, + li: {attrs: {value: null}}, + link: { + attrs: { + href: null, + type: null, + hreflang: HTML_LANGUAGE_CODES, + media: media, + sizes: [ + "all", + "16x16", + "16x16 32x32", + "16x16 32x32 64x64" + ] + } + }, + map: {attrs: {name: null}}, + mark: simple, + menu: { + attrs: { + label: null, + type: ["list", "context", "toolbar"] + } + }, + meta: { + attrs: { + content: null, + charset: charsets, + name: [ + "viewport", "application-name", "author", + "description", "generator", "keywords" + ], + "http-equiv": [ + "content-language", + "content-type", + "default-style", + "refresh" + ] + } + }, + meter: { + attrs: { + value: null, + min: null, + low: null, + high: null, + max: null, + optimum: null + } + }, + nav: simple, + noframes: simple, + noscript: simple, + object: { + attrs: { + data: null, + type: null, + name: null, + usemap: null, + form: null, + width: null, + height: null, + typemustmatch: ["", "typemustmatch"] + } + }, + ol: { + attrs: { + reversed: ["", "reversed"], + start: null, + type: ["1", "a", "A", "i", "I"] + } + }, + optgroup: { + attrs: { + disabled: ["", "disabled"], + label: null + } + }, + option: { + attrs: { + disabled: ["", "disabled"], + label: null, + selected: ["", "selected"], + value: null + } + }, + output: {attrs: {"for": null, form: null, name: null}}, + p: simple, + param: {attrs: {name: null, value: null}}, + pre: simple, + progress: {attrs: {value: null, max: null}}, + q: {attrs: {cite: null}}, + rp: simple, + rt: simple, + ruby: simple, + s: simple, + samp: simple, + script: { + attrs: { + type: ["text/javascript"], + src: null, + async: ["", "async"], + defer: ["", "defer"], + charset: charsets + } + }, + section: simple, + select: { + attrs: { + form: null, + name: null, + size: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + multiple: ["", "multiple"] + } + }, + small: simple, + source: {attrs: {src: null, type: null, media: null}}, + span: simple, + strike: simple, + strong: simple, + style: { + attrs: { + type: ["text/css"], + media: media, + scoped: null + } + }, + sub: simple, + summary: simple, + sup: simple, + table: simple, + tbody: simple, + td: { + attrs: { + colspan: null, + rowspan: null, + headers: null + } + }, + textarea: { + attrs: { + dirname: null, + form: null, + maxlength: null, + name: null, + placeholder: null, + rows: null, + cols: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + readonly: ["", "readonly"], + required: ["", "required"], + wrap: ["soft", "hard"] + } + }, + tfoot: simple, + th: { + attrs: { + colspan: null, + rowspan: null, + headers: null, + scope: ["row", "col", "rowgroup", "colgroup"] + } + }, + thead: simple, + time: {attrs: {datetime: null}}, + title: simple, + tr: simple, + track: { + attrs: { + src: null, + label: null, + "default": null, + kind: [ + "subtitles", + "captions", + "descriptions", + "chapters", + "metadata" + ], + srclang: HTML_LANGUAGE_CODES + } + }, + tt: simple, + u: simple, + ul: simple, + "var": simple, + video: { + attrs: { + src: null, + poster: null, + width: null, + height: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["auto", "metadata", "none"], + autoplay: ["", "autoplay"], + mediagroup: ["movie"], + muted: ["", "muted"], + controls: ["", "controls"] + } + }, + wbr: simple + }; + const globalAttrs = { + accesskey: ( + "a b c d e f g h i j k l m n o p q r s t u v w x y z " + + "0 1 2 3 4 5 6 7 8 9" + ).split(" "), + class: null, + contenteditable: ["true", "false"], + contextmenu: null, + dir: ["ltr", "rtl", "auto"], + draggable: ["true", "false", "auto"], + dropzone: ["copy", "move", "link", "string:", "file:"], + hidden: ["hidden"], + id: null, + inert: ["inert"], + itemid: null, + itemprop: null, + itemref: null, + itemscope: ["itemscope"], + itemtype: null, + lang: ["en", "es"], + spellcheck: ["true", "false"], + autocorrect: ["true", "false"], + autocapitalize: ["true", "false"], + style: null, + tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + title: null, + translate: ["yes", "no"], + onclick: null, + rel: [ + "stylesheet", "alternate", "author", "bookmark", "help", + "license", "next", "nofollow", "noreferrer", "prefetch", + "prev", "search", "tag" + ] + }; + Object.keys(globalAttrs).forEach(function (attribute) { + simple.attrs[attribute] = globalAttrs[attribute]; + }); + Object.keys(schema).forEach(function (tag) { + if (schema[tag] === simple) { + return; + } + Object.keys(globalAttrs).forEach(function (attribute) { + schema[tag].attrs[attribute] = globalAttrs[attribute]; + }); + }); + return schema; + } + + function _xmlHints(CodeMirror, editor, suppliedOptions) { + const options = suppliedOptions || {}; + const tags = options.schemaInfo; + const quoteOption = options.quoteChar || "\""; + const matchInMiddle = options.matchInMiddle; + if (!tags) { + return; + } + + const cursor = editor.getCursor(); + const token = editor.getTokenAt(cursor); + if (token.end > cursor.ch) { + token.end = cursor.ch; + token.string = token.string.slice(0, cursor.ch - token.start); + } + const inner = CodeMirror.innerMode(editor.getMode(), token.state); + if (!inner.mode || typeof inner.mode.xmlCurrentTag !== "function") { + return; + } + + const matches = function (candidate, typed) { + return matchInMiddle ? + candidate.indexOf(typed) >= 0 : + candidate.lastIndexOf(typed, 0) === 0; + }; + const result = []; + let replaceToken = false; + let prefix; + const tag = /\btag\b/.test(token.type || "") && + !/>$/.test(token.string); + const tagName = tag && /^\w/.test(token.string); + let tagStart; + let tagType; + + if (tagName) { + const before = (editor.getLine(cursor.line) || "").slice( + Math.max(0, token.start - 2), + token.start + ); + tagType = /<\/$/.test(before) ? + "close" : + /<$/.test(before) ? "open" : null; + if (tagType) { + tagStart = token.start - (tagType === "close" ? 2 : 1); + } + } else if (tag && token.string === "<") { + tagType = "open"; + } else if (tag && token.string === ""); + } + } else { + const tagInfo = currentTag && tags[currentTag.name]; + let attrs = tagInfo && tagInfo.attrs; + const globalAttrs = tags["!attrs"]; + if (!attrs && !globalAttrs) { + return; + } + if (!attrs) { + attrs = globalAttrs; + } else if (globalAttrs) { + attrs = Object.assign({}, globalAttrs, attrs); + } + + if (token.type === "string" || token.string === "=") { + const before = editor.getRange( + CodeMirror.Pos( + cursor.line, + Math.max(0, cursor.ch - 60) + ), + CodeMirror.Pos( + cursor.line, + token.type === "string" ? + token.start : + token.end + ) + ); + const attributeName = + /([^\s\u00a0=<>"']+)=$/.exec(before); + let attributeValues; + if (!attributeName || + !Object.prototype.hasOwnProperty.call( + attrs, + attributeName[1] + ) || + !(attributeValues = attrs[attributeName[1]])) { + return; + } + if (typeof attributeValues === "function") { + attributeValues = attributeValues.call(this, editor); + } + + let quote = quoteOption; + if (token.type === "string") { + prefix = token.string; + let openingQuoteLength = 0; + if (/['"]/.test(token.string.charAt(0))) { + quote = token.string.charAt(0); + prefix = token.string.slice(1); + openingQuoteLength++; + } + const tokenLength = token.string.length; + if (/['"]/.test(token.string.charAt(tokenLength - 1))) { + quote = token.string.charAt(tokenLength - 1); + prefix = token.string.substr( + openingQuoteLength, + tokenLength - 2 + ); + } + if (openingQuoteLength) { + const line = editor.getLine(cursor.line) || ""; + if (line.length > token.end && + line.charAt(token.end) === quote) { + token.end++; + } + } + replaceToken = true; + } + + const finishValues = function (resolvedValues) { + if (resolvedValues) { + for (let index = 0; + index < resolvedValues.length; + index++) { + const value = resolvedValues[index]; + if (!prefix || matches(value, prefix)) { + result.push(quote + value + quote); + } + } + } + return { + from: replaceToken ? + CodeMirror.Pos(cursor.line, token.start) : + cursor, + list: result, + to: replaceToken ? + CodeMirror.Pos(cursor.line, token.end) : + cursor + }; + }; + if (attributeValues && attributeValues.then) { + return attributeValues.then(finishValues); + } + return finishValues(attributeValues); + } + + if (token.type === "attribute") { + prefix = token.string; + replaceToken = true; + } + Object.keys(attrs).forEach(function (name) { + if (!prefix || matches(name, prefix)) { + result.push(name); + } + }); + } + + return { + from: replaceToken ? + CodeMirror.Pos( + cursor.line, + tagStart === undefined ? token.start : tagStart + ) : + cursor, + list: result, + to: replaceToken ? + CodeMirror.Pos(cursor.line, token.end) : + cursor + }; + } + + function installXMLHint(CodeMirror) { + return _installOnce(CodeMirror, "xmlHint", function () { + CodeMirror.registerHelper("hint", "xml", function (editor, options) { + return _xmlHints.call(this, CodeMirror, editor, options); + }); + }); + } + + function installHTMLHint(CodeMirror) { + installXMLHint(CodeMirror); + return _installOnce(CodeMirror, "htmlHint", function () { + CodeMirror.htmlSchema = _createHTMLSchema(); + CodeMirror.registerHelper("hint", "html", function (editor, options) { + const localOptions = { + schemaInfo: CodeMirror.htmlSchema + }; + if (options) { + for (const name in options) { + localOptions[name] = options[name]; + } + } + return CodeMirror.hint.xml( + editor, + localOptions + ); + }); + }); + } + + const JAVASCRIPT_KEYWORDS = ( + "break case catch class const continue debugger default delete do else " + + "export extends false finally for function if in import instanceof " + + "new null return super switch this throw true try typeof var void " + + "while with yield" + ).split(" "); + const COFFEESCRIPT_KEYWORDS = ( + "and break catch class continue delete do else extends false finally " + + "for if in instanceof isnt new no not null of off on or return switch " + + "then throw true try typeof until void while with yes" + ).split(" "); + const STRING_PROPERTIES = ( + "charAt charCodeAt indexOf lastIndexOf substring substr slice trim " + + "trimLeft trimRight toUpperCase toLowerCase split concat match replace " + + "search" + ).split(" "); + const ARRAY_PROPERTIES = ( + "length concat join splice push pop shift unshift slice reverse sort " + + "indexOf lastIndexOf every some filter forEach map reduce reduceRight" + ).split(" "); + const FUNCTION_PROPERTIES = "prototype apply call bind".split(" "); + + function _safePropertyNames(value) { + const names = []; + const seen = new Set(); + try { + for (let object = value; object; object = Object.getPrototypeOf(object)) { + Object.getOwnPropertyNames(object).forEach(function (name) { + if (!seen.has(name)) { + seen.add(name); + names.push(name); + } + }); + } + } catch (error) { + return names; + } + return names; + } + + function _coffeeScriptToken(editor, cursor) { + const token = editor.getTokenAt(cursor); + if (cursor.ch === token.start + 1 && + token.string.charAt(0) === ".") { + token.end = token.start; + token.string = "."; + token.type = "property"; + } else if (/^\.[\w$_]*$/.test(token.string)) { + token.type = "property"; + token.start++; + token.string = token.string.replace(/\./, ""); + } + return token; + } + + function _javascriptCompletions( + token, + context, + keywords, + suppliedOptions + ) { + const options = suppliedOptions || {}; + const found = []; + const start = token.string; + const globalScope = options.globalScope || + (typeof window !== "undefined" ? window : {}); + + const maybeAdd = function (name) { + if (typeof name === "string" && + name.lastIndexOf(start, 0) === 0 && + found.indexOf(name) === -1) { + found.push(name); + } + }; + const gatherCompletions = function (value) { + if (typeof value === "string") { + STRING_PROPERTIES.forEach(maybeAdd); + } else if (Array.isArray(value)) { + ARRAY_PROPERTIES.forEach(maybeAdd); + } else if (typeof value === "function") { + FUNCTION_PROPERTIES.forEach(maybeAdd); + } + _safePropertyNames(value).forEach(maybeAdd); + }; + + if (context && context.length) { + const objectToken = context.pop(); + let base; + if (objectToken.type && + objectToken.type.indexOf("variable") === 0) { + if (options.additionalContext) { + base = options.additionalContext[objectToken.string]; + } + if (options.useGlobalScope !== false) { + base = base || globalScope[objectToken.string]; + } + } else if (objectToken.type === "string") { + base = ""; + } else if (objectToken.type === "atom") { + base = 1; + } else if (objectToken.type === "function") { + if (globalScope.jQuery !== null && + globalScope.jQuery !== undefined && + (objectToken.string === "$" || + objectToken.string === "jQuery") && + typeof globalScope.jQuery === "function") { + base = globalScope.jQuery(); + } else if (globalScope._ !== null && + globalScope._ !== undefined && + objectToken.string === "_" && + typeof globalScope._ === "function") { + base = globalScope._(); + } + } + while (base !== null && + base !== undefined && + context.length) { + try { + base = base[context.pop().string]; + } catch (error) { + base = undefined; + } + } + if (base !== null && base !== undefined) { + gatherCompletions(base); + } + } else { + let variable; + for (variable = token.state && token.state.localVars; + variable; + variable = variable.next) { + maybeAdd(variable.name); + } + for (let scope = token.state && token.state.context; + scope; + scope = scope.prev) { + for (variable = scope.vars; + variable; + variable = variable.next) { + maybeAdd(variable.name); + } + } + for (variable = token.state && token.state.globalVars; + variable; + variable = variable.next) { + maybeAdd(variable.name); + } + if (options.additionalContext !== null && + options.additionalContext !== undefined) { + for (const name in options.additionalContext) { + maybeAdd(name); + } + } + if (options.useGlobalScope !== false) { + gatherCompletions(globalScope); + } + keywords.forEach(maybeAdd); + } + return found; + } + + function _javascriptHint( + CodeMirror, + editor, + suppliedOptions, + coffeescript + ) { + const cursor = editor.getCursor(); + let token = coffeescript ? + _coffeeScriptToken(editor, cursor) : + editor.getTokenAt(cursor); + if (/\b(?:string|comment)\b/.test(token.type || "")) { + return; + } + const inner = CodeMirror.innerMode(editor.getMode(), token.state); + if (inner.mode && inner.mode.helperType === "json") { + return; + } + token.state = inner.state; + + if (!/^[\w$_]*$/.test(token.string)) { + token = { + end: cursor.ch, + start: cursor.ch, + state: token.state, + string: "", + type: token.string === "." ? "property" : null + }; + } else if (token.end > cursor.ch) { + token.end = cursor.ch; + token.string = token.string.slice( + 0, + cursor.ch - token.start + ); + } + + let propertyToken = token; + let context; + while (propertyToken.type === "property") { + propertyToken = coffeescript ? + _coffeeScriptToken( + editor, + CodeMirror.Pos(cursor.line, propertyToken.start) + ) : + editor.getTokenAt( + CodeMirror.Pos(cursor.line, propertyToken.start) + ); + if (propertyToken.string !== ".") { + return; + } + propertyToken = coffeescript ? + _coffeeScriptToken( + editor, + CodeMirror.Pos(cursor.line, propertyToken.start) + ) : + editor.getTokenAt( + CodeMirror.Pos(cursor.line, propertyToken.start) + ); + if (!context) { + context = []; + } + context.push(propertyToken); + } + + return { + from: CodeMirror.Pos(cursor.line, token.start), + list: _javascriptCompletions( + token, + context, + coffeescript ? + COFFEESCRIPT_KEYWORDS : + JAVASCRIPT_KEYWORDS, + suppliedOptions + ), + to: CodeMirror.Pos(cursor.line, token.end) + }; + } + + function installJavaScriptHint(CodeMirror) { + return _installOnce(CodeMirror, "javascriptHint", function () { + CodeMirror.registerHelper( + "hint", + "javascript", + function (editor, options) { + return _javascriptHint( + CodeMirror, + editor, + options, + false + ); + } + ); + CodeMirror.registerHelper( + "hint", + "coffeescript", + function (editor, options) { + return _javascriptHint( + CodeMirror, + editor, + options, + true + ); + } + ); + }); + } + + const SQL_QUERY_SEPARATOR = ";"; + const SQL_ALIAS_KEYWORD = "AS"; + + function _sqlItemText(item) { + return typeof item === "string" ? + item : + item && item.text || ""; + } + + function _sqlShallowClone(object) { + return Object.assign({}, object); + } + + function _sqlWrapTable(name, value) { + if (Array.isArray(value)) { + return { + columns: value, + text: name + }; + } + if (typeof value === "string") { + return { + columns: [], + text: value + }; + } + return Object.assign( + {columns: [], text: name}, + value || {} + ); + } + + function _sqlTables(input) { + const tables = {}; + if (Array.isArray(input)) { + for (let index = input.length - 1; index >= 0; index--) { + const item = input[index]; + const name = _sqlItemText(item); + if (name) { + tables[name.toUpperCase()] = _sqlWrapTable(name, item); + } + } + } else { + Object.keys(input || {}).forEach(function (name) { + tables[name.toUpperCase()] = _sqlWrapTable(name, input[name]); + }); + } + return tables; + } + + function _sqlMatches(search, item) { + return _sqlItemText(item).substr(0, search.length).toUpperCase() === + search.toUpperCase(); + } + + function _sqlAddMatches(result, search, values, formatter) { + if (Array.isArray(values)) { + values.forEach(function (value) { + if (_sqlMatches(search, value)) { + result.push(formatter(value)); + } + }); + return; + } + Object.keys(values || {}).forEach(function (name) { + let value = values[name]; + if (!value || value === true) { + value = name; + } else { + value = value.displayText ? + { + displayText: value.displayText, + text: value.text + } : + value.text; + } + if (_sqlMatches(search, value)) { + result.push(formatter(value)); + } + }); + } + + function _sqlModeConfig(CodeMirror, editor, field) { + const currentMode = editor.getModeAt(editor.getCursor()); + if (currentMode && currentMode.config && + currentMode.config[field]) { + return currentMode.config[field]; + } + const genericSQL = CodeMirror.resolveMode("text/x-sql"); + return genericSQL && genericSQL[field]; + } + + function _sqlIdentifierQuote(CodeMirror, editor) { + return _sqlModeConfig( + CodeMirror, + editor, + "identifierQuote" + ) || "`"; + } + + function _sqlCleanName(name, identifierQuote) { + let cleaned = name; + if (cleaned.charAt(0) === ".") { + cleaned = cleaned.substr(1); + } + const escapedQuote = identifierQuote.replace( + /[.*+?^${}()|[\]\\]/g, + "\\$&" + ); + const doubledQuote = identifierQuote + identifierQuote; + return cleaned.split(doubledQuote).map(function (part) { + return part.replace(new RegExp(escapedQuote, "g"), ""); + }).join(identifierQuote); + } + + function _sqlInsertIdentifierQuotes(item, identifierQuote) { + const parts = _sqlItemText(item).split("."); + const escapedQuote = identifierQuote.replace( + /[.*+?^${}()|[\]\\]/g, + "\\$&" + ); + for (let index = 0; index < parts.length; index++) { + parts[index] = identifierQuote + + parts[index].replace( + new RegExp(escapedQuote, "g"), + identifierQuote + identifierQuote + ) + + identifierQuote; + } + const escaped = parts.join("."); + if (typeof item === "string") { + return escaped; + } + const result = _sqlShallowClone(item); + result.text = escaped; + return result; + } + + function _sqlFindTableByAlias( + CodeMirror, + alias, + editor, + tables + ) { + const doc = editor.doc; + const fullQuery = doc.getValue(); + const aliasUpperCase = alias.toUpperCase(); + let previousWord = ""; + let table = ""; + const separators = []; + let separatorIndex = fullQuery.indexOf(SQL_QUERY_SEPARATOR); + while (separatorIndex !== -1) { + separators.push(doc.posFromIndex(separatorIndex)); + separatorIndex = fullQuery.indexOf( + SQL_QUERY_SEPARATOR, + separatorIndex + 1 + ); + } + separators.unshift(CodeMirror.Pos(0, 0)); + separators.push(CodeMirror.Pos( + editor.lastLine(), + (editor.getLine(editor.lastLine()) || "").length + )); + + const current = editor.getCursor(); + let previousSeparator = null; + let validRange = { + end: separators[separators.length - 1], + start: separators[0] + }; + for (let index = 0; index < separators.length; index++) { + const separator = separators[index]; + if ((!previousSeparator || + CodeMirror.cmpPos(current, previousSeparator) > 0) && + CodeMirror.cmpPos(current, separator) <= 0) { + validRange = { + end: separator, + start: previousSeparator + }; + break; + } + previousSeparator = separator; + } + + if (validRange.start) { + const queryLines = doc.getRange( + validRange.start, + validRange.end, + false + ); + for (let index = 0; index < queryLines.length; index++) { + const words = queryLines[index].split(/\s+/); + for (let wordIndex = 0; + wordIndex < words.length; + wordIndex++) { + const word = words[wordIndex].replace(/[`",;]/g, ""); + if (!word) { + continue; + } + const upperWord = word.toUpperCase(); + if (upperWord === aliasUpperCase && + tables[previousWord.toUpperCase()]) { + table = previousWord; + } + if (upperWord !== SQL_ALIAS_KEYWORD) { + previousWord = word; + } + } + if (table) { + break; + } + } + } + return table; + } + + function _sqlNameCompletion( + CodeMirror, + cursor, + initialToken, + result, + editor, + tables, + defaultTable, + identifierQuote + ) { + let token = initialToken; + let useIdentifierQuotes = false; + const nameParts = []; + let start = token.start; + let continueReading = true; + while (continueReading) { + continueReading = token.string.charAt(0) === "."; + useIdentifierQuotes = useIdentifierQuotes || + token.string.charAt(0) === identifierQuote; + start = token.start; + nameParts.unshift(_sqlCleanName(token.string, identifierQuote)); + token = editor.getTokenAt( + CodeMirror.Pos(cursor.line, token.start) + ); + if (token.string === ".") { + continueReading = true; + token = editor.getTokenAt( + CodeMirror.Pos(cursor.line, token.start) + ); + } + } + + let search = nameParts.join("."); + _sqlAddMatches(result, search, tables, function (item) { + return useIdentifierQuotes ? + _sqlInsertIdentifierQuotes(item, identifierQuote) : + item; + }); + _sqlAddMatches(result, search, defaultTable, function (item) { + return useIdentifierQuotes ? + _sqlInsertIdentifierQuotes(item, identifierQuote) : + item; + }); + + search = nameParts.pop(); + let tableName = nameParts.join("."); + let alias = false; + const aliasTable = tableName; + if (!tables[tableName.toUpperCase()]) { + const oldTableName = tableName; + tableName = _sqlFindTableByAlias( + CodeMirror, + tableName, + editor, + tables + ); + alias = tableName !== oldTableName; + } + + const table = tables[tableName.toUpperCase()]; + const columns = table && table.columns; + if (columns) { + _sqlAddMatches(result, search, columns, function (item) { + const tableInsert = alias ? aliasTable : tableName; + let completion; + if (typeof item === "string") { + completion = tableInsert + "." + item; + } else { + completion = _sqlShallowClone(item); + completion.text = tableInsert + "." + item.text; + } + return useIdentifierQuotes ? + _sqlInsertIdentifierQuotes( + completion, + identifierQuote + ) : + completion; + }); + } + return start; + } + + function _sqlObjectOrClass(item, className) { + if (typeof item === "object") { + return Object.assign({}, item, {className: className}); + } + return { + className: className, + text: item + }; + } + + function installSQLHint(CodeMirror) { + return _installOnce(CodeMirror, "sqlHint", function () { + CodeMirror.registerHelper("hint", "sql", function (editor, options) { + const settings = options || {}; + const tables = _sqlTables(settings.tables); + const identifierQuote = _sqlIdentifierQuote( + CodeMirror, + editor + ); + const keywords = _sqlModeConfig( + CodeMirror, + editor, + "keywords" + ) || []; + const defaultTableName = settings.defaultTable; + let defaultTable = defaultTableName && + tables[String(defaultTableName).toUpperCase()]; + if (defaultTableName && !defaultTable) { + const aliasTable = _sqlFindTableByAlias( + CodeMirror, + defaultTableName, + editor, + tables + ); + defaultTable = tables[String(aliasTable).toUpperCase()]; + } + defaultTable = defaultTable && defaultTable.columns || []; + + const cursor = editor.getCursor(); + const result = []; + let token = editor.getTokenAt(cursor); + if (token.end > cursor.ch) { + token.end = cursor.ch; + token.string = token.string.slice( + 0, + cursor.ch - token.start + ); + } + + let start; + let end; + let search; + if (/^[.`"'\w@][\w$#]*$/.test(token.string)) { + search = token.string; + start = token.start; + end = token.end; + } else { + start = cursor.ch; + end = cursor.ch; + search = ""; + } + + if (search.charAt(0) === "." || + search.charAt(0) === identifierQuote) { + start = _sqlNameCompletion( + CodeMirror, + cursor, + token, + result, + editor, + tables, + defaultTable, + identifierQuote + ); + } else { + _sqlAddMatches( + result, + search, + defaultTable, + function (item) { + return _sqlObjectOrClass( + item, + "CodeMirror-hint-table " + + "CodeMirror-hint-default-table" + ); + } + ); + _sqlAddMatches( + result, + search, + tables, + function (item) { + return _sqlObjectOrClass( + item, + "CodeMirror-hint-table" + ); + } + ); + if (!settings.disableKeywords) { + _sqlAddMatches( + result, + search, + keywords, + function (keyword) { + return _sqlObjectOrClass( + keyword.toUpperCase(), + "CodeMirror-hint-keyword" + ); + } + ); + } + } + + return { + from: CodeMirror.Pos(cursor.line, start), + list: result, + to: CodeMirror.Pos(cursor.line, end) + }; + }); + }); + } + + function _lintAnnotationNode(editor, annotation) { + const ownerDocument = _documentFor(editor); + const node = ownerDocument.createElement("div"); + const severity = annotation.severity || "error"; + node.className = + `CodeMirror-lint-message CodeMirror-lint-message-${severity}`; + if (annotation.messageHTML !== undefined) { + node.innerHTML = annotation.messageHTML; + } else { + node.appendChild( + ownerDocument.createTextNode( + String(annotation.message || "") + ) + ); + } + return node; + } + + function _removeLintTooltip(record, immediately) { + if (!record || record.removed) { + return; + } + record.target.removeEventListener("mouseout", record.hide); + record.ownerDocument.removeEventListener( + "mousemove", + record.position + ); + record.ownerWindow.clearInterval(record.poll); + record.ownerWindow.clearTimeout(record.removeTimeout); + if (immediately) { + record.removed = true; + _removeNode(record.node); + record.state.tooltips.delete(record); + if (record.state.activeTooltip === record) { + record.state.activeTooltip = null; + } + return; + } + record.node.style.opacity = "0"; + record.removeTimeout = record.ownerWindow.setTimeout(function () { + _removeLintTooltip(record, true); + }, 600); + if (record.state.activeTooltip === record) { + record.state.activeTooltip = null; + } + } + + function _clearLintTooltips(state) { + if (!state || !state.tooltips) { + return; + } + Array.from(state.tooltips).forEach(function (record) { + _removeLintTooltip(record, true); + }); + } + + function _showLintTooltip(editor, event, annotations, target) { + const state = editor.state.lint; + if (!state || !annotations.length || !target) { + return; + } + if (state.activeTooltip && + state.activeTooltip.target === target) { + return; + } + _clearLintTooltips(state); + + const ownerDocument = _documentFor(editor); + const ownerWindow = ownerDocument.defaultView || window; + const tooltip = ownerDocument.createElement("div"); + const themeClasses = String( + editor.getOption("theme") || "default" + ).split(/\s+/).filter(Boolean).map(function (themeName) { + return `cm-s-${themeName}`; + }); + tooltip.className = ["CodeMirror-lint-tooltip"] + .concat(themeClasses) + .join(" "); + annotations.forEach(function (annotation) { + tooltip.appendChild(_lintAnnotationNode(editor, annotation)); + }); + const parent = state.options.selfContain ? + editor.getWrapperElement() : + ownerDocument.body; + parent.appendChild(tooltip); + + const record = { + hide: null, + node: tooltip, + ownerDocument: ownerDocument, + ownerWindow: ownerWindow, + poll: null, + position: null, + removeTimeout: null, + removed: false, + state: state, + target: target + }; + record.position = function (moveEvent) { + if (!record.node.parentNode) { + _removeLintTooltip(record, true); + return; + } + const viewportWidth = ownerWindow.innerWidth || + ownerDocument.documentElement.clientWidth; + const top = Math.max( + 0, + moveEvent.clientY - record.node.offsetHeight - 5 + ); + const left = Math.max( + 0, + Math.min( + moveEvent.clientX + 5, + viewportWidth - record.node.offsetWidth + ) + ); + record.node.style.top = `${top}px`; + record.node.style.left = `${left}px`; + }; + record.hide = function (mouseOutEvent) { + if (mouseOutEvent && + record.target.contains(mouseOutEvent.relatedTarget)) { + return; + } + _removeLintTooltip(record, false); + }; + + target.addEventListener("mouseout", record.hide); + ownerDocument.addEventListener("mousemove", record.position); + record.poll = ownerWindow.setInterval(function () { + if (!record.target.isConnected) { + _removeLintTooltip(record, true); + } + }, 400); + state.tooltips.add(record); + state.activeTooltip = record; + record.position(event); + record.node.style.opacity = "1"; + } + + function _lintAnnotationsForTarget(editor, event) { + const state = editor.state.lint; + const wrapper = editor.getWrapperElement(); + let target = event.target || event.srcElement; + if (target && target.nodeType !== 1) { + target = target.parentElement; + } + if (!state || !target || !wrapper.contains(target)) { + return null; + } + + for (let node = target; node && node !== wrapper; + node = node.parentNode) { + if (node._phoenixLintAnnotations) { + return { + annotations: node._phoenixLintAnnotations, + target: node + }; + } + } + if (state.options.tooltips === "gutter") { + return null; + } + + const lintMark = target.closest && + target.closest(".CodeMirror-lint-mark"); + if (!lintMark || !wrapper.contains(lintMark)) { + return null; + } + const rectangle = lintMark.getBoundingClientRect(); + const position = editor.coordsChar({ + left: (rectangle.left + rectangle.right) / 2, + top: (rectangle.top + rectangle.bottom) / 2 + }, "client"); + const annotations = editor.findMarksAt(position) + .map(function (marker) { + return marker.__annotation; + }) + .filter(function (annotation, index, allAnnotations) { + return annotation && + allAnnotations.indexOf(annotation) === index; + }); + return annotations.length ? { + annotations: annotations, + target: lintMark + } : null; + } + + function _handleLintMouseOver(editor, event) { + const match = _lintAnnotationsForTarget(editor, event); + if (match) { + _showLintTooltip( + editor, + event, + match.annotations, + match.target + ); + } + } + + function _clearLint(editor) { + const state = editor.state.lint; + if (!state) { + return; + } + _clearLintTooltips(state); + if (state.hasGutter) { + editor.clearGutter(LINT_GUTTER_ID); + } + state.marked.forEach(function (marker) { + marker.clear(); + }); + state.marked.length = 0; + state.errorLines.forEach(function (entry) { + editor.removeLineClass(entry.line, "wrap", entry.className); + }); + state.errorLines.length = 0; + } + + function _lintSeverity(left, right) { + const weights = { + error: 3, + warning: 2, + info: 1 + }; + return (weights[right] || 3) > (weights[left] || 0) ? + right : + left; + } + + function _applyLintAnnotations(CodeMirror, editor, annotations) { + const state = editor.state.lint; + if (!state) { + return []; + } + const flat = Array.isArray(annotations) ? annotations : []; + const grouped = []; + _clearLint(editor); + + flat.forEach(function (originalAnnotation) { + if (!originalAnnotation || !originalAnnotation.from) { + return; + } + const annotation = state.options.formatAnnotation ? + state.options.formatAnnotation(originalAnnotation) : + originalAnnotation; + annotation.severity = annotation.severity || "error"; + const line = annotation.from.line; + if (!grouped[line]) { + grouped[line] = []; + } + grouped[line].push(annotation); + if (annotation.to) { + state.marked.push(editor.markText( + annotation.from, + annotation.to, + { + className: + "CodeMirror-lint-mark " + + `CodeMirror-lint-mark-${annotation.severity}`, + __annotation: annotation + } + )); + } + }); + + grouped.forEach(function (lineAnnotations, line) { + if (!lineAnnotations) { + return; + } + let severity = "info"; + lineAnnotations.forEach(function (annotation) { + severity = _lintSeverity( + severity, + annotation.severity || "error" + ); + }); + if (state.hasGutter) { + const ownerDocument = _documentFor(editor); + const marker = ownerDocument.createElement("div"); + marker.className = + "CodeMirror-lint-marker " + + `CodeMirror-lint-marker-${severity}`; + marker._phoenixLintAnnotations = lineAnnotations; + marker.setAttribute("role", "img"); + marker.setAttribute("aria-label", lineAnnotations.map( + function (annotation) { + return _lintAnnotationNode( + editor, + annotation + ).textContent; + } + ).join("\n")); + if (lineAnnotations.length > 1) { + const multiple = ownerDocument.createElement("div"); + multiple.className = + "CodeMirror-lint-marker " + + "CodeMirror-lint-marker-multiple"; + multiple._phoenixLintAnnotations = lineAnnotations; + marker.appendChild(multiple); + } + editor.setGutterMarker(line, LINT_GUTTER_ID, marker); + } + if (state.options.highlightLines) { + const className = `CodeMirror-lint-line-${severity}`; + editor.addLineClass(line, "wrap", className); + state.errorLines.push({ + className: className, + line: line + }); + } + }); + + state.annotations = flat; + state.grouped = grouped; + if (typeof state.options.onUpdateLinting === "function") { + state.options.onUpdateLinting(flat, grouped, editor); + } + return flat; + } + + function _runLint(CodeMirror, editor) { + const state = editor.state.lint; + if (!state) { + return Promise.resolve([]); + } + const provider = state.options.getAnnotations || + editor.getHelper(CodeMirror.Pos(0, 0), "lint"); + if (typeof provider !== "function") { + return Promise.resolve([]); + } + const requestId = ++state.waitingFor; + const finish = function (annotations) { + if (editor.state.lint !== state || + state.waitingFor !== requestId) { + return []; + } + return _applyLintAnnotations( + CodeMirror, + editor, + annotations + ); + }; + + if (state.options.async || provider.async) { + return new Promise(function (resolve, reject) { + let completed = false; + const callback = function (annotations, alternate) { + if (completed) { + return; + } + completed = true; + try { + resolve(finish( + Array.isArray(alternate) ? + alternate : + annotations + )); + } catch (error) { + reject(error); + } + }; + try { + provider( + editor.getValue(), + callback, + state.linterOptions, + editor + ); + } catch (error) { + reject(error); + } + }); + } + + try { + const result = provider( + editor.getValue(), + state.linterOptions, + editor + ); + if (result && typeof result.then === "function") { + return result.then(finish); + } + return Promise.resolve(finish(result)); + } catch (error) { + return Promise.reject(error); + } + } + + function _disableLint(CodeMirror, editor) { + const state = editor.state.lint; + if (!state) { + return; + } + const ownerWindow = _documentFor(editor).defaultView || window; + ownerWindow.clearTimeout(state.timeout); + state.waitingFor++; + _clearLint(editor); + if (state.changeHandler) { + editor.off("change", state.changeHandler); + } + if (state.mouseOverHandler) { + editor.getWrapperElement().removeEventListener( + "mouseover", + state.mouseOverHandler + ); + } + editor.state.lint = null; + CodeMirror.signal(editor, "lintStop", editor); + } + + function installLint(CodeMirror) { + return _installOnce(CodeMirror, "lint", function () { + if (!CodeMirror.helpers.lint) { + CodeMirror.registerHelper("lint", "_phoenixEmpty", function () { + return []; + }); + delete CodeMirror.lint._phoenixEmpty; + } + CodeMirror.defineOption("lint", false, function (editor, value, oldValue) { + if (oldValue && oldValue !== CodeMirror.Init) { + _disableLint(CodeMirror, editor); + } + if (!value) { + return; + } + const configuration = typeof value === "function" ? + {getAnnotations: value} : + value === true ? + {} : + value; + const defaults = { + async: false, + delay: 500, + formatAnnotation: null, + getAnnotations: null, + highlightLines: false, + lintOnChange: true, + onUpdateLinting: null, + selfContain: null, + tooltips: true + }; + const options = Object.assign({}, defaults); + const linterOptions = Object.assign( + {}, + configuration && configuration.options || {} + ); + Object.keys(configuration || {}).forEach(function (name) { + if (Object.prototype.hasOwnProperty.call( + defaults, + name + )) { + if (configuration[name] !== null) { + options[name] = configuration[name]; + } + } else if (!configuration.options) { + linterOptions[name] = configuration[name]; + } + }); + const state = { + annotations: [], + changeHandler: null, + errorLines: [], + grouped: [], + hasGutter: (editor.getOption("gutters") || []) + .indexOf(LINT_GUTTER_ID) !== -1, + linterOptions: linterOptions, + marked: [], + mouseOverHandler: null, + options: options, + timeout: null, + tooltips: new Set(), + waitingFor: 0 + }; + state.changeHandler = function () { + const ownerWindow = + _documentFor(editor).defaultView || window; + state.waitingFor++; + _clearLintTooltips(state); + ownerWindow.clearTimeout(state.timeout); + if (!options.lintOnChange) { + return; + } + state.timeout = ownerWindow.setTimeout(function () { + state.timeout = null; + _runLint(CodeMirror, editor).catch(function () {}); + }, options.delay); + }; + state.mouseOverHandler = function (event) { + _handleLintMouseOver(editor, event); + }; + editor.state.lint = state; + editor.on("change", state.changeHandler); + if (options.tooltips !== false) { + editor.getWrapperElement().addEventListener( + "mouseover", + state.mouseOverHandler + ); + } + _runLint(CodeMirror, editor).catch(function () {}); + CodeMirror.signal(editor, "lintStart", editor); + }); + CodeMirror.defineExtension("performLint", function () { + const state = this.state.lint; + if (state) { + const ownerWindow = + _documentFor(this).defaultView || window; + ownerWindow.clearTimeout(state.timeout); + state.timeout = null; + } + return _runLint(CodeMirror, this); + }); + }); + } + + function _globalValue(name) { + return typeof window !== "undefined" ? window[name] : undefined; + } + + function installCoffeeLint(CodeMirror) { + return _installOnce(CodeMirror, "coffeeLint", function () { + CodeMirror.registerHelper( + "lint", + "coffeescript", + function (text) { + const engine = _globalValue("coffeelint"); + if (!engine || typeof engine.lint !== "function") { + return []; + } + try { + return engine.lint(text).map(function (error) { + return { + from: CodeMirror.Pos( + Math.max(0, error.lineNumber - 1), + 0 + ), + message: error.message, + severity: error.level, + to: CodeMirror.Pos( + Math.max(0, error.lineNumber), + 0 + ) + }; + }); + } catch (error) { + const location = error.location || {}; + return [{ + from: CodeMirror.Pos( + location.first_line || 0, + location.first_column || 0 + ), + message: error.message, + severity: "error", + to: CodeMirror.Pos( + location.last_line || + location.first_line || + 0, + location.last_column || + location.first_column || + 0 + ) + }]; + } + } + ); + }); + } + + function installCSSLint(CodeMirror) { + return _installOnce(CodeMirror, "cssLint", function () { + CodeMirror.registerHelper("lint", "css", function (text, options) { + const engine = _globalValue("CSSLint"); + if (!engine || typeof engine.verify !== "function") { + return []; + } + const result = engine.verify(text, options || {}); + return (result.messages || []).map(function (message) { + return { + from: CodeMirror.Pos( + Math.max(0, message.line - 1), + Math.max(0, message.col - 1) + ), + message: message.message, + severity: message.type, + to: CodeMirror.Pos( + Math.max(0, message.line - 1), + Math.max(0, message.col) + ) + }; + }); + }); + }); + } + + const HTML_LINT_DEFAULT_RULES = { + "attr-lowercase": true, + "attr-no-duplication": true, + "attr-value-double-quotes": true, + "doctype-first": false, + "id-unique": true, + "spec-char-escape": true, + "src-not-empty": true, + "tag-pair": true, + "tagname-lowercase": true + }; + + function installHTMLLint(CodeMirror) { + return _installOnce(CodeMirror, "htmlLint", function () { + CodeMirror.registerHelper("lint", "html", function (text, options) { + let engine = _globalValue("HTMLHint"); + if (engine && !engine.verify) { + engine = engine.default || engine.HTMLHint; + } + if (!engine || typeof engine.verify !== "function") { + return []; + } + const messages = engine.verify( + text, + options && options.rules || HTML_LINT_DEFAULT_RULES + ); + return messages.map(function (message) { + return { + from: CodeMirror.Pos( + Math.max(0, message.line - 1), + Math.max(0, message.col - 1) + ), + message: message.message, + severity: message.type, + to: CodeMirror.Pos( + Math.max(0, message.line - 1), + Math.max(0, message.col) + ) + }; + }); + }); + }); + } + + function installJavaScriptLint(CodeMirror) { + return _installOnce(CodeMirror, "javascriptLint", function () { + CodeMirror.registerHelper( + "lint", + "javascript", + function (text, suppliedOptions) { + const engine = _globalValue("JSHINT"); + if (typeof engine !== "function") { + return []; + } + const options = Object.assign({}, suppliedOptions || {}); + if (!options.indent) { + options.indent = 1; + } + engine(text, options, options.globals); + const data = typeof engine.data === "function" ? + engine.data() : + {}; + const errors = data.errors || engine.errors || []; + const result = []; + errors.forEach(function (error) { + if (!error || error.line <= 0) { + return; + } + const start = Math.max(0, error.character - 1); + let end = start + 1; + if (error.evidence) { + const extra = error.evidence + .substring(start) + .search(/.\b/); + if (extra > -1) { + end += extra; + } + } + result.push({ + from: CodeMirror.Pos(error.line - 1, start), + message: error.reason, + severity: error.code && + error.code.charAt(0) === "W" ? + "warning" : + "error", + to: CodeMirror.Pos(error.line - 1, end) + }); + }); + return result; + } + ); + }); + } + + function _jsonErrorPosition(CodeMirror, text, error) { + const match = /position\s+(\d+)/i.exec(error && error.message || ""); + if (!match) { + return CodeMirror.Pos(0, 0); + } + const offset = Math.max(0, Number(match[1]) || 0); + const before = text.slice(0, offset); + const lines = before.split(/\r\n?|\n/); + return CodeMirror.Pos(lines.length - 1, lines[lines.length - 1].length); + } + + function installJSONLint(CodeMirror) { + return _installOnce(CodeMirror, "jsonLint", function () { + CodeMirror.registerHelper("lint", "json", function (text) { + const exported = _globalValue("jsonlint"); + const engine = exported && (exported.parser || exported); + if (engine && typeof engine.parse === "function") { + const found = []; + const previous = engine.parseError; + engine.parseError = function (message, hash) { + const location = hash && hash.loc || {}; + found.push({ + from: CodeMirror.Pos( + Math.max(0, (location.first_line || 1) - 1), + location.first_column || 0 + ), + message: message, + to: CodeMirror.Pos( + Math.max(0, (location.last_line || 1) - 1), + location.last_column || + location.first_column || + 0 + ) + }); + }; + try { + engine.parse(text); + } catch (error) { + if (!found.length) { + const position = _jsonErrorPosition( + CodeMirror, + text, + error + ); + found.push({ + from: position, + message: error.message, + to: position + }); + } + } finally { + engine.parseError = previous; + } + return found; + } + try { + JSON.parse(text); + return []; + } catch (error) { + const position = _jsonErrorPosition( + CodeMirror, + text, + error + ); + return [{ + from: position, + message: error.message, + to: position + }]; + } + }); + }); + } + + function installYAMLLint(CodeMirror) { + return _installOnce(CodeMirror, "yamlLint", function () { + CodeMirror.registerHelper("lint", "yaml", function (text) { + const engine = _globalValue("jsyaml"); + if (!engine || typeof engine.loadAll !== "function") { + return []; + } + try { + engine.loadAll(text); + return []; + } catch (error) { + const mark = error.mark || {}; + const position = CodeMirror.Pos( + mark.line || 0, + mark.column || 0 + ); + return [{ + from: position, + message: error.message, + to: position + }]; + } + }); + }); + } + + function installLoadMode(CodeMirror) { + return _installOnce(CodeMirror, "loadMode", function () { + if (!CodeMirror.modeURL) { + CodeMirror.modeURL = "../mode/%N/%N.js"; + } + CodeMirror.requireMode = function (modeSpecification, callback, options) { + const mode = typeof modeSpecification === "string" ? + modeSpecification : + modeSpecification && modeSpecification.name; + const done = typeof callback === "function" ? + callback : + function () {}; + if (!mode) { + done(false); + return false; + } + if (CodeMirror.hasMode(mode) || CodeMirror.loadMode(mode)) { + done(true); + return true; + } + const path = options && typeof options.path === "function" ? + options.path(mode) : + CodeMirror.modeURL.replace(/%N/g, mode); + if (options && typeof options.loadMode === "function") { + options.loadMode(path, function () { + const loaded = CodeMirror.hasMode(mode) || + CodeMirror.loadMode(mode); + done(loaded); + }); + return true; + } + done(false); + return false; + }; + CodeMirror.autoLoadMode = function (editor, mode, options) { + if (CodeMirror.hasMode(mode) || CodeMirror.loadMode(mode)) { + return true; + } + return CodeMirror.requireMode(mode, function (loaded) { + if (loaded) { + editor.setOption( + "mode", + editor.getOption("mode") + ); + } + }, options); + }; + }); + } + + function installMultiplexTest(CodeMirror) { + return _installOnce(CodeMirror, "multiplexTest", function () { + // The upstream file is a browser test script rather than an addon. + // Recognizing it is sufficient; executing its global test harness + // would be unsafe inside Phoenix. + }); + } + + function installRunMode(CodeMirror) { + return _installOnce(CodeMirror, "runModeExtended", function () { + LegacyAddons.install( + CodeMirror, + "addon/runmode/runmode" + ); + }); + } + + function _nodeText(node, output) { + if (node.nodeType === 3) { + output.push(node.nodeValue); + return; + } + for (let child = node.firstChild; child; child = child.nextSibling) { + _nodeText(child, output); + if (/^(P|LI|DIV|H[1-6]|PRE|BLOCKQUOTE|TD)$/.test( + node.nodeName + )) { + output.push("\n"); + } + } + } + + function installColorize(CodeMirror) { + installRunMode(CodeMirror); + return _installOnce(CodeMirror, "colorize", function () { + CodeMirror.colorize = function (collection, defaultMode) { + const nodes = collection || + document.body.getElementsByTagName("pre"); + Array.prototype.forEach.call(nodes, function (node) { + const mode = node.getAttribute("data-lang") || + defaultMode; + if (!mode) { + return; + } + const text = []; + _nodeText(node, text); + node.textContent = ""; + CodeMirror.runMode(text.join(""), mode, node); + CodeMirror.addClass(node, "cm-s-default"); + }); + }; + }); + } + + function SimpleScrollbarBar( + CodeMirror, + className, + orientation, + scroll, + ownerDocument + ) { + this.orientation = orientation; + this.scroll = scroll; + this.screen = 1; + this.total = 1; + this.size = 1; + this.pos = 0; + this.ownerDocument = ownerDocument; + this.node = ownerDocument.createElement("div"); + this.node.className = `${className}-${orientation}`; + this.inner = this.node.appendChild( + ownerDocument.createElement("div") + ); + const axis = orientation === "horizontal" ? "pageX" : "pageY"; + const bar = this; + CodeMirror.on(this.inner, "mousedown", function (event) { + if (event.which !== undefined && event.which !== 1) { + return; + } + CodeMirror.e_preventDefault(event); + const start = event[axis]; + const startPosition = bar.pos; + const done = function () { + CodeMirror.off(ownerDocument, "mousemove", move); + CodeMirror.off(ownerDocument, "mouseup", done); + }; + const move = function (moveEvent) { + if (moveEvent.which !== undefined && + moveEvent.which !== 1) { + done(); + return; + } + bar.moveTo( + startPosition + + (moveEvent[axis] - start) * (bar.total / bar.size) + ); + }; + CodeMirror.on(ownerDocument, "mousemove", move); + CodeMirror.on(ownerDocument, "mouseup", done); + }); + CodeMirror.on(this.node, "click", function (event) { + CodeMirror.e_preventDefault(event); + const rectangle = bar.inner.getBoundingClientRect(); + let direction = 0; + if (orientation === "horizontal") { + direction = event.clientX < rectangle.left ? + -1 : + event.clientX > rectangle.right ? + 1 : + 0; + } else { + direction = event.clientY < rectangle.top ? + -1 : + event.clientY > rectangle.bottom ? + 1 : + 0; + } + bar.moveTo(bar.pos + direction * bar.screen); + }); + const onWheel = function (event) { + const pixels = CodeMirror.wheelEventPixels(event); + const delta = orientation === "horizontal" ? + pixels.x : + pixels.y; + const oldPosition = bar.pos; + bar.moveTo(bar.pos + delta); + if (bar.pos !== oldPosition) { + CodeMirror.e_preventDefault(event); + } + }; + CodeMirror.on(this.node, "wheel", onWheel); + CodeMirror.on(this.node, "mousewheel", onWheel); + CodeMirror.on(this.node, "DOMMouseScroll", onWheel); + } + + SimpleScrollbarBar.prototype.setPos = function (position, force) { + const maximum = Math.max(0, this.total - this.screen); + const next = Math.max(0, Math.min(position, maximum)); + if (!force && next === this.pos) { + return false; + } + this.pos = next; + this.inner.style[ + this.orientation === "horizontal" ? "left" : "top" + ] = `${next * (this.size / Math.max(1, this.total))}px`; + return true; + }; + + SimpleScrollbarBar.prototype.moveTo = function (position) { + if (this.setPos(position)) { + this.scroll(position, this.orientation); + } + }; + + SimpleScrollbarBar.prototype.update = function ( + scrollSize, + clientSize, + barSize + ) { + const changed = this.screen !== clientSize || + this.total !== scrollSize || + this.size !== barSize; + this.screen = clientSize; + this.total = Math.max(1, scrollSize); + this.size = Math.max(0, barSize); + let buttonSize = this.screen * (this.size / this.total); + if (buttonSize < 10) { + this.size = Math.max(0, this.size - (10 - buttonSize)); + buttonSize = 10; + } + this.inner.style[ + this.orientation === "horizontal" ? "width" : "height" + ] = `${buttonSize}px`; + this.setPos(this.pos, changed); + }; + + function SimpleScrollbars( + CodeMirror, + className, + place, + scroll, + editor + ) { + const ownerDocument = _documentFor(editor); + this.addClass = className; + this.horiz = new SimpleScrollbarBar( + CodeMirror, + className, + "horizontal", + scroll, + ownerDocument + ); + place(this.horiz.node); + this.vert = new SimpleScrollbarBar( + CodeMirror, + className, + "vertical", + scroll, + ownerDocument + ); + place(this.vert.node); + this.width = null; + } + + SimpleScrollbars.prototype.update = function (measure) { + if (this.width === null) { + const ownerWindow = + this.horiz.ownerDocument.defaultView || window; + const style = ownerWindow.getComputedStyle ? + ownerWindow.getComputedStyle(this.horiz.node) : + this.horiz.node.currentStyle; + this.width = style ? parseInt(style.height, 10) : 0; + } + const width = this.width || 0; + const needsHorizontal = + measure.scrollWidth > measure.clientWidth + 1; + const needsVertical = + measure.scrollHeight > measure.clientHeight + 1; + this.vert.node.style.display = needsVertical ? "block" : "none"; + this.horiz.node.style.display = + needsHorizontal ? "block" : "none"; + if (needsVertical) { + this.vert.update( + measure.scrollHeight, + measure.clientHeight, + measure.viewHeight - (needsHorizontal ? width : 0) + ); + this.vert.node.style.bottom = + needsHorizontal ? `${width}px` : "0"; + } + if (needsHorizontal) { + this.horiz.update( + measure.scrollWidth, + measure.clientWidth, + measure.viewWidth - (needsVertical ? width : 0) - + (measure.barLeft || 0) + ); + this.horiz.node.style.right = + needsVertical ? `${width}px` : "0"; + this.horiz.node.style.left = `${measure.barLeft || 0}px`; + } + return { + bottom: needsHorizontal ? width : 0, + right: needsVertical ? width : 0 + }; + }; + + SimpleScrollbars.prototype.setScrollTop = function (position) { + this.vert.setPos(position); + }; + + SimpleScrollbars.prototype.setScrollLeft = function (position) { + this.horiz.setPos(position); + }; + + SimpleScrollbars.prototype.clear = function () { + _removeNode(this.horiz.node); + _removeNode(this.vert.node); + }; + + function installSimpleScrollbars(CodeMirror) { + return _installOnce(CodeMirror, "simpleScrollbars", function () { + CodeMirror.scrollbarModel.simple = function ( + place, + scroll, + editor + ) { + return new SimpleScrollbars( + CodeMirror, + "CodeMirror-simplescroll", + place, + scroll, + editor + ); + }; + CodeMirror.scrollbarModel.overlay = function ( + place, + scroll, + editor + ) { + return new SimpleScrollbars( + CodeMirror, + "CodeMirror-overlayscroll", + place, + scroll, + editor + ); + }; + }); + } + + function _selectionPointerRectangles(editor) { + const wrapper = editor.getWrapperElement(); + return Array.prototype.reduce.call( + wrapper.querySelectorAll( + ".cm-selectionBackground, .CodeMirror-selected" + ), + function (rectangles, node) { + return rectangles.concat( + Array.prototype.slice.call(node.getClientRects()) + ); + }, + [] + ); + } + + function _updateSelectionPointer(editor) { + const state = editor.state.selectionPointer; + if (!state) { + return; + } + if (state.rectangles === null && state.mouseX !== null) { + state.rectangles = editor.somethingSelected() ? + _selectionPointerRectangles(editor) : + []; + } + const inside = state.mouseX !== null && + (state.rectangles || []).some(function (rectangle) { + return rectangle.left <= state.mouseX && + rectangle.right >= state.mouseX && + rectangle.top <= state.mouseY && + rectangle.bottom >= state.mouseY; + }); + const lineSpace = editor.getLineSpaceElement ? + editor.getLineSpaceElement() : + editor.getWrapperElement(); + lineSpace.style.cursor = inside ? state.value : ""; + } + + function _scheduleSelectionPointer(editor) { + const state = editor.state.selectionPointer; + if (!state || state.willUpdate) { + return; + } + state.willUpdate = true; + window.setTimeout(function () { + if (editor.state.selectionPointer === state) { + _updateSelectionPointer(editor); + state.willUpdate = false; + } + }, 50); + } + + function _resetSelectionPointer(editor) { + const state = editor.state.selectionPointer; + if (state) { + state.rectangles = null; + _scheduleSelectionPointer(editor); + } + } + + function installSelectionPointer(CodeMirror) { + return _installOnce(CodeMirror, "selectionPointer", function () { + CodeMirror.defineOption( + "selectionPointer", + false, + function (editor, value) { + const previous = editor.state.selectionPointer; + const wrapper = editor.getWrapperElement(); + const lineSpace = editor.getLineSpaceElement ? + editor.getLineSpaceElement() : + wrapper; + if (previous) { + CodeMirror.off( + wrapper, + "mousemove", + previous.mousemove + ); + CodeMirror.off( + wrapper, + "mouseout", + previous.mouseout + ); + CodeMirror.off( + window, + "scroll", + previous.windowScroll + ); + editor.off( + "cursorActivity", + previous.reset + ); + editor.off("scroll", previous.reset); + lineSpace.style.cursor = ""; + editor.state.selectionPointer = null; + } + if (!value) { + return; + } + const state = { + mouseX: null, + mouseY: null, + rectangles: null, + reset: function () { + _resetSelectionPointer(editor); + }, + value: typeof value === "string" ? + value : + "default", + willUpdate: false, + windowScroll: function () { + _resetSelectionPointer(editor); + } + }; + state.mousemove = function (event) { + if (event.buttons === undefined ? + event.which : + event.buttons) { + state.mouseX = null; + state.mouseY = null; + } else { + state.mouseX = event.clientX; + state.mouseY = event.clientY; + } + _scheduleSelectionPointer(editor); + }; + state.mouseout = function (event) { + if (!wrapper.contains(event.relatedTarget)) { + state.mouseX = null; + state.mouseY = null; + _scheduleSelectionPointer(editor); + } + }; + editor.state.selectionPointer = state; + CodeMirror.on(wrapper, "mousemove", state.mousemove); + CodeMirror.on(wrapper, "mouseout", state.mouseout); + CodeMirror.on(window, "scroll", state.windowScroll); + editor.on("cursorActivity", state.reset); + editor.on("scroll", state.reset); + } + ); + }); + } + + function _mergeLineKey(line, ignoreWhitespace) { + return ignoreWhitespace ? + String(line).replace(/[ \t]/g, "") : + String(line); + } + + const MERGE_LCS_CELL_LIMIT = 50000; + const MERGE_MYERS_TRACE_LIMIT = 50000; + const MERGE_MYERS_WORK_LIMIT = 1000000; + + function _mergeMyersChunks( + leftKeys, + rightKeys, + leftOffset, + rightOffset + ) { + const trace = []; + let frontier = new Map([[0, 0]]); + let traceCells = 0; + let work = 0; + const maximumDistance = leftKeys.length + rightKeys.length; + + for (let distance = 0; + distance <= maximumDistance; + distance++) { + const diagonalCount = distance + 1; + if (traceCells + diagonalCount > + MERGE_MYERS_TRACE_LIMIT) { + return null; + } + traceCells += diagonalCount; + const current = new Map(); + for (let diagonal = -distance; + diagonal <= distance; + diagonal += 2) { + work++; + if (work > MERGE_MYERS_WORK_LIMIT) { + return null; + } + let leftIndex; + if (distance === 0) { + leftIndex = 0; + } else if (diagonal === -distance || + (diagonal !== distance && + frontier.get(diagonal - 1) < + frontier.get(diagonal + 1))) { + leftIndex = frontier.get(diagonal + 1); + } else { + leftIndex = frontier.get(diagonal - 1) + 1; + } + let rightIndex = leftIndex - diagonal; + while (leftIndex < leftKeys.length && + rightIndex < rightKeys.length && + leftKeys[leftIndex] === rightKeys[rightIndex]) { + leftIndex++; + rightIndex++; + work++; + if (work > MERGE_MYERS_WORK_LIMIT) { + return null; + } + } + current.set(diagonal, leftIndex); + if (leftIndex === leftKeys.length && + rightIndex === rightKeys.length) { + trace.push(current); + return _mergeMyersTraceToChunks( + trace, + distance, + leftKeys.length, + rightKeys.length, + leftOffset, + rightOffset + ); + } + } + trace.push(current); + frontier = current; + } + return null; + } + + function _mergeMyersTraceToChunks( + trace, + distance, + leftLength, + rightLength, + leftOffset, + rightOffset + ) { + const edits = []; + let leftIndex = leftLength; + let rightIndex = rightLength; + for (let currentDistance = distance; + currentDistance > 0; + currentDistance--) { + const previous = trace[currentDistance - 1]; + const diagonal = leftIndex - rightIndex; + let previousDiagonal; + if (diagonal === -currentDistance || + (diagonal !== currentDistance && + previous.get(diagonal - 1) < + previous.get(diagonal + 1))) { + previousDiagonal = diagonal + 1; + } else { + previousDiagonal = diagonal - 1; + } + const previousLeft = previous.get(previousDiagonal); + const previousRight = previousLeft - previousDiagonal; + if (previousDiagonal === diagonal + 1) { + edits.push({ + editFrom: rightOffset + previousRight, + editTo: rightOffset + previousRight + 1, + origFrom: leftOffset + previousLeft, + origTo: leftOffset + previousLeft + }); + } else { + edits.push({ + editFrom: rightOffset + previousRight, + editTo: rightOffset + previousRight, + origFrom: leftOffset + previousLeft, + origTo: leftOffset + previousLeft + 1 + }); + } + leftIndex = previousLeft; + rightIndex = previousRight; + } + edits.reverse(); + + const chunks = []; + edits.forEach(function (edit) { + const active = chunks[chunks.length - 1]; + if (active && + active.origTo === edit.origFrom && + active.editTo === edit.editFrom) { + active.origTo = edit.origTo; + active.editTo = edit.editTo; + } else { + chunks.push(edit); + } + }); + return chunks; + } + + function _diffChunks(leftText, rightText, ignoreWhitespace) { + const left = String(leftText).split(/\r\n?|\n/); + const right = String(rightText).split(/\r\n?|\n/); + const leftKeys = left.map(function (line) { + return _mergeLineKey(line, ignoreWhitespace); + }); + const rightKeys = right.map(function (line) { + return _mergeLineKey(line, ignoreWhitespace); + }); + if (leftKeys.join("\n") === rightKeys.join("\n")) { + return []; + } + const cells = (left.length + 1) * (right.length + 1); + if (cells > MERGE_LCS_CELL_LIMIT) { + let prefix = 0; + while (prefix < left.length && + prefix < right.length && + leftKeys[prefix] === rightKeys[prefix]) { + prefix++; + } + let leftSuffix = left.length; + let rightSuffix = right.length; + while (leftSuffix > prefix && + rightSuffix > prefix && + leftKeys[leftSuffix - 1] === + rightKeys[rightSuffix - 1]) { + leftSuffix--; + rightSuffix--; + } + const myersChunks = _mergeMyersChunks( + leftKeys.slice(prefix, leftSuffix), + rightKeys.slice(prefix, rightSuffix), + prefix, + prefix + ); + if (myersChunks) { + return myersChunks; + } + return [{ + editFrom: prefix, + editTo: rightSuffix, + origFrom: prefix, + origTo: leftSuffix + }]; + } + + const table = Array.from({length: left.length + 1}, function () { + return new Uint32Array(right.length + 1); + }); + for (let leftIndex = left.length - 1; leftIndex >= 0; leftIndex--) { + for (let rightIndex = right.length - 1; + rightIndex >= 0; + rightIndex--) { + table[leftIndex][rightIndex] = + leftKeys[leftIndex] === rightKeys[rightIndex] ? + table[leftIndex + 1][rightIndex + 1] + 1 : + Math.max( + table[leftIndex + 1][rightIndex], + table[leftIndex][rightIndex + 1] + ); + } + } + + const chunks = []; + let leftIndex = 0; + let rightIndex = 0; + let active = null; + const startChunk = function () { + if (!active) { + active = { + editFrom: rightIndex, + editTo: rightIndex, + origFrom: leftIndex, + origTo: leftIndex + }; + } + }; + const finishChunk = function () { + if (active) { + chunks.push(active); + active = null; + } + }; + + while (leftIndex < left.length || rightIndex < right.length) { + if (leftIndex < left.length && + rightIndex < right.length && + leftKeys[leftIndex] === rightKeys[rightIndex]) { + finishChunk(); + leftIndex++; + rightIndex++; + } else if (rightIndex < right.length && + (leftIndex === left.length || + table[leftIndex][rightIndex + 1] >= + table[leftIndex + 1][rightIndex])) { + startChunk(); + rightIndex++; + active.editTo = rightIndex; + } else { + startChunk(); + leftIndex++; + active.origTo = leftIndex; + } + } + finishChunk(); + return chunks; + } + + function _clearElement(node) { + if (!node) { + return; + } + while (node.firstChild) { + node.removeChild(node.firstChild); + } + } + + function _clearDiffClasses(diffView) { + diffView.lineClasses.forEach(function (entry) { + entry.editor.removeLineClass( + entry.line, + entry.location, + entry.className + ); + }); + diffView.lineClasses.length = 0; + diffView.textMarks.forEach(function (marker) { + marker.clear(); + }); + diffView.textMarks.length = 0; + } + + function _recordMergeLineClass( + diffView, + editor, + line, + className + ) { + if (line < editor.firstLine() || line > editor.lastLine()) { + return; + } + diffView.classes.classLocation.forEach(function (location) { + const handle = editor.addLineClass( + line, + location, + className + ); + if (handle) { + diffView.lineClasses.push({ + className: className, + editor: editor, + line: handle, + location: location + }); + } + }); + } + + function _markMergeChunkLines( + diffView, + editor, + from, + to + ) { + const classes = diffView.classes; + if (from === to) { + const boundaryLine = Math.min( + Math.max(from, editor.firstLine()), + editor.lastLine() + ); + _recordMergeLineClass( + diffView, + editor, + boundaryLine, + from > editor.firstLine() ? classes.end : classes.start + ); + return; + } + for (let line = from; line < to; line++) { + _recordMergeLineClass( + diffView, + editor, + line, + classes.chunk + ); + if (line === from) { + _recordMergeLineClass( + diffView, + editor, + line, + classes.start + ); + } + if (line === to - 1) { + _recordMergeLineClass( + diffView, + editor, + line, + classes.end + ); + } + } + } + + function _markMergeInlineChanges(diffView, chunk) { + const pairCount = Math.min( + chunk.editTo - chunk.editFrom, + chunk.origTo - chunk.origFrom + ); + for (let offset = 0; offset < pairCount; offset++) { + const editLineNumber = chunk.editFrom + offset; + const originalLineNumber = chunk.origFrom + offset; + const editLine = diffView.edit.getLine(editLineNumber) || ""; + const originalLine = + diffView.orig.getLine(originalLineNumber) || ""; + if (_mergeLineKey( + editLine, + diffView.mv.options.ignoreWhitespace + ) === _mergeLineKey( + originalLine, + diffView.mv.options.ignoreWhitespace + )) { + continue; + } + let prefix = 0; + while (prefix < editLine.length && + prefix < originalLine.length && + editLine.charAt(prefix) === + originalLine.charAt(prefix)) { + prefix++; + } + let editSuffix = editLine.length; + let originalSuffix = originalLine.length; + while (editSuffix > prefix && + originalSuffix > prefix && + editLine.charAt(editSuffix - 1) === + originalLine.charAt(originalSuffix - 1)) { + editSuffix--; + originalSuffix--; + } + if (editSuffix > prefix) { + diffView.textMarks.push(diffView.edit.markText( + CodeMirrorPosition( + diffView.edit, + editLineNumber, + prefix + ), + CodeMirrorPosition( + diffView.edit, + editLineNumber, + editSuffix + ), + {className: diffView.classes.insert} + )); + } + if (originalSuffix > prefix) { + diffView.textMarks.push(diffView.orig.markText( + CodeMirrorPosition( + diffView.orig, + originalLineNumber, + prefix + ), + CodeMirrorPosition( + diffView.orig, + originalLineNumber, + originalSuffix + ), + {className: diffView.classes.del} + )); + } + } + } + + function _markDiffLines(diffView) { + _clearDiffClasses(diffView); + if (!diffView.showDifferences) { + return; + } + diffView.chunks.forEach(function (chunk) { + _markMergeChunkLines( + diffView, + diffView.edit, + chunk.editFrom, + chunk.editTo + ); + _markMergeChunkLines( + diffView, + diffView.orig, + chunk.origFrom, + chunk.origTo + ); + _markMergeInlineChanges(diffView, chunk); + }); + } + + function _mergeChunkStart(editor, from, to) { + if (to > editor.lastLine()) { + return CodeMirrorPosition( + editor, + Math.max(editor.firstLine(), from - 1) + ); + } + return CodeMirrorPosition(editor, from, 0); + } + + function _copyMergeChunk(diffView, to, from, chunk) { + if (diffView.diffOutOfDate) { + return; + } + const originalStart = _mergeChunkStart( + from, + chunk.origFrom, + chunk.origTo + ); + const originalEnd = CodeMirrorPosition( + from, + chunk.origTo, + 0 + ); + const editStart = _mergeChunkStart( + to, + chunk.editFrom, + chunk.editTo + ); + const editEnd = CodeMirrorPosition( + to, + chunk.editTo, + 0 + ); + const handler = diffView.mv.options.revertChunk; + if (typeof handler === "function") { + handler( + diffView.mv, + from, + originalStart, + originalEnd, + to, + editStart, + editEnd + ); + } else { + to.replaceRange( + from.getRange(originalStart, originalEnd), + editStart, + editEnd + ); + } + } + + function _mergeChunkTop(editor, from, to) { + const line = from === to && from > editor.lastLine() ? + editor.lastLine() + 1 : + from; + return editor.heightAtLine(line, "local"); + } + + function _mergeSvgPath(diffView, chunk) { + if (!diffView.svg || !diffView.gap) { + return; + } + const ownerDocument = diffView.gap.ownerDocument; + const width = diffView.gap.offsetWidth || 1; + let originalTop = _mergeChunkTop( + diffView.orig, + chunk.origFrom, + chunk.origTo + ); + let originalBottom = diffView.orig.heightAtLine( + chunk.origTo, + "local" + ); + let editTop = _mergeChunkTop( + diffView.edit, + chunk.editFrom, + chunk.editTo + ); + let editBottom = diffView.edit.heightAtLine( + chunk.editTo, + "local" + ); + if (diffView.type === "left") { + const top = originalTop; + const bottom = originalBottom; + originalTop = editTop; + originalBottom = editBottom; + editTop = top; + editBottom = bottom; + } + const path = ownerDocument.createElementNS( + "http://www.w3.org/2000/svg", + "path" + ); + path.setAttribute( + "d", + `M -1 ${editTop} C ${width / 2} ${editTop} ` + + `${width / 2} ${originalTop} ${width + 2} ${originalTop} ` + + `L ${width + 2} ${originalBottom} ` + + `C ${width / 2} ${originalBottom} ` + + `${width / 2} ${editBottom} -1 ${editBottom} z` + ); + path.setAttribute("class", diffView.classes.connect); + diffView.svg.appendChild(path); + } + + function _mergeButton(diffView, chunk, reverse) { + const ownerDocument = diffView.gap.ownerDocument; + const button = ownerDocument.createElement("div"); + button.className = reverse ? + "CodeMirror-merge-copy-reverse" : + "CodeMirror-merge-copy"; + const pointsRight = reverse ? + diffView.type === "right" : + diffView.type === "left"; + button.textContent = pointsRight ? "\u21dd" : "\u21dc"; + button.chunk = chunk; + button.mergeReverse = reverse; + button.setAttribute("role", "button"); + button.setAttribute("tabindex", "0"); + button.title = diffView.edit.phrase( + reverse ? + "Push to right" : + diffView.mv.options.allowEditingOriginals ? + "Push to left" : + "Revert chunk" + ); + button.setAttribute("aria-label", button.title); + const topEditor = reverse ? diffView.edit : diffView.orig; + const topFrom = reverse ? chunk.editFrom : chunk.origFrom; + const topTo = reverse ? chunk.editTo : chunk.origTo; + button.style.top = `${_mergeChunkTop( + topEditor, + topFrom, + topTo + )}px`; + if (reverse) { + if (diffView.type === "right") { + button.style.left = "2px"; + } else { + button.style.right = "2px"; + } + } + return button; + } + + function _renderMergeGap(diffView) { + if (!diffView.gap) { + return; + } + _clearElement(diffView.copyButtons); + _clearElement(diffView.svg); + if (!diffView.showDifferences) { + return; + } + if (diffView.svg) { + diffView.svg.setAttribute( + "width", + diffView.gap.offsetWidth || 1 + ); + diffView.svg.setAttribute( + "height", + diffView.gap.offsetHeight || 1 + ); + } + diffView.chunks.forEach(function (chunk) { + _mergeSvgPath(diffView, chunk); + if (diffView.copyButtons) { + diffView.copyButtons.appendChild( + _mergeButton(diffView, chunk, false) + ); + if (diffView.mv.options.allowEditingOriginals) { + diffView.copyButtons.appendChild( + _mergeButton(diffView, chunk, true) + ); + } + } + }); + } + + function _clearMergeAligners(mergeView) { + (mergeView.aligners || []).forEach(function (widget) { + widget.clear(); + }); + mergeView.aligners = []; + } + + function _mergeSpacer(editor, line, height) { + if (height <= 1) { + return null; + } + const ownerDocument = _documentFor(editor); + const node = ownerDocument.createElement("div"); + node.className = "CodeMirror-merge-spacer"; + node.style.height = `${height}px`; + node.style.minWidth = "1px"; + const above = line <= editor.lastLine(); + const targetLine = Math.min( + Math.max(line, editor.firstLine()), + editor.lastLine() + ); + return editor.addLineWidget(targetLine, node, { + above: above, + handleMouseEvents: true, + height: height, + mergeSpacer: true + }); + } + + function _alignMergeView(mergeView) { + _clearMergeAligners(mergeView); + if (mergeView.options.connect !== "align") { + return; + } + [mergeView.left, mergeView.right].forEach(function (diffView) { + if (!diffView) { + return; + } + diffView.chunks.forEach(function (chunk) { + const editTop = diffView.edit.heightAtLine( + chunk.editFrom, + "local" + ); + const originalTop = diffView.orig.heightAtLine( + chunk.origFrom, + "local" + ); + const difference = editTop - originalTop; + const widget = difference > 1 ? + _mergeSpacer( + diffView.orig, + chunk.origFrom, + difference + ) : + difference < -1 ? + _mergeSpacer( + diffView.edit, + chunk.editFrom, + -difference + ) : + null; + if (widget) { + mergeView.aligners.push(widget); + } + }); + }); + } + + function _syncMergeScroll(diffView, toOriginal) { + if (!diffView.lockScroll || diffView.syncingScroll) { + return; + } + const source = toOriginal ? diffView.edit : diffView.orig; + const target = toOriginal ? diffView.orig : diffView.edit; + const now = Date.now(); + if (source.state.mergeScrollSetBy === diffView && + source.state.mergeScrollSetAt + 250 > now) { + return; + } + const sourceInfo = source.getScrollInfo(); + const targetInfo = target.getScrollInfo(); + const sourceRange = Math.max( + 0, + sourceInfo.height - sourceInfo.clientHeight + ); + const targetRange = Math.max( + 0, + targetInfo.height - targetInfo.clientHeight + ); + const ratio = sourceRange ? + sourceInfo.top / sourceRange : + 0; + diffView.syncingScroll = true; + target.scrollTo(sourceInfo.left, ratio * targetRange); + target.state.mergeScrollSetAt = now; + target.state.mergeScrollSetBy = diffView; + diffView.syncingScroll = false; + } + + function _setMergeScrollLock(diffView, value, synchronize) { + diffView.lockScroll = Boolean(value); + if (diffView.lockButton) { + const method = diffView.lockScroll ? + CodeMirrorSafeAddClass : + CodeMirrorSafeRemoveClass; + method( + diffView.lockButton, + "CodeMirror-merge-scrolllock-enabled" + ); + } + if (diffView.lockScroll && synchronize !== false) { + _syncMergeScroll(diffView, true); + } + } + + function _initializeMergeGap(diffView, gap) { + const CodeMirror = diffView.mv.CodeMirror; + const ownerDocument = gap.ownerDocument; + diffView.gap = gap; + const lock = ownerDocument.createElement("div"); + lock.className = "CodeMirror-merge-scrolllock"; + lock.title = diffView.edit.phrase("Toggle locked scrolling"); + lock.setAttribute("aria-label", lock.title); + lock.setAttribute("role", "button"); + lock.setAttribute("tabindex", "0"); + const lockWrapper = ownerDocument.createElement("div"); + lockWrapper.className = "CodeMirror-merge-scrolllock-wrap"; + lockWrapper.appendChild(lock); + gap.appendChild(lockWrapper); + diffView.lockButton = lock; + diffView.lockHandler = function (event) { + if (event.type === "click" || + event.key === "Enter" || + event.code === "Space") { + _setMergeScrollLock( + diffView, + !diffView.lockScroll + ); + } + }; + CodeMirror.on(lock, "click", diffView.lockHandler); + CodeMirror.on(lock, "keyup", diffView.lockHandler); + + if (diffView.mv.options.revertButtons !== false) { + const copyButtons = ownerDocument.createElement("div"); + copyButtons.className = + `CodeMirror-merge-copybuttons-${diffView.type}`; + diffView.copyButtons = copyButtons; + diffView.copyHandler = function (event) { + if (event.type === "keyup" && + event.key !== "Enter" && + event.code !== "Space") { + return; + } + const button = event.target; + if (!button || !button.chunk) { + return; + } + if (button.mergeReverse) { + _copyMergeChunk( + diffView, + diffView.orig, + diffView.edit, + { + editFrom: button.chunk.origFrom, + editTo: button.chunk.origTo, + origFrom: button.chunk.editFrom, + origTo: button.chunk.editTo + } + ); + } else { + _copyMergeChunk( + diffView, + diffView.edit, + diffView.orig, + button.chunk + ); + } + }; + CodeMirror.on(copyButtons, "click", diffView.copyHandler); + CodeMirror.on(copyButtons, "keyup", diffView.copyHandler); + gap.insertBefore(copyButtons, lockWrapper); + } + + if (diffView.mv.options.connect !== "align" && + typeof ownerDocument.createElementNS === "function") { + const svg = ownerDocument.createElementNS( + "http://www.w3.org/2000/svg", + "svg" + ); + diffView.svg = svg; + gap.appendChild(svg); + } + _setMergeScrollLock(diffView, true, false); + _renderMergeGap(diffView); + } + + function _clearMergeCollapses(mergeView) { + const marks = mergeView.collapsedMarks || []; + mergeView.collapsedMarks = []; + marks.forEach(function (marker) { + if (marker.mergeCollapsedEditor) { + marker.mergeCollapsedEditor.removeLineClass( + marker.mergeCollapsedLine, + "wrap", + "CodeMirror-merge-collapsed-line" + ); + } + marker.clear(); + }); + } + + function _refreshDiffView(diffView) { + const CodeMirror = diffView.mv.CodeMirror; + diffView.chunks = _diffChunks( + diffView.orig.getValue(), + diffView.edit.getValue(), + diffView.mv.options.ignoreWhitespace + ); + diffView.diff = diffView.chunks; + diffView.diffOutOfDate = false; + _markDiffLines(diffView); + _renderMergeGap(diffView); + CodeMirror.signal( + diffView.edit, + "updateDiff", + diffView.diff + ); + if (diffView.mv.options.connect === "align") { + _alignMergeView(diffView.mv); + } + } + + function CompatDiffView(mergeView, type, originalEditor) { + const CodeMirror = mergeView.CodeMirror; + this.mv = mergeView; + this.type = type; + this.edit = mergeView.edit; + this.orig = originalEditor; + this.chunks = []; + this.diff = []; + this.diffOutOfDate = false; + this.lineClasses = []; + this.textMarks = []; + this.classes = type === "left" ? + { + chunk: "CodeMirror-merge-l-chunk", + connect: "CodeMirror-merge-l-connect", + del: "CodeMirror-merge-l-deleted", + end: "CodeMirror-merge-l-chunk-end", + insert: "CodeMirror-merge-l-inserted", + start: "CodeMirror-merge-l-chunk-start" + } : + { + chunk: "CodeMirror-merge-r-chunk", + connect: "CodeMirror-merge-r-connect", + del: "CodeMirror-merge-r-deleted", + end: "CodeMirror-merge-r-chunk-end", + insert: "CodeMirror-merge-r-inserted", + start: "CodeMirror-merge-r-chunk-start" + }; + const classLocation = mergeView.options.chunkClassLocation || + "background"; + this.classes.classLocation = Array.isArray(classLocation) ? + classLocation.slice() : + [classLocation]; + this.showDifferences = mergeView.options.showDifferences !== false; + const diffView = this; + this.changeHandler = function () { + _clearMergeCollapses(diffView.mv); + diffView.diffOutOfDate = true; + _refreshDiffView(diffView); + }; + this.editScrollHandler = function () { + _syncMergeScroll(diffView, true); + _renderMergeGap(diffView); + }; + this.originalScrollHandler = function () { + _syncMergeScroll(diffView, false); + _renderMergeGap(diffView); + }; + this.resizeHandler = function () { + _renderMergeGap(diffView); + _alignMergeView(diffView.mv); + }; + this.edit.on("change", this.changeHandler); + this.orig.on("change", this.changeHandler); + this.edit.on("scroll", this.editScrollHandler); + this.orig.on("scroll", this.originalScrollHandler); + CodeMirror.on( + mergeView.ownerWindow, + "resize", + this.resizeHandler + ); + this.edit.state.diffViews = + (this.edit.state.diffViews || []).concat(this); + this.orig.state.diffViews = + (this.orig.state.diffViews || []).concat(this); + _refreshDiffView(this); + } + + CompatDiffView.prototype.setShowDifferences = function (value) { + this.showDifferences = value !== false; + _markDiffLines(this); + _renderMergeGap(this); + }; + + CompatDiffView.prototype.destroy = function () { + const CodeMirror = this.mv.CodeMirror; + this.edit.off("change", this.changeHandler); + this.orig.off("change", this.changeHandler); + this.edit.off("scroll", this.editScrollHandler); + this.orig.off("scroll", this.originalScrollHandler); + CodeMirror.off( + this.mv.ownerWindow, + "resize", + this.resizeHandler + ); + if (this.lockButton) { + CodeMirror.off( + this.lockButton, + "click", + this.lockHandler + ); + CodeMirror.off( + this.lockButton, + "keyup", + this.lockHandler + ); + } + if (this.copyButtons) { + CodeMirror.off( + this.copyButtons, + "click", + this.copyHandler + ); + CodeMirror.off( + this.copyButtons, + "keyup", + this.copyHandler + ); + } + _clearDiffClasses(this); + this.edit.state.diffViews = (this.edit.state.diffViews || []) + .filter(function (candidate) { + return candidate !== this; + }, this); + this.orig.state.diffViews = (this.orig.state.diffViews || []) + .filter(function (candidate) { + return candidate !== this; + }, this); + }; + + function _matchingOriginalLine(editLine, chunks) { + let editStart = 0; + let originalStart = 0; + for (let index = 0; index < chunks.length; index++) { + const chunk = chunks[index]; + if (chunk.editTo > editLine && + chunk.editFrom <= editLine) { + return null; + } + if (chunk.editFrom > editLine) { + break; + } + editStart = chunk.editTo; + originalStart = chunk.origTo; + } + return originalStart + editLine - editStart; + } + + function _collapseMergeRange( + mergeView, + editor, + from, + to + ) { + const CodeMirror = mergeView.CodeMirror; + const ownerDocument = _documentFor(editor); + const widget = ownerDocument.createElement("span"); + widget.className = "CodeMirror-merge-collapsed-widget"; + widget.title = editor.phrase( + "Identical text collapsed. Click to expand." + ); + editor.addLineClass( + from, + "wrap", + "CodeMirror-merge-collapsed-line" + ); + const marker = editor.markText( + CodeMirrorPosition(editor, from, 0), + CodeMirrorPosition(editor, to - 1), + { + clearOnEnter: true, + collapsed: true, + inclusiveLeft: true, + inclusiveRight: true, + replacedWith: widget + } + ); + marker.mergeCollapsedLine = from; + marker.mergeCollapsedEditor = editor; + const clear = function () { + _clearMergeCollapses(mergeView); + }; + CodeMirror.on(widget, "click", clear); + return marker; + } + + function _collapseIdenticalStretches(mergeView, suppliedMargin) { + const margin = typeof suppliedMargin === "number" ? + suppliedMargin : + 2; + const editor = mergeView.edit; + const clear = []; + for (let line = editor.firstLine(); + line <= editor.lastLine(); + line++) { + clear[line - editor.firstLine()] = true; + } + [mergeView.left, mergeView.right].forEach(function (diffView) { + if (!diffView) { + return; + } + diffView.chunks.forEach(function (chunk) { + const from = Math.max( + editor.firstLine(), + chunk.editFrom - margin + ); + const to = Math.min( + editor.lastLine() + 1, + chunk.editTo + margin + ); + for (let line = from; line < to; line++) { + clear[line - editor.firstLine()] = false; + } + }); + }); + for (let index = 0; index < clear.length; index++) { + if (!clear[index]) { + continue; + } + const startIndex = index; + while (index + 1 < clear.length && clear[index + 1]) { + index++; + } + const size = index - startIndex + 1; + if (size <= margin) { + continue; + } + const editLine = startIndex + editor.firstLine(); + const marks = [ + _collapseMergeRange( + mergeView, + editor, + editLine, + editLine + size + ) + ]; + [mergeView.left, mergeView.right].forEach(function (diffView) { + if (!diffView) { + return; + } + const originalLine = _matchingOriginalLine( + editLine, + diffView.chunks + ); + if (originalLine !== null) { + marks.push(_collapseMergeRange( + mergeView, + diffView.orig, + originalLine, + originalLine + size + )); + } + }); + mergeView.collapsedMarks = + mergeView.collapsedMarks.concat(marks); + if (typeof mergeView.options.onCollapse === "function") { + mergeView.options.onCollapse( + mergeView, + editLine, + size, + marks[0] + ); + } + } + } + + function _mergeEditorOptions(options, value, original) { + const omitted = new Set([ + "allowEditingOriginals", + "chunkClassLocation", + "collapseIdentical", + "connect", + "ignoreWhitespace", + "onCollapse", + "orig", + "origLeft", + "origRight", + "revertButtons", + "revertChunk", + "showDifferences" + ]); + const result = {}; + Object.keys(options || {}).forEach(function (name) { + if (!omitted.has(name)) { + result[name] = options[name]; + } + }); + result.value = value === null || value === undefined ? + "" : + value; + if (original && !options.allowEditingOriginals) { + result.readOnly = true; + } + return result; + } + + function _mergePane(ownerDocument, className) { + const pane = ownerDocument.createElement("div"); + pane.className = className; + return pane; + } + + function _nearbyDiff(CodeMirror, editor, direction) { + const views = editor.state.diffViews || []; + const start = editor.getCursor().line; + let found = null; + views.forEach(function (diffView) { + if (diffView.diffOutOfDate) { + _refreshDiffView(diffView); + } + const original = editor === diffView.orig; + diffView.chunks.forEach(function (chunk) { + const line = original ? + direction < 0 ? + chunk.origTo - 1 : + chunk.origFrom : + direction < 0 ? + chunk.editTo - 1 : + chunk.editFrom; + if (direction < 0 && line < start && + (found === null || line > found)) { + found = line; + } + if (direction > 0 && line > start && + (found === null || line < found)) { + found = line; + } + }); + }); + if (found === null) { + return CodeMirror.Pass; + } + editor.setCursor(found, 0); + return true; + } + + function installMerge(CodeMirror) { + return _installOnce(CodeMirror, "merge", function () { + function MergeView(node, suppliedOptions) { + if (!(this instanceof MergeView)) { + return new MergeView(node, suppliedOptions); + } + const options = suppliedOptions || {}; + const ownerDocument = node.ownerDocument || document; + const originalRight = options.origRight === undefined ? + options.orig : + options.origRight; + const hasLeft = options.origLeft !== undefined && + options.origLeft !== null; + const hasRight = originalRight !== undefined && + originalRight !== null; + const paneCount = 1 + Number(hasLeft) + Number(hasRight); + this.CodeMirror = CodeMirror; + this.options = options; + this.ownerWindow = ownerDocument.defaultView || window; + this.left = null; + this.right = null; + this.aligners = []; + this.collapsedMarks = []; + this.wrap = ownerDocument.createElement("div"); + this.wrap.className = + `CodeMirror-merge CodeMirror-merge-${paneCount}pane`; + node.appendChild(this.wrap); + + let leftPane; + let rightPane; + if (hasLeft) { + leftPane = _mergePane( + ownerDocument, + "CodeMirror-merge-pane CodeMirror-merge-left" + ); + this.wrap.appendChild(leftPane); + this.leftGap = _mergePane( + ownerDocument, + "CodeMirror-merge-gap CodeMirror-merge-gap-left" + ); + this.wrap.appendChild(this.leftGap); + } + const editPane = _mergePane( + ownerDocument, + "CodeMirror-merge-pane CodeMirror-merge-editor" + ); + this.wrap.appendChild(editPane); + if (hasRight) { + this.rightGap = _mergePane( + ownerDocument, + "CodeMirror-merge-gap CodeMirror-merge-gap-right" + ); + this.wrap.appendChild(this.rightGap); + rightPane = _mergePane( + ownerDocument, + "CodeMirror-merge-pane CodeMirror-merge-right " + + "CodeMirror-merge-pane-rightmost" + ); + this.wrap.appendChild(rightPane); + } else { + CodeMirror.addClass( + editPane, + "CodeMirror-merge-pane-rightmost" + ); + } + + this.edit = CodeMirror( + editPane, + _mergeEditorOptions(options, options.value, false) + ); + if (hasLeft) { + const original = CodeMirror( + leftPane, + _mergeEditorOptions( + options, + options.origLeft, + true + ) + ); + this.left = new CompatDiffView( + this, + "left", + original + ); + _initializeMergeGap(this.left, this.leftGap); + } + if (hasRight) { + const original = CodeMirror( + rightPane, + _mergeEditorOptions( + options, + originalRight, + true + ) + ); + this.right = new CompatDiffView( + this, + "right", + original + ); + _initializeMergeGap(this.right, this.rightGap); + } + if (options.collapseIdentical) { + this.edit.operation(function () { + _collapseIdenticalStretches( + this, + options.collapseIdentical + ); + }.bind(this)); + } + } + + MergeView.prototype.editor = function () { + return this.edit; + }; + MergeView.prototype.rightOriginal = function () { + return this.right && this.right.orig; + }; + MergeView.prototype.leftOriginal = function () { + return this.left && this.left.orig; + }; + MergeView.prototype.setShowDifferences = function (value) { + if (this.right) { + this.right.setShowDifferences(value); + } + if (this.left) { + this.left.setShowDifferences(value); + } + }; + MergeView.prototype.rightChunks = function () { + if (this.right) { + if (this.right.diffOutOfDate) { + _refreshDiffView(this.right); + } + return this.right.chunks; + } + }; + MergeView.prototype.leftChunks = function () { + if (this.left) { + if (this.left.diffOutOfDate) { + _refreshDiffView(this.left); + } + return this.left.chunks; + } + }; + MergeView.prototype.destroy = function () { + _clearMergeCollapses(this); + _clearMergeAligners(this); + if (this.left) { + this.left.destroy(); + this.left.orig.destroy(); + } + if (this.right) { + this.right.destroy(); + this.right.orig.destroy(); + } + this.edit.destroy(); + _removeNode(this.wrap); + }; + + CodeMirror.MergeView = MergeView; + CodeMirror.commands.goNextDiff = function (editor) { + return _nearbyDiff(CodeMirror, editor, 1); + }; + CodeMirror.commands.goPrevDiff = function (editor) { + return _nearbyDiff(CodeMirror, editor, -1); + }; + }); + } + + function _ternDocValue(server, entry) { + let value = entry.doc.getValue(); + if (typeof server.options.fileFilter === "function") { + value = server.options.fileFilter( + value, + entry.name, + entry.doc + ); + } + return value; + } + + function _ternDocument(value) { + return value && typeof value.getDoc === "function" ? + value.getDoc() : + value; + } + + function _ternFindDoc(server, value, suppliedName) { + const doc = _ternDocument(value); + if (typeof value === "string") { + return server.docs[value]; + } + const names = Object.keys(server.docs); + for (let index = 0; index < names.length; index++) { + if (server.docs[names[index]].doc === doc) { + return server.docs[names[index]]; + } + } + if (!doc || typeof doc.getValue !== "function") { + return null; + } + let name = suppliedName; + if (!name) { + let suffix = 0; + do { + name = `[doc${suffix || ""}]`; + suffix++; + } while (server.docs[name]); + } + return server.addDoc(name, doc); + } + + function _ternResolveDoc(server, value) { + if (typeof value === "string") { + return server.docs[value]; + } + return _ternFindDoc(server, value); + } + + function _ternCloseArgHints(server) { + if (!server.activeArgHints) { + return; + } + const tooltip = server.activeArgHints; + if (typeof tooltip.clear === "function") { + tooltip.clear(); + } + server.activeArgHints = null; + _ternRemoveTooltip(server, tooltip); + } + + function _ternTrackChange(server, doc, change) { + const entry = _ternFindDoc(server, doc); + if (!entry) { + return; + } + const cached = server.cachedArgHints; + if (cached && cached.doc === doc && change && + CodeMirrorCmpPos(cached.start, change.to) >= 0) { + server.cachedArgHints = null; + } + if (!change) { + entry.changed = { + from: 0, + to: entry.doc.lineCount() + }; + return; + } + const insertedLineCount = Array.isArray(change.text) ? + change.text.length : + String(change.text || "").split(/\r\n?|\n/).length; + const end = change.from.line + + Math.max(0, insertedLineCount - 1); + if (!entry.changed) { + entry.changed = { + from: change.from.line, + to: end + 1 + }; + return; + } + entry.changed.from = Math.min( + entry.changed.from, + change.from.line + ); + entry.changed.to = Math.max( + entry.changed.to - + Math.max(0, change.to.line - end), + end + 1 + ); + } + + function CodeMirrorCmpPos(left, right) { + return left.line - right.line || left.ch - right.ch; + } + + function _ternSendDoc(server, entry) { + server.server.request({ + files: [{ + name: entry.name, + text: _ternDocValue(server, entry), + type: "full" + }] + }, function (error) { + if (!error) { + entry.changed = null; + } + }); + } + + function _ternUnavailableServer() { + const files = Object.create(null); + return { + addFile: function (name, text) { + files[name] = text; + }, + delFile: function (name) { + delete files[name]; + }, + request: function (_body, callback) { + const error = new Error( + "Tern is unavailable in this Phoenix runtime." + ); + error.code = "PHOENIX_TERN_UNAVAILABLE"; + callback(error); + } + }; + } + + function _ternWorkerServer(server) { + if (typeof Worker !== "function" || !server.options.workerScript) { + return _ternUnavailableServer(); + } + const worker = new Worker(server.options.workerScript); + server.worker = worker; + let nextId = 0; + const pending = {}; + const send = function (data, callback) { + if (callback) { + data.id = ++nextId; + pending[data.id] = callback; + } + worker.postMessage(data); + }; + worker.postMessage({ + defs: server.options.defs, + plugins: server.options.plugins, + scripts: server.options.workerDeps, + type: "init" + }); + worker.onmessage = function (event) { + const data = event.data || {}; + if (data.type === "getFile") { + const entry = server.docs[data.name]; + if (entry) { + send({ + err: null, + id: data.id, + text: _ternDocValue(server, entry), + type: "getFile" + }); + } else if (typeof server.options.getFile === "function") { + server.options.getFile(data.name, function ( + error, + text + ) { + if (arguments.length === 1) { + text = error; + error = null; + } + send({ + err: error ? String(error) : null, + id: data.id, + text: text, + type: "getFile" + }); + }); + } else { + send({ + err: null, + id: data.id, + text: null, + type: "getFile" + }); + } + } else if (data.id && pending[data.id]) { + pending[data.id](data.err, data.body); + delete pending[data.id]; + } + }; + worker.onerror = function (error) { + Object.keys(pending).forEach(function (id) { + pending[id](error); + delete pending[id]; + }); + }; + return { + addFile: function (name, text) { + send({name: name, text: text, type: "add"}); + }, + delFile: function (name) { + send({name: name, type: "del"}); + }, + request: function (body, callback) { + send({body: body, type: "req"}, callback); + } + }; + } + + function _ternRequestBody(server, editor, query, position) { + const entry = _ternFindDoc(server, editor.getDoc()); + const queryObject = typeof query === "string" ? + {type: query} : + Object.assign({}, query || {}); + const fullDocuments = Boolean(queryObject.fullDocs); + delete queryObject.fullDocs; + queryObject.lineCharPositions = true; + if (queryObject.end === null || + queryObject.end === undefined) { + const cursor = position || editor.getCursor("end"); + queryObject.end = { + ch: cursor.ch, + line: cursor.line + }; + if (editor.somethingSelected()) { + const start = editor.getCursor("start"); + queryObject.start = { + ch: start.ch, + line: start.line + }; + } + } + queryObject.file = entry.name; + const files = []; + if (entry.changed || fullDocuments) { + files.push({ + name: entry.name, + text: _ternDocValue(server, entry), + type: "full" + }); + entry.changed = null; + } + Object.keys(server.docs).forEach(function (name) { + const other = server.docs[name]; + if (other !== entry && other.changed) { + files.push({ + name: other.name, + text: _ternDocValue(server, other), + type: "full" + }); + other.changed = null; + } + }); + const request = { + files: files, + query: queryObject + }; + const extra = server.options.queryOptions && + server.options.queryOptions[queryObject.type]; + if (extra) { + Object.assign(queryObject, extra); + } + return request; + } + + function _ternTypeClass(type) { + let suffix; + if (type === "?") { + suffix = "unknown"; + } else if (type === "number" || + type === "string" || + type === "bool") { + suffix = type; + } else if (/^fn\(/.test(type || "")) { + suffix = "fn"; + } else if (/^\[/.test(type || "")) { + suffix = "array"; + } else { + suffix = "object"; + } + return "CodeMirror-Tern-completion " + + `CodeMirror-Tern-completion-${suffix}`; + } + + function _ternRemoveTooltip(server, tooltip) { + if (!tooltip) { + return; + } + if (typeof tooltip.clearActivity === "function") { + tooltip.clearActivity(); + } + window.clearTimeout(tooltip.removeTimer); + _removeNode(tooltip); + server.tooltips = server.tooltips.filter(function (candidate) { + return candidate !== tooltip; + }); + if (tooltip.editor && + tooltip.editor.state.ternTooltip === tooltip) { + tooltip.editor.state.ternTooltip = null; + } + } + + function _ternOnEditorActivity(editor, callback) { + ["cursorActivity", "blur", "scroll", "setDoc"].forEach( + function (eventName) { + editor.on(eventName, callback); + } + ); + return function () { + ["cursorActivity", "blur", "scroll", "setDoc"].forEach( + function (eventName) { + editor.off(eventName, callback); + } + ); + }; + } + + function _ternTooltip(server, editor, content, className) { + const ownerDocument = _documentFor(editor); + const node = ownerDocument.createElement("div"); + node.className = "CodeMirror-Tern-tooltip" + + (className ? ` ${className}` : ""); + if (content && content.nodeType) { + node.appendChild(content); + } else { + node.textContent = String(content || ""); + } + const coordinates = editor.cursorCoords(null, "page"); + node.style.left = `${coordinates.right + 1}px`; + node.style.top = `${coordinates.bottom}px`; + const hintOptions = editor.getOption("hintOptions") || {}; + const container = hintOptions.container || ownerDocument.body; + container.appendChild(node); + node.editor = editor; + server.tooltips.push(node); + + const ownerWindow = ownerDocument.defaultView || window; + const bounds = node.getBoundingClientRect(); + if (bounds.bottom > ownerWindow.innerHeight) { + node.style.top = `${Math.max( + 0, + coordinates.top - bounds.height + )}px`; + } + if (bounds.right > ownerWindow.innerWidth) { + node.style.left = `${Math.max( + 0, + coordinates.right - bounds.width + )}px`; + } + return node; + } + + function _ternTemporaryTooltip(server, editor, content) { + if (editor.state.ternTooltip) { + _ternRemoveTooltip( + server, + editor.state.ternTooltip + ); + } + const tooltip = _ternTooltip( + server, + editor, + content + ); + editor.state.ternTooltip = tooltip; + let pointerInside = false; + let expired = false; + const clear = function () { + if (pointerInside && expired) { + return; + } + _ternRemoveTooltip(server, tooltip); + }; + const mouseOver = function () { + pointerInside = true; + }; + const mouseOut = function (event) { + if (!event.relatedTarget || + !tooltip.contains(event.relatedTarget)) { + pointerInside = false; + if (expired) { + clear(); + } + } + }; + tooltip.addEventListener("mouseover", mouseOver); + tooltip.addEventListener("mouseout", mouseOut); + tooltip.clearActivity = _ternOnEditorActivity( + editor, + clear + ); + tooltip.removeTimer = window.setTimeout(function () { + expired = true; + if (!pointerInside) { + clear(); + } + }, server.options.hintDelay || 1700); + return tooltip; + } + + function _ternShowError(server, editor, error) { + if (typeof server.options.showError === "function") { + server.options.showError(editor, error); + return; + } + _ternTemporaryTooltip(server, editor, String(error)); + } + + function _ternHint(CodeMirror, server, editor, callback) { + server.request(editor, { + docs: true, + types: true, + type: "completions", + urls: true + }, function (error, data) { + if (error || !data) { + if (error) { + _ternShowError(server, editor, error); + } + callback(null); + return; + } + const cursor = editor.getCursor(); + const from = data.start ? + CodeMirror.Pos(data.start.line, data.start.ch) : + cursor; + const to = data.end ? + CodeMirror.Pos(data.end.line, data.end.ch) : + cursor; + const opening = CodeMirror.Pos( + from.line, + Math.max(0, from.ch - 2) + ); + const closing = CodeMirror.Pos(to.line, to.ch + 2); + const appendClosingBracket = + editor.getRange(opening, from) === "[\"" && + editor.getRange(to, closing) !== "\"]"; + const completions = data.completions || []; + const list = completions.map(function (completion) { + if (typeof completion === "string") { + return completion; + } + let className = _ternTypeClass(completion.type); + if (data.guess) { + className += " CodeMirror-Tern-completion-guess"; + } + return { + className: className, + data: completion, + displayText: completion.displayName || + completion.name, + text: completion.name + + (appendClosingBracket ? "\"]" : "") + }; + }); + const result = { + from: from, + list: list, + to: to + }; + let tooltip = null; + CodeMirror.on(result, "close", function () { + _ternRemoveTooltip(server, tooltip); + tooltip = null; + }); + CodeMirror.on(result, "update", function () { + _ternRemoveTooltip(server, tooltip); + tooltip = null; + }); + CodeMirror.on(result, "select", function ( + completion, + node + ) { + _ternRemoveTooltip(server, tooltip); + tooltip = null; + const dataItem = completion && completion.data; + const content = + typeof server.options.completionTip === "function" ? + server.options.completionTip(dataItem) : + dataItem && dataItem.doc; + if (content && node) { + tooltip = _ternTooltip( + server, + editor, + content, + "CodeMirror-Tern-hint-doc" + ); + } + }); + callback(result); + }); + } + + function _ternContextInfo( + server, + editor, + position, + queryName, + callback + ) { + return server.request( + editor, + queryName, + function (error, data) { + if (error) { + _ternShowError(server, editor, error); + return; + } + const ownerDocument = _documentFor(editor); + let content; + if (typeof server.options.typeTip === "function") { + content = server.options.typeTip(data); + } else { + content = ownerDocument.createElement("span"); + const type = ownerDocument.createElement("strong"); + type.textContent = data && data.type || + editor.phrase("not found"); + content.appendChild(type); + if (data && data.doc) { + content.appendChild(ownerDocument.createTextNode( + ` \u2014 ${data.doc}` + )); + } + if (data && data.url) { + content.appendChild( + ownerDocument.createTextNode(" ") + ); + const link = ownerDocument.createElement("a"); + link.href = data.url; + link.target = "_blank"; + link.textContent = "[docs]"; + content.appendChild(link); + } + } + if (content) { + _ternTemporaryTooltip(server, editor, content); + } + if (typeof callback === "function") { + callback(); + } + }, + position + ); + } + + function _ternParseFunctionType(text) { + const args = []; + let position = 3; + const skipMatching = function (endPattern) { + let depth = 0; + const start = position; + for (;;) { + const character = text.charAt(position); + if (!character || + endPattern.test(character) && !depth) { + return text.slice(start, position); + } + if (/[{[(]/.test(character)) { + depth++; + } else if (/[}\])]/.test(character)) { + depth--; + } + position++; + } + }; + if (text.charAt(position) !== ")") { + for (;;) { + let name = text.slice(position).match( + /^([^, ([{]+): / + ); + if (name) { + position += name[0].length; + name = name[1]; + } + args.push({ + name: name || null, + type: skipMatching(/[),]/) + }); + if (!text.charAt(position) || + text.charAt(position) === ")") { + break; + } + position += 2; + } + } + const returnType = text.slice(position).match( + /^\) -> (.*)$/ + ); + return { + args: args, + rettype: returnType && returnType[1] + }; + } + + function _ternShowArgHints(server, editor, argumentPosition) { + _ternCloseArgHints(server); + const cached = server.cachedArgHints; + if (!cached) { + return; + } + const ownerDocument = _documentFor(editor); + const content = ownerDocument.createElement("span"); + if (cached.guess) { + content.className = "CodeMirror-Tern-fhint-guess"; + } + const name = ownerDocument.createElement("span"); + name.className = "CodeMirror-Tern-fname"; + name.textContent = cached.name; + content.appendChild(name); + content.appendChild(ownerDocument.createTextNode("(")); + cached.type.args.forEach(function (argument, index) { + if (index) { + content.appendChild( + ownerDocument.createTextNode(", ") + ); + } + const argumentName = ownerDocument.createElement("span"); + argumentName.className = "CodeMirror-Tern-farg" + + (index === argumentPosition ? + " CodeMirror-Tern-farg-current" : + ""); + argumentName.textContent = argument.name || "?"; + content.appendChild(argumentName); + if (argument.type !== "?") { + content.appendChild( + ownerDocument.createTextNode(":\u00a0") + ); + const type = ownerDocument.createElement("span"); + type.className = "CodeMirror-Tern-type"; + type.textContent = argument.type; + content.appendChild(type); + } + }); + content.appendChild(ownerDocument.createTextNode( + cached.type.rettype ? ") ->\u00a0" : ")" + )); + if (cached.type.rettype) { + const returnType = ownerDocument.createElement("span"); + returnType.className = "CodeMirror-Tern-type"; + returnType.textContent = cached.type.rettype; + content.appendChild(returnType); + } + const tooltip = _ternTooltip( + server, + editor, + content + ); + tooltip.clear = _ternOnEditorActivity(editor, function () { + if (server.activeArgHints === tooltip) { + _ternCloseArgHints(server); + } + }); + server.activeArgHints = tooltip; + } + + function _ternUpdateArgHints(server, editor) { + _ternCloseArgHints(server); + if (editor.somethingSelected()) { + return; + } + const token = editor.getTokenAt(editor.getCursor()); + const inner = server.CodeMirror.innerMode( + editor.getMode(), + token && token.state + ); + const lexical = inner && inner.state && + inner.state.lexical; + if (!inner || !inner.mode || + inner.mode.name !== "javascript" || + !lexical || lexical.info !== "call") { + return; + } + const argumentPosition = lexical.pos || 0; + const tabSize = editor.getOption("tabSize"); + let line = editor.getCursor().line; + const minimumLine = Math.max(editor.firstLine(), line - 9); + let character = null; + for (; line >= minimumLine; line--) { + const text = editor.getLine(line); + let extra = 0; + let searchPosition = 0; + for (;;) { + const tab = text.indexOf("\t", searchPosition); + if (tab === -1) { + break; + } + extra += tabSize - (tab + extra) % tabSize - 1; + searchPosition = tab + 1; + } + character = lexical.column - extra; + if (text.charAt(character) === "(") { + break; + } + } + if (line < minimumLine) { + return; + } + const start = {line: line, ch: character}; + const cached = server.cachedArgHints; + if (cached && cached.doc === editor.getDoc() && + CodeMirrorCmpPos(start, cached.start) === 0) { + _ternShowArgHints( + server, + editor, + argumentPosition + ); + return; + } + return server.request( + editor, + { + end: start, + preferFunction: true, + type: "type" + }, + function (error, data) { + if (error || !data || !/^fn\(/.test(data.type || "")) { + return; + } + server.cachedArgHints = { + doc: editor.getDoc(), + guess: data.guess, + name: data.exprName || data.name || "fn", + start: start, + type: _ternParseFunctionType(data.type) + }; + _ternShowArgHints( + server, + editor, + argumentPosition + ); + } + ); + } + + function _ternMoveTo(server, current, target, start, end) { + target.doc.setSelection(start, end || start); + if (current !== target && + typeof server.options.switchToDoc === "function") { + _ternCloseArgHints(server); + server.options.switchToDoc(target.name, target.doc); + } + } + + function _ternFindContext(doc, data) { + if (!data.context || data.contextOffset === undefined) { + return { + end: data.end || data.start, + start: data.start + }; + } + const before = data.context.slice( + 0, + data.contextOffset + ).split("\n"); + const startLine = data.start.line - (before.length - 1); + const sourceLine = doc.getLine(startLine) || ""; + const start = { + ch: (before.length === 1 ? + data.start.ch : + sourceLine.length) - before[0].length, + line: startLine + }; + let text = sourceLine.slice(start.ch); + for (let line = startLine + 1; + line < doc.lineCount() && + text.length < data.context.length; + line++) { + text += `\n${doc.getLine(line)}`; + } + if (text.slice(0, data.context.length) === data.context) { + return data; + } + const cursor = doc.getSearchCursor(data.context, { + ch: 0, + line: doc.firstLine() + }, {caseFold: false}); + let nearest = null; + let nearestDistance = Infinity; + while (cursor.findNext()) { + const found = cursor.from(); + let distance = Math.abs(found.line - start.line) * 10000; + if (!distance) { + distance = Math.abs(found.ch - start.ch); + } + if (distance < nearestDistance) { + nearest = found; + nearestDistance = distance; + } + } + if (!nearest) { + return null; + } + if (before.length === 1) { + nearest.ch += before[0].length; + } else { + nearest = { + ch: before[before.length - 1].length, + line: nearest.line + before.length - 1 + }; + } + const end = data.start.line === data.end.line ? + { + ch: nearest.ch + data.end.ch - data.start.ch, + line: nearest.line + } : + { + ch: data.end.ch, + line: nearest.line + data.end.line - data.start.line + }; + return {end: end, start: nearest}; + } + + function _ternInterestingExpression(editor) { + const position = editor.getCursor("end"); + const token = editor.getTokenAt(position); + if (token.start < position.ch && token.type === "comment") { + return false; + } + return /[\w)\]]/.test( + editor.getLine(position.line).slice( + Math.max(position.ch - 1, 0), + position.ch + 1 + ) + ); + } + + function _ternDialog(editor, text, callback) { + const ownerDocument = _documentFor(editor); + const fragment = ownerDocument.createDocumentFragment(); + fragment.appendChild( + ownerDocument.createTextNode(`${text}: `) + ); + const input = ownerDocument.createElement("input"); + input.type = "text"; + fragment.appendChild(input); + return editor.openDialog(fragment, callback); + } + + function _ternApplyChanges(server, changes) { + const byFile = Object.create(null); + (changes || []).forEach(function (change) { + if (!byFile[change.file]) { + byFile[change.file] = []; + } + byFile[change.file].push(change); + }); + server.renameGeneration++; + Object.keys(byFile).forEach(function (name) { + const entry = server.docs[name]; + if (!entry) { + return; + } + const fileChanges = byFile[name].sort( + function (left, right) { + return CodeMirrorCmpPos( + right.start, + left.start + ); + } + ); + fileChanges.forEach(function (change) { + entry.doc.replaceRange( + change.text, + change.start, + change.end, + `*rename${server.renameGeneration}` + ); + }); + }); + } + + function installTern(CodeMirror) { + installDialog(CodeMirror); + return _installOnce(CodeMirror, "tern", function () { + function TernServer(suppliedOptions) { + const server = this; + this.CodeMirror = CodeMirror; + this.options = suppliedOptions || {}; + this.options.plugins = this.options.plugins || {}; + if (!this.options.plugins["doc_comment"]) { + this.options.plugins["doc_comment"] = true; + } + this.docs = Object.create(null); + this.cachedArgHints = null; + this.activeArgHints = null; + this.jumpStack = []; + this.renameGeneration = 0; + this.tooltips = []; + if (this.options.server) { + this.server = this.options.server; + } else if (this.options.useWorker) { + this.server = _ternWorkerServer(this); + } else { + const tern = _globalValue("tern"); + this.server = tern && typeof tern.Server === "function" ? + new tern.Server({ + async: true, + defs: this.options.defs || [], + getFile: function (name, callback) { + const entry = server.docs[name]; + if (entry) { + callback(_ternDocValue(server, entry)); + } else if (typeof server.options.getFile === + "function") { + server.options.getFile(name, callback); + } else { + callback(null); + } + }, + plugins: this.options.plugins + }) : + _ternUnavailableServer(); + } + this.trackChange = function (doc, change) { + _ternTrackChange(server, doc, change); + }; + this.getHint = function (editor, callback) { + return _ternHint( + CodeMirror, + server, + editor, + callback + ); + }; + this.getHint.async = true; + } + + TernServer.prototype.addDoc = function (name, doc) { + const document = _ternDocument(doc); + const existing = this.docs[name]; + if (existing) { + CodeMirror.off( + existing.doc, + "change", + this.trackChange + ); + this.server.delFile(name); + } + const entry = { + changed: null, + doc: document, + name: name + }; + this.docs[name] = entry; + this.server.addFile(name, _ternDocValue(this, entry)); + CodeMirror.on(document, "change", this.trackChange); + return entry; + }; + TernServer.prototype.delDoc = function (identifier) { + const entry = _ternResolveDoc(this, identifier); + if (!entry) { + return; + } + CodeMirror.off(entry.doc, "change", this.trackChange); + delete this.docs[entry.name]; + this.server.delFile(entry.name); + }; + TernServer.prototype.hideDoc = function (identifier) { + _ternCloseArgHints(this); + const entry = _ternResolveDoc(this, identifier); + if (entry && entry.changed) { + _ternSendDoc(this, entry); + } + }; + TernServer.prototype.complete = function (editor) { + return editor.showHint({hint: this.getHint}); + }; + TernServer.prototype.request = function ( + editor, + query, + callback, + position + ) { + const entry = _ternFindDoc(this, editor.getDoc()); + const request = _ternRequestBody( + this, + editor, + query, + position + ); + const server = this; + this.server.request(request, function (error, data) { + let response = data; + if (!error && + typeof server.options.responseFilter === + "function") { + response = server.options.responseFilter( + entry, + query, + request, + error, + data + ); + } + if (typeof callback === "function") { + callback(error, response); + } + }); + }; + TernServer.prototype.showType = function ( + editor, + position, + callback + ) { + return _ternContextInfo( + this, + editor, + position, + "type", + callback + ); + }; + TernServer.prototype.showDocs = function ( + editor, + position, + callback + ) { + return _ternContextInfo( + this, + editor, + position, + "documentation", + callback + ); + }; + TernServer.prototype.updateArgHints = function (editor) { + return _ternUpdateArgHints(this, editor); + }; + TernServer.prototype.jumpToDef = function (editor) { + const ternServer = this; + const jump = function (variable) { + const query = { + type: "definition", + variable: variable || null + }; + const current = _ternFindDoc( + ternServer, + editor.getDoc() + ); + ternServer.request(editor, query, function ( + error, + data + ) { + if (error) { + _ternShowError( + ternServer, + editor, + error + ); + return; + } + if (data && !data.file && data.url) { + const ownerDocument = _documentFor(editor); + const ownerWindow = + ownerDocument.defaultView || window; + ownerWindow.open(data.url); + return; + } + const target = data && data.file && + ternServer.docs[data.file]; + const found = target && data.start ? + _ternFindContext(target.doc, data) : + null; + if (!target || !found) { + _ternShowError( + ternServer, + editor, + editor.phrase( + "Could not find a definition." + ) + ); + return; + } + ternServer.jumpStack.push({ + end: editor.getCursor("to"), + file: current.name, + start: editor.getCursor("from") + }); + _ternMoveTo( + ternServer, + current, + target, + found.start, + found.end + ); + }); + }; + if (!_ternInterestingExpression(editor)) { + return _ternDialog( + editor, + editor.phrase("Jump to variable"), + function (name) { + if (name) { + jump(name); + } + } + ); + } + return jump(); + }; + TernServer.prototype.jumpBack = function (editor) { + const position = this.jumpStack.pop(); + const target = position && + this.docs[position.file]; + if (!target) { + return CodeMirror.Pass; + } + const current = _ternFindDoc( + this, + editor.getDoc() + ); + _ternMoveTo( + this, + current, + target, + position.start, + position.end + ); + return true; + }; + TernServer.prototype.rename = function (editor) { + const token = editor.getTokenAt(editor.getCursor()); + if (!token || !/\w/.test(token.string || "")) { + _ternShowError( + this, + editor, + editor.phrase("Not at a variable") + ); + return; + } + const ternServer = this; + return _ternDialog( + editor, + editor.phrase(`New name for ${token.string}`), + function (newName) { + ternServer.request( + editor, + { + fullDocs: true, + newName: newName, + type: "rename" + }, + function (error, data) { + if (error) { + _ternShowError( + ternServer, + editor, + error + ); + return; + } + _ternApplyChanges( + ternServer, + data && data.changes + ); + } + ); + } + ); + }; + TernServer.prototype.selectName = function (editor) { + const ternServer = this; + const entry = _ternFindDoc( + this, + editor.getDoc() + ); + return this.request( + editor, + {type: "refs"}, + function (error, data) { + if (error) { + _ternShowError( + ternServer, + editor, + error + ); + return; + } + const ranges = []; + let primary = 0; + const cursor = editor.getCursor(); + (data && data.refs || []).forEach(function (ref) { + if (ref.file !== entry.name) { + return; + } + ranges.push({ + anchor: ref.start, + head: ref.end + }); + if (CodeMirror.cmpPos( + cursor, + ref.start + ) >= 0 && CodeMirror.cmpPos( + cursor, + ref.end + ) <= 0) { + primary = ranges.length - 1; + } + }); + if (ranges.length) { + editor.setSelections(ranges, primary); + } + } + ); + }; + TernServer.prototype.destroy = function () { + const ternServer = this; + _ternCloseArgHints(this); + this.tooltips.slice().forEach(function (tooltip) { + _ternRemoveTooltip(ternServer, tooltip); + }); + this.cachedArgHints = null; + Object.keys(this.docs).forEach(function (name) { + ternServer.delDoc(name); + }); + if (this.worker) { + this.worker.terminate(); + this.worker = null; + } + }; + + CodeMirror.TernServer = TernServer; + }); + } + + function installTernWorker(CodeMirror) { + installTern(CodeMirror); + return _installOnce(CodeMirror, "ternWorker", function () { + // The historical worker module is a worker entry point, not a + // browser addon. TernServer's worker facade implements its wire + // protocol without mutating the Phoenix window. + }); + } + + function _samePosition(left, right) { + return left.line === right.line && left.ch === right.ch; + } + + function installEmacs(CodeMirror) { + installDialog(CodeMirror); + return _installOnce(CodeMirror, "emacs", function () { + const commands = CodeMirror.commands; + const killRing = []; + let lastKill = null; + + const addToRing = function (text) { + killRing.push(text); + if (killRing.length > 50) { + killRing.shift(); + } + }; + const growRing = function (text) { + if (!killRing.length) { + addToRing(text); + } else { + killRing[killRing.length - 1] += text; + } + }; + const ringValue = function (index) { + const offset = Number(index) || 1; + return killRing[ + Math.max(0, killRing.length - Math.abs(offset)) + ] || ""; + }; + const popRing = function () { + if (killRing.length > 1) { + killRing.pop(); + } + return ringValue(1); + }; + const kill = function (editor, from, to, ring, text) { + const killed = text === null || text === undefined ? + editor.getRange(from, to) : + text; + if (ring === "grow" && lastKill && + lastKill.editor === editor && + _samePosition(from, lastKill.position) && + editor.isClean(lastKill.generation)) { + growRing(killed); + } else if (ring !== false) { + addToRing(killed); + } + editor.replaceRange("", from, to, "+delete"); + lastKill = ring === "grow" ? { + editor: editor, + generation: editor.changeGeneration(), + position: from + } : null; + }; + const byChar = function (editor, position, direction) { + return editor.findPosH(position, direction, "char", true); + }; + const byWord = function (editor, position, direction) { + return editor.findPosH(position, direction, "word", true); + }; + const byLine = function (editor, position, direction) { + return editor.findPosV(position, direction, "line"); + }; + const byPage = function (editor, position, direction) { + return editor.findPosV(position, direction, "page"); + }; + const byParagraph = function (editor, position, direction) { + let line = position.line; + let text = editor.getLine(line) || ""; + let sawText = /\S/.test( + direction < 0 ? + text.slice(0, position.ch) : + text.slice(position.ch) + ); + while (true) { + line += direction; + if (line < editor.firstLine() || + line > editor.lastLine()) { + const edge = line - direction; + return CodeMirror.Pos( + edge, + direction < 0 ? + 0 : + (editor.getLine(edge) || "").length + ); + } + text = editor.getLine(line) || ""; + if (/\S/.test(text)) { + sawText = true; + } else if (sawText) { + return CodeMirror.Pos(line, 0); + } + } + }; + const bySentence = function (editor, position, direction) { + let line = position.line; + let ch = position.ch; + let text = editor.getLine(line) || ""; + let sawWord = false; + while (true) { + const next = text.charAt( + ch + (direction < 0 ? -1 : 0) + ); + if (!next) { + const edge = direction < 0 ? + editor.firstLine() : + editor.lastLine(); + if (line === edge) { + return CodeMirror.Pos(line, ch); + } + text = editor.getLine(line + direction) || ""; + if (!/\S/.test(text)) { + return CodeMirror.Pos(line, ch); + } + line += direction; + ch = direction < 0 ? text.length : 0; + } else { + if (sawWord && /[!?.]/.test(next)) { + return CodeMirror.Pos( + line, + ch + (direction > 0 ? 1 : 0) + ); + } + if (!sawWord) { + sawWord = /\w/.test(next); + } + ch += direction; + } + } + }; + const byExpression = function (editor, position, direction) { + const bracket = editor.findMatchingBracket && + editor.findMatchingBracket(position, {strict: true}); + if (bracket && bracket.match && + (bracket.forward ? 1 : -1) === direction) { + return direction > 0 ? + CodeMirror.Pos( + bracket.to.line, + bracket.to.ch + 1 + ) : + bracket.to; + } + const token = editor.getTokenAt(position); + const edge = CodeMirror.Pos( + position.line, + direction < 0 ? token.start : token.end + ); + if (!_samePosition(edge, position)) { + return edge; + } + return byChar(editor, position, direction); + }; + const clearPrefix = function (editor) { + editor.state.emacsPrefix = null; + }; + const prefix = function (editor, precise) { + const value = editor.state.emacsPrefix; + if (!value) { + return precise ? null : 1; + } + clearPrefix(editor); + return value === "-" ? -1 : Number(value); + }; + const repeated = function (command) { + const operation = typeof command === "string" ? + function (editor) { + editor.execCommand(command); + } : + command; + return function (editor) { + const count = prefix(editor); + const direction = count < 0 ? -1 : 1; + for (let index = 0; + index < Math.abs(count); + index++) { + operation(editor, direction); + } + }; + }; + const findEnd = function (editor, position, boundary, direction) { + let count = prefix(editor); + let actualDirection = direction; + if (count < 0) { + actualDirection = -actualDirection; + count = -count; + } + let current = position; + for (let index = 0; index < count; index++) { + const next = boundary( + editor, + current, + actualDirection + ); + if (_samePosition(next, current)) { + break; + } + current = next; + } + return current; + }; + const move = function (boundary, direction) { + const command = function (editor) { + editor.extendSelection( + findEnd( + editor, + editor.getCursor(), + boundary, + direction + ) + ); + }; + command.motion = true; + return command; + }; + const killTo = function (editor, boundary, direction, ring) { + const selections = editor.listSelections(); + for (let index = selections.length - 1; + index >= 0; + index--) { + const cursor = selections[index].head; + kill( + editor, + cursor, + findEnd(editor, cursor, boundary, direction), + ring + ); + } + }; + const killRegion = function (editor, ring) { + if (!editor.somethingSelected()) { + return false; + } + const selections = editor.listSelections(); + for (let index = selections.length - 1; + index >= 0; + index--) { + kill( + editor, + selections[index].anchor, + selections[index].head, + ring + ); + } + return true; + }; + const operateOnWord = function (editor, operation) { + const start = editor.getCursor(); + const end = editor.findPosH(start, 1, "word"); + editor.replaceRange( + operation(editor.getRange(start, end)), + start, + end + ); + }; + const addPrefix = function (editor, digit) { + if (editor.state.emacsPrefix) { + if (digit !== "-") { + editor.state.emacsPrefix += digit; + } + } else { + editor.state.emacsPrefix = digit; + } + }; + + commands.setMark = function (editor) { + editor.setCursor(editor.getCursor()); + editor.setExtending(!editor.getExtending()); + }; + commands.killRegion = function (editor) { + return killRegion(editor, true); + }; + commands.killLineEmacs = repeated(function (editor) { + const start = editor.getCursor(); + let end = CodeMirror.Pos( + start.line, + (editor.getLine(start.line) || "").length + ); + let text = editor.getRange(start, end); + if (!/\S/.test(text) && start.line < editor.lastLine()) { + text += "\n"; + end = CodeMirror.Pos(start.line + 1, 0); + } + kill(editor, start, end, "grow", text); + }); + commands.killRingSave = function (editor) { + addToRing(editor.getSelection()); + editor.setExtending(false); + editor.setCursor(editor.getCursor()); + }; + commands.yank = function (editor) { + const start = editor.getCursor(); + editor.replaceRange( + ringValue(prefix(editor)), + start, + start, + "paste" + ); + editor.setSelection(start, editor.getCursor()); + }; + commands.yankPop = function (editor) { + editor.replaceSelection(popRing(), "around", "paste"); + }; + commands.forwardChar = move(byChar, 1); + commands.backwardChar = move(byChar, -1); + commands.deleteChar = function (editor) { + killTo(editor, byChar, 1, false); + }; + commands.deleteForwardChar = function (editor) { + if (!killRegion(editor, false)) { + killTo(editor, byChar, 1, false); + } + }; + commands.deleteBackwardChar = function (editor) { + if (!killRegion(editor, false)) { + killTo(editor, byChar, -1, false); + } + }; + commands.forwardWord = move(byWord, 1); + commands.backwardWord = move(byWord, -1); + commands.killWord = function (editor) { + killTo(editor, byWord, 1, "grow"); + }; + commands.backwardKillWord = function (editor) { + killTo(editor, byWord, -1, "grow"); + }; + commands.nextLine = move(byLine, 1); + commands.previousLine = move(byLine, -1); + commands.scrollDownCommand = move(byPage, -1); + commands.scrollUpCommand = move(byPage, 1); + commands.backwardParagraph = move(byParagraph, -1); + commands.forwardParagraph = move(byParagraph, 1); + commands.backwardSentence = move(bySentence, -1); + commands.forwardSentence = move(bySentence, 1); + commands.killSentence = function (editor) { + killTo(editor, bySentence, 1, "grow"); + }; + commands.backwardKillSentence = function (editor) { + killTo(editor, bySentence, -1, "grow"); + }; + commands.killSexp = function (editor) { + killTo(editor, byExpression, 1, "grow"); + }; + commands.backwardKillSexp = function (editor) { + killTo(editor, byExpression, -1, "grow"); + }; + commands.forwardSexp = move(byExpression, 1); + commands.backwardSexp = move(byExpression, -1); + commands.markSexp = function (editor) { + const cursor = editor.getCursor(); + editor.setSelection( + findEnd(editor, cursor, byExpression, 1), + cursor + ); + }; + commands.transposeSexps = function (editor) { + const leftStart = byExpression( + editor, + editor.getCursor(), + -1 + ); + const leftEnd = byExpression(editor, leftStart, 1); + const rightEnd = byExpression(editor, leftEnd, 1); + const rightStart = byExpression(editor, rightEnd, -1); + editor.replaceRange( + editor.getRange(rightStart, rightEnd) + + editor.getRange(leftEnd, rightStart) + + editor.getRange(leftStart, leftEnd), + leftStart, + rightEnd + ); + }; + commands.backwardUpList = repeated(function (editor) { + const cursor = editor.getCursor(); + for (let line = cursor.line; + line >= editor.firstLine(); + line--) { + const text = editor.getLine(line) || ""; + const limit = line === cursor.line ? + cursor.ch : + text.length; + for (let ch = limit - 1; ch >= 0; ch--) { + if (/[([{]/.test(text.charAt(ch))) { + editor.extendSelection( + CodeMirror.Pos(line, ch) + ); + return; + } + } + } + }); + commands.justOneSpace = function (editor) { + const position = editor.getCursor(); + const text = editor.getLine(position.line) || ""; + let from = position.ch; + let to = position.ch; + while (from && /\s/.test(text.charAt(from - 1))) { + from--; + } + while (to < text.length && /\s/.test(text.charAt(to))) { + to++; + } + editor.replaceRange( + " ", + CodeMirror.Pos(position.line, from), + CodeMirror.Pos(position.line, to) + ); + }; + commands.openLine = repeated(function (editor) { + editor.replaceSelection("\n", "start"); + }); + commands.transposeCharsRepeatable = repeated( + "transposeChars" + ); + commands.capitalizeWord = repeated(function (editor) { + operateOnWord(editor, function (word) { + const letter = word.search(/\w/); + return letter === -1 ? + word : + word.slice(0, letter) + + word.charAt(letter).toUpperCase() + + word.slice(letter + 1).toLowerCase(); + }); + }); + commands.upcaseWord = repeated(function (editor) { + operateOnWord(editor, function (word) { + return word.toUpperCase(); + }); + }); + commands.downcaseWord = repeated(function (editor) { + operateOnWord(editor, function (word) { + return word.toLowerCase(); + }); + }); + commands.undoRepeatable = repeated("undo"); + commands.keyboardQuit = function (editor) { + if (commands.clearSearch) { + commands.clearSearch(editor); + } + editor.setExtending(false); + editor.setCursor(editor.getCursor()); + }; + commands.newline = repeated(function (editor) { + editor.replaceSelection("\n", "end"); + }); + commands.gotoLine = function (editor) { + const line = prefix(editor, true); + if (line !== null && line > 0) { + editor.setCursor(line - 1); + return; + } + const submit = function (value) { + const number = Number(value); + if (number > 0 && Number.isInteger(number)) { + editor.setCursor(number - 1); + } + }; + if (editor.openDialog) { + editor.openDialog( + "", + submit, + {bottom: true} + ); + } else if (typeof window.prompt === "function") { + submit(window.prompt("Goto line", "")); + } + }; + commands.indentRigidly = function (editor) { + editor.indentSelection( + prefix(editor, true) || + editor.getOption("indentUnit") + ); + }; + commands.exchangePointAndMark = function (editor) { + editor.setSelection( + editor.getCursor("head"), + editor.getCursor("anchor") + ); + }; + commands.quotedInsertTab = repeated("insertTab"); + commands.universalArgument = function (editor) { + addPrefix(editor, "4"); + }; + + CodeMirror.emacs = { + kill: kill, + killRegion: killRegion, + repeated: repeated + }; + const keyMap = CodeMirror.keyMap.emacs = + CodeMirror.normalizeKeyMap({ + "Alt-/": "autocomplete", + "Alt-A": "backwardSentence", + "Alt-B": "backwardWord", + "Alt-Backspace": "backwardKillWord", + "Alt-C": "capitalizeWord", + "Alt-D": "killWord", + "Alt-E": "forwardSentence", + "Alt-F": "forwardWord", + "Alt-G G": "gotoLine", + "Alt-K": "killSentence", + "Alt-L": "downcaseWord", + "Alt-Left": "backwardWord", + "Alt-Right": "forwardWord", + "Alt-Space": "justOneSpace", + "Alt-U": "upcaseWord", + "Alt-V": "scrollDownCommand", + "Alt-W": "killRingSave", + "Alt-Y": "yankPop", + "Alt-{": "backwardParagraph", + "Alt-}": "forwardParagraph", + "Alt-;": "toggleComment", + "Backspace": "deleteBackwardChar", + "Cmd-Z": "undoRepeatable", + "Ctrl-/": "undoRepeatable", + "Ctrl-A": "goLineStart", + "Ctrl-Alt-B": "backwardSexp", + "Ctrl-Alt-Backspace": "backwardKillSexp", + "Ctrl-Alt-F": "forwardSexp", + "Ctrl-Alt-K": "killSexp", + "Ctrl-Alt-T": "transposeSexps", + "Ctrl-Alt-U": "backwardUpList", + "Ctrl-B": "backwardChar", + "Ctrl-D": "deleteChar", + "Ctrl-Down": "forwardParagraph", + "Ctrl-E": "goLineEnd", + "Ctrl-F": "forwardChar", + "Ctrl-G": "keyboardQuit", + "Ctrl-H": "deleteBackwardChar", + "Ctrl-J": "newline", + "Ctrl-K": "killLineEmacs", + "Ctrl-N": "nextLine", + "Ctrl-O": "openLine", + "Ctrl-P": "previousLine", + "Ctrl-Q Tab": "quotedInsertTab", + "Ctrl-R": "findPersistentPrev", + "Ctrl-S": "findPersistentNext", + "Ctrl-Shift-2": "setMark", + "Ctrl-Space": "setMark", + "Ctrl-T": "transposeCharsRepeatable", + "Ctrl-U": "universalArgument", + "Ctrl-Up": "backwardParagraph", + "Ctrl-V": "scrollUpCommand", + "Ctrl-W": "killRegion", + "Ctrl-X Ctrl-S": "save", + "Ctrl-X Ctrl-W": "save", + "Ctrl-X Ctrl-X": "exchangePointAndMark", + "Ctrl-X Delete": "backwardKillSentence", + "Ctrl-X F": "open", + "Ctrl-X H": "selectAll", + "Ctrl-X K": "close", + "Ctrl-X S": "saveAll", + "Ctrl-X Tab": "indentRigidly", + "Ctrl-X U": "undoRepeatable", + "Ctrl-Y": "yank", + "Ctrl-Z": "undoRepeatable", + "Delete": "deleteForwardChar", + "Down": "nextLine", + "End": "goLineEnd", + "Home": "goLineStart", + "Left": "backwardChar", + "PageDown": "scrollUpCommand", + "PageUp": "scrollDownCommand", + "Right": "forwardChar", + "Shift-Alt-,": "goDocStart", + "Shift-Alt-.": "goDocEnd", + "Shift-Alt-5": "replace", + "Shift-Ctrl--": "undoRepeatable", + "Shift-Ctrl-Alt-2": "markSexp", + "Shift-Ctrl-Z": "redo", + "Tab": "indentAuto", + "Up": "previousLine", + "Enter": "newlineAndIndent", + fallthrough: "default" + }); + for (let digit = 0; digit < 10; digit++) { + const text = String(digit); + keyMap[`Ctrl-${text}`] = function (editor) { + addPrefix(editor, text); + }; + } + keyMap["Ctrl--"] = function (editor) { + addPrefix(editor, "-"); + }; + }); + } + + function installAll(CodeMirror) { + installDialog(CodeMirror); + installAutoRefresh(CodeMirror); + installFullScreen(CodeMirror); + installPanel(CodeMirror); + installContinueList(CodeMirror); + installFoldCode(CodeMirror); + installFoldGutter(CodeMirror); + installIndentFold(CodeMirror); + installCSSHint(CodeMirror); + installHTMLHint(CodeMirror); + installJavaScriptHint(CodeMirror); + installSQLHint(CodeMirror); + installLint(CodeMirror); + installCoffeeLint(CodeMirror); + installCSSLint(CodeMirror); + installHTMLLint(CodeMirror); + installJavaScriptLint(CodeMirror); + installJSONLint(CodeMirror); + installYAMLLint(CodeMirror); + installMerge(CodeMirror); + installLoadMode(CodeMirror); + installMultiplexTest(CodeMirror); + installRunMode(CodeMirror); + installColorize(CodeMirror); + installSimpleScrollbars(CodeMirror); + installSelectionPointer(CodeMirror); + installTern(CodeMirror); + installTernWorker(CodeMirror); + installHardWrap(CodeMirror); + installEmacs(CodeMirror); + return true; + } + + function install(CodeMirror, moduleName) { + if (!moduleName) { + return installAll(CodeMirror); + } + switch (ADDON_PATHS[_normalizePath(moduleName)]) { + case "dialog": + return installDialog(CodeMirror); + case "autoRefresh": + return installAutoRefresh(CodeMirror); + case "fullScreen": + return installFullScreen(CodeMirror); + case "panel": + return installPanel(CodeMirror); + case "continueList": + return installContinueList(CodeMirror); + case "foldCode": + return installFoldCode(CodeMirror); + case "foldGutter": + return installFoldGutter(CodeMirror); + case "indentFold": + return installIndentFold(CodeMirror); + case "cssHint": + return installCSSHint(CodeMirror); + case "htmlHint": + return installHTMLHint(CodeMirror); + case "javascriptHint": + return installJavaScriptHint(CodeMirror); + case "sqlHint": + return installSQLHint(CodeMirror); + case "xmlHint": + return installXMLHint(CodeMirror); + case "lint": + return installLint(CodeMirror); + case "coffeeLint": + return installCoffeeLint(CodeMirror); + case "cssLint": + return installCSSLint(CodeMirror); + case "htmlLint": + return installHTMLLint(CodeMirror); + case "javascriptLint": + return installJavaScriptLint(CodeMirror); + case "jsonLint": + return installJSONLint(CodeMirror); + case "yamlLint": + return installYAMLLint(CodeMirror); + case "merge": + return installMerge(CodeMirror); + case "loadMode": + return installLoadMode(CodeMirror); + case "multiplexTest": + return installMultiplexTest(CodeMirror); + case "runMode": + return installRunMode(CodeMirror); + case "colorize": + return installColorize(CodeMirror); + case "simpleScrollbars": + return installSimpleScrollbars(CodeMirror); + case "selectionPointer": + return installSelectionPointer(CodeMirror); + case "tern": + return installTern(CodeMirror); + case "ternWorker": + return installTernWorker(CodeMirror); + case "hardWrap": + return installHardWrap(CodeMirror); + case "emacs": + return installEmacs(CodeMirror); + default: + return false; + } + } + + exports.install = install; + exports.installAll = installAll; + exports.isSupported = function (moduleName) { + return Boolean(ADDON_PATHS[_normalizePath(moduleName)]); + }; + exports.supportedPaths = supportedPaths; +}); diff --git a/src/editor/CodeMirrorLegacyFileSystem.js b/src/editor/CodeMirrorLegacyFileSystem.js new file mode 100644 index 0000000000..742a42db01 --- /dev/null +++ b/src/editor/CodeMirrorLegacyFileSystem.js @@ -0,0 +1,365 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global define, window*/ + +/** + * Exposes a narrow, read-only filesystem view for historical CodeMirror 5 + * paths that extensions probe before enabling functionality. + * + * The entries are backed by Phoenix's CodeMirror 6 compatibility modules and + * styles. No CodeMirror 5 files are shipped, read, or written. + */ +define(function (require, exports, module) { + + const CodeMirrorLegacyModuleLoader = + require("editor/CodeMirrorLegacyModuleLoader"), + CodeMirrorLegacyText = require("text"), + FileSystem = require("filesystem/FileSystem"), + FileSystemError = require("filesystem/FileSystemError"), + FileSystemStats = require("filesystem/FileSystemStats"); + const INSTALL_MARKER = "__phoenixCodeMirrorLegacyFileSystem"; + const VIRTUAL_ENTRY_MARKER = "__phoenixCodeMirrorLegacyVirtualEntry"; + const LEGACY_RESOURCE_PATTERN = + /^thirdparty\/CodeMirror(?:2)?\/(.+)$/; + const LEGACY_THEME_DIRECTORY_PATTERN = + /^thirdparty\/CodeMirror(?:2)?\/theme\/?$/; + const VIRTUAL_MTIME = new Date(0); + const virtualFileEntries = new Map(); + const virtualDirectoryEntries = new Map(); + const ENTRY_METADATA_PROPERTIES = [ + "_path", + "_name", + "_parentPath", + "_id", + "_isFile", + "_isDirectory" + ]; + + function _normalizeInputPath(path) { + return typeof path === "string" ? + Phoenix.VFS.getPathForVirtualServingURL(path) || path : + path; + } + + function _getApplicationRootPath() { + let pathname = window.location.pathname; + try { + pathname = decodeURI(pathname); + } catch (error) { + // Keep the encoded path. FileUtils follows the same path shape, + // so exact prefix matching remains safer than broad virtualization. + } + return pathname.slice(0, pathname.lastIndexOf("/")).replace(/\/+$/, ""); + } + + function _getLegacyResourcePath(path) { + if (typeof path !== "string") { + return null; + } + const fullPath = _normalizeInputPath(path).replace(/[?#].*$/, ""); + const applicationRoot = _getApplicationRootPath(); + const expectedPrefix = `${applicationRoot}/`; + if (fullPath.indexOf(expectedPrefix) !== 0) { + return null; + } + + const relativePath = fullPath.slice(expectedPrefix.length); + return LEGACY_RESOURCE_PATTERN.test(relativePath) ? + relativePath : null; + } + + function _createStats(isFile, size, hash) { + return new FileSystemStats({ + isFile: isFile, + mtime: VIRTUAL_MTIME, + size: size, + hash: hash + }); + } + + function _rejectMutation(callback) { + if (typeof callback === "function") { + callback(FileSystemError.NOT_WRITABLE); + } + } + + function _copyImmutableEntryMetadata(entry, virtualEntry) { + const descriptors = {}; + ENTRY_METADATA_PROPERTIES.forEach(function (propertyName) { + descriptors[propertyName] = { + value: entry[propertyName], + enumerable: Object.prototype.propertyIsEnumerable.call( + entry, + propertyName + ) + }; + }); + Object.defineProperties(virtualEntry, descriptors); + } + + function _createCommonEntry(entry, stats) { + const virtualEntry = Object.create(Object.getPrototypeOf(entry)); + _copyImmutableEntryMetadata(entry, virtualEntry); + Object.defineProperty(virtualEntry, VIRTUAL_ENTRY_MARKER, { + value: true + }); + virtualEntry.exists = function (callback) { + callback(null, true); + }; + virtualEntry.stat = function (callback) { + callback(null, stats); + }; + virtualEntry.rename = function (newFullPath, callback) { + _rejectMutation(callback); + }; + virtualEntry.unlink = function (callback) { + _rejectMutation(callback); + }; + virtualEntry.moveToTrash = function (callback) { + _rejectMutation(callback); + }; + return virtualEntry; + } + + function _getVirtualFileContent(fullPath) { + const resourcePath = _getLegacyResourcePath(fullPath); + if (!resourcePath) { + return null; + } + + if (/\.js$/i.test(resourcePath)) { + const moduleName = resourcePath.replace(/\.js$/i, ""); + const legacyPath = + CodeMirrorLegacyModuleLoader.getLegacyPath(moduleName); + if (legacyPath && legacyPath.indexOf("mode/") === 0) { + const pathParts = legacyPath.split("/"); + if (pathParts.length !== 3 || + pathParts[1] !== pathParts[2]) { + return null; + } + } + try { + CodeMirrorLegacyModuleLoader.resolveLegacyModule(moduleName); + } catch (error) { + return null; + } + return "/* Phoenix CodeMirror 6 compatibility module. */\n" + + "define" + "(function () {\n" + + ` return brackets.getModule(${JSON.stringify(moduleName)});\n` + + "});\n"; + } + + if (/\.css$/i.test(resourcePath)) { + try { + return CodeMirrorLegacyText.getCompatibilityContent(resourcePath); + } catch (error) { + return null; + } + } + + return null; + } + + function _decorateFile(entry, content) { + const cacheKey = entry.fullPath; + const cachedEntry = virtualFileEntries.get(cacheKey); + if (cachedEntry) { + return cachedEntry; + } + const stats = _createStats( + true, + content.length, + `phoenix-cm6-compat:${entry.fullPath}` + ); + const virtualEntry = _createCommonEntry(entry, stats); + virtualEntry.read = function (options, callback) { + if (typeof options === "function") { + callback = options; + } + callback(null, content, "utf8", stats); + }; + virtualEntry.write = function (data, options, callback) { + if (typeof options === "function") { + callback = options; + } + _rejectMutation(callback); + }; + virtualFileEntries.set(cacheKey, virtualEntry); + return virtualEntry; + } + + function _decorateDirectory(entry) { + const cacheKey = entry.fullPath; + const cachedEntry = virtualDirectoryEntries.get(cacheKey); + if (cachedEntry) { + return cachedEntry; + } + const stats = _createStats( + false, + 0, + `phoenix-cm6-compat:${entry.fullPath}` + ); + const virtualEntry = _createCommonEntry(entry, stats); + virtualEntry.getContents = function (callback) { + const entries = CodeMirrorLegacyText.legacyThemeNames.map( + function (themeName) { + return FileSystem.getFileForPath( + `${entry.fullPath}${themeName}.css` + ); + } + ); + const entriesStats = entries.map(function (themeEntry) { + const content = _getVirtualFileContent(themeEntry.fullPath); + return _createStats( + true, + content.length, + `phoenix-cm6-compat:${themeEntry.fullPath}` + ); + }); + callback(null, entries, entriesStats, undefined); + }; + virtualEntry.create = function (callback) { + _rejectMutation(callback); + }; + virtualDirectoryEntries.set(cacheKey, virtualEntry); + return virtualEntry; + } + + function _getVirtualEntry( + path, + originalGetFileForPath, + originalGetDirectoryForPath + ) { + const candidateResourcePath = _getLegacyResourcePath(path); + if (!candidateResourcePath) { + return null; + } + + if (LEGACY_THEME_DIRECTORY_PATTERN.test(candidateResourcePath)) { + const directoryEntry = originalGetDirectoryForPath(path); + const canonicalResourcePath = + _getLegacyResourcePath(directoryEntry.fullPath); + return canonicalResourcePath && + LEGACY_THEME_DIRECTORY_PATTERN.test(canonicalResourcePath) ? + _decorateDirectory(directoryEntry) : null; + } + + const fileEntry = originalGetFileForPath(path); + const content = _getVirtualFileContent(fileEntry.fullPath); + if (content === null) { + return null; + } + return _decorateFile(fileEntry, content); + } + + function install() { + if (!window.brackets || + typeof window.brackets.getModule !== "function") { + throw new Error( + "CodeMirror legacy filesystem compatibility must be installed " + + "after the global brackets API is initialized." + ); + } + if (FileSystem.getFileForPath[INSTALL_MARKER]) { + return true; + } + + const originalGetFileForPath = FileSystem.getFileForPath; + const originalGetDirectoryForPath = FileSystem.getDirectoryForPath; + const originalExistsAsync = FileSystem.existsAsync; + const originalResolve = FileSystem.resolve; + const originalResolveAsync = FileSystem.resolveAsync; + + const getFileForPath = function (path) { + const entry = originalGetFileForPath(path); + const content = _getVirtualFileContent(entry.fullPath); + return content === null ? entry : _decorateFile(entry, content); + }; + const getDirectoryForPath = function (path) { + const entry = originalGetDirectoryForPath(path); + const resourcePath = _getLegacyResourcePath(entry.fullPath); + return resourcePath && + LEGACY_THEME_DIRECTORY_PATTERN.test(resourcePath) ? + _decorateDirectory(entry) : entry; + }; + const existsAsync = function (path) { + const virtualEntry = _getVirtualEntry( + path, + originalGetFileForPath, + originalGetDirectoryForPath + ); + return virtualEntry ? + Promise.resolve(true) : originalExistsAsync(path); + }; + const resolve = function (path, callback) { + const virtualEntry = _getVirtualEntry( + path, + originalGetFileForPath, + originalGetDirectoryForPath + ); + if (!virtualEntry) { + return originalResolve(path, callback); + } + virtualEntry.stat(function (err, stats) { + callback(err, virtualEntry, stats); + }); + }; + const resolveAsync = function (path) { + const virtualEntry = _getVirtualEntry( + path, + originalGetFileForPath, + originalGetDirectoryForPath + ); + if (!virtualEntry) { + return originalResolveAsync(path); + } + return virtualEntry.statAsync().then(function (stats) { + return { + entry: virtualEntry, + stat: stats + }; + }); + }; + + [ + getFileForPath, + getDirectoryForPath, + existsAsync, + resolve, + resolveAsync + ].forEach(function (wrappedFunction) { + Object.defineProperty(wrappedFunction, INSTALL_MARKER, { + value: true + }); + }); + FileSystem.getFileForPath = getFileForPath; + FileSystem.getDirectoryForPath = getDirectoryForPath; + FileSystem.existsAsync = existsAsync; + FileSystem.resolve = resolve; + FileSystem.resolveAsync = resolveAsync; + return true; + } + + exports.install = install; + exports.isInstalled = function () { + return Boolean(FileSystem.getFileForPath[INSTALL_MARKER]); + }; +}); diff --git a/src/editor/CodeMirrorLegacyModeMeta.js b/src/editor/CodeMirrorLegacyModeMeta.js new file mode 100644 index 0000000000..dc93078384 --- /dev/null +++ b/src/editor/CodeMirrorLegacyModeMeta.js @@ -0,0 +1,276 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived mode metadata compatibility. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + +/** + * Installs the public API and exact metadata shipped by CodeMirror 5.65.16's + * mode/meta.js module without loading the CodeMirror 5 runtime. + */ +define(function (require, exports, module) { + + function _createModeInfo() { + return [ + {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]}, + {name: "PGP", mimes: ["application/pgp", "application/pgp-encrypted", "application/pgp-keys", "application/pgp-signature"], mode: "asciiarmor", ext: ["asc", "pgp", "sig"]}, + {name: "ASN.1", mime: "text/x-ttcn-asn", mode: "asn.1", ext: ["asn", "asn1"]}, + {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i}, + {name: "Brainfuck", mime: "text/x-brainfuck", mode: "brainfuck", ext: ["b", "bf"]}, + {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h", "ino"]}, + {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]}, + {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy", "cbl"]}, + {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp", "cs"]}, + {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj", "cljc", "cljx"]}, + {name: "ClojureScript", mime: "text/x-clojurescript", mode: "clojure", ext: ["cljs"]}, + {name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]}, + {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists\.txt$/}, + {name: "CoffeeScript", mimes: ["application/vnd.coffeescript", "text/coffeescript", "text/x-coffeescript"], mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]}, + {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]}, + {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]}, + {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]}, + {name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]}, + {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]}, + {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]}, + {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]}, + {name: "Dart", mimes: ["application/dart", "text/x-dart"], mode: "dart", ext: ["dart"]}, + {name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]}, + {name: "Django", mime: "text/x-django", mode: "django"}, + {name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile", file: /^Dockerfile$/}, + {name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]}, + {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]}, + {name: "EBNF", mime: "text/x-ebnf", mode: "ebnf"}, + {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]}, + {name: "edn", mime: "application/edn", mode: "clojure", ext: ["edn"]}, + {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]}, + {name: "Elm", mime: "text/x-elm", mode: "elm", ext: ["elm"]}, + {name: "Embedded JavaScript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, + {name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]}, + {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, + {name: "Esper", mime: "text/x-esper", mode: "sql"}, + {name: "Factor", mime: "text/x-factor", mode: "factor", ext: ["factor"]}, + {name: "FCL", mime: "text/x-fcl", mode: "fcl"}, + {name: "Forth", mime: "text/x-forth", mode: "forth", ext: ["forth", "fth", "4th"]}, + {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90", "f95"]}, + {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]}, + {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]}, + {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, + {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history)\.md$/i}, + {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, + {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"], file: /^Jenkinsfile$/}, + {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, + {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, + {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]}, + {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]}, + {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]}, + {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"], alias: ["asp", "aspx"]}, + {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm", "handlebars", "hbs"], alias: ["xhtml"]}, + {name: "HTTP", mime: "message/http", mode: "http"}, + {name: "IDL", mime: "text/x-idl", mode: "idl", ext: ["pro"]}, + {name: "Pug", mime: "text/x-pug", mode: "pug", ext: ["jade", "pug"], alias: ["jade"]}, + {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]}, + {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"], alias: ["jsp"]}, + {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"], mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]}, + {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]}, + {name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]}, + {name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]}, + {name: "Jinja2", mime: "text/jinja2", mode: "jinja2", ext: ["j2", "jinja", "jinja2"]}, + {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"], alias: ["jl"]}, + {name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]}, + {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]}, + {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]}, + {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]}, + {name: "Markdown", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]}, + {name: "mIRC", mime: "text/mirc", mode: "mirc"}, + {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, + {name: "Mathematica", mime: "text/x-mathematica", mode: "mathematica", ext: ["m", "nb", "wl", "wls"]}, + {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, + {name: "MUMPS", mime: "text/x-mumps", mode: "mumps", ext: ["mps"]}, + {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, + {name: "mbox", mime: "application/mbox", mode: "mbox", ext: ["mbox"]}, + {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, + {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i}, + {name: "NSIS", mime: "text/x-nsis", mode: "nsis", ext: ["nsh", "nsi"]}, + {name: "NTriples", mimes: ["application/n-triples", "application/n-quads", "text/n-triples"], mode: "ntriples", ext: ["nt", "nq"]}, + {name: "Objective-C", mime: "text/x-objectivec", mode: "clike", ext: ["m"], alias: ["objective-c", "objc"]}, + {name: "Objective-C++", mime: "text/x-objectivec++", mode: "clike", ext: ["mm"], alias: ["objective-c++", "objc++"]}, + {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]}, + {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]}, + {name: "Oz", mime: "text/x-oz", mode: "oz", ext: ["oz"]}, + {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]}, + {name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]}, + {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]}, + {name: "PHP", mimes: ["text/x-php", "application/x-httpd-php", "application/x-httpd-php-open"], mode: "php", ext: ["php", "php3", "php4", "php5", "php7", "phtml"]}, + {name: "Pig", mime: "text/x-pig", mode: "pig", ext: ["pig"]}, + {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]}, + {name: "PLSQL", mime: "text/x-plsql", mode: "sql", ext: ["pls"]}, + {name: "PostgreSQL", mime: "text/x-pgsql", mode: "sql"}, + {name: "PowerShell", mime: "application/x-powershell", mode: "powershell", ext: ["ps1", "psd1", "psm1"]}, + {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"], alias: ["ini", "properties"]}, + {name: "ProtoBuf", mime: "text/x-protobuf", mode: "protobuf", ext: ["proto"]}, + {name: "Python", mime: "text/x-python", mode: "python", ext: ["BUILD", "bzl", "py", "pyw"], file: /^(BUCK|BUILD)$/}, + {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]}, + {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]}, + {name: "R", mime: "text/x-rsrc", mode: "r", ext: ["r", "R"], alias: ["rscript"]}, + {name: "reStructuredText", mime: "text/x-rst", mode: "rst", ext: ["rst"], alias: ["rst"]}, + {name: "RPM Changes", mime: "text/x-rpm-changes", mode: "rpm"}, + {name: "RPM Spec", mime: "text/x-rpm-spec", mode: "rpm", ext: ["spec"]}, + {name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: ["rb"], alias: ["jruby", "macruby", "rake", "rb", "rbx"]}, + {name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: ["rs"]}, + {name: "SAS", mime: "text/x-sas", mode: "sas", ext: ["sas"]}, + {name: "Sass", mime: "text/x-sass", mode: "sass", ext: ["sass"]}, + {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]}, + {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]}, + {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]}, + {name: "Shell", mimes: ["text/x-sh", "application/x-sh"], mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"], file: /^PKGBUILD$/}, + {name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]}, + {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]}, + {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]}, + {name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]}, + {name: "Solr", mime: "text/x-solr", mode: "solr"}, + {name: "SML", mime: "text/x-sml", mode: "mllike", ext: ["sml", "sig", "fun", "smackspec"]}, + {name: "Soy", mime: "text/x-soy", mode: "soy", ext: ["soy"], alias: ["closure template"]}, + {name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]}, + {name: "Spreadsheet", mime: "text/x-spreadsheet", mode: "spreadsheet", alias: ["excel", "formula"]}, + {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]}, + {name: "SQLite", mime: "text/x-sqlite", mode: "sql"}, + {name: "Squirrel", mime: "text/x-squirrel", mode: "clike", ext: ["nut"]}, + {name: "Stylus", mime: "text/x-styl", mode: "stylus", ext: ["styl"]}, + {name: "Swift", mime: "text/x-swift", mode: "swift", ext: ["swift"]}, + {name: "sTeX", mime: "text/x-stex", mode: "stex"}, + {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx", "tex"], alias: ["tex"]}, + {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v", "sv", "svh"]}, + {name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]}, + {name: "Textile", mime: "text/x-textile", mode: "textile", ext: ["textile"]}, + {name: "TiddlyWiki", mime: "text/x-tiddlywiki", mode: "tiddlywiki"}, + {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, + {name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]}, + {name: "Tornado", mime: "text/x-tornado", mode: "tornado"}, + {name: "troff", mime: "text/troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]}, + {name: "TTCN", mime: "text/x-ttcn", mode: "ttcn", ext: ["ttcn", "ttcn3", "ttcnpp"]}, + {name: "TTCN_CFG", mime: "text/x-ttcn-cfg", mode: "ttcn-cfg", ext: ["cfg"]}, + {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]}, + {name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"], alias: ["ts"]}, + {name: "TypeScript-JSX", mime: "text/typescript-jsx", mode: "jsx", ext: ["tsx"], alias: ["tsx"]}, + {name: "Twig", mime: "text/x-twig", mode: "twig"}, + {name: "Web IDL", mime: "text/x-webidl", mode: "webidl", ext: ["webidl"]}, + {name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]}, + {name: "VBScript", mime: "text/vbscript", mode: "vbscript", ext: ["vbs"]}, + {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]}, + {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]}, + {name: "VHDL", mime: "text/x-vhdl", mode: "vhdl", ext: ["vhd", "vhdl"]}, + {name: "Vue.js Component", mimes: ["script/x-vue", "text/x-vue"], mode: "vue", ext: ["vue"]}, + {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd", "svg"], alias: ["rss", "wsdl", "xsd"]}, + {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]}, + {name: "Yacas", mime: "text/x-yacas", mode: "yacas", ext: ["ys"]}, + {name: "YAML", mimes: ["text/x-yaml", "text/yaml"], mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]}, + {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}, + {name: "mscgen", mime: "text/x-mscgen", mode: "mscgen", ext: ["mscgen", "mscin", "msc"]}, + {name: "xu", mime: "text/x-xu", mode: "mscgen", ext: ["xu"]}, + {name: "msgenny", mime: "text/x-msgenny", mode: "mscgen", ext: ["msgenny"]}, + {name: "WebAssembly", mime: "text/webassembly", mode: "wast", ext: ["wat", "wast"]} + ]; + } + + function install(CodeMirror) { + CodeMirror.modeInfo = _createModeInfo(); + + // Ensure all modes have a mime property for backwards compatibility. + for (let i = 0; i < CodeMirror.modeInfo.length; i++) { + const info = CodeMirror.modeInfo[i]; + if (info.mimes) { + info.mime = info.mimes[0]; + } + } + + CodeMirror.findModeByMIME = function (mime) { + const normalizedMIME = mime.toLowerCase(); + for (let i = 0; i < CodeMirror.modeInfo.length; i++) { + const info = CodeMirror.modeInfo[i]; + if (info.mime == normalizedMIME) { // eslint-disable-line eqeqeq + return info; + } + if (info.mimes) { + for (let j = 0; j < info.mimes.length; j++) { + if (info.mimes[j] == normalizedMIME) { // eslint-disable-line eqeqeq + return info; + } + } + } + } + if (/\+xml$/.test(normalizedMIME)) { + return CodeMirror.findModeByMIME("application/xml"); + } + if (/\+json$/.test(normalizedMIME)) { + return CodeMirror.findModeByMIME("application/json"); + } + }; + + CodeMirror.findModeByExtension = function (extension) { + const normalizedExtension = extension.toLowerCase(); + for (let i = 0; i < CodeMirror.modeInfo.length; i++) { + const info = CodeMirror.modeInfo[i]; + if (info.ext) { + for (let j = 0; j < info.ext.length; j++) { + if (info.ext[j] == normalizedExtension) { // eslint-disable-line eqeqeq + return info; + } + } + } + } + }; + + CodeMirror.findModeByFileName = function (filename) { + for (let i = 0; i < CodeMirror.modeInfo.length; i++) { + const info = CodeMirror.modeInfo[i]; + if (info.file && info.file.test(filename)) { + return info; + } + } + const dot = filename.lastIndexOf("."); + const extension = dot > -1 && + filename.substring(dot + 1, filename.length); + if (extension) { + return CodeMirror.findModeByExtension(extension); + } + }; + + CodeMirror.findModeByName = function (name) { + const normalizedName = name.toLowerCase(); + for (let i = 0; i < CodeMirror.modeInfo.length; i++) { + const info = CodeMirror.modeInfo[i]; + if (info.name.toLowerCase() == normalizedName) { // eslint-disable-line eqeqeq + return info; + } + if (info.alias) { + for (let j = 0; j < info.alias.length; j++) { + if (info.alias[j].toLowerCase() == normalizedName) { // eslint-disable-line eqeqeq + return info; + } + } + } + } + }; + + return CodeMirror; + } + + exports.install = install; +}); diff --git a/src/editor/CodeMirrorLegacyModesCompat.js b/src/editor/CodeMirrorLegacyModesCompat.js new file mode 100644 index 0000000000..288dfb536d --- /dev/null +++ b/src/editor/CodeMirrorLegacyModesCompat.js @@ -0,0 +1,1346 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived mode compatibility. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + +/** + * Ports the CodeMirror 5.65.16 modes that are not published by + * @codemirror/legacy-modes. The factories run against Phoenix's CM6-backed + * CodeMirror facade and do not load the CodeMirror 5 runtime. + */ +define(function (require, exports, module) { + + const RSTSlimCompat = require("editor/CodeMirrorLegacyRSTSlimCompat"); + const installedTargets = new WeakSet(); + + function installDjango(CodeMirror) { + CodeMirror.defineMode("django:inner", function () { + let keywords = [ + "block", "endblock", "for", "endfor", "true", "false", + "filter", "endfilter", "loop", "none", "self", "super", + "if", "elif", "endif", "as", "else", "import", "with", + "endwith", "without", "context", "ifequal", "endifequal", + "ifnotequal", "endifnotequal", "extends", "include", "load", + "comment", "endcomment", "empty", "url", "static", "trans", + "blocktrans", "endblocktrans", "now", "regroup", "lorem", + "ifchanged", "endifchanged", "firstof", "debug", "cycle", + "csrf_token", "autoescape", "endautoescape", "spaceless", + "endspaceless", "ssi", "templatetag", "verbatim", + "endverbatim", "widthratio" + ], + filters = [ + "add", "addslashes", "capfirst", "center", "cut", "date", + "default", "default_if_none", "dictsort", + "dictsortreversed", "divisibleby", "escape", "escapejs", + "filesizeformat", "first", "floatformat", "force_escape", + "get_digit", "iriencode", "join", "last", "length", + "length_is", "linebreaks", "linebreaksbr", "linenumbers", + "ljust", "lower", "make_list", "phone2numeric", "pluralize", + "pprint", "random", "removetags", "rjust", "safe", + "safeseq", "slice", "slugify", "stringformat", "striptags", + "time", "timesince", "timeuntil", "title", "truncatechars", + "truncatechars_html", "truncatewords", + "truncatewords_html", "unordered_list", "upper", + "urlencode", "urlize", "urlizetrunc", "wordcount", + "wordwrap", "yesno" + ], + operators = ["==", "!=", "<", ">", "<=", ">="], + wordOperators = ["in", "not", "or", "and"]; + + keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b"); + filters = new RegExp("^\\b(" + filters.join("|") + ")\\b"); + operators = new RegExp("^\\b(" + operators.join("|") + ")\\b"); + wordOperators = new RegExp( + "^\\b(" + wordOperators.join("|") + ")\\b" + ); + + function tokenBase(stream, state) { + if (stream.match("{{")) { + state.tokenize = inVariable; + return "tag"; + } else if (stream.match("{%")) { + state.tokenize = inTag; + return "tag"; + } else if (stream.match("{#")) { + state.tokenize = inComment; + return "comment"; + } + + while (!stream.eol()) { + stream.next(); + if (stream.match(/\{[{%#]/, false)) { + break; + } + } + return null; + } + + function inString(delimiter, previousTokenizer) { + return function (stream, state) { + if (!state.escapeNext && stream.eat(delimiter)) { + state.tokenize = previousTokenizer; + } else { + if (state.escapeNext) { + state.escapeNext = false; + } + const character = stream.next(); + if (character === "\\") { + state.escapeNext = true; + } + } + return "string"; + }; + } + + function readPropertyOrFilter(stream, state) { + if (state.waitDot) { + state.waitDot = false; + if (stream.peek() !== ".") { + return "null"; + } + if (stream.match(/\.\W+/)) { + return "error"; + } + if (stream.eat(".")) { + state.waitProperty = true; + return "null"; + } + throw new Error("Unexpected error while waiting for property."); + } + + if (state.waitPipe) { + state.waitPipe = false; + if (stream.peek() !== "|") { + return "null"; + } + if (stream.match(/\.\W+/)) { + return "error"; + } + if (stream.eat("|")) { + state.waitFilter = true; + return "null"; + } + throw new Error("Unexpected error while waiting for filter."); + } + + if (state.waitProperty) { + state.waitProperty = false; + if (stream.match(/\b(\w+)\b/)) { + state.waitDot = true; + state.waitPipe = true; + return "property"; + } + } + + if (state.waitFilter) { + state.waitFilter = false; + if (stream.match(filters)) { + return "variable-2"; + } + } + return undefined; + } + + function readCommonValue(stream, state) { + if (stream.eatSpace()) { + state.waitProperty = false; + return "null"; + } + if (stream.match(/\b\d+(\.\d+)?\b/)) { + return "number"; + } + if (stream.match("'")) { + state.tokenize = inString("'", state.tokenize); + return "string"; + } + if (stream.match('"')) { + state.tokenize = inString('"', state.tokenize); + return "string"; + } + return undefined; + } + + function clearWaitingState(state) { + state.waitProperty = null; + state.waitFilter = null; + state.waitDot = null; + state.waitPipe = null; + } + + function inVariable(stream, state) { + const propertyOrFilter = readPropertyOrFilter(stream, state); + if (propertyOrFilter !== undefined) { + return propertyOrFilter; + } + + const commonValue = readCommonValue(stream, state); + if (commonValue !== undefined) { + return commonValue; + } + + if (stream.match(/\b(\w+)\b/) && !state.foundVariable) { + state.waitDot = true; + state.waitPipe = true; + return "variable"; + } + + if (stream.match("}}")) { + clearWaitingState(state); + state.tokenize = tokenBase; + return "tag"; + } + + stream.next(); + return "null"; + } + + function inTag(stream, state) { + const propertyOrFilter = readPropertyOrFilter(stream, state); + if (propertyOrFilter !== undefined) { + return propertyOrFilter; + } + + const commonValue = readCommonValue(stream, state); + if (commonValue !== undefined) { + return commonValue; + } + + if (stream.match(operators)) { + return "operator"; + } + if (stream.match(wordOperators)) { + return "keyword"; + } + + const keywordMatch = stream.match(keywords); + if (keywordMatch) { + if (keywordMatch[0] === "comment") { + state.blockCommentTag = true; + } + return "keyword"; + } + + if (stream.match(/\b(\w+)\b/)) { + state.waitDot = true; + state.waitPipe = true; + return "variable"; + } + + if (stream.match("%}")) { + clearWaitingState(state); + if (state.blockCommentTag) { + state.blockCommentTag = false; + state.tokenize = inBlockComment; + } else { + state.tokenize = tokenBase; + } + return "tag"; + } + + stream.next(); + return "null"; + } + + function inComment(stream, state) { + if (stream.match(/^.*?#\}/)) { + state.tokenize = tokenBase; + } else { + stream.skipToEnd(); + } + return "comment"; + } + + function inBlockComment(stream, state) { + if (stream.match(/\{%\s*endcomment\s*%\}/, false)) { + state.tokenize = inTag; + stream.match("{%"); + return "tag"; + } + stream.next(); + return "comment"; + } + + return { + startState: function () { + return {tokenize: tokenBase}; + }, + token: function (stream, state) { + return state.tokenize(stream, state); + }, + blockCommentStart: "{% comment %}", + blockCommentEnd: "{% endcomment %}" + }; + }); + + CodeMirror.defineMode("django", function (config) { + const htmlBase = CodeMirror.getMode(config, "text/html"); + const djangoInner = CodeMirror.getMode(config, "django:inner"); + return CodeMirror.overlayMode(htmlBase, djangoInner); + }, "htmlmixed"); + CodeMirror.defineMIME("text/x-django", "django"); + } + + function installHaml(CodeMirror) { + CodeMirror.defineMode("haml", function (config) { + const htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); + const rubyMode = CodeMirror.getMode(config, "ruby"); + + function rubyInQuote(endQuote) { + return function (stream, state) { + const character = stream.peek(); + if (character === endQuote && + state.rubyState.tokenize.length === 1) { + stream.next(); + state.tokenize = html; + return "closeAttributeTag"; + } + return ruby(stream, state); + }; + } + + function ruby(stream, state) { + if (stream.match("-#")) { + stream.skipToEnd(); + return "comment"; + } + return rubyMode.token(stream, state.rubyState); + } + + function html(stream, state) { + const character = stream.peek(); + if (state.previousToken.style === "comment" && + state.indented > state.previousToken.indented) { + stream.skipToEnd(); + return "commentLine"; + } + + if (state.startOfLine) { + if (character === "!" && stream.match("!!")) { + stream.skipToEnd(); + return "tag"; + } else if (stream.match(/^%[\w:#.]+=/)) { + state.tokenize = ruby; + return "hamlTag"; + } else if (stream.match(/^%[\w:]+/)) { + return "hamlTag"; + } else if (character === "/") { + stream.skipToEnd(); + return "comment"; + } + } + + if ((state.startOfLine || + state.previousToken.style === "hamlTag") && + (character === "#" || character === ".")) { + stream.match(/[\w-#.]+/); + return "hamlAttribute"; + } + + if (state.startOfLine && !stream.match("-->", false) && + (character === "=" || character === "-")) { + state.tokenize = ruby; + return state.tokenize(stream, state); + } + + if (state.previousToken.style === "hamlTag" || + state.previousToken.style === "closeAttributeTag" || + state.previousToken.style === "hamlAttribute") { + if (character === "(") { + state.tokenize = rubyInQuote(")"); + return state.tokenize(stream, state); + } else if (character === "{" && + !stream.match(/^\{%.*/)) { + state.tokenize = rubyInQuote("}"); + return state.tokenize(stream, state); + } + } + + return htmlMode.token(stream, state.htmlState); + } + + return { + startState: function () { + return { + htmlState: CodeMirror.startState(htmlMode), + rubyState: CodeMirror.startState(rubyMode), + indented: 0, + previousToken: {style: null, indented: 0}, + tokenize: html + }; + }, + copyState: function (state) { + return { + htmlState: CodeMirror.copyState( + htmlMode, + state.htmlState + ), + rubyState: CodeMirror.copyState( + rubyMode, + state.rubyState + ), + indented: state.indented, + previousToken: state.previousToken, + tokenize: state.tokenize + }; + }, + token: function (stream, state) { + if (stream.sol()) { + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) { + return null; + } + let style = state.tokenize(stream, state); + state.startOfLine = false; + if (style && style !== "commentLine") { + state.previousToken = { + style: style, + indented: state.indented + }; + } + if (stream.eol() && state.tokenize === ruby) { + stream.backUp(1); + const character = stream.peek(); + stream.next(); + if (character && character !== ",") { + state.tokenize = html; + } + } + if (style === "hamlTag") { + style = "tag"; + } else if (style === "commentLine") { + style = "comment"; + } else if (style === "hamlAttribute") { + style = "attribute"; + } else if (style === "closeAttributeTag") { + style = null; + } + return style; + } + }; + }, "htmlmixed", "ruby"); + + CodeMirror.defineMIME("text/x-haml", "haml"); + } + + function installHaskellLiterate(CodeMirror) { + CodeMirror.defineMode( + "haskell-literate", + function (config, parserConfig) { + const baseMode = CodeMirror.getMode( + config, + parserConfig && parserConfig.base || "haskell" + ); + + function currentMode(state) { + return state.inCode ? baseMode : null; + } + + return { + startState: function () { + return { + inCode: false, + baseState: CodeMirror.startState(baseMode) + }; + }, + copyState: function (state) { + return { + inCode: state.inCode, + baseState: CodeMirror.copyState( + baseMode, + state.baseState + ) + }; + }, + token: function (stream, state) { + if (stream.sol()) { + state.inCode = Boolean(stream.eat(">")); + if (state.inCode) { + return "meta"; + } + } + if (state.inCode) { + return baseMode.token(stream, state.baseState); + } + stream.skipToEnd(); + return "comment"; + }, + innerMode: function (state) { + const mode = currentMode(state); + return mode ? { + state: state.baseState, + mode: mode + } : null; + } + }; + }, + "haskell" + ); + CodeMirror.defineMIME( + "text/x-literate-haskell", + "haskell-literate" + ); + } + + function installSmarty(CodeMirror) { + CodeMirror.defineMode("smarty", function (config, parserConfig) { + const rightDelimiter = parserConfig.rightDelimiter || "}"; + const leftDelimiter = parserConfig.leftDelimiter || "{"; + const version = parserConfig.version || 2; + const baseMode = CodeMirror.getMode( + config, + parserConfig.baseMode || "null" + ); + const keyFunctions = [ + "debug", "extends", "function", "include", "literal" + ]; + const regs = { + operatorChars: /[+\-*&%=<>!?]/, + validIdentifier: /[a-zA-Z0-9_]/, + stringChar: /['"]/ + }; + let last; + + function cont(style, lastType) { + last = lastType; + return style; + } + + function chain(stream, state, parser) { + state.tokenize = parser; + return parser(stream, state); + } + + function doesNotCount(stream, position) { + const effectivePosition = position === null || + position === undefined ? + stream.pos : + position; + return version === 3 && leftDelimiter === "{" && + (effectivePosition === stream.string.length || + /\s/.test(stream.string.charAt(effectivePosition))); + } + + function tokenTop(stream, state) { + const string = stream.string; + let nextMatch; + for (let scan = stream.pos;;) { + nextMatch = string.indexOf(leftDelimiter, scan); + scan = nextMatch + leftDelimiter.length; + if (nextMatch === -1 || + !doesNotCount( + stream, + nextMatch + leftDelimiter.length + )) { + break; + } + } + + if (nextMatch === stream.pos) { + stream.match(leftDelimiter); + if (stream.eat("*")) { + return chain( + stream, + state, + tokenBlock("comment", "*" + rightDelimiter) + ); + } + state.depth++; + state.tokenize = tokenSmarty; + last = "startTag"; + return "tag"; + } + + if (nextMatch > -1) { + stream.string = string.slice(0, nextMatch); + } + const token = baseMode.token(stream, state.base); + if (nextMatch > -1) { + stream.string = string; + } + return token; + } + + function tokenSmarty(stream, state) { + if (stream.match(rightDelimiter, true)) { + if (version === 3) { + state.depth--; + if (state.depth <= 0) { + state.tokenize = tokenTop; + } + } else { + state.tokenize = tokenTop; + } + return cont("tag", null); + } + + if (stream.match(leftDelimiter, true)) { + state.depth++; + return cont("tag", "startTag"); + } + + const character = stream.next(); + if (character === "$") { + stream.eatWhile(regs.validIdentifier); + return cont("variable-2", "variable"); + } else if (character === "|") { + return cont("operator", "pipe"); + } else if (character === ".") { + return cont("operator", "property"); + } else if (regs.stringChar.test(character)) { + state.tokenize = tokenAttribute(character); + return cont("string", "string"); + } else if (regs.operatorChars.test(character)) { + stream.eatWhile(regs.operatorChars); + return cont("operator", "operator"); + } else if (character === "[" || character === "]") { + return cont("bracket", "bracket"); + } else if (character === "(" || character === ")") { + return cont("bracket", "operator"); + } else if (/\d/.test(character)) { + stream.eatWhile(/\d/); + return cont("number", "number"); + } + + if (state.last === "variable") { + if (character === "@") { + stream.eatWhile(regs.validIdentifier); + return cont("property", "property"); + } else if (character === "|") { + stream.eatWhile(regs.validIdentifier); + return cont("qualifier", "modifier"); + } + } else if (state.last === "pipe") { + stream.eatWhile(regs.validIdentifier); + return cont("qualifier", "modifier"); + } else if (state.last === "whitespace") { + stream.eatWhile(regs.validIdentifier); + return cont("attribute", "modifier"); + } else if (state.last === "property") { + stream.eatWhile(regs.validIdentifier); + return cont("property", null); + } else if (/\s/.test(character)) { + last = "whitespace"; + return null; + } + + let string = ""; + if (character !== "/") { + string += character; + } + let nextCharacter = null; + while ((nextCharacter = stream.eat(regs.validIdentifier))) { + string += nextCharacter; + } + for (let index = 0; index < keyFunctions.length; index++) { + if (keyFunctions[index] === string) { + return cont("keyword", "keyword"); + } + } + if (/\s/.test(character)) { + return null; + } + return cont("tag", "tag"); + } + + function tokenAttribute(quote) { + return function (stream, state) { + let previousCharacter = null; + let currentCharacter = null; + while (!stream.eol()) { + currentCharacter = stream.peek(); + if (stream.next() === quote && + previousCharacter !== "\\") { + state.tokenize = tokenSmarty; + break; + } + previousCharacter = currentCharacter; + } + return "string"; + }; + } + + function tokenBlock(style, terminator) { + return function (stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = tokenTop; + break; + } + stream.next(); + } + return style; + }; + } + + return { + startState: function () { + return { + base: CodeMirror.startState(baseMode), + tokenize: tokenTop, + last: null, + depth: 0 + }; + }, + copyState: function (state) { + return { + base: CodeMirror.copyState(baseMode, state.base), + tokenize: state.tokenize, + last: state.last, + depth: state.depth + }; + }, + innerMode: function (state) { + if (state.tokenize === tokenTop) { + return {mode: baseMode, state: state.base}; + } + return undefined; + }, + token: function (stream, state) { + const style = state.tokenize(stream, state); + state.last = last; + return style; + }, + indent: function (state, text) { + if (state.tokenize === tokenTop && baseMode.indent) { + return baseMode.indent(state.base, text); + } + return CodeMirror.Pass; + }, + blockCommentStart: leftDelimiter + "*", + blockCommentEnd: "*" + rightDelimiter + }; + }); + + CodeMirror.defineMIME("text/x-smarty", "smarty"); + } + + function installSoy(CodeMirror) { + const indentingTags = [ + "template", "literal", "msg", "fallbackmsg", "let", "if", + "elseif", "else", "switch", "case", "default", "foreach", + "ifempty", "for", "call", "param", "deltemplate", "delcall", + "log" + ]; + + CodeMirror.defineMode("soy", function (config) { + const textMode = CodeMirror.getMode(config, "text/plain"); + const modes = { + html: CodeMirror.getMode(config, { + name: "text/html", + multilineTagIndentFactor: 2, + multilineTagIndentPastTag: false + }), + attributes: textMode, + text: textMode, + uri: textMode, + css: CodeMirror.getMode(config, "text/css"), + js: CodeMirror.getMode(config, { + name: "text/javascript", + statementIndent: 2 * config.indentUnit + }) + }; + + function last(array) { + return array[array.length - 1]; + } + + function tokenUntil(stream, state, untilRegExp) { + if (stream.sol()) { + let indent; + for (indent = 0; indent < state.indent; indent++) { + if (!stream.eat(/\s/)) { + break; + } + } + if (indent) { + return null; + } + } + const oldString = stream.string; + const match = untilRegExp.exec(oldString.substr(stream.pos)); + if (match) { + stream.string = oldString.substr( + 0, + stream.pos + match.index + ); + } + const result = stream.hideFirstChars(state.indent, function () { + const localState = last(state.localStates); + return localState.mode.token(stream, localState.state); + }); + stream.string = oldString; + return result; + } + + function contains(list, element) { + let current = list; + while (current) { + if (current.element === element) { + return true; + } + current = current.next; + } + return false; + } + + function prepend(list, element) { + return { + element: element, + next: list + }; + } + + function ref(list, name, loose) { + return contains(list, name) ? + "variable-2" : + (loose ? "variable" : "variable-2 error"); + } + + function popScope(state) { + if (state.scopes) { + state.variables = state.scopes.element; + state.scopes = state.scopes.next; + } + } + + return { + startState: function () { + return { + kind: [], + kindTag: [], + soyState: [], + templates: null, + variables: prepend(null, "ij"), + scopes: null, + indent: 0, + quoteKind: null, + localStates: [{ + mode: modes.html, + state: CodeMirror.startState(modes.html) + }] + }; + }, + copyState: function (state) { + return { + tag: state.tag, + kind: state.kind.concat([]), + kindTag: state.kindTag.concat([]), + soyState: state.soyState.concat([]), + templates: state.templates, + variables: state.variables, + scopes: state.scopes, + indent: state.indent, + quoteKind: state.quoteKind, + localStates: state.localStates.map(function (localState) { + return { + mode: localState.mode, + state: CodeMirror.copyState( + localState.mode, + localState.state + ) + }; + }) + }; + }, + token: function (stream, state) { + let match; + switch (last(state.soyState)) { + case "comment": + if (stream.match(/^.*?\*\//)) { + state.soyState.pop(); + } else { + stream.skipToEnd(); + } + if (!state.scopes) { + const paramExpression = /@param\??\s+(\S+)/g; + const current = stream.current(); + while ((match = paramExpression.exec(current))) { + state.variables = prepend( + state.variables, + match[1] + ); + } + } + return "comment"; + + case "templ-def": + match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/); + if (match) { + state.templates = prepend( + state.templates, + match[1] + ); + state.scopes = prepend( + state.scopes, + state.variables + ); + state.soyState.pop(); + return "def"; + } + stream.next(); + return null; + + case "templ-ref": + match = stream.match(/^\.?([\w]+)/); + if (match) { + state.soyState.pop(); + if (match[0][0] === ".") { + return ref(state.templates, match[1], true); + } + return "variable"; + } + stream.next(); + return null; + + case "param-def": + match = stream.match(/^\w+/); + if (match) { + state.variables = prepend( + state.variables, + match[0] + ); + state.soyState.pop(); + state.soyState.push("param-type"); + return "def"; + } + stream.next(); + return null; + + case "param-type": + if (stream.peek() === "}") { + state.soyState.pop(); + return null; + } + if (stream.eatWhile(/^[\w]+/)) { + return "variable-3"; + } + stream.next(); + return null; + + case "var-def": + match = stream.match(/^\$([\w]+)/); + if (match) { + state.variables = prepend( + state.variables, + match[1] + ); + state.soyState.pop(); + return "def"; + } + stream.next(); + return null; + + case "tag": + if (stream.match(/^\/?}/)) { + if (state.tag === "/template" || + state.tag === "/deltemplate") { + popScope(state); + state.variables = prepend(null, "ij"); + state.indent = 0; + } else { + if (state.tag === "/for" || + state.tag === "/foreach") { + popScope(state); + } + state.indent -= config.indentUnit * + (stream.current() === "/}" || + indentingTags.indexOf(state.tag) === -1 ? + 2 : + 1); + } + state.soyState.pop(); + return "keyword"; + } + + match = stream.match(/^([\w?]+)(?==)/); + if (match) { + if (stream.current() === "kind") { + const kindMatch = stream.match( + /^="([^"]+)/, + false + ); + if (kindMatch) { + const kind = kindMatch[1]; + state.kind.push(kind); + state.kindTag.push(state.tag); + const mode = modes[kind] || modes.html; + const localState = last(state.localStates); + if (localState.mode.indent) { + state.indent += localState.mode.indent( + localState.state, + "" + ); + } + state.localStates.push({ + mode: mode, + state: CodeMirror.startState(mode) + }); + } + } + return "attribute"; + } + + match = stream.match(/^["']/); + if (match) { + state.soyState.push("string"); + state.quoteKind = match; + return "string"; + } + match = stream.match(/^\$([\w]+)/); + if (match) { + return ref(state.variables, match[1]); + } + match = stream.match(/^\w+/); + if (match) { + return /^(?:as|and|or|not|in)$/.test(match[0]) ? + "keyword" : + null; + } + stream.next(); + return null; + + case "literal": + if (stream.match(/^(?=\{\/literal})/)) { + state.indent -= config.indentUnit; + state.soyState.pop(); + return this.token(stream, state); + } + return tokenUntil(stream, state, /\{\/literal}/); + + case "string": + match = stream.match(/^.*?(["']|\\[\s\S])/); + if (!match) { + stream.skipToEnd(); + } else if (match[1] === state.quoteKind) { + state.quoteKind = null; + state.soyState.pop(); + } + return "string"; + default: + break; + } + + if (stream.match(/^\/\*/)) { + state.soyState.push("comment"); + if (!state.scopes) { + state.variables = prepend(null, "ij"); + } + return "comment"; + } else if (stream.match( + stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/ + )) { + if (!state.scopes) { + state.variables = prepend(null, "ij"); + } + return "comment"; + } else if (stream.match(/^\{literal}/)) { + state.indent += config.indentUnit; + state.soyState.push("literal"); + return "keyword"; + } + + match = stream.match( + /^\{([/@\\]?\w+\??)(?=[\s}])/ + ); + if (match) { + if (match[1] !== "/switch") { + state.indent += ( + /^(\/|(else|elseif|ifempty|case|fallbackmsg|default)$)/ + .test(match[1]) && + state.tag !== "switch" ? + 1 : + 2 + ) * config.indentUnit; + } + state.tag = match[1]; + if (state.tag === "/" + last(state.kindTag)) { + state.kind.pop(); + state.kindTag.pop(); + state.localStates.pop(); + const localState = last(state.localStates); + if (localState.mode.indent) { + state.indent -= localState.mode.indent( + localState.state, + "" + ); + } + } + state.soyState.push("tag"); + if (state.tag === "template" || + state.tag === "deltemplate") { + state.soyState.push("templ-def"); + } else if (state.tag === "call" || + state.tag === "delcall") { + state.soyState.push("templ-ref"); + } else if (state.tag === "let") { + state.soyState.push("var-def"); + } else if (state.tag === "for" || + state.tag === "foreach") { + state.scopes = prepend( + state.scopes, + state.variables + ); + state.soyState.push("var-def"); + } else if (state.tag === "namespace") { + if (!state.scopes) { + state.variables = prepend(null, "ij"); + } + } else if (state.tag.match( + /^@(?:param\??|inject)/ + )) { + state.soyState.push("param-def"); + } + return "keyword"; + } else if (stream.eat("{")) { + state.tag = "print"; + state.indent += 2 * config.indentUnit; + state.soyState.push("tag"); + return "keyword"; + } + + return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/); + }, + indent: function (state, textAfter) { + let indent = state.indent; + const top = last(state.soyState); + if (top === "comment") { + return CodeMirror.Pass; + } + + if (top === "literal") { + if (/^\{\/literal}/.test(textAfter)) { + indent -= config.indentUnit; + } + } else { + if (/^\s*\{\/(template|deltemplate)\b/.test( + textAfter + )) { + return 0; + } + if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/ + .test(textAfter)) { + indent -= config.indentUnit; + } + if (state.tag !== "switch" && + /^\{(case|default)\b/.test(textAfter)) { + indent -= config.indentUnit; + } + if (/^\{\/switch\b/.test(textAfter)) { + indent -= config.indentUnit; + } + } + + const localState = last(state.localStates); + if (indent && localState.mode.indent) { + indent += localState.mode.indent( + localState.state, + textAfter + ); + } + return indent; + }, + innerMode: function (state) { + if (state.soyState.length && + last(state.soyState) !== "literal") { + return null; + } + return last(state.localStates); + }, + electricInput: + /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/, + lineComment: "//", + blockCommentStart: "/*", + blockCommentEnd: "*/", + blockCommentContinue: " * ", + useInnerComments: false, + fold: "indent" + }; + }, "htmlmixed"); + + CodeMirror.registerHelper( + "hintWords", + "soy", + indentingTags.concat([ + "delpackage", "namespace", "alias", "print", "css", + "debugger" + ]) + ); + CodeMirror.defineMIME("text/x-soy", "soy"); + } + + function installTornado(CodeMirror) { + CodeMirror.defineMode("tornado:inner", function () { + const keywords = new RegExp( + "^((" + [ + "and", "as", "assert", "autoescape", "block", "break", + "class", "comment", "context", "continue", "datetime", + "def", "del", "elif", "else", "end", "escape", "except", + "exec", "extends", "false", "finally", "for", "from", + "global", "if", "import", "in", "include", "is", + "json_encode", "lambda", "length", "linkify", "load", + "module", "none", "not", "or", "pass", "print", "put", + "raise", "raw", "return", "self", "set", "squeeze", + "super", "true", "try", "url_escape", "while", "with", + "without", "xhtml_escape", "yield" + ].join(")|(") + "))\\b" + ); + + function tokenBase(stream, state) { + stream.eatWhile(/[^{]/); + const character = stream.next(); + if (character === "{") { + const close = stream.eat(/\{|%|#/); + if (close) { + state.tokenize = inTag(close); + return "tag"; + } + } + return undefined; + } + + function inTag(initialClose) { + const close = initialClose === "{" ? "}" : initialClose; + return function (stream, state) { + const character = stream.next(); + if (character === close && stream.eat("}")) { + state.tokenize = tokenBase; + return "tag"; + } + if (stream.match(keywords)) { + return "keyword"; + } + return close === "#" ? "comment" : "string"; + }; + } + + return { + startState: function () { + return {tokenize: tokenBase}; + }, + token: function (stream, state) { + return state.tokenize(stream, state); + } + }; + }); + + CodeMirror.defineMode("tornado", function (config) { + const htmlBase = CodeMirror.getMode(config, "text/html"); + const tornadoInner = CodeMirror.getMode(config, "tornado:inner"); + return CodeMirror.overlayMode(htmlBase, tornadoInner); + }, "htmlmixed"); + CodeMirror.defineMIME("text/x-tornado", "tornado"); + } + + function installYAMLFrontmatter(CodeMirror) { + const START = 0; + const FRONTMATTER = 1; + const BODY = 2; + + CodeMirror.defineMode( + "yaml-frontmatter", + function (config, parserConfig) { + const yamlMode = CodeMirror.getMode(config, "yaml"); + const inner = CodeMirror.getMode( + config, + parserConfig && parserConfig.base || "gfm" + ); + + function currentMode(state) { + return state.state === BODY ? inner : yamlMode; + } + + return { + startState: function () { + return { + state: START, + inner: CodeMirror.startState(yamlMode) + }; + }, + copyState: function (state) { + return { + state: state.state, + inner: CodeMirror.copyState( + currentMode(state), + state.inner + ) + }; + }, + token: function (stream, state) { + if (state.state === START) { + if (stream.match(/---/, false)) { + state.state = FRONTMATTER; + return yamlMode.token(stream, state.inner); + } + state.state = BODY; + state.inner = CodeMirror.startState(inner); + return inner.token(stream, state.inner); + } else if (state.state === FRONTMATTER) { + const end = stream.sol() && + stream.match(/---/, false); + const style = yamlMode.token(stream, state.inner); + if (end) { + state.state = BODY; + state.inner = CodeMirror.startState(inner); + } + return style; + } + return inner.token(stream, state.inner); + }, + innerMode: function (state) { + return { + mode: currentMode(state), + state: state.inner + }; + }, + blankLine: function (state) { + const mode = currentMode(state); + if (mode.blankLine) { + return mode.blankLine(state.inner); + } + return undefined; + } + }; + }, + "yaml", + "gfm" + ); + } + + function install(CodeMirror) { + if (!CodeMirror || installedTargets.has(CodeMirror)) { + return; + } + installedTargets.add(CodeMirror); + + installDjango(CodeMirror); + installHaml(CodeMirror); + installHaskellLiterate(CodeMirror); + installSmarty(CodeMirror); + installSoy(CodeMirror); + installTornado(CodeMirror); + installYAMLFrontmatter(CodeMirror); + RSTSlimCompat.install(CodeMirror); + } + + exports.install = install; +}); diff --git a/src/editor/CodeMirrorLegacyModuleLoader.js b/src/editor/CodeMirrorLegacyModuleLoader.js new file mode 100644 index 0000000000..195f9f1a68 --- /dev/null +++ b/src/editor/CodeMirrorLegacyModuleLoader.js @@ -0,0 +1,271 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global define, window*/ + +/** + * Resolves historical CodeMirror 5 AMD module IDs to Phoenix's CodeMirror 6 + * compatibility facade. This keeps third-party extensions loadable without + * shipping the old CodeMirror package or mirroring its addon/mode file tree. + */ +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + LegacyAddons = require("editor/CodeMirrorLegacyAddons"), + LegacyExtendedAddons = + require("editor/CodeMirrorLegacyExtendedAddons"), + SublimeCompat = require("editor/CodeMirrorSublimeCompat"), + TwigCompat = require("editor/CodeMirrorTwigCompat"), + VimCompat = require("editor/CodeMirrorVimCompat"); + const LEGACY_MODULE_PATTERN = /^thirdparty\/CodeMirror(?:2)?(?:\/|$)/; + const LOAD_WRAPPER_MARKER = "__phoenixCodeMirrorLegacyModuleLoader"; + const CORE_MODULES = new Set([ + "", + "lib/codemirror" + ]); + const FACADE_ADDON_MODULES = new Set([ + "addon/display/placeholder", + "addon/display/rulers", + "addon/edit/closebrackets", + "addon/edit/matchbrackets", + "addon/mode/multiplex", + "addon/mode/overlay", + "addon/mode/simple", + "addon/scroll/annotatescrollbar", + "addon/scroll/scrollpastend", + "addon/search/match-highlighter", + "addon/search/matchesonscrollbar", + "addon/selection/active-line" + ]); + const COMPAT_ADDON_MODULES = new Set([ + "addon/hint/anyword-hint", + "addon/hint/show-hint", + "addon/search/jump-to-line", + "addon/search/search" + ]); + const SUBLIME_KEYMAP_MODULE = "keymap/sublime"; + const VIM_KEYMAP_MODULE = "keymap/vim"; + const MODE_META_MODULE = "mode/meta"; + + TwigCompat.install(CodeMirror); + + function isLegacyModule(moduleName) { + return typeof moduleName === "string" && + LEGACY_MODULE_PATTERN.test(moduleName); + } + + function getLegacyPath(moduleName) { + if (!isLegacyModule(moduleName)) { + return null; + } + return moduleName + .replace(/^thirdparty\/CodeMirror(?:2)?\/?/, "") + .replace(/[?#].*$/, "") + .replace(/\.js$/, ""); + } + + function getModeName(moduleName) { + const legacyPath = getLegacyPath(moduleName); + if (legacyPath === null) { + return null; + } + const pathParts = legacyPath.split("/"); + if (pathParts[0] !== "mode" || !pathParts[1]) { + return null; + } + return pathParts[1]; + } + + function createCompatibilityError(moduleName, detail) { + const error = new Error( + `Unsupported CodeMirror 5 module "${moduleName}". ${detail} ` + + "Phoenix uses CodeMirror 6 and will not load CodeMirror 5 code." + ); + error.code = "PHOENIX_UNSUPPORTED_CODEMIRROR5_MODULE"; + return error; + } + + function getModuleType(moduleName) { + const legacyPath = getLegacyPath(moduleName); + if (legacyPath === null) { + return null; + } + if (CORE_MODULES.has(legacyPath)) { + return "core"; + } + if (legacyPath === MODE_META_MODULE) { + return "mode-meta"; + } + if (legacyPath.indexOf("mode/") === 0) { + return "mode"; + } + if (LegacyAddons.isSupported(legacyPath)) { + return "addon"; + } + if (LegacyExtendedAddons.isSupported(legacyPath)) { + return "extended-addon"; + } + if (FACADE_ADDON_MODULES.has(legacyPath)) { + return "facade-addon"; + } + if (COMPAT_ADDON_MODULES.has(legacyPath)) { + return "compat-addon"; + } + if (legacyPath === SUBLIME_KEYMAP_MODULE) { + return "sublime-keymap"; + } + if (legacyPath === VIM_KEYMAP_MODULE) { + return "vim-keymap"; + } + if (legacyPath.indexOf("theme/") === 0) { + return "theme"; + } + return "unsupported"; + } + + function resolveLegacyModule(moduleName) { + const legacyPath = getLegacyPath(moduleName); + const moduleType = getModuleType(moduleName); + if (!moduleType) { + throw new TypeError(`Not a legacy CodeMirror module: ${moduleName}`); + } + + if (moduleType === "core" || + moduleType === "mode-meta" || + moduleType === "facade-addon" || + moduleType === "theme") { + return CodeMirror; + } + + if (moduleType === "compat-addon") { + if (!CodeMirror.installLegacyCompatibility(legacyPath)) { + throw createCompatibilityError( + moduleName, + "Its CM6-backed compatibility behavior could not be installed." + ); + } + return CodeMirror; + } + + if (moduleType === "addon") { + if (!LegacyAddons.install(CodeMirror, legacyPath)) { + throw createCompatibilityError( + moduleName, + "Its addon behavior could not be installed." + ); + } + return CodeMirror; + } + + if (moduleType === "extended-addon") { + if (!LegacyExtendedAddons.install(CodeMirror, legacyPath)) { + throw createCompatibilityError( + moduleName, + "Its extended CM6-backed addon behavior could not be installed." + ); + } + return CodeMirror; + } + + if (moduleType === "sublime-keymap") { + LegacyAddons.install(CodeMirror, "addon/comment/comment"); + SublimeCompat.install(CodeMirror); + return CodeMirror; + } + + if (moduleType === "vim-keymap") { + VimCompat.install(CodeMirror); + return CodeMirror; + } + + const modeName = getModeName(moduleName); + if (moduleType === "mode") { + if (!modeName || !CodeMirror.loadMode(modeName)) { + throw createCompatibilityError( + moduleName, + `The "${modeName || legacyPath}" mode has no bundled ` + + "CodeMirror 6 compatibility parser." + ); + } + return CodeMirror; + } + + throw createCompatibilityError( + moduleName, + "No CM6-backed compatibility implementation is registered for this path." + ); + } + + /** + * Installs a RequireJS transport wrapper that supplies an AMD module for + * any historical CodeMirror path before RequireJS attempts a network load. + * + * @param {Object=} loader RequireJS global + * @param {function(string, Array, function())=} defineModule AMD define + * @return {boolean} Whether the compatibility loader is installed + */ + function install(loader, defineModule) { + const requireLoader = loader || window.requirejs || window.require; + const amdDefine = defineModule || window.define; + if (!requireLoader || typeof requireLoader.load !== "function" || + typeof amdDefine !== "function") { + return false; + } + if (requireLoader.load[LOAD_WRAPPER_MARKER]) { + return true; + } + + const originalLoad = requireLoader.load; + const compatibilityLoad = function (context, moduleName, url) { + if (!isLegacyModule(moduleName)) { + return originalLoad.call(requireLoader, context, moduleName, url); + } + + let compatibilityModule; + let compatibilityError; + try { + compatibilityModule = resolveLegacyModule(moduleName); + } catch (error) { + compatibilityError = error; + } + amdDefine(moduleName, [], function () { + if (compatibilityError) { + throw compatibilityError; + } + return compatibilityModule; + }); + context.completeLoad(moduleName); + }; + compatibilityLoad[LOAD_WRAPPER_MARKER] = true; + compatibilityLoad.originalLoad = originalLoad; + requireLoader.load = compatibilityLoad; + return true; + } + + install(); + + exports.createCompatibilityError = createCompatibilityError; + exports.getLegacyPath = getLegacyPath; + exports.getModeName = getModeName; + exports.getModuleType = getModuleType; + exports.install = install; + exports.isLegacyModule = isLegacyModule; + exports.resolveLegacyModule = resolveLegacyModule; +}); diff --git a/src/editor/CodeMirrorLegacyRSTSlimCompat.js b/src/editor/CodeMirrorLegacyRSTSlimCompat.js new file mode 100644 index 0000000000..9ac141f4f7 --- /dev/null +++ b/src/editor/CodeMirrorLegacyRSTSlimCompat.js @@ -0,0 +1,1466 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5.65.16-derived RST and Slim mode compatibility. + * CodeMirror copyright (c) by Marijn Haverbeke and others. + * Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh. + * Distributed under the MIT license. See thirdparty/licences/codemirror5-derived.markdown. + */ + +/** + * Ports the historical CodeMirror 5.65.16 RST and Slim stream modes to + * Phoenix's CM6-backed CodeMirror compatibility facade. + */ +define(function (require, exports, module) { + + const installedTargets = new WeakSet(); + + function installRst(CodeMirror) { + CodeMirror.defineMode("rst", function (config, options) { + const strongPattern = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/; + const emphasisPattern = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/; + const literalPattern = /^``[^`\s](?:[^`]*[^`\s])``/; + + const numberPattern = /^(?:[\d]+(?:[\.,]\d+)*)/; + const positivePattern = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; + const negativePattern = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; + + const uriProtocolPattern = "[Hh][Tt][Tt][Pp][Ss]?://"; + const uriDomainPattern = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})"; + const uriPathPattern = + "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*"; + const uriPattern = new RegExp( + "^" + uriProtocolPattern + uriDomainPattern + uriPathPattern + ); + + const overlay = { + token: function (stream) { + if (stream.match(strongPattern) && + stream.match(/\W+|$/, false)) { + return "strong"; + } + if (stream.match(emphasisPattern) && + stream.match(/\W+|$/, false)) { + return "em"; + } + if (stream.match(literalPattern) && + stream.match(/\W+|$/, false)) { + return "string-2"; + } + if (stream.match(numberPattern)) { + return "number"; + } + if (stream.match(positivePattern)) { + return "positive"; + } + if (stream.match(negativePattern)) { + return "negative"; + } + if (stream.match(uriPattern)) { + return "link"; + } + + while (!stream.eol()) { + stream.next(); + if (stream.match(strongPattern, false)) { + break; + } + if (stream.match(emphasisPattern, false)) { + break; + } + if (stream.match(literalPattern, false)) { + break; + } + if (stream.match(numberPattern, false)) { + break; + } + if (stream.match(positivePattern, false)) { + break; + } + if (stream.match(negativePattern, false)) { + break; + } + if (stream.match(uriPattern, false)) { + break; + } + } + + return null; + } + }; + + const mode = CodeMirror.getMode( + config, + options.backdrop || "rst-base" + ); + + return CodeMirror.overlayMode(mode, overlay, true); + }, "python", "stex"); + + CodeMirror.defineMode("rst-base", function (config) { + function format(string) { + const args = Array.prototype.slice.call(arguments, 1); + return string.replace(/{(\d+)}/g, function (match, n) { + return typeof args[n] !== "undefined" ? args[n] : match; + }); + } + + const pythonMode = CodeMirror.getMode(config, "python"); + const stexMode = CodeMirror.getMode(config, "stex"); + + const SEPARATOR = "\\s+"; + const TAIL = "(?:\\s*|\\W|$)"; + const tailPattern = new RegExp(format("^{0}", TAIL)); + + const NAME = + "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; + const namePattern = new RegExp(format("^{0}", NAME)); + const NAME_WITH_WHITESPACE = + "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; + const REFERENCE_NAME = format( + "(?:{0}|`{1}`)", + NAME, + NAME_WITH_WHITESPACE + ); + + const TEXT_WITHOUT_PIPE = + "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)"; + const TEXT_WITHOUT_BACKTICK = "(?:[^\\`]+)"; + const textWithoutBacktickPattern = new RegExp( + format("^{0}", TEXT_WITHOUT_BACKTICK) + ); + + const sectionPattern = new RegExp( + "^([!'#$%&\"()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~])\\1{3,}\\s*$" + ); + const explicitPattern = new RegExp( + format("^\\.\\.{0}", SEPARATOR) + ); + const linkPattern = new RegExp( + format("^_{0}:{1}|^__:{1}", REFERENCE_NAME, TAIL) + ); + const directivePattern = new RegExp( + format("^{0}::{1}", REFERENCE_NAME, TAIL) + ); + const substitutionPattern = new RegExp( + format( + "^\\|{0}\\|{1}{2}::{3}", + TEXT_WITHOUT_PIPE, + SEPARATOR, + REFERENCE_NAME, + TAIL + ) + ); + const footnotePattern = new RegExp( + format( + "^\\[(?:\\d+|#{0}?|\\*)]{1}", + REFERENCE_NAME, + TAIL + ) + ); + const citationPattern = new RegExp( + format("^\\[{0}\\]{1}", REFERENCE_NAME, TAIL) + ); + + const substitutionReferencePattern = new RegExp( + format("^\\|{0}\\|", TEXT_WITHOUT_PIPE) + ); + const footnoteReferencePattern = new RegExp( + format("^\\[(?:\\d+|#{0}?|\\*)]_", REFERENCE_NAME) + ); + const citationReferencePattern = new RegExp( + format("^\\[{0}\\]_", REFERENCE_NAME) + ); + const linkReferencePattern = new RegExp( + format("^{0}__?", REFERENCE_NAME) + ); + const quotedLinkReferencePattern = new RegExp( + format("^`{0}`_", TEXT_WITHOUT_BACKTICK) + ); + + const prefixRolePattern = new RegExp( + format( + "^:{0}:`{1}`{2}", + NAME, + TEXT_WITHOUT_BACKTICK, + TAIL + ) + ); + const suffixRolePattern = new RegExp( + format( + "^`{1}`:{0}:{2}", + NAME, + TEXT_WITHOUT_BACKTICK, + TAIL + ) + ); + const rolePattern = new RegExp( + format("^:{0}:{1}", NAME, TAIL) + ); + + const directiveNamePattern = new RegExp( + format("^{0}", REFERENCE_NAME) + ); + const directiveTailPattern = new RegExp( + format("^::{0}", TAIL) + ); + const substitutionTextPattern = new RegExp( + format("^\\|{0}\\|", TEXT_WITHOUT_PIPE) + ); + const substitutionSeparatorPattern = new RegExp( + format("^{0}", SEPARATOR) + ); + const substitutionNamePattern = new RegExp( + format("^{0}", REFERENCE_NAME) + ); + const substitutionTailPattern = new RegExp( + format("^::{0}", TAIL) + ); + const linkHeadPattern = new RegExp("^_"); + const linkNamePattern = new RegExp( + format("^{0}|_", REFERENCE_NAME) + ); + const linkTailPattern = new RegExp(format("^:{0}", TAIL)); + + const verbatimPattern = new RegExp("^::\\s*$"); + const examplesPattern = new RegExp( + "^\\s+(?:>>>|In \\[\\d+\\]:)\\s" + ); + + function context(phaseValue, stageValue, mode, local) { + return { + phase: phaseValue, + stage: stageValue, + mode: mode, + local: local + }; + } + + function change(state, tokenizer, ctx) { + state.tok = tokenizer; + state.ctx = ctx || {}; + } + + function stage(state) { + return state.ctx.stage || 0; + } + + function phase(state) { + return state.ctx.phase; + } + + function toNormal(stream, state) { + let token = null; + + if (stream.sol() && stream.match(examplesPattern, false)) { + change(state, toMode, { + mode: pythonMode, + local: CodeMirror.startState(pythonMode) + }); + } else if (stream.sol() && stream.match(explicitPattern)) { + change(state, toExplicit); + token = "meta"; + } else if (stream.sol() && stream.match(sectionPattern)) { + change(state, toNormal); + token = "header"; + } else if (phase(state) === prefixRolePattern || + stream.match(prefixRolePattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toNormal, + context(prefixRolePattern, 1) + ); + stream.match(/^:/); + token = "meta"; + break; + case 1: + change( + state, + toNormal, + context(prefixRolePattern, 2) + ); + stream.match(namePattern); + token = "keyword"; + + if (stream.current().match(/^(?:math|latex)/)) { + state.tmpStex = true; + } + break; + case 2: + change( + state, + toNormal, + context(prefixRolePattern, 3) + ); + stream.match(/^:`/); + token = "meta"; + break; + case 3: + if (state.tmpStex) { + state.tmpStex = undefined; + state.tmp = { + mode: stexMode, + local: CodeMirror.startState(stexMode) + }; + } + + if (state.tmp) { + if (stream.peek() === "`") { + change( + state, + toNormal, + context(prefixRolePattern, 4) + ); + state.tmp = undefined; + break; + } + + token = state.tmp.mode.token( + stream, + state.tmp.local + ); + break; + } + + change( + state, + toNormal, + context(prefixRolePattern, 4) + ); + stream.match(textWithoutBacktickPattern); + token = "string"; + break; + case 4: + change( + state, + toNormal, + context(prefixRolePattern, 5) + ); + stream.match(/^`/); + token = "meta"; + break; + case 5: + change( + state, + toNormal, + context(prefixRolePattern, 6) + ); + stream.match(tailPattern); + break; + default: + change(state, toNormal); + } + } else if (phase(state) === suffixRolePattern || + stream.match(suffixRolePattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toNormal, + context(suffixRolePattern, 1) + ); + stream.match(/^`/); + token = "meta"; + break; + case 1: + change( + state, + toNormal, + context(suffixRolePattern, 2) + ); + stream.match(textWithoutBacktickPattern); + token = "string"; + break; + case 2: + change( + state, + toNormal, + context(suffixRolePattern, 3) + ); + stream.match(/^`:/); + token = "meta"; + break; + case 3: + change( + state, + toNormal, + context(suffixRolePattern, 4) + ); + stream.match(namePattern); + token = "keyword"; + break; + case 4: + change( + state, + toNormal, + context(suffixRolePattern, 5) + ); + stream.match(/^:/); + token = "meta"; + break; + case 5: + change( + state, + toNormal, + context(suffixRolePattern, 6) + ); + stream.match(tailPattern); + break; + default: + change(state, toNormal); + } + } else if (phase(state) === rolePattern || + stream.match(rolePattern, false)) { + switch (stage(state)) { + case 0: + change(state, toNormal, context(rolePattern, 1)); + stream.match(/^:/); + token = "meta"; + break; + case 1: + change(state, toNormal, context(rolePattern, 2)); + stream.match(namePattern); + token = "keyword"; + break; + case 2: + change(state, toNormal, context(rolePattern, 3)); + stream.match(/^:/); + token = "meta"; + break; + case 3: + change(state, toNormal, context(rolePattern, 4)); + stream.match(tailPattern); + break; + default: + change(state, toNormal); + } + } else if (phase(state) === substitutionReferencePattern || + stream.match(substitutionReferencePattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toNormal, + context(substitutionReferencePattern, 1) + ); + stream.match(substitutionTextPattern); + token = "variable-2"; + break; + case 1: + change( + state, + toNormal, + context(substitutionReferencePattern, 2) + ); + if (stream.match(/^_?_?/)) { + token = "link"; + } + break; + default: + change(state, toNormal); + } + } else if (stream.match(footnoteReferencePattern)) { + change(state, toNormal); + token = "quote"; + } else if (stream.match(citationReferencePattern)) { + change(state, toNormal); + token = "quote"; + } else if (stream.match(linkReferencePattern)) { + change(state, toNormal); + if (!stream.peek() || stream.peek().match(/^\W$/)) { + token = "link"; + } + } else if (phase(state) === quotedLinkReferencePattern || + stream.match(quotedLinkReferencePattern, false)) { + switch (stage(state)) { + case 0: + if (!stream.peek() || + stream.peek().match(/^\W$/)) { + change( + state, + toNormal, + context(quotedLinkReferencePattern, 1) + ); + } else { + stream.match(quotedLinkReferencePattern); + } + break; + case 1: + change( + state, + toNormal, + context(quotedLinkReferencePattern, 2) + ); + stream.match(/^`/); + token = "link"; + break; + case 2: + change( + state, + toNormal, + context(quotedLinkReferencePattern, 3) + ); + stream.match(textWithoutBacktickPattern); + break; + case 3: + change( + state, + toNormal, + context(quotedLinkReferencePattern, 4) + ); + stream.match(/^`_/); + token = "link"; + break; + default: + change(state, toNormal); + } + } else if (stream.match(verbatimPattern)) { + change(state, toVerbatim); + } else if (stream.next()) { + change(state, toNormal); + } + + return token; + } + + function toExplicit(stream, state) { + let token = null; + + if (phase(state) === substitutionPattern || + stream.match(substitutionPattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toExplicit, + context(substitutionPattern, 1) + ); + stream.match(substitutionTextPattern); + token = "variable-2"; + break; + case 1: + change( + state, + toExplicit, + context(substitutionPattern, 2) + ); + stream.match(substitutionSeparatorPattern); + break; + case 2: + change( + state, + toExplicit, + context(substitutionPattern, 3) + ); + stream.match(substitutionNamePattern); + token = "keyword"; + break; + case 3: + change( + state, + toExplicit, + context(substitutionPattern, 4) + ); + stream.match(substitutionTailPattern); + token = "meta"; + break; + default: + change(state, toNormal); + } + } else if (phase(state) === directivePattern || + stream.match(directivePattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toExplicit, + context(directivePattern, 1) + ); + stream.match(directiveNamePattern); + token = "keyword"; + + if (stream.current().match(/^(?:math|latex)/)) { + state.tmpStex = true; + } else if (stream.current().match(/^python/)) { + state.tmpPy = true; + } + break; + case 1: + change( + state, + toExplicit, + context(directivePattern, 2) + ); + stream.match(directiveTailPattern); + token = "meta"; + + if (stream.match(/^latex\s*$/) || state.tmpStex) { + state.tmpStex = undefined; + change(state, toMode, { + mode: stexMode, + local: CodeMirror.startState(stexMode) + }); + } + break; + case 2: + change( + state, + toExplicit, + context(directivePattern, 3) + ); + if (stream.match(/^python\s*$/) || state.tmpPy) { + state.tmpPy = undefined; + change(state, toMode, { + mode: pythonMode, + local: CodeMirror.startState(pythonMode) + }); + } + break; + default: + change(state, toNormal); + } + } else if (phase(state) === linkPattern || + stream.match(linkPattern, false)) { + switch (stage(state)) { + case 0: + change( + state, + toExplicit, + context(linkPattern, 1) + ); + stream.match(linkHeadPattern); + stream.match(linkNamePattern); + token = "link"; + break; + case 1: + change( + state, + toExplicit, + context(linkPattern, 2) + ); + stream.match(linkTailPattern); + token = "meta"; + break; + default: + change(state, toNormal); + } + } else if (stream.match(footnotePattern)) { + change(state, toNormal); + token = "quote"; + } else if (stream.match(citationPattern)) { + change(state, toNormal); + token = "quote"; + } else { + stream.eatSpace(); + if (stream.eol()) { + change(state, toNormal); + } else { + stream.skipToEnd(); + change(state, toComment); + token = "comment"; + } + } + + return token; + } + + function toComment(stream, state) { + return asBlock(stream, state, "comment"); + } + + function toVerbatim(stream, state) { + return asBlock(stream, state, "meta"); + } + + function asBlock(stream, state, token) { + if (stream.eol() || stream.eatSpace()) { + stream.skipToEnd(); + return token; + } + change(state, toNormal); + return null; + } + + function toMode(stream, state) { + if (state.ctx.mode && state.ctx.local) { + if (stream.sol()) { + if (!stream.eatSpace()) { + change(state, toNormal); + } + return null; + } + + return state.ctx.mode.token(stream, state.ctx.local); + } + + change(state, toNormal); + return null; + } + + return { + startState: function () { + return { + tok: toNormal, + ctx: context(undefined, 0) + }; + }, + + copyState: function (state) { + let ctx = state.ctx; + let tmp = state.tmp; + if (ctx.local) { + ctx = { + mode: ctx.mode, + local: CodeMirror.copyState(ctx.mode, ctx.local) + }; + } + if (tmp) { + tmp = { + mode: tmp.mode, + local: CodeMirror.copyState(tmp.mode, tmp.local) + }; + } + return { + tok: state.tok, + ctx: ctx, + tmp: tmp + }; + }, + + innerMode: function (state) { + if (state.tmp) { + return { + state: state.tmp.local, + mode: state.tmp.mode + }; + } + if (state.ctx.mode) { + return { + state: state.ctx.local, + mode: state.ctx.mode + }; + } + return null; + }, + + token: function (stream, state) { + return state.tok(stream, state); + } + }; + }, "python", "stex"); + + CodeMirror.defineMIME("text/x-rst", "rst"); + } + + function installSlim(CodeMirror) { + CodeMirror.defineMode("slim", function (config) { + const htmlMode = CodeMirror.getMode(config, { + name: "htmlmixed" + }); + const rubyMode = CodeMirror.getMode(config, "ruby"); + const modes = { + html: htmlMode, + ruby: rubyMode + }; + const embedded = { + ruby: "ruby", + javascript: "javascript", + css: "text/css", + sass: "text/x-sass", + scss: "text/x-scss", + less: "text/x-less", + styl: "text/x-styl", + coffee: "coffeescript", + asciidoc: "text/x-asciidoc", + markdown: "text/x-markdown", + textile: "text/x-textile", + creole: "text/x-creole", + wiki: "text/x-wiki", + mediawiki: "text/x-mediawiki", + rdoc: "text/x-rdoc", + builder: "text/x-builder", + nokogiri: "text/x-nokogiri", + erb: "application/x-erb" + }; + const embeddedRegexp = (function (map) { + const names = []; + for (const key in map) { + names.push(key); + } + return new RegExp("^(" + names.join("|") + "):"); + }(embedded)); + + const styleMap = { + commentLine: "comment", + slimSwitch: "operator special", + slimTag: "tag", + slimId: "attribute def", + slimClass: "attribute qualifier", + slimAttribute: "attribute", + slimSubmode: "keyword special", + closeAttributeTag: null, + slimDoctype: null, + lineContinuation: null + }; + const closing = { + "{": "}", + "[": "]", + "(": ")" + }; + + const nameStartChar = + "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D" + + "\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF" + + "\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; + const nameChar = + nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; + const nameRegexp = new RegExp( + "^[" + ":" + nameStartChar + "]" + + "(?::[" + nameChar + "]|[" + nameChar + "]*)" + ); + const attributeNameRegexp = new RegExp( + "^[" + ":" + nameStartChar + "]" + + "[:\\." + nameChar + "]*(?=\\s*=)" + ); + const wrappedAttributeNameRegexp = new RegExp( + "^[" + ":" + nameStartChar + "]" + + "[:\\." + nameChar + "]*" + ); + const classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; + const classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; + + function backup(pos, tokenize, style) { + const restore = function (stream, state) { + state.tokenize = tokenize; + if (stream.pos < pos) { + stream.pos = pos; + return style; + } + return state.tokenize(stream, state); + }; + return function (stream, state) { + state.tokenize = restore; + return tokenize(stream, state); + }; + } + + function maybeBackup(stream, state, pattern, offset, style) { + const current = stream.current(); + const index = current.search(pattern); + if (index > -1) { + state.tokenize = backup( + stream.pos, + state.tokenize, + style + ); + stream.backUp(current.length - index - offset); + } + return style; + } + + function continueLine(state, column) { + state.stack = { + parent: state.stack, + style: "continuation", + indented: column, + tokenize: state.line + }; + state.line = state.tokenize; + } + + function finishContinue(state) { + if (state.line === state.tokenize) { + state.line = state.stack.tokenize; + state.stack = state.stack.parent; + } + } + + function lineContinuable(column, tokenize) { + return function (stream, state) { + finishContinue(state); + if (stream.match(/^\\$/)) { + continueLine(state, column); + return "lineContinuation"; + } + const style = tokenize(stream, state); + if (stream.eol() && + stream.current().match( + /(?:^|[^\\])(?:\\\\)*\\$/ + )) { + stream.backUp(1); + } + return style; + }; + } + + function commaContinuable(column, tokenize) { + return function (stream, state) { + finishContinue(state); + const style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/,$/)) { + continueLine(state, column); + } + return style; + }; + } + + function rubyInQuote(endQuote, tokenize) { + return function (stream, state) { + const character = stream.peek(); + if (character === endQuote && + state.rubyState.tokenize.length === 1) { + stream.next(); + state.tokenize = tokenize; + return "closeAttributeTag"; + } + return ruby(stream, state); + }; + } + + function startRubySplat(tokenize) { + let rubyState; + const runSplat = function (stream, state) { + if (state.rubyState.tokenize.length === 1 && + !state.rubyState.context.prev) { + stream.backUp(1); + if (stream.eatSpace()) { + state.rubyState = rubyState; + state.tokenize = tokenize; + return tokenize(stream, state); + } + stream.next(); + } + return ruby(stream, state); + }; + return function (stream, state) { + rubyState = state.rubyState; + state.rubyState = CodeMirror.startState(rubyMode); + state.tokenize = runSplat; + return ruby(stream, state); + }; + } + + function ruby(stream, state) { + return rubyMode.token(stream, state.rubyState); + } + + function htmlLine(stream, state) { + if (stream.match(/^\\$/)) { + return "lineContinuation"; + } + return html(stream, state); + } + + function html(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + return maybeBackup( + stream, + state, + /[^\\]#\{/, + 1, + htmlMode.token(stream, state.htmlState) + ); + } + + function startHtmlLine(lastTokenize) { + return function (stream, state) { + const style = htmlLine(stream, state); + if (stream.eol()) { + state.tokenize = lastTokenize; + } + return style; + }; + } + + function startHtmlMode(stream, state, offset) { + state.stack = { + parent: state.stack, + style: "html", + indented: stream.column() + offset, + tokenize: state.line + }; + state.line = state.tokenize = html; + return null; + } + + function comment(stream, state) { + stream.skipToEnd(); + return state.stack.style; + } + + function commentMode(stream, state) { + state.stack = { + parent: state.stack, + style: "comment", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = comment; + return comment(stream, state); + } + + function attributeWrapper(stream, state) { + if (stream.eat(state.stack.endQuote)) { + state.line = state.stack.line; + state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + return null; + } + if (stream.match(wrappedAttributeNameRegexp)) { + state.tokenize = attributeWrapperAssign; + return "slimAttribute"; + } + stream.next(); + return null; + } + + function attributeWrapperAssign(stream, state) { + if (stream.match(/^==?/)) { + state.tokenize = attributeWrapperValue; + return null; + } + return attributeWrapper(stream, state); + } + + function attributeWrapperValue(stream, state) { + const character = stream.peek(); + if (character === "\"" || character === "'") { + state.tokenize = readQuoted( + character, + "string", + true, + false, + attributeWrapper + ); + stream.next(); + return state.tokenize(stream, state); + } + if (character === "[") { + return startRubySplat(attributeWrapper)(stream, state); + } + if (stream.match(/^(true|false|nil)\b/)) { + state.tokenize = attributeWrapper; + return "keyword"; + } + return startRubySplat(attributeWrapper)(stream, state); + } + + function startAttributeWrapperMode( + state, + endQuote, + tokenize + ) { + state.stack = { + parent: state.stack, + style: "wrapper", + indented: state.indented + 1, + tokenize: tokenize, + line: state.line, + endQuote: endQuote + }; + state.line = state.tokenize = attributeWrapper; + return null; + } + + function sub(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + const subStream = new CodeMirror.StringStream( + stream.string.slice(state.stack.indented), + stream.tabSize + ); + subStream.pos = stream.pos - state.stack.indented; + subStream.start = stream.start - state.stack.indented; + subStream.lastColumnPos = + stream.lastColumnPos - state.stack.indented; + subStream.lastColumnValue = + stream.lastColumnValue - state.stack.indented; + const style = state.subMode.token( + subStream, + state.subState + ); + stream.pos = subStream.pos + state.stack.indented; + return style; + } + + function firstSub(stream, state) { + state.stack.indented = stream.column(); + state.line = state.tokenize = sub; + return state.tokenize(stream, state); + } + + function createMode(modeName) { + const query = embedded[modeName]; + const spec = CodeMirror.mimeModes[query]; + if (spec) { + return CodeMirror.getMode(config, spec); + } + const factory = CodeMirror.modes[query]; + if (factory) { + return factory(config, { + name: query + }); + } + return CodeMirror.getMode(config, "null"); + } + + function getMode(modeName) { + if (!modes.hasOwnProperty(modeName)) { + modes[modeName] = createMode(modeName); + } + return modes[modeName]; + } + + function startSubMode(modeName, state) { + const subMode = getMode(modeName); + const subState = CodeMirror.startState(subMode); + + state.subMode = subMode; + state.subState = subState; + + state.stack = { + parent: state.stack, + style: "sub", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = state.tokenize = firstSub; + return "slimSubmode"; + } + + function doctypeLine(stream, state) { + stream.skipToEnd(); + return "slimDoctype"; + } + + function startLine(stream, state) { + const character = stream.peek(); + if (character === "<") { + state.tokenize = startHtmlLine(state.tokenize); + return state.tokenize(stream, state); + } + if (stream.match(/^[|']/)) { + return startHtmlMode(stream, state, 1); + } + if (stream.match(/^\/(!|\[\w+])?/)) { + return commentMode(stream, state); + } + if (stream.match(/^(-|==?[<>]?)/)) { + state.tokenize = lineContinuable( + stream.column(), + commaContinuable(stream.column(), ruby) + ); + return "slimSwitch"; + } + if (stream.match(/^doctype\b/)) { + state.tokenize = doctypeLine; + return "keyword"; + } + + const match = stream.match(embeddedRegexp); + if (match) { + return startSubMode(match[1], state); + } + + return slimTag(stream, state); + } + + function slim(stream, state) { + if (state.startOfLine) { + return startLine(stream, state); + } + return slimTag(stream, state); + } + + function slimTag(stream, state) { + if (stream.eat("*")) { + state.tokenize = startRubySplat(slimTagExtras); + return null; + } + if (stream.match(nameRegexp)) { + state.tokenize = slimTagExtras; + return "slimTag"; + } + return slimClass(stream, state); + } + + function slimTagExtras(stream, state) { + if (stream.match(/^(<>?|> state.indented && + state.last !== "slimSubmode") { + state.line = state.tokenize = + state.stack.tokenize; + state.stack = state.stack.parent; + state.subMode = null; + state.subState = null; + } + } + if (stream.eatSpace()) { + return null; + } + const style = state.tokenize(stream, state); + state.startOfLine = false; + if (style) { + state.last = style; + } + return styleMap.hasOwnProperty(style) ? + styleMap[style] : style; + }, + + blankLine: function (state) { + if (state.subMode && state.subMode.blankLine) { + return state.subMode.blankLine(state.subState); + } + return undefined; + }, + + innerMode: function (state) { + if (state.subMode) { + return { + state: state.subState, + mode: state.subMode + }; + } + return { + state: state, + mode: mode + }; + } + }; + return mode; + }, "htmlmixed", "ruby"); + + CodeMirror.defineMIME("text/x-slim", "slim"); + CodeMirror.defineMIME("application/x-slim", "slim"); + } + + function install(CodeMirror) { + if (!CodeMirror || installedTargets.has(CodeMirror)) { + return; + } + installedTargets.add(CodeMirror); + + installRst(CodeMirror); + installSlim(CodeMirror); + } + + exports.install = install; +}); diff --git a/src/editor/CodeMirrorLegacyText.js b/src/editor/CodeMirrorLegacyText.js new file mode 100644 index 0000000000..eb059ab19e --- /dev/null +++ b/src/editor/CodeMirrorLegacyText.js @@ -0,0 +1,187 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global define*/ + +/** + * Transparent wrapper around the RequireJS text plugin. + * + * Historical extensions sometimes request CodeMirror 5 styles through the + * RequireJS text plugin. Phoenix no longer ships those assets, so styles whose + * behavior is supplied by the CM6 surface resolve to an explicit compatibility + * comment without issuing a network request. Stock legacy themes are compiled + * into Phoenix's CM6 stylesheet, so their historical text imports resolve + * without loading a CodeMirror 5 asset. + */ +define(["text-base"], function (BaseText) { + + const LEGACY_RESOURCE_PATTERN = + /^thirdparty\/CodeMirror(?:2)?(?:\/(.*))?$/; + const SUPPORTED_STYLES = new Set([ + "addon/dialog/dialog.css", + "addon/display/fullscreen.css", + "addon/fold/foldgutter.css", + "addon/hint/show-hint.css", + "addon/lint/lint.css", + "addon/merge/merge.css", + "addon/scroll/simplescrollbars.css", + "addon/search/match-highlighter.css", + "addon/search/matchesonscrollbar.css", + "addon/tern/tern.css", + "mode/tiddlywiki/tiddlywiki.css", + "mode/tiki/tiki.css", + "lib/codemirror.css" + ]); + const LEGACY_THEME_NAMES = Object.freeze([ + "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" + ]); + const LEGACY_THEME_NAMES_SET = new Set(LEGACY_THEME_NAMES); + const LEGACY_THEME_STYLE_PATTERN = /^theme\/([^/]+)\.css$/; + const buildMap = {}; + + function getLegacyResourcePath(resourceName) { + if (typeof resourceName !== "string") { + return null; + } + const parsed = BaseText.parseName(resourceName); + const normalizedName = ( + parsed.moduleName + (parsed.ext ? `.${parsed.ext}` : "") + ).replace(/[?#].*$/, ""); + const match = LEGACY_RESOURCE_PATTERN.exec(normalizedName); + return match ? match[1] || "" : null; + } + + function createCompatibilityError(resourceName) { + const error = new Error( + `Unsupported CodeMirror 5 resource "${resourceName}". ` + + "Phoenix uses CodeMirror 6 and does not ship or load CM5 assets." + ); + error.code = "PHOENIX_UNSUPPORTED_CODEMIRROR5_RESOURCE"; + return error; + } + + function getLegacyThemeName(resourcePath) { + const match = LEGACY_THEME_STYLE_PATTERN.exec(resourcePath); + return match && LEGACY_THEME_NAMES_SET.has(match[1]) ? + match[1] : null; + } + + function getCompatibilityContent(resourceName) { + const resourcePath = getLegacyResourcePath(resourceName); + if (resourcePath === null) { + return null; + } + const legacyThemeName = getLegacyThemeName(resourcePath); + if (!SUPPORTED_STYLES.has(resourcePath) && + !legacyThemeName) { + throw createCompatibilityError(resourceName); + } + if (legacyThemeName) { + return "/* Phoenix CodeMirror 6 compatibility stylesheet; " + + `the ${legacyThemeName} theme is bundled by Phoenix. */\n`; + } + return "/* Phoenix CodeMirror 6 compatibility stylesheet; " + + "the equivalent styles are supplied by the application. */\n"; + } + + function load(name, req, onLoad, config) { + let content; + try { + content = getCompatibilityContent(name); + } catch (error) { + if (onLoad.error) { + onLoad.error(error); + return; + } + throw error; + } + + if (content === null) { + BaseText.load(name, req, onLoad, config); + return; + } + if (config && config.isBuild) { + buildMap[name] = content; + } + onLoad(content); + } + + function write(pluginName, moduleName, writeModule, config) { + if (!Object.prototype.hasOwnProperty.call(buildMap, moduleName)) { + BaseText.write(pluginName, moduleName, writeModule, config); + return; + } + const content = BaseText.jsEscape(buildMap[moduleName]); + const moduleDefinition = "de" + + `fine(function () { return '${content}';});\n`; + writeModule.asModule( + `${pluginName}!${moduleName}`, + moduleDefinition + ); + } + + function writeFile(pluginName, moduleName, req, writeModule, config) { + let content; + try { + content = getCompatibilityContent(moduleName); + } catch (error) { + throw error; + } + if (content === null) { + BaseText.writeFile(pluginName, moduleName, req, writeModule, config); + return; + } + + const parsed = BaseText.parseName(moduleName); + const extension = parsed.ext ? `.${parsed.ext}` : ""; + const nonStripName = parsed.moduleName + extension; + const fileName = req.toUrl(nonStripName) + ".js"; + buildMap[nonStripName] = content; + const textWrite = function (contents) { + return writeModule(fileName, contents); + }; + textWrite.asModule = function (name, contents) { + return writeModule.asModule(name, fileName, contents); + }; + write(pluginName, nonStripName, textWrite, config); + } + + return Object.assign(Object.create(BaseText), { + getCompatibilityContent: getCompatibilityContent, + getLegacyResourcePath: getLegacyResourcePath, + legacyThemeNames: LEGACY_THEME_NAMES, + load: load, + write: write, + writeFile: writeFile + }); +}); diff --git a/src/editor/CodeMirrorSublimeCompat.js b/src/editor/CodeMirrorSublimeCompat.js new file mode 100644 index 0000000000..37300dcec2 --- /dev/null +++ b/src/editor/CodeMirrorSublimeCompat.js @@ -0,0 +1,1244 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5 Sublime compatibility implementation. + * + * The command behavior and canonical key bindings are based on the CodeMirror + * 5 Sublime keymap. CodeMirror is distributed under the following MIT license: + * See thirdparty/licences/codemirror5-derived.markdown. + * + * Copyright (C) 2017 by Marijn Haverbeke and others + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +/** + * CM5 Sublime-keymap compatibility implemented against the CM6-backed editor + * facade. This module intentionally takes CodeMirror as an install argument so + * loading it cannot introduce a CodeMirrorCompat/CodeMirror6Adapter cycle. + */ +define(function (require, exports, module) { + + const installedFacades = new WeakSet(); + + function _position(CodeMirror, line, character) { + return CodeMirror.Pos(line, character); + } + + function _samePosition(CodeMirror, left, right) { + return CodeMirror.cmpPos(left, right) === 0; + } + + function _wordAt(CodeMirror, editor, position) { + const line = editor.getLine(position.line) || ""; + let start = Math.max(0, Math.min(position.ch, line.length)); + let end = start; + + while (start > 0 && CodeMirror.isWordChar(line.charAt(start - 1))) { + start--; + } + while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) { + end++; + } + + return { + from: _position(CodeMirror, position.line, start), + to: _position(CodeMirror, position.line, end), + word: line.slice(start, end) + }; + } + + function _escapeRegExp(text) { + return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + + function _rangeIsSelected(CodeMirror, ranges, from, to) { + return ranges.some(function (range) { + return _samePosition(CodeMirror, range.from(), from) && + _samePosition(CodeMirror, range.to(), to); + }); + } + + function _subwordCategory(CodeMirror, character) { + if (character === "_" || !CodeMirror.isWordChar(character)) { + return "separator"; + } + return character.toUpperCase() === character ? "upper" : "lower"; + } + + function _findSubword(CodeMirror, editor, start, direction) { + if (direction < 0 && start.ch === 0) { + return editor.clipPos(_position(CodeMirror, start.line - 1)); + } + + const text = editor.getLine(start.line) || ""; + if (direction > 0 && start.ch >= text.length) { + return editor.clipPos(_position(CodeMirror, start.line + 1, 0)); + } + + const edge = direction < 0 ? 0 : text.length; + let boundary = start.ch; + let category = null; + let inWord = false; + + for (let characterIndex = start.ch; + characterIndex !== edge; + characterIndex += direction) { + const character = text.charAt( + direction < 0 ? characterIndex - 1 : characterIndex + ); + const nextCategory = _subwordCategory(CodeMirror, character); + + if (!inWord) { + if (nextCategory === "separator") { + boundary = characterIndex + direction; + continue; + } + inWord = true; + category = nextCategory; + continue; + } + + if (category === nextCategory) { + continue; + } + + if (category === "lower" && + nextCategory === "upper" && + direction < 0) { + characterIndex--; + } else if (category === "upper" && + nextCategory === "lower" && + direction > 0) { + if (characterIndex === boundary + 1) { + category = "lower"; + continue; + } + characterIndex--; + } + boundary = characterIndex; + break; + } + + if (inWord && boundary === start.ch) { + boundary = edge; + } + return _position(CodeMirror, start.line, boundary); + } + + function _moveSubword(CodeMirror, editor, direction) { + const extending = Boolean( + editor.state && editor.state.shift || + typeof editor.getExtending === "function" && editor.getExtending() + ); + editor.extendSelectionsBy(function (range) { + if (!extending && !range.empty()) { + return direction < 0 ? range.from() : range.to(); + } + return _findSubword(CodeMirror, editor, range.head, direction); + }, { + origin: "+move" + }); + } + + function _scrollLine(editor, direction) { + const scrollInfo = editor.getScrollInfo(); + if (!editor.somethingSelected()) { + const boundaryLine = direction < 0 ? + editor.lineAtHeight( + scrollInfo.top + scrollInfo.clientHeight, + "local" + ) : + editor.lineAtHeight(scrollInfo.top, "local") + 1; + const cursorLine = editor.getCursor().line; + if (direction < 0 ? + cursorLine >= boundaryLine : + cursorLine <= boundaryLine) { + editor.execCommand(direction < 0 ? "goLineUp" : "goLineDown"); + } + } + editor.scrollTo( + null, + scrollInfo.top + direction * editor.defaultTextHeight() + ); + } + + function _insertLine(CodeMirror, editor, above) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const selectedLines = []; + editor.listSelections().forEach(function (range) { + if (selectedLines.indexOf(range.head.line) === -1) { + selectedLines.push(range.head.line); + } + }); + selectedLines.sort(function (left, right) { + return left - right; + }); + + const cursors = []; + editor.operation(function () { + selectedLines.forEach(function (originalLine, insertionIndex) { + const line = originalLine + insertionIndex; + let insertionPosition; + let cursorPosition; + + if (above) { + insertionPosition = _position(CodeMirror, line, 0); + cursorPosition = insertionPosition; + editor.replaceRange( + "\n", + insertionPosition, + insertionPosition, + "+insertLine" + ); + } else if (line < editor.lastLine()) { + insertionPosition = _position(CodeMirror, line + 1, 0); + cursorPosition = insertionPosition; + editor.replaceRange( + "\n", + insertionPosition, + insertionPosition, + "+insertLine" + ); + } else { + insertionPosition = _position( + CodeMirror, + line, + (editor.getLine(line) || "").length + ); + cursorPosition = _position(CodeMirror, line + 1, 0); + editor.replaceRange( + "\n", + insertionPosition, + insertionPosition, + "+insertLine" + ); + } + cursors.push({ + anchor: cursorPosition, + head: cursorPosition + }); + }); + + editor.setSelections(cursors, cursors.length - 1, { + scroll: false + }); + editor.indentSelection("smart"); + }); + } + + function _selectionLineSpan(range) { + const from = range.from(); + const to = range.to(); + let end = to.line; + if (!range.empty() && to.ch === 0 && end > from.line) { + end--; + } + return { + start: from.line, + end: end + }; + } + + function _lineBlocks(ranges) { + const blocks = ranges.map(_selectionLineSpan).sort(function (left, right) { + return left.start - right.start || left.end - right.end; + }); + const merged = []; + + blocks.forEach(function (block) { + const previous = merged[merged.length - 1]; + if (previous && block.start <= previous.end + 1) { + previous.end = Math.max(previous.end, block.end); + } else { + merged.push({ + start: block.start, + end: block.end + }); + } + }); + return merged; + } + + function _replaceWholeLines(CodeMirror, editor, start, end, lines, origin) { + const lastLine = editor.lastLine(); + const reachesDocumentEnd = end >= lastLine; + const to = reachesDocumentEnd ? + _position( + CodeMirror, + lastLine, + (editor.getLine(lastLine) || "").length + ) : + _position(CodeMirror, end + 1, 0); + const replacement = lines.join("\n") + (reachesDocumentEnd ? "" : "\n"); + editor.replaceRange( + replacement, + _position(CodeMirror, start, 0), + to, + origin + ); + } + + function _swapLines(CodeMirror, editor, direction) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const selections = editor.listSelections(); + const blocks = _lineBlocks(selections); + const movableBlocks = blocks.filter(function (block) { + return direction < 0 ? + block.start > editor.firstLine() : + block.end < editor.lastLine(); + }); + const orderedBlocks = direction < 0 ? + movableBlocks : + movableBlocks.slice().reverse(); + + editor.operation(function () { + orderedBlocks.forEach(function (block) { + const neighborLine = direction < 0 ? + block.start - 1 : + block.end + 1; + const blockLines = []; + for (let line = block.start; line <= block.end; line++) { + blockLines.push(editor.getLine(line) || ""); + } + const replacement = direction < 0 ? + blockLines.concat(editor.getLine(neighborLine) || "") : + [editor.getLine(neighborLine) || ""].concat(blockLines); + _replaceWholeLines( + CodeMirror, + editor, + Math.min(neighborLine, block.start), + Math.max(neighborLine, block.end), + replacement, + "+swapLine" + ); + }); + + const movedSelections = selections.map(function (range) { + const span = _selectionLineSpan(range); + const block = movableBlocks.find(function (candidate) { + return span.start >= candidate.start && + span.end <= candidate.end; + }); + const offset = block ? direction : 0; + return { + anchor: _position( + CodeMirror, + range.anchor.line + offset, + range.anchor.ch + ), + head: _position( + CodeMirror, + range.head.line + offset, + range.head.ch + ) + }; + }); + editor.setSelections(movedSelections, undefined, { + scroll: false + }); + editor.scrollIntoView(); + }); + } + + function _selectBetweenBrackets(CodeMirror, editor) { + const matchingBracket = { + "(": ")", + "[": "]", + "{": "}", + "<": ">" + }; + const nextSelections = []; + + for (const range of editor.listSelections()) { + let opening = editor.scanForBracket(range.head, -1); + if (!opening) { + return false; + } + + let scanPosition = range.head; + while (opening) { + const closing = editor.scanForBracket(scanPosition, 1); + if (!closing) { + return false; + } + if (closing.ch === matchingBracket[opening.ch]) { + const start = _position( + CodeMirror, + opening.pos.line, + opening.pos.ch + 1 + ); + if (_samePosition(CodeMirror, start, range.from()) && + _samePosition(CodeMirror, closing.pos, range.to())) { + opening = editor.scanForBracket(opening.pos, -1); + scanPosition = closing.pos; + continue; + } + nextSelections.push({ + anchor: start, + head: closing.pos + }); + break; + } + scanPosition = _position( + CodeMirror, + closing.pos.line, + closing.pos.ch + 1 + ); + } + if (!opening) { + return false; + } + } + + editor.setSelections(nextSelections); + return true; + } + + function _joinLines(CodeMirror, editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const blocks = _lineBlocks(editor.listSelections()).reverse(); + editor.operation(function () { + blocks.forEach(function (block) { + const lastJoinLine = Math.min(block.end, editor.lastLine() - 1); + for (let line = lastJoinLine; line >= block.start; line--) { + const nextLine = editor.getLine(line + 1) || ""; + const indentation = (nextLine.match(/^\s*/) || [""])[0].length; + editor.replaceRange( + " ", + _position( + CodeMirror, + line, + (editor.getLine(line) || "").length + ), + _position(CodeMirror, line + 1, indentation), + "+joinLines" + ); + } + }); + }); + } + + function _duplicateSelections(CodeMirror, editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const selections = editor.listSelections().slice().sort(function (left, right) { + return CodeMirror.cmpPos(right.from(), left.from()); + }); + editor.operation(function () { + selections.forEach(function (range) { + if (range.empty()) { + editor.replaceRange( + (editor.getLine(range.head.line) || "") + "\n", + _position(CodeMirror, range.head.line, 0), + undefined, + "+duplicateLine" + ); + } else { + editor.replaceRange( + editor.getRange(range.from(), range.to()), + range.from(), + undefined, + "+duplicateLine" + ); + } + }); + editor.scrollIntoView(); + }); + } + + function _sortLines(CodeMirror, editor, caseSensitive, direction) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const selections = editor.listSelections(); + const selectedBlocks = _lineBlocks(selections.filter(function (range) { + return !range.empty(); + })); + const blocks = selectedBlocks.length ? selectedBlocks : [{ + start: editor.firstLine(), + end: editor.lastLine() + }]; + + editor.operation(function () { + blocks.slice().reverse().forEach(function (block) { + const start = _position(CodeMirror, block.start, 0); + const end = _position(CodeMirror, block.end); + const lines = editor.getRange(start, end, false); + lines.sort(function (left, right) { + let comparableLeft = left; + let comparableRight = right; + if (!caseSensitive) { + comparableLeft = comparableLeft.toUpperCase(); + comparableRight = comparableRight.toUpperCase(); + } + if (comparableLeft < comparableRight) { + return -direction; + } + if (comparableLeft > comparableRight) { + return direction; + } + return 0; + }); + editor.replaceRange( + lines.join("\n"), + start, + end, + "+sortLines" + ); + }); + + if (selectedBlocks.length) { + editor.setSelections(selectedBlocks.map(function (block) { + return { + anchor: _position(CodeMirror, block.start, 0), + head: editor.clipPos( + _position(CodeMirror, block.end + 1, 0) + ) + }; + }), 0); + } + }); + } + + function _bookmarkRanges(editor) { + const state = editor.state; + const marks = state.sublimeBookmarks || []; + state.sublimeBookmarks = marks.filter(function (mark) { + return Boolean(mark && mark.find()); + }); + return state.sublimeBookmarks; + } + + function _modifyWordOrSelection(CodeMirror, editor, transform) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const changes = editor.listSelections().map(function (range) { + const target = range.empty() ? + _wordAt(CodeMirror, editor, range.head) : + { + from: range.from(), + to: range.to(), + word: editor.getRange(range.from(), range.to()) + }; + return { + from: target.from, + to: target.to, + replacement: transform(target.word) + }; + }).filter(function (change, index, allChanges) { + return change.from && change.to && + allChanges.findIndex(function (candidate) { + return _samePosition(CodeMirror, candidate.from, change.from) && + _samePosition(CodeMirror, candidate.to, change.to); + }) === index; + }).sort(function (left, right) { + return CodeMirror.cmpPos(right.from, left.from); + }); + + editor.operation(function () { + changes.forEach(function (change) { + editor.replaceRange( + change.replacement, + change.from, + change.to, + "case" + ); + }); + }); + } + + function _findTarget(CodeMirror, editor) { + let from = editor.getCursor("from"); + let to = editor.getCursor("to"); + let word; + if (_samePosition(CodeMirror, from, to)) { + word = _wordAt(CodeMirror, editor, from); + if (!word.word) { + return null; + } + from = word.from; + to = word.to; + } + return { + from: from, + to: to, + query: editor.getRange(from, to), + word: word + }; + } + + function _findAndSelect(CodeMirror, editor, forward) { + const target = _findTarget(CodeMirror, editor); + if (!target) { + return; + } + + let cursor = editor.getSearchCursor( + target.query, + forward ? target.to : target.from + ); + let found = forward ? cursor.findNext() : cursor.findPrevious(); + if (!found) { + cursor = editor.getSearchCursor( + target.query, + forward ? + _position(CodeMirror, editor.firstLine(), 0) : + editor.clipPos(_position(CodeMirror, editor.lastLine())) + ); + found = forward ? cursor.findNext() : cursor.findPrevious(); + } + if (found) { + editor.setSelection(cursor.from(), cursor.to()); + } else if (target.word) { + editor.setSelection(target.from, target.to); + } + } + + function _defineCommands(CodeMirror) { + const commands = CodeMirror.commands; + + commands.goSubwordLeft = function (editor) { + return _moveSubword(CodeMirror, editor, -1); + }; + commands.goSubwordRight = function (editor) { + return _moveSubword(CodeMirror, editor, 1); + }; + commands.scrollLineUp = function (editor) { + return _scrollLine(editor, -1); + }; + commands.scrollLineDown = function (editor) { + return _scrollLine(editor, 1); + }; + commands.splitSelectionByLine = function (editor) { + return editor.splitSelectionByLine(); + }; + commands.singleSelectionTop = function (editor) { + const selection = editor.listSelections()[0]; + if (selection) { + editor.setSelection(selection.anchor, selection.head, { + scroll: false + }); + } + }; + commands.selectLine = function (editor) { + editor.setSelections(editor.listSelections().map(function (range) { + return { + anchor: _position(CodeMirror, range.from().line, 0), + head: editor.clipPos( + _position(CodeMirror, range.to().line + 1, 0) + ) + }; + })); + }; + commands.insertLineAfter = function (editor) { + return _insertLine(CodeMirror, editor, false); + }; + commands.insertLineBefore = function (editor) { + return _insertLine(CodeMirror, editor, true); + }; + commands.selectNextOccurrence = function (editor) { + let from = editor.getCursor("from"); + let to = editor.getCursor("to"); + let query = editor.getRange(from, to); + let fullWord = editor.state.sublimeFindFullWord === query && Boolean(query); + + if (_samePosition(CodeMirror, from, to)) { + const word = _wordAt(CodeMirror, editor, from); + if (!word.word) { + return; + } + editor.setSelection(word.from, word.to); + editor.state.sublimeFindFullWord = word.word; + return; + } + + query = editor.getRange(from, to); + fullWord = fullWord && Boolean(query); + const searchQuery = fullWord ? + new RegExp("\\b" + _escapeRegExp(query) + "\\b") : + query; + let cursor = editor.getSearchCursor(searchQuery, to); + let found = cursor.findNext(); + if (!found) { + cursor = editor.getSearchCursor( + searchQuery, + _position(CodeMirror, editor.firstLine(), 0) + ); + found = cursor.findNext(); + } + if (!found || _rangeIsSelected( + CodeMirror, + editor.listSelections(), + cursor.from(), + cursor.to() + )) { + return; + } + editor.addSelection(cursor.from(), cursor.to()); + editor.state.sublimeFindFullWord = fullWord ? query : null; + }; + commands.skipAndSelectNextOccurrence = function (editor) { + const previous = { + anchor: editor.getCursor("anchor"), + head: editor.getCursor("head") + }; + if (_samePosition(CodeMirror, previous.anchor, previous.head)) { + commands.selectNextOccurrence(editor); + return; + } + commands.selectNextOccurrence(editor); + const remaining = editor.listSelections().filter(function (range) { + return !( + _samePosition(CodeMirror, range.anchor, previous.anchor) && + _samePosition(CodeMirror, range.head, previous.head) + ); + }); + if (remaining.length) { + editor.setSelections(remaining); + } + }; + + function addCursorToLine(editor, direction) { + const newSelections = []; + editor.listSelections().forEach(function (range) { + const anchor = editor.findPosV( + range.anchor, + direction, + "line", + range.anchor.goalColumn + ); + const head = editor.findPosV( + range.head, + direction, + "line", + range.head.goalColumn + ); + newSelections.push(range); + newSelections.push({ + anchor: anchor, + head: head + }); + }); + editor.setSelections(newSelections); + } + + commands.addCursorToPrevLine = function (editor) { + return addCursorToLine(editor, -1); + }; + commands.addCursorToNextLine = function (editor) { + return addCursorToLine(editor, 1); + }; + commands.selectScope = function (editor) { + if (!_selectBetweenBrackets(CodeMirror, editor)) { + return commands.selectAll(editor); + } + }; + commands.selectBetweenBrackets = function (editor) { + if (!_selectBetweenBrackets(CodeMirror, editor)) { + return CodeMirror.Pass; + } + }; + commands.goToBracket = function (editor) { + editor.extendSelectionsBy(function (range) { + const forward = editor.scanForBracket(range.head, 1); + if (forward && + !_samePosition(CodeMirror, forward.pos, range.head)) { + return forward.pos; + } + const backward = editor.scanForBracket(range.head, -1); + return backward ? + _position( + CodeMirror, + backward.pos.line, + backward.pos.ch + 1 + ) : + range.head; + }, { + origin: "+move" + }); + }; + commands.swapLineUp = function (editor) { + return _swapLines(CodeMirror, editor, -1); + }; + commands.swapLineDown = function (editor) { + return _swapLines(CodeMirror, editor, 1); + }; + commands.toggleCommentIndented = function (editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + return editor.toggleComment({indent: true}); + }; + commands.joinLines = function (editor) { + return _joinLines(CodeMirror, editor); + }; + commands.duplicateLine = function (editor) { + return _duplicateSelections(CodeMirror, editor); + }; + commands.sortLines = function (editor) { + return _sortLines(CodeMirror, editor, true, 1); + }; + commands.reverseSortLines = function (editor) { + return _sortLines(CodeMirror, editor, true, -1); + }; + commands.sortLinesInsensitive = function (editor) { + return _sortLines(CodeMirror, editor, false, 1); + }; + commands.reverseSortLinesInsensitive = function (editor) { + return _sortLines(CodeMirror, editor, false, -1); + }; + commands.nextBookmark = function (editor) { + const marks = _bookmarkRanges(editor); + for (let attempt = 0; attempt < marks.length; attempt++) { + const mark = marks.shift(); + const found = mark.find(); + if (found) { + marks.push(mark); + editor.setSelection(found.from, found.to); + return; + } + } + }; + commands.prevBookmark = function (editor) { + const marks = _bookmarkRanges(editor); + for (let attempt = 0; attempt < marks.length; attempt++) { + const mark = marks.pop(); + const found = mark.find(); + if (found) { + marks.unshift(mark); + editor.setSelection(found.from, found.to); + return; + } + } + }; + commands.toggleBookmark = function (editor) { + const marks = _bookmarkRanges(editor); + editor.listSelections().forEach(function (range) { + const found = range.empty() ? + editor.findMarksAt(range.from()) : + editor.findMarks(range.from(), range.to()); + const bookmark = found.find(function (mark) { + return mark.sublimeBookmark; + }); + if (bookmark) { + bookmark.clear(); + const markIndex = marks.indexOf(bookmark); + if (markIndex !== -1) { + marks.splice(markIndex, 1); + } + return; + } + marks.push(editor.markText(range.from(), range.to(), { + sublimeBookmark: true, + clearWhenEmpty: false + })); + }); + }; + commands.clearBookmarks = function (editor) { + const marks = _bookmarkRanges(editor); + marks.slice().forEach(function (mark) { + mark.clear(); + }); + marks.length = 0; + }; + commands.selectBookmarks = function (editor) { + const ranges = _bookmarkRanges(editor).map(function (mark) { + const found = mark.find(); + return found ? { + anchor: found.from, + head: found.to + } : null; + }).filter(Boolean); + if (ranges.length) { + editor.setSelections(ranges, 0); + } + }; + commands.smartBackspace = function (editor) { + if (editor.somethingSelected()) { + return CodeMirror.Pass; + } + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + + const indentUnit = Math.max(1, editor.getOption("indentUnit") || 4); + const tabSize = Math.max(1, editor.getOption("tabSize") || 4); + const cursors = editor.listSelections().map(function (range) { + return range.head; + }).sort(function (left, right) { + return CodeMirror.cmpPos(right, left); + }); + editor.operation(function () { + cursors.forEach(function (cursor) { + const before = editor.getRange( + _position(CodeMirror, cursor.line, 0), + cursor + ); + const column = CodeMirror.countColumn(before, null, tabSize); + let deleteFrom = editor.findPosH(cursor, -1, "char", false); + if (before && !/\S/.test(before) && + column % indentUnit === 0) { + const previousColumn = Math.max(0, column - indentUnit); + const previousCharacter = CodeMirror.findColumn( + before, + previousColumn, + tabSize + ); + if (previousCharacter !== cursor.ch) { + deleteFrom = _position( + CodeMirror, + cursor.line, + previousCharacter + ); + } + } + editor.replaceRange( + "", + deleteFrom, + cursor, + "+delete" + ); + }); + }); + }; + commands.delLineRight = function (editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + const ranges = editor.listSelections().slice().sort(function (left, right) { + return CodeMirror.cmpPos(right.anchor, left.anchor); + }); + editor.operation(function () { + ranges.forEach(function (range) { + const targetLine = range.to().line; + editor.replaceRange( + "", + range.anchor, + _position( + CodeMirror, + targetLine, + (editor.getLine(targetLine) || "").length + ), + "+delete" + ); + }); + editor.scrollIntoView(); + }); + }; + commands.upcaseAtCursor = function (editor) { + return _modifyWordOrSelection( + CodeMirror, + editor, + function (text) { + return text.toUpperCase(); + } + ); + }; + commands.downcaseAtCursor = function (editor) { + return _modifyWordOrSelection( + CodeMirror, + editor, + function (text) { + return text.toLowerCase(); + } + ); + }; + commands.setSublimeMark = function (editor) { + if (editor.state.sublimeMark) { + editor.state.sublimeMark.clear(); + } + editor.state.sublimeMark = editor.setBookmark(editor.getCursor()); + }; + commands.selectToSublimeMark = function (editor) { + const found = editor.state.sublimeMark && + editor.state.sublimeMark.find(); + if (found) { + editor.setSelection(editor.getCursor(), found); + } + }; + commands.deleteToSublimeMark = function (editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + const found = editor.state.sublimeMark && + editor.state.sublimeMark.find(); + if (!found) { + return; + } + let from = editor.getCursor(); + let to = found; + if (CodeMirror.cmpPos(from, to) > 0) { + const swap = from; + from = to; + to = swap; + } + editor.state.sublimeKilled = editor.getRange(from, to); + editor.replaceRange("", from, to, "+delete"); + }; + commands.swapWithSublimeMark = function (editor) { + const found = editor.state.sublimeMark && + editor.state.sublimeMark.find(); + if (!found) { + return; + } + editor.state.sublimeMark.clear(); + editor.state.sublimeMark = editor.setBookmark(editor.getCursor()); + editor.setCursor(found); + }; + commands.sublimeYank = function (editor) { + if (editor.isReadOnly()) { + return CodeMirror.Pass; + } + if (editor.state.sublimeKilled !== undefined) { + editor.replaceSelection( + editor.state.sublimeKilled, + null, + "paste" + ); + } + }; + commands.showInCenter = function (editor) { + const coordinates = editor.cursorCoords(null, "local"); + const scrollInfo = editor.getScrollInfo(); + editor.scrollTo( + null, + (coordinates.top + coordinates.bottom) / 2 - + scrollInfo.clientHeight / 2 + ); + }; + commands.findUnder = function (editor) { + return _findAndSelect(CodeMirror, editor, true); + }; + commands.findUnderPrevious = function (editor) { + return _findAndSelect(CodeMirror, editor, false); + }; + commands.findAllUnder = function (editor) { + const target = _findTarget(CodeMirror, editor); + if (!target) { + return; + } + const cursor = editor.getSearchCursor(target.query); + const matches = []; + let primaryIndex = 0; + while (cursor.findNext()) { + const match = { + anchor: cursor.from(), + head: cursor.to() + }; + if (CodeMirror.cmpPos(match.anchor, target.from) <= 0) { + primaryIndex = matches.length; + } + matches.push(match); + } + if (matches.length) { + editor.setSelections(matches, primaryIndex); + } + }; + } + + function _defineKeyMaps(CodeMirror) { + const keyMap = CodeMirror.keyMap; + keyMap.macSublime = { + "Cmd-Left": "goLineStartSmart", + "Shift-Tab": "indentLess", + "Shift-Ctrl-K": "deleteLine", + "Alt-Q": "wrapLines", + "Ctrl-Left": "goSubwordLeft", + "Ctrl-Right": "goSubwordRight", + "Ctrl-Alt-Up": "scrollLineUp", + "Ctrl-Alt-Down": "scrollLineDown", + "Cmd-L": "selectLine", + "Shift-Cmd-L": "splitSelectionByLine", + "Esc": "singleSelectionTop", + "Cmd-Enter": "insertLineAfter", + "Shift-Cmd-Enter": "insertLineBefore", + "Cmd-D": "selectNextOccurrence", + "Shift-Cmd-Space": "selectScope", + "Shift-Cmd-M": "selectBetweenBrackets", + "Cmd-M": "goToBracket", + "Cmd-Ctrl-Up": "swapLineUp", + "Cmd-Ctrl-Down": "swapLineDown", + "Cmd-/": "toggleCommentIndented", + "Cmd-J": "joinLines", + "Shift-Cmd-D": "duplicateLine", + F5: "sortLines", + "Shift-F5": "reverseSortLines", + "Cmd-F5": "sortLinesInsensitive", + "Shift-Cmd-F5": "reverseSortLinesInsensitive", + F2: "nextBookmark", + "Shift-F2": "prevBookmark", + "Cmd-F2": "toggleBookmark", + "Shift-Cmd-F2": "clearBookmarks", + "Alt-F2": "selectBookmarks", + Backspace: "smartBackspace", + "Cmd-K Cmd-D": "skipAndSelectNextOccurrence", + "Cmd-K Cmd-K": "delLineRight", + "Cmd-K Cmd-U": "upcaseAtCursor", + "Cmd-K Cmd-L": "downcaseAtCursor", + "Cmd-K Cmd-Space": "setSublimeMark", + "Cmd-K Cmd-A": "selectToSublimeMark", + "Cmd-K Cmd-W": "deleteToSublimeMark", + "Cmd-K Cmd-X": "swapWithSublimeMark", + "Cmd-K Cmd-Y": "sublimeYank", + "Cmd-K Cmd-C": "showInCenter", + "Cmd-K Cmd-G": "clearBookmarks", + "Cmd-K Cmd-Backspace": "delLineLeft", + "Cmd-K Cmd-1": "foldAll", + "Cmd-K Cmd-0": "unfoldAll", + "Cmd-K Cmd-J": "unfoldAll", + "Ctrl-Shift-Up": "addCursorToPrevLine", + "Ctrl-Shift-Down": "addCursorToNextLine", + "Cmd-F3": "findUnder", + "Shift-Cmd-F3": "findUnderPrevious", + "Alt-F3": "findAllUnder", + "Shift-Cmd-[": "fold", + "Shift-Cmd-]": "unfold", + "Cmd-I": "findIncremental", + "Shift-Cmd-I": "findIncrementalReverse", + "Cmd-H": "replace", + F3: "findNext", + "Shift-F3": "findPrev", + fallthrough: "macDefault" + }; + CodeMirror.normalizeKeyMap(keyMap.macSublime); + + keyMap.pcSublime = { + "Shift-Tab": "indentLess", + "Shift-Ctrl-K": "deleteLine", + "Alt-Q": "wrapLines", + "Ctrl-T": "transposeChars", + "Alt-Left": "goSubwordLeft", + "Alt-Right": "goSubwordRight", + "Ctrl-Up": "scrollLineUp", + "Ctrl-Down": "scrollLineDown", + "Ctrl-L": "selectLine", + "Shift-Ctrl-L": "splitSelectionByLine", + Esc: "singleSelectionTop", + "Ctrl-Enter": "insertLineAfter", + "Shift-Ctrl-Enter": "insertLineBefore", + "Ctrl-D": "selectNextOccurrence", + "Shift-Ctrl-Space": "selectScope", + "Shift-Ctrl-M": "selectBetweenBrackets", + "Ctrl-M": "goToBracket", + "Shift-Ctrl-Up": "swapLineUp", + "Shift-Ctrl-Down": "swapLineDown", + "Ctrl-/": "toggleCommentIndented", + "Ctrl-J": "joinLines", + "Shift-Ctrl-D": "duplicateLine", + F9: "sortLines", + "Shift-F9": "reverseSortLines", + "Ctrl-F9": "sortLinesInsensitive", + "Shift-Ctrl-F9": "reverseSortLinesInsensitive", + F2: "nextBookmark", + "Shift-F2": "prevBookmark", + "Ctrl-F2": "toggleBookmark", + "Shift-Ctrl-F2": "clearBookmarks", + "Alt-F2": "selectBookmarks", + Backspace: "smartBackspace", + "Ctrl-K Ctrl-D": "skipAndSelectNextOccurrence", + "Ctrl-K Ctrl-K": "delLineRight", + "Ctrl-K Ctrl-U": "upcaseAtCursor", + "Ctrl-K Ctrl-L": "downcaseAtCursor", + "Ctrl-K Ctrl-Space": "setSublimeMark", + "Ctrl-K Ctrl-A": "selectToSublimeMark", + "Ctrl-K Ctrl-W": "deleteToSublimeMark", + "Ctrl-K Ctrl-X": "swapWithSublimeMark", + "Ctrl-K Ctrl-Y": "sublimeYank", + "Ctrl-K Ctrl-C": "showInCenter", + "Ctrl-K Ctrl-G": "clearBookmarks", + "Ctrl-K Ctrl-Backspace": "delLineLeft", + "Ctrl-K Ctrl-1": "foldAll", + "Ctrl-K Ctrl-0": "unfoldAll", + "Ctrl-K Ctrl-J": "unfoldAll", + "Ctrl-Alt-Up": "addCursorToPrevLine", + "Ctrl-Alt-Down": "addCursorToNextLine", + "Ctrl-F3": "findUnder", + "Shift-Ctrl-F3": "findUnderPrevious", + "Alt-F3": "findAllUnder", + "Shift-Ctrl-[": "fold", + "Shift-Ctrl-]": "unfold", + "Ctrl-I": "findIncremental", + "Shift-Ctrl-I": "findIncrementalReverse", + "Ctrl-H": "replace", + F3: "findNext", + "Shift-F3": "findPrev", + fallthrough: "pcDefault" + }; + CodeMirror.normalizeKeyMap(keyMap.pcSublime); + + keyMap.sublime = keyMap.default === keyMap.macDefault ? + keyMap.macSublime : + keyMap.pcSublime; + } + + /** + * Installs Sublime-compatible commands and keymaps on a CodeMirror facade. + * @param {!Function} CodeMirror CM6-backed CodeMirror compatibility facade + * @return {!Function} The installed facade + */ + function install(CodeMirror) { + if (!CodeMirror || !CodeMirror.commands || !CodeMirror.keyMap) { + throw new TypeError("A CodeMirror compatibility facade is required."); + } + if (installedFacades.has(CodeMirror)) { + return CodeMirror; + } + + _defineCommands(CodeMirror); + _defineKeyMaps(CodeMirror); + installedFacades.add(CodeMirror); + return CodeMirror; + } + + module.exports = { + install: install + }; +}); diff --git a/src/editor/CodeMirrorTwigCompat.js b/src/editor/CodeMirrorTwigCompat.js new file mode 100644 index 0000000000..839062cf63 --- /dev/null +++ b/src/editor/CodeMirrorTwigCompat.js @@ -0,0 +1,250 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5 Twig compatibility implementation. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + +/*global define*/ + +/** + * Provides the historical CodeMirror Twig stream modes on top of Phoenix's + * CM6-backed compatibility facade. No CodeMirror 5 runtime code is loaded. + */ +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"); + const INSTALL_MARKER = "__phoenixCodeMirror6TwigCompat"; + const KEYWORDS = [ + "and", + "as", + "autoescape", + "endautoescape", + "block", + "do", + "endblock", + "else", + "elseif", + "extends", + "for", + "endfor", + "embed", + "endembed", + "filter", + "endfilter", + "flush", + "from", + "if", + "endif", + "in", + "is", + "include", + "import", + "not", + "or", + "set", + "spaceless", + "endspaceless", + "with", + "endwith", + "trans", + "endtrans", + "blocktrans", + "endblocktrans", + "macro", + "endmacro", + "use", + "verbatim", + "endverbatim" + ]; + const ATOMS = [ + "true", + "false", + "null", + "empty", + "defined", + "divisibleby", + "divisible by", + "even", + "odd", + "iterable", + "sameas", + "same as" + ]; + const KEYWORD_PATTERN = new RegExp( + "((" + KEYWORDS.join(")|(") + "))\\b" + ); + const ATOM_PATTERN = new RegExp( + "((" + ATOMS.join(")|(") + "))\\b" + ); + const OPERATOR_PATTERN = /^[+\-*&%=<>!?|~^]/; + const SIGN_PATTERN = /^[:\[\(\{]/; + const NUMBER_PATTERN = /^(\d[+\-*\/])?\d+(\.\d+)?/; + + function _tokenInner(stream, state) { + const nextCharacter = stream.peek(); + + if (state.incomment) { + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/[#}]/); + state.incomment = false; + } + return "comment"; + } + + if (state.intag) { + if (state.operator) { + state.operator = false; + if (stream.match(ATOM_PATTERN)) { + return "atom"; + } + if (stream.match(NUMBER_PATTERN)) { + return "number"; + } + } + + if (state.sign) { + state.sign = false; + if (stream.match(ATOM_PATTERN)) { + return "atom"; + } + if (stream.match(NUMBER_PATTERN)) { + return "number"; + } + } + + if (state.instring) { + if (nextCharacter === state.instring) { + state.instring = false; + } + stream.next(); + return "string"; + } + + if (nextCharacter === "'" || nextCharacter === "\"") { + state.instring = nextCharacter; + stream.next(); + return "string"; + } + + if (stream.match(state.intag + "}") || + stream.eat("-") && stream.match(state.intag + "}")) { + state.intag = false; + return "tag"; + } + + if (stream.match(OPERATOR_PATTERN)) { + state.operator = true; + return "operator"; + } + + if (stream.match(SIGN_PATTERN)) { + state.sign = true; + } else if (stream.eat(" ") || stream.sol()) { + if (stream.match(KEYWORD_PATTERN)) { + return "keyword"; + } + if (stream.match(ATOM_PATTERN)) { + return "atom"; + } + if (stream.match(NUMBER_PATTERN)) { + return "number"; + } + if (stream.sol()) { + stream.next(); + } + } else { + stream.next(); + } + return "variable"; + } + + if (stream.eat("{")) { + if (stream.eat("#")) { + state.incomment = true; + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/[#}]/); + state.incomment = false; + } + return "comment"; + } + + const delimiter = stream.eat(/\{|%/); + if (delimiter) { + state.intag = delimiter === "{" ? "}" : delimiter; + stream.eat("-"); + return "tag"; + } + } + + stream.next(); + return null; + } + + function _createInnerMode() { + return { + startState: function () { + return {}; + }, + token: _tokenInner + }; + } + + function install(target) { + const codeMirror = target || CodeMirror; + if (codeMirror[INSTALL_MARKER]) { + return codeMirror; + } + + codeMirror.defineMode("twig:inner", function () { + return _createInnerMode(); + }); + codeMirror.defineMode("twig", function (config, parserConfig) { + const twigInner = codeMirror.getMode(config, "twig:inner"); + if (!parserConfig || !parserConfig.base) { + return twigInner; + } + return codeMirror.multiplexingMode( + codeMirror.getMode(config, parserConfig.base), + { + open: /\{[{#%]/, + close: /[}#%]\}/, + mode: twigInner, + parseDelimiters: true + } + ); + }); + codeMirror.defineMIME("text/x-twig", "twig"); + + Object.defineProperty(codeMirror, INSTALL_MARKER, { + configurable: false, + enumerable: false, + value: true + }); + return codeMirror; + } + + install(CodeMirror); + + exports.install = install; +}); diff --git a/src/editor/CodeMirrorVimCompat.js b/src/editor/CodeMirrorVimCompat.js new file mode 100644 index 0000000000..0404d04eee --- /dev/null +++ b/src/editor/CodeMirrorVimCompat.js @@ -0,0 +1,509 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global define, window*/ + +/*! DONT_STRIP_MINIFY: Replit Vim-derived compatibility code. + * Third-party license notice: + * thirdparty/licences/codemirror-vim-derived.markdown. + */ + +/** + * Installs the editor-agnostic Replit Vim engine on Phoenix's CM6-backed + * CodeMirror compatibility facade. The Phoenix adapter remains the only + * editor object and EditorView.state.doc remains the only text model. + */ +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + LegacyAddons = require("editor/CodeMirrorLegacyAddons"), + CM6 = require("thirdparty/CodeMirror6/codemirror6"); + + const INSTALL_MARKER = "__phoenixCodeMirror6VimCompat"; + const DIALOG_BRIDGE_MARKER = "__phoenixVimDialogBridge"; + const MODIFIERS = { + Shift: "S", + Ctrl: "C", + Alt: "A", + Cmd: "D", + Mod: "A", + CapsLock: "" + }; + const SPECIAL_KEYS = { + Enter: "CR", + Backspace: "BS", + Delete: "Del", + Insert: "Ins" + }; + + let Vim; + + function _templateNode(template) { + if (template && template.nodeType) { + return template; + } + const holder = window.document.createElement("div"); + holder.innerHTML = String(template || ""); + if (holder.childNodes.length === 1) { + return holder.removeChild(holder.firstChild); + } + const fragment = window.document.createDocumentFragment(); + while (holder.firstChild) { + fragment.appendChild(holder.firstChild); + } + return fragment; + } + + function _legacyTemplate(template) { + if (!template || !template.nodeType) { + return template; + } + const text = String(template.textContent || ""); + if (template.classList && + template.classList.contains("cm-vim-message") && + /^recording @/.test(text)) { + return `(${text.slice(0, "recording".length)})${text.slice("recording".length)}`; + } + if (template.outerHTML) { + return template.outerHTML; + } + const holder = window.document.createElement("div"); + holder.appendChild(template.cloneNode(true)); + return holder.innerHTML; + } + + function _closeDialog(editor, dialog, restoreFocus) { + if (!dialog || dialog.__phoenixClosed) { + return; + } + dialog.__phoenixClosed = true; + const wrapper = editor.getWrapperElement(); + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + if (!wrapper.querySelector(".CodeMirror-dialog")) { + CodeMirror.rmClass(wrapper, "dialog-opened"); + } + let stateChanged = false; + if (editor.state.dialog === dialog) { + editor.state.dialog = null; + stateChanged = true; + } + if (editor.state.vimDialog === dialog) { + editor.state.vimDialog = null; + editor.state.vimDialogClose = null; + stateChanged = true; + } + if (stateChanged) { + CodeMirror.signal(editor, "dialog"); + } + if (restoreFocus && !editor.state.dialog) { + editor.focus(); + } + } + + function _openDialog(template, callback, suppliedOptions) { + const editor = this; + const options = suppliedOptions || {}; + if (editor.state.currentNotificationClose) { + editor.state.currentNotificationClose(); + } + if (editor.state.vimDialogClose) { + editor.state.vimDialogClose(); + } + + const dialog = window.document.createElement("div"); + dialog.className = "CodeMirror-dialog phoenix-cm6-vim-dialog"; + if (options.bottom) { + dialog.classList.add("CodeMirror-dialog-bottom"); + } else { + dialog.classList.add("CodeMirror-dialog-top"); + } + dialog.appendChild(_templateNode(template)); + const wrapper = editor.getWrapperElement(); + wrapper.appendChild(dialog); + CodeMirror.addClass(wrapper, "dialog-opened"); + + let closed = false; + const close = function (newValue) { + const input = dialog.querySelector("input"); + if (typeof newValue === "string" && input) { + input.value = newValue; + return; + } + if (closed) { + return; + } + closed = true; + _closeDialog(editor, dialog, true); + if (typeof options.onClose === "function") { + options.onClose(dialog); + } + }; + + editor.state.dialog = dialog; + editor.state.vimDialog = dialog; + editor.state.vimDialogClose = close; + CodeMirror.signal(editor, "dialog"); + + const input = dialog.querySelector("input"); + if (input) { + if (options.value !== undefined) { + input.value = options.value; + if (options.selectValueOnOpen !== false) { + input.select(); + } + } + if (typeof options.onInput === "function") { + CodeMirror.on(input, "input", function (event) { + options.onInput(event, input.value, close); + }); + } + if (typeof options.onKeyUp === "function") { + CodeMirror.on(input, "keyup", function (event) { + options.onKeyUp(event, input.value, close); + }); + } + CodeMirror.on(input, "keydown", function (event) { + if (typeof options.onKeyDown === "function" && + options.onKeyDown(event, input.value, close)) { + return; + } + if (event.keyCode === 13 && typeof callback === "function") { + callback(input.value); + } + if (event.keyCode === 27 || + options.closeOnEnter !== false && event.keyCode === 13) { + input.blur(); + CodeMirror.e_stop(event); + close(); + } + }); + if (options.closeOnBlur !== false) { + CodeMirror.on(input, "blur", function () { + window.setTimeout(function () { + if (window.document.activeElement !== input) { + close(); + } + }, 0); + }); + } + input.focus(); + } + return close; + } + + function _openNotification(template, suppliedOptions) { + const editor = this; + const options = suppliedOptions || {}; + const previousClose = editor.state.currentNotificationClose || + editor.state.vimNotificationClose || + editor.state.closeVimNotification; + if (previousClose) { + previousClose(); + } + + const dialog = window.document.createElement("div"); + dialog.className = "CodeMirror-dialog phoenix-cm6-vim-notification"; + dialog.classList.add(options.bottom ? + "CodeMirror-dialog-bottom" : + "CodeMirror-dialog-top"); + dialog.appendChild(_templateNode(template)); + const wrapper = editor.getWrapperElement(); + wrapper.appendChild(dialog); + CodeMirror.addClass(wrapper, "dialog-opened"); + + let timer; + const close = function () { + if (timer) { + window.clearTimeout(timer); + timer = null; + } + _closeDialog(editor, dialog, false); + if (editor.state.currentNotificationClose === close) { + editor.state.currentNotificationClose = null; + } + if (editor.state.vimNotificationClose === close) { + editor.state.vimNotificationClose = null; + } + if (editor.state.closeVimNotification === close) { + editor.state.closeVimNotification = null; + } + }; + editor.state.dialog = dialog; + editor.state.currentNotificationClose = close; + editor.state.vimNotificationClose = close; + CodeMirror.signal(editor, "dialog"); + CodeMirror.on(dialog, "click", function (event) { + event.preventDefault(); + close(); + }); + + const duration = options.duration === undefined ? 5000 : options.duration; + if (duration) { + timer = window.setTimeout(close, duration); + } + return close; + } + + function _installDialogBridge(editor) { + if (!editor || editor[DIALOG_BRIDGE_MARKER]) { + return; + } + editor[DIALOG_BRIDGE_MARKER] = true; + + editor.on("vim-command-done", function () { + if (editor.state.vim) { + editor.state.vim.status = ""; + } + }); + editor.on("vim-mode-change", function (event) { + if (!editor.state.vim || !event) { + return; + } + editor.state.vim.mode = event.mode; + if (event.subMode) { + editor.state.vim.mode += event.subMode === "linewise" ? + " line" : + " block"; + } + editor.state.vim.status = ""; + }); + + const defaultOpenDialog = editor.openDialog; + const defaultOpenNotification = editor.openNotification; + let customOpenDialog = null; + let customOpenNotification = null; + + Object.defineProperty(editor, "openDialog", { + configurable: true, + enumerable: true, + get: function () { + return function (template, callback, options) { + const handler = customOpenDialog || defaultOpenDialog; + return handler.call( + editor, + customOpenDialog ? _legacyTemplate(template) : template, + callback, + options + ); + }; + }, + set: function (handler) { + customOpenDialog = typeof handler === "function" ? handler : null; + } + }); + + Object.defineProperty(editor, "openNotification", { + configurable: true, + enumerable: true, + get: function () { + return function (template, options) { + const handler = customOpenNotification || defaultOpenNotification; + return handler.call( + editor, + customOpenNotification ? _legacyTemplate(template) : template, + options + ); + }; + }, + set: function (handler) { + customOpenNotification = + typeof handler === "function" ? handler : null; + } + }); + } + + function _toVimKey(key) { + if (key.charAt(0) === "'") { + return key.charAt(1); + } + + const pieces = key.split(/-(?!$)/); + const lastPiece = pieces[pieces.length - 1]; + if (pieces.length === 1 && lastPiece.length === 1) { + return false; + } + if (pieces.length === 2 && pieces[0] === "Shift" && + lastPiece.length === 1) { + return false; + } + + let hasCharacter = false; + for (let index = 0; index < pieces.length; index++) { + const piece = pieces[index]; + if (Object.prototype.hasOwnProperty.call(MODIFIERS, piece)) { + pieces[index] = MODIFIERS[piece]; + } else { + hasCharacter = true; + } + if (Object.prototype.hasOwnProperty.call(SPECIAL_KEYS, piece)) { + pieces[index] = SPECIAL_KEYS[piece]; + } + } + if (!hasCharacter) { + return false; + } + if (/^[A-Z]$/.test(lastPiece)) { + pieces[pieces.length - 1] = lastPiece.toLowerCase(); + } + return "<" + pieces.join("-") + ">"; + } + + function _vimKey(key, editor) { + if (!editor) { + return; + } + if (Object.prototype.hasOwnProperty.call(this, key)) { + return this[key]; + } + const vimKey = _toVimKey(key); + if (!vimKey) { + return false; + } + return function () { + let vimState = Vim.maybeInitVimState_(editor); + vimState.status = (vimState.status || "") + vimKey; + + let handled = Vim.multiSelectHandleKey(editor, vimKey, "user"); + vimState = Vim.maybeInitVimState_(editor); + if (!handled && vimState.insertMode && editor.state.overwrite) { + if (vimKey.length === 1 && !/\n/.test(vimKey)) { + editor.overWriteSelection(vimKey); + handled = true; + } else if (vimKey === "") { + CodeMirror.commands.goCharLeft(editor); + handled = true; + } + } + if (handled) { + CodeMirror.signal(editor, "vim-keypress", vimKey); + return true; + } + return CodeMirror.Pass; + }; + } + + function _transformCursor(editor, range) { + const vimState = editor.state.vim; + if (!vimState || vimState.insertMode || !vimState.sel || + !vimState.sel.head) { + return range.head; + } + const head = vimState.sel.head; + if (vimState.visualBlock && range.head.line !== head.line) { + return; + } + if (range.from() === range.anchor && !range.empty() && + range.head.line === head.line && + range.head.ch !== head.ch) { + return CodeMirror.Pos(range.head.line, range.head.ch - 1); + } + return range.head; + } + + function _usesFatCursor(keyMap) { + return keyMap === CodeMirror.keyMap.vim || + keyMap === CodeMirror.keyMap["vim-replace"]; + } + + function _detachVimMap(editor, next) { + if (_usesFatCursor(this) && !_usesFatCursor(next)) { + editor.options.$customCursor = null; + CodeMirror.rmClass(editor.getWrapperElement(), "cm-fat-cursor"); + CodeMirror.rmClass(editor.getWrapperElement(), "cm-vimMode"); + } + if (!next || next.attach !== _attachVimMap) { + Vim.leaveVimMode(editor); + } + } + + function _attachVimMap(editor, previous) { + _installDialogBridge(editor); + if (_usesFatCursor(this)) { + if (editor.curOp) { + editor.curOp.selectionChanged = true; + } + editor.options.$customCursor = _transformCursor; + CodeMirror.addClass(editor.getWrapperElement(), "cm-fat-cursor"); + CodeMirror.addClass(editor.getWrapperElement(), "cm-vimMode"); + } + if (!previous || previous.attach !== _attachVimMap) { + Vim.enterVimMode(editor); + } + } + + function install(target) { + const codeMirror = target || CodeMirror; + if (codeMirror[INSTALL_MARKER]) { + return codeMirror.Vim; + } + + LegacyAddons.install(codeMirror, "addon/comment/comment"); + LegacyAddons.install(codeMirror, "addon/fold/xml-fold"); + if (!codeMirror.commands.toggleLineComment) { + codeMirror.commands.toggleLineComment = + codeMirror.commands.toggleComment; + } + Vim = CM6.initVim(codeMirror); + codeMirror.Vim = Vim; + codeMirror.keyMap["vim-insert"] = { + fallthrough: ["default"], + attach: _attachVimMap, + detach: _detachVimMap, + call: _vimKey + }; + codeMirror.keyMap["vim-replace"] = { + Backspace: "goCharLeft", + fallthrough: ["vim-insert"], + attach: _attachVimMap, + detach: _detachVimMap + }; + codeMirror.keyMap.vim = { + attach: _attachVimMap, + detach: _detachVimMap, + call: _vimKey + }; + + codeMirror.defineExtension("openDialog", _openDialog); + codeMirror.defineExtension("openNotification", _openNotification); + codeMirror.defineInitHook(_installDialogBridge); + codeMirror.defineOption("vimMode", false, function (editor, value, oldValue) { + if (value && editor.getOption("keyMap") !== "vim") { + editor.setOption("keyMap", "vim"); + } else if (!value && oldValue !== codeMirror.Init && + /^vim/.test(editor.getOption("keyMap"))) { + editor.setOption("keyMap", "default"); + } + }); + + Object.defineProperty(codeMirror, INSTALL_MARKER, { + configurable: false, + enumerable: false, + value: true + }); + return Vim; + } + + install(CodeMirror); + + exports.install = install; + exports.toVimKey = _toVimKey; + exports.Vim = Vim; +}); diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 8e0b9b2b84..cc01b596a9 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -75,7 +75,7 @@ define(function (require, exports, module) { let CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror6Adapter = require("editor/CodeMirror6Adapter").CodeMirror6Adapter, LanguageManager = require("language/LanguageManager"), EventDispatcher = require("utils/EventDispatcher"), PerfUtils = require("utils/PerfUtils"), @@ -183,7 +183,11 @@ define(function (require, exports, module) { * @return {CodeMirror.Pos} */ function _copyPos(pos) { - return new CodeMirror.Pos(pos.line, pos.ch); + return { + line: pos.line, + ch: pos.ch, + sticky: pos.sticky === undefined ? null : pos.sticky + }; } /** @@ -355,9 +359,7 @@ define(function (require, exports, module) { // This line ensures that the class is applied to any editor created after the fact $container.toggleClass("show-line-padding", Boolean(!this._getOption("showLineNumbers"))); - // Create the CodeMirror instance - // (note: CodeMirror doesn't actually require using 'new', but jslint complains without it) - this._codeMirror = new CodeMirror(container, { + const codeMirrorOptions = { autoCloseBrackets: currentOptions[CLOSE_BRACKETS], autoCloseTags: currentOptions[CLOSE_TAGS], coverGutterNextToScrollbar: true, @@ -382,7 +384,11 @@ define(function (require, exports, module) { styleActiveLine: currentOptions[STYLE_ACTIVE_LINE], tabSize: currentOptions[TAB_SIZE], readOnly: isReadOnly - }); + }; + + this._codeMirror = new CodeMirror6Adapter(container, codeMirrorOptions); + this._codeMirrorView = this._codeMirror._view; + this._editorEngine = "codemirror6"; // Override default drag image in Safari (and harmless in others): // Safari shows a text image by default when dragging from CodeMirror, @@ -475,12 +481,20 @@ define(function (require, exports, module) { const $cmElement = this.$el; $cmElement[0].addEventListener("wheel", (event) => { - const $editor = $cmElement.find(".CodeMirror-scroll"); + const editorElement = this.getScrollerElement(); + if (!editorElement) { + return; + } + // We need to scale the scroll by the factor of line height. This became a problem after we added // the custom line height feature causing jumping scrolls esp in safari and mac if we dont do // this scroll scaling. - const lineHeight = parseFloat(getComputedStyle($editor[0]).lineHeight); - const defaultHeight = 14, scrollScaleFactor = lineHeight / defaultHeight; + const defaultHeight = 14; + const measuredLineHeight = parseFloat(getComputedStyle(editorElement).lineHeight); + const lineHeight = Number.isFinite(measuredLineHeight) && measuredLineHeight > 0 ? + measuredLineHeight : + defaultHeight; + const scrollScaleFactor = lineHeight / defaultHeight; // when user is pressing the 'Shift' key, we need to convert the vertical scroll to horizontal scroll if (event.shiftKey) { @@ -492,7 +506,7 @@ define(function (require, exports, module) { // apply the horizontal scrolling if (horizontalDelta !== 0) { - $editor[0].scrollLeft += horizontalDelta; + editorElement.scrollLeft += horizontalDelta; event.preventDefault(); return; } @@ -500,7 +514,7 @@ define(function (require, exports, module) { // apply horizontal scrolling if present. for the diagonal scrolling if (event.deltaX !== 0) { - $editor[0].scrollLeft += event.deltaX; + editorElement.scrollLeft += event.deltaX; } // apply the vertical scrolling normally @@ -515,9 +529,9 @@ define(function (require, exports, module) { scrollAmount = event.deltaY * defaultHeight; } else { // Page mode - delta is in pages, convert to viewport height - scrollAmount = event.deltaY * $editor[0].clientHeight; + scrollAmount = event.deltaY * editorElement.clientHeight; } - $editor[0].scrollTop += scrollAmount * _mouseWheelScrollSensitivity; + editorElement.scrollTop += scrollAmount * _mouseWheelScrollSensitivity; event.preventDefault(); } }); @@ -578,9 +592,11 @@ define(function (require, exports, module) { Editor.prototype.destroy = function () { this.trigger("beforeDestroy", this); + const rootElement = this.getRootElement(); + // CodeMirror docs for getWrapperElement() say all you have to do is "Remove this from your // tree to delete an editor instance." - $(this.getRootElement()).remove(); + $(rootElement).remove(); _instances.splice(_instances.indexOf(this), 1); @@ -608,6 +624,8 @@ define(function (require, exports, module) { this._inlineWidgets.forEach(function (inlineWidget) { self._removeInlineWidgetInternal(inlineWidget); }); + + this._codeMirror.destroy(); }; /** @@ -916,7 +934,11 @@ define(function (require, exports, module) { if (expandTabs) { ch = this.getColOffset({ line: line, ch: ch }); } - this._codeMirror.setCursor(line, ch); + this._codeMirror.setCursor( + line, + ch, + center ? {scroll: false} : undefined + ); if (center) { this.centerOnCursor(); } @@ -1171,9 +1193,9 @@ define(function (require, exports, module) { */ Editor.prototype.getSelectedText = function (allSelections) { if (allSelections) { - return this._codeMirror.getSelection(); + return this._codeMirror.getSelections().join("\n"); } - var sel = this.getSelection(); + const sel = this.getSelection(); return this.document.getRange(sel.start, sel.end); }; @@ -1693,9 +1715,36 @@ define(function (require, exports, module) { * Replace the editor's undo history with the one provided, which must be a value * as returned by getHistory. Note that this will have entirely undefined results * if the editor content isn't also the same as it was when getHistory was called. + * + * @param {{done: !Array, undone: !Array}} history A history object returned by getHistory(). + */ + Editor.prototype.setHistory = function (history) { + return this._codeMirror.setHistory(history); + }; + + /** + * Returns whether the editor's current history generation is clean. + * @param {number=} generation Optional generation returned by changeGeneration(). + * @return {boolean} */ - Editor.prototype.setHistory = function () { - return this._codeMirror.setHistory(); + Editor.prototype.isClean = function (generation) { + return this._codeMirror.isClean(generation); + }; + + /** + * Marks the current history generation as clean. + * @return {number} + */ + Editor.prototype.markClean = function () { + return this._codeMirror.markClean(); + }; + + /** + * Returns the active editing-surface backend. + * @return {"codemirror6"} + */ + Editor.prototype.getEditorEngine = function () { + return this._editorEngine; }; /** @@ -1786,9 +1835,10 @@ define(function (require, exports, module) { * @param {string} [select] The optional select argument can be used to change selection. Passing "around" * will cause the new text to be selected, passing "start" will collapse the selection to the start * of the inserted text. + * @param {?string} [origin] An optional edit origin passed to change events and used for history grouping. */ - Editor.prototype.replaceSelection = function (replacement, select) { - this._codeMirror.replaceSelection(replacement, select); + Editor.prototype.replaceSelection = function (replacement, select, origin) { + this._codeMirror.replaceSelection(replacement, select, origin); }; /** @@ -1798,9 +1848,10 @@ define(function (require, exports, module) { * @param {string} [select] The optional select argument can be used to change selection. Passing "around" * will cause the new text to be selected, passing "start" will collapse the selection to the start * of the inserted text. + * @param {?string} [origin] An optional edit origin passed to change events and used for history grouping. */ - Editor.prototype.replaceSelections = function (replacement, select) { - this._codeMirror.replaceSelections(replacement, select); + Editor.prototype.replaceSelections = function (replacement, select, origin) { + this._codeMirror.replaceSelections(replacement, select, origin); }; /** @@ -2093,10 +2144,10 @@ define(function (require, exports, module) { * FUTURE: This is fairly CodeMirror-specific. Logic that depends on this may break if we switch * editors. * @private - * @return {!HTMLDivElement} The editor's lineSpace element. + * @return {!HTMLDivElement} The editor's lineSpace element. */ Editor.prototype._getLineSpaceElement = function () { - return $(".CodeMirror-lines", this.getScrollerElement()).children().get(0); + return this._codeMirror.getLineSpaceElement(); }; /** @@ -2502,6 +2553,20 @@ define(function (require, exports, module) { */ Editor.prototype._codeMirror = null; + /** + * The active editor backend name. + * @private + * @type {"codemirror6"} + */ + Editor.prototype._editorEngine = "codemirror6"; + + /** + * Native CodeMirror 6 view when the CM6 backend is active. + * @private + * @type {?Object} + */ + Editor.prototype._codeMirrorView = null; + /** * @private * @type {!{id:number, data:Object[]}} diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 4b01e0c1a3..c8c6ea2278 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -36,7 +36,7 @@ define(function (require, exports, module) { WorkspaceManager = require("view/WorkspaceManager"), StringUtils = require("utils/StringUtils"), TokenUtils = require("utils/TokenUtils"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror = require("editor/CodeMirrorCompat"), _ = require("thirdparty/lodash"), ChangeHelper = require("editor/EditorHelper/ChangeHelper"), LanguageManager = require("language/LanguageManager"), diff --git a/src/editor/EditorHelper/ChangeHelper.js b/src/editor/EditorHelper/ChangeHelper.js index d9a18c1e09..59151e3dd7 100644 --- a/src/editor/EditorHelper/ChangeHelper.js +++ b/src/editor/EditorHelper/ChangeHelper.js @@ -32,7 +32,7 @@ define(function (require, exports, module) { let _pasteInterceptor = null; let _keyEventInterceptor = null; - const CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + const CodeMirror = require("editor/CodeMirrorCompat"), Menus = require("command/Menus"); function _applyChanges(changeList) { @@ -154,7 +154,7 @@ define(function (require, exports, module) { */ function _handleDocumentDeleted(event) { // Pass the delete event along as the cause (needed in MultiRangeInlineEditor) - self.trigger("lostContent", event); + this.trigger("lostContent", event); } /** diff --git a/src/editor/EditorHelper/EditorPreferences.js b/src/editor/EditorHelper/EditorPreferences.js index a96430b1c4..72fcb97945 100644 --- a/src/editor/EditorHelper/EditorPreferences.js +++ b/src/editor/EditorHelper/EditorPreferences.js @@ -165,7 +165,7 @@ define(function (require, exports, module) { PreferencesManager.definePreference(INDENT_LINE_COMMENT, "boolean", true, { description: Strings.DESCRIPTION_INDENT_LINE_COMMENT }); - PreferencesManager.definePreference(INPUT_STYLE, "string", "textarea", { + PreferencesManager.definePreference(INPUT_STYLE, "string", "contenteditable", { description: Strings.DESCRIPTION_INPUT_STYLE }); PreferencesManager.definePreference(MOUSE_WHEEL_SCROLL_SENSITIVITY, "number", 1, { diff --git a/src/editor/EditorHelper/ErrorPopupHelper.js b/src/editor/EditorHelper/ErrorPopupHelper.js index ca8d5f3d36..2514a340a7 100644 --- a/src/editor/EditorHelper/ErrorPopupHelper.js +++ b/src/editor/EditorHelper/ErrorPopupHelper.js @@ -85,9 +85,11 @@ define(function (require, exports, module) { // Determine if arrow is above or below cursorCoord = self._codeMirror.charCoords(cursorPos); + const $editorHolder = $("#editor-holder"), + editorOffset = $editorHolder.offset() || {top: 0, left: 0}; // Assume popover height is max of 2 lines - arrowBelow = (cursorCoord.top > 100); + arrowBelow = (cursorCoord.top - editorOffset.top > 100); // Text is dynamic, so build popover first so we can measure final width self._$messagePopover = $("
").addClass("popover-message").appendTo($("body")); @@ -112,7 +114,7 @@ define(function (require, exports, module) { }; // See if popover is clipped on any side - clip = ViewUtils.getElementClipSize($("#editor-holder"), popoverRect); + clip = ViewUtils.getElementClipSize($editorHolder, popoverRect); // Prevent horizontal clipping if (clip.left > 0) { diff --git a/src/editor/EditorHelper/IndentHelper.js b/src/editor/EditorHelper/IndentHelper.js index 9425066a0e..685bd50ba0 100644 --- a/src/editor/EditorHelper/IndentHelper.js +++ b/src/editor/EditorHelper/IndentHelper.js @@ -26,7 +26,7 @@ define(function (require, exports, module) { const _ = require("thirdparty/lodash"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror = require("editor/CodeMirrorCompat"), PreferencesManager = require("preferences/PreferencesManager"), EditorPreferences = require("./EditorPreferences"); diff --git a/src/editor/EditorHelper/InlineWidgetHelper.js b/src/editor/EditorHelper/InlineWidgetHelper.js index 6dd904ec52..bff24c53ce 100644 --- a/src/editor/EditorHelper/InlineWidgetHelper.js +++ b/src/editor/EditorHelper/InlineWidgetHelper.js @@ -27,7 +27,7 @@ define(function (require, exports, module) { const AnimationUtils = require("utils/AnimationUtils"), Async = require("utils/Async"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); + CodeMirror = require("editor/CodeMirrorCompat"); /** * ****** Update actual public API doc in Editor.js ***** diff --git a/src/editor/EditorHelper/ScrollbarHelper.js b/src/editor/EditorHelper/ScrollbarHelper.js index b675566900..9e8f82b159 100644 --- a/src/editor/EditorHelper/ScrollbarHelper.js +++ b/src/editor/EditorHelper/ScrollbarHelper.js @@ -31,10 +31,8 @@ define(function (require, exports, module) { * viewport at a time (painfully slow to reach a far-off spot in a large file). A click on the * thumb is left to the native drag. * - * CodeMirror's "native" scrollbars are real overflow:scroll
s (.CodeMirror-vscrollbar / - * .CodeMirror-hscrollbar); setting their scroll offset syncs the editor, since CodeMirror listens - * to their scroll event. Unlike most native scrollbars, this webview still delivers mousedown on - * them, so we can intercept a track click. + * CodeMirror 6 renders native scrollbars on its scroll DOM element. Setting that element's scroll + * offset updates the editor, and this webview still delivers mousedown on the scrollbar track. * * @param {!Editor} editor */ @@ -48,19 +46,22 @@ define(function (require, exports, module) { // Capture phase so we can suppress the native paging before it runs. wrapper.addEventListener("mousedown", function (e) { const el = e.target; - if (e.button !== 0 || !el || !el.classList) { + if (e.button !== 0 || el !== cm.getScrollerElement()) { return; } + let axis; - if (el.classList.contains("CodeMirror-vscrollbar")) { + const rect = el.getBoundingClientRect(); + const scrollbarWidth = el.offsetWidth - el.clientWidth; + const scrollbarHeight = el.offsetHeight - el.clientHeight; + if (scrollbarWidth > 0 && e.clientX >= rect.right - scrollbarWidth) { axis = "v"; - } else if (el.classList.contains("CodeMirror-hscrollbar")) { + } else if (scrollbarHeight > 0 && e.clientY >= rect.bottom - scrollbarHeight) { axis = "h"; } else { return; } - const rect = el.getBoundingClientRect(); const view = (axis === "v") ? el.clientHeight : el.clientWidth; // visible track px const full = (axis === "v") ? el.scrollHeight : el.scrollWidth; // scrollable px if (full <= view) { diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 36e613070b..8615e2897c 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -353,7 +353,7 @@ define(function (require, exports, module) { _$hiddenEditorsContainer = $("#hidden-editors"); } // Create an editor - var editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer); + const editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer); // and hide it editor.setVisible(false); } diff --git a/src/editor/InlineTextEditor.js b/src/editor/InlineTextEditor.js index f1d09e91ff..4343e45952 100644 --- a/src/editor/InlineTextEditor.js +++ b/src/editor/InlineTextEditor.js @@ -26,7 +26,7 @@ define(function (require, exports, module) { // Load dependent modules - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), EventDispatcher = require("utils/EventDispatcher"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), @@ -99,7 +99,7 @@ define(function (require, exports, module) { var maxWidth = 0; allHostedEditors.forEach(function (editor) { - var $gutter = $(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers"); + var $gutter = $(editor._codeMirror.getGutterElement()).find(".cm-lineNumbers"); $gutter.css("min-width", ""); var curWidth = $gutter.width(); if (curWidth > maxWidth) { @@ -115,7 +115,7 @@ define(function (require, exports, module) { maxWidth = maxWidth + "px"; allHostedEditors.forEach(function (editor) { - $(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers").css("min-width", maxWidth); + $(editor._codeMirror.getGutterElement()).find(".cm-lineNumbers").css("min-width", maxWidth); // Force CodeMirror to refresh the gutter editor._codeMirror.setOption("gutters", editor._codeMirror.getOption("gutters")); diff --git a/src/extensions/default/CSSAtRuleCodeHints/unittests.js b/src/extensions/default/CSSAtRuleCodeHints/unittests.js index 40edecb5f3..1de0f2ade8 100644 --- a/src/extensions/default/CSSAtRuleCodeHints/unittests.js +++ b/src/extensions/default/CSSAtRuleCodeHints/unittests.js @@ -29,10 +29,10 @@ define(function (require, exports, module) { describe("CSS '@' rules Code Hinting", function () { - var defaultContent = "@ { \n" + - "} \n" + - " \n" + - "@m "; + const defaultContent = "@ { \n" + + "} \n" + + " \n" + + "@m "; var testDocument, testEditor; @@ -111,29 +111,9 @@ define(function (require, exports, module) { } - var modesToTest = ['css', 'scss', 'less'], - modeCounter; - - - var selectMode = function () { - return modesToTest[modeCounter]; - }; + const modesToTest = ["css", "scss", "less"]; describe("'@' rules in styles mode (selection of correct restricted block based on input)", function () { - - beforeEach(function () { - // create Editor instance (containing a CodeMirror instance) - var mock = SpecRunnerUtils.createMockEditor(defaultContent, selectMode()); - testEditor = mock.editor; - testDocument = mock.doc; - }); - - afterEach(function () { - SpecRunnerUtils.destroyMockEditor(testDocument); - testEditor = null; - testDocument = null; - }); - var testAllHints = function () { testEditor.setCursorPos({ line: 0, ch: 1 }); // after @ var hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); @@ -164,36 +144,37 @@ define(function (require, exports, module) { expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, 'c')).toBe(false); }; - for (modeCounter in modesToTest) { - it("should list all rule hints right after @", testAllHints); - it("should list filtered rule hints right after @m", testFilteredHints); - it("should not list rule hints on space", testNoHintsOnSpace); - it("should not list rule hints if the cursor is before @", testNoHints); - } + modesToTest.forEach(function (mode) { + describe(mode.toUpperCase(), function () { + beforeEach(function () { + setupTest(defaultContent, mode); + }); + + afterEach(tearDownTest); + + it("should list all rule hints right after @", testAllHints); + it("should list filtered rule hints right after @m", testFilteredHints); + it("should not list rule hints on space", testNoHintsOnSpace); + it("should not list rule hints if the cursor is before @", testNoHints); + }); + }); }); describe("'@' rules in LESS mode (selection of correct restricted block based on input)", function () { - defaultContent = "@ { \n" + - "} \n" + - " \n" + - "@m \n" + - "@green: green;\n" + - ".div { \n" + - "color: @" + - "} \n"; + const lessContent = "@ { \n" + + "} \n" + + " \n" + + "@m \n" + + "@green: green;\n" + + ".div { \n" + + "color: @" + + "} \n"; beforeEach(function () { - // create Editor instance (containing a CodeMirror instance) - var mock = SpecRunnerUtils.createMockEditor(defaultContent, "less"); - testEditor = mock.editor; - testDocument = mock.doc; + setupTest(lessContent, "less"); }); - afterEach(function () { - SpecRunnerUtils.destroyMockEditor(testDocument); - testEditor = null; - testDocument = null; - }); + afterEach(tearDownTest); it("should not list rule hints in less variable evaluation scope", function () { testEditor.setCursorPos({ line: 3, ch: 3 }); // after { @@ -204,17 +185,10 @@ define(function (require, exports, module) { describe("'@' rule hint insertion", function () { beforeEach(function () { - // create Editor instance (containing a CodeMirror instance) - var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); - testEditor = mock.editor; - testDocument = mock.doc; + setupTest(defaultContent, "css"); }); - afterEach(function () { - SpecRunnerUtils.destroyMockEditor(testDocument); - testEditor = null; - testDocument = null; - }); + afterEach(tearDownTest); it("should insert @rule selected", function () { testEditor.setCursorPos({ line: 0, ch: 1 }); // cursor after '@' @@ -231,6 +205,33 @@ define(function (require, exports, module) { }); }); + describe("'@' rule hints in embedded HTML styles", function () { + const embeddedHTMLContent = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + beforeEach(function () { + setupTest(embeddedHTMLContent, "html"); + }); + + afterEach(tearDownTest); + + it("uses the embedded CSS parser state and lists matching hints", function () { + const cursor = { line: 3, ch: 2 }; + testEditor.setCursorPos(cursor); + + const token = testEditor._codeMirror.getTokenAt(cursor); + expect(token.state.localState).toBeTruthy(); + expect(token.state.localState.context.type).toBe("at"); + + const hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); + verifyListsAreIdentical(hintList, ["@media"]); + }); + }); + }); }); - diff --git a/src/extensions/default/CSSPseudoSelectorHints/unittests.js b/src/extensions/default/CSSPseudoSelectorHints/unittests.js index db30afe488..89da0fdc99 100644 --- a/src/extensions/default/CSSPseudoSelectorHints/unittests.js +++ b/src/extensions/default/CSSPseudoSelectorHints/unittests.js @@ -31,14 +31,14 @@ define(function (require, exports, module) { describe("unit:CSS Pseudo class/element Code Hinting", function () { - var defaultContent = ".selector1: { \n" + - "} \n" + - ".selector2:: { \n" + - "} \n" + - ".selector3:n { \n" + - "} \n" + - ".selector4::f { \n" + - "} \n"; + const defaultContent = ".selector1: { \n" + + "} \n" + + ".selector2:: { \n" + + "} \n" + + ".selector3:n { \n" + + "} \n" + + ".selector4::f { \n" + + "} \n"; var testDocument, testEditor; @@ -67,28 +67,9 @@ define(function (require, exports, module) { } - var modesToTest = ['css', 'scss', 'less'], - modeCounter; - - - var selectMode = function () { - return modesToTest[modeCounter]; - }; + const modesToTest = ["css", "scss", "less"]; describe("Pseudo classes in different style modes", function () { - beforeEach(function () { - // create Editor instance (containing a CodeMirror instance) - var mock = SpecRunnerUtils.createMockEditor(defaultContent, selectMode()); - testEditor = mock.editor; - testDocument = mock.doc; - }); - - afterEach(function () { - SpecRunnerUtils.destroyMockEditor(testDocument); - testEditor = null; - testDocument = null; - }); - var testAllHints = function () { testEditor.setCursorPos({ line: 0, ch: 11 }); // after : var hintList = expectHints(CSSPseudoSelectorCodeHints.pseudoSelectorHints); @@ -112,29 +93,29 @@ define(function (require, exports, module) { expect(CSSPseudoSelectorCodeHints.pseudoSelectorHints.hasHints(testEditor, 'a')).toBe(false); }; - for (modeCounter in modesToTest) { - it("should list all Pseudo selectors right after :", testAllHints); - it("should list filtered pseudo selectors right after :n", testFilteredHints); - it("should not list rule hints if the cursor is before :", testNoHints); - } + modesToTest.forEach(function (mode) { + describe(mode.toUpperCase(), function () { + beforeEach(function () { + const mock = SpecRunnerUtils.createMockEditor(defaultContent, mode); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("should list all Pseudo selectors right after :", testAllHints); + it("should list filtered pseudo selectors right after :n", testFilteredHints); + it("should not list rule hints if the cursor is before :", testNoHints); + }); + }); }); describe("Pseudo elements in various style modes", function () { - - beforeEach(function () { - // create Editor instance (containing a CodeMirror instance) - var mock = SpecRunnerUtils.createMockEditor(defaultContent, selectMode()); - testEditor = mock.editor; - testDocument = mock.doc; - }); - - afterEach(function () { - SpecRunnerUtils.destroyMockEditor(testDocument); - testEditor = null; - testDocument = null; - }); - var testAllHints = function () { testEditor.setCursorPos({ line: 2, ch: 12 }); // after :: var hintList = expectHints(CSSPseudoSelectorCodeHints.pseudoSelectorHints); @@ -155,14 +136,72 @@ define(function (require, exports, module) { expect(CSSPseudoSelectorCodeHints.pseudoSelectorHints.hasHints(testEditor, 'c')).toBe(false); }; - for (modeCounter in modesToTest) { - it("should list all Pseudo selectors right after :", testAllHints); - it("should list filtered pseudo selectors right after ::f", testFilteredHints); - it("should not list rule hints if the cursor is before :", testNoHints); - } + modesToTest.forEach(function (mode) { + describe(mode.toUpperCase(), function () { + beforeEach(function () { + const mock = SpecRunnerUtils.createMockEditor(defaultContent, mode); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("should list all Pseudo selectors right after :", testAllHints); + it("should list filtered pseudo selectors right after ::f", testFilteredHints); + it("should not list rule hints if the cursor is before :", testNoHints); + }); + }); }); + describe("Pseudo selector hints in embedded HTML styles", function () { + const embeddedHTMLContent = ""; + + beforeEach(function () { + const mock = SpecRunnerUtils.createMockEditor(embeddedHTMLContent, "html"); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("uses the embedded CSS parser state for pseudo classes and elements", function () { + const classCursor = { line: 1, ch: 11 }; + testEditor.setCursorPos(classCursor); + + const classToken = testEditor._codeMirror.getTokenAt(classCursor); + expect(classToken.state.localState).toBeTruthy(); + expect(classToken.state.localState.context).toBeTruthy(); + + let hintList = expectHints(CSSPseudoSelectorCodeHints.pseudoSelectorHints); + verifyListsAreIdentical(hintList, ["not(selectors)", + "nth-child(n)", + "nth-last-child(n)", + "nth-last-of-type(n)", + "nth-of-type(n)"]); + + const elementCursor = { line: 2, ch: 12 }; + testEditor.setCursorPos(elementCursor); + + const elementToken = testEditor._codeMirror.getTokenAt(elementCursor); + expect(elementToken.state.localState).toBeTruthy(); + expect(elementToken.state.localState.context).toBeTruthy(); + + hintList = expectHints(CSSPseudoSelectorCodeHints.pseudoSelectorHints); + verifyListsAreIdentical(hintList, ["first-letter", "first-line"]); + }); + }); + }); }); - diff --git a/src/extensions/default/CodeFolding/Prefs.js b/src/extensions/default/CodeFolding/Prefs.js index 7d2d1e2332..c484400b4f 100644 --- a/src/extensions/default/CodeFolding/Prefs.js +++ b/src/extensions/default/CodeFolding/Prefs.js @@ -112,7 +112,11 @@ define(function (require, exports, module) { * Clears all the saved line folds for all documents. */ function clearAllFolds() { - PreferencesManager.setViewState(FOLDS_PREF_KEY, {}); + PreferencesManager.setViewState( + FOLDS_PREF_KEY, + {}, + PreferencesManager.STATE_PROJECT_CONTEXT + ); } module.exports.getFolds = getFolds; diff --git a/src/extensions/default/CodeFolding/foldhelpers/foldcode.js b/src/extensions/default/CodeFolding/foldhelpers/foldcode.js index 083018d08d..6fcc87cc26 100644 --- a/src/extensions/default/CodeFolding/foldhelpers/foldcode.js +++ b/src/extensions/default/CodeFolding/foldhelpers/foldcode.js @@ -3,9 +3,13 @@ // Based on http://codemirror.net/addon/fold/foldcode.js // Modified by Patrick Oladimeji for Brackets +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + define(function (require, exports, module) { - var CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), prefs = require("Prefs"); /** diff --git a/src/extensions/default/CodeFolding/foldhelpers/foldgutter.js b/src/extensions/default/CodeFolding/foldhelpers/foldgutter.js index bafe7186d6..78f8f43ca3 100644 --- a/src/extensions/default/CodeFolding/foldhelpers/foldgutter.js +++ b/src/extensions/default/CodeFolding/foldhelpers/foldgutter.js @@ -3,24 +3,57 @@ // Based on http://codemirror.net/addon/fold/foldgutter.js // Modified by Patrick Oladimeji for Brackets +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + define(function (require, exports, module) { - var CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), prefs = require("Prefs"); function State(options) { this.options = options; this.from = this.to = 0; + this.changeUpdate = null; + this.viewportRefresh = null; + this.active = true; } function parseOptions(opts) { - if (opts === true) { opts = {}; } + opts = opts === true ? {} : Object.assign({}, opts || {}); if (!opts.gutter) { opts.gutter = "CodeMirror-foldgutter"; } if (!opts.indicatorOpen) { opts.indicatorOpen = "CodeMirror-foldgutter-open"; } if (!opts.indicatorFolded) { opts.indicatorFolded = "CodeMirror-foldgutter-folded"; } return opts; } + function isActive(cm, state) { + return Boolean( + state && + state.active && + !cm._destroyed && + cm.state.foldGutter === state + ); + } + + function clearTimer(state, name) { + if (state && state[name] !== null) { + window.clearTimeout(state[name]); + state[name] = null; + } + } + + function scheduleChangeUpdate(cm, state, delay, update) { + clearTimer(state, "changeUpdate"); + state.changeUpdate = window.setTimeout(function () { + state.changeUpdate = null; + if (isActive(cm, state)) { + update(); + } + }, delay); + } + /** * Utility for creating fold markers in fold gutter * @param {string} spec the className for the marker @@ -48,8 +81,14 @@ define(function (require, exports, module) { * @param {!number} to the ending line for the update */ function updateFoldInfo(cm, from, to) { + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } + cm._lineFolds = cm._lineFolds || {}; + var minFoldSize = prefs.getSetting("minFoldSize") || 2; - var opts = cm.state.foldGutter.options; + var opts = state.options; var fade = prefs.getSetting("hideUntilMouseover"); var $gutter = $(cm.getGutterElement()); var i = from; @@ -81,10 +120,16 @@ define(function (require, exports, module) { viewport change event isn't fired by CodeMirror. The setTimeout is a workaround to trigger the gutter update after the viewport has been drawn. */ - if (i === to) { - window.setTimeout(function () { + if (i === to && state.viewportRefresh === null) { + state.viewportRefresh = window.setTimeout(function () { + state.viewportRefresh = null; + if (!isActive(cm, state)) { + return; + } var vp = cm.getViewport(); - updateFoldInfo(cm, vp.from, vp.to); + if (vp.from !== vp.to) { + updateFoldInfo(cm, vp.from, vp.to); + } }, 200); } @@ -128,14 +173,18 @@ define(function (require, exports, module) { * @param {?number} to the end line number for the update */ function updateInViewport(cm, from, to) { - var vp = cm.getViewport(), state = cm.state.foldGutter; + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { return; } + const vp = cm.getViewport(); from = isNaN(from) ? vp.from : from; to = isNaN(to) ? vp.to : to; - if (!state) { return; } cm.operation(function () { - updateFoldInfo(cm, from, to); + if (isActive(cm, state)) { + updateFoldInfo(cm, from, to); + } }); + if (!isActive(cm, state)) { return; } state.from = from; state.to = to; } @@ -260,6 +309,10 @@ define(function (require, exports, module) { * @param {!Object} changeObj detailed information about the change that occurred in the document */ function onChange(cm, changeObj) { + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } if (changeObj.origin === "setValue") { //text content has changed outside of brackets var folds = cm.getValidFolds(cm._lineFolds); cm._lineFolds = folds; @@ -267,7 +320,6 @@ define(function (require, exports, module) { cm.foldCode(+line); }); } else { - var state = cm.state.foldGutter; var lineChanges = changeObj.text.length - changeObj.removed.length; // for undo actions that add new line(s) to the document first update the folds cache as normal // and then update the folds cache with any line folds that exist in the new lines @@ -282,10 +334,9 @@ define(function (require, exports, module) { } state.from = changeObj.from.line; state.to = 0; - window.clearTimeout(state.changeUpdate); - state.changeUpdate = window.setTimeout(function () { + scheduleChangeUpdate(cm, state, 600, function () { updateInViewport(cm); - }, 600); + }); } } @@ -294,9 +345,11 @@ define(function (require, exports, module) { * @param {!CodeMirror} cm the CodeMirror instance for the active editor */ function onViewportChange(cm) { - var state = cm.state.foldGutter; - window.clearTimeout(state.changeUpdate); - state.changeUpdate = window.setTimeout(function () { + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } + scheduleChangeUpdate(cm, state, 400, function () { var vp = cm.getViewport(); if (state.from === state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { updateInViewport(cm); @@ -316,7 +369,7 @@ define(function (require, exports, module) { } }); } - }, 400); + }); } /** @@ -325,13 +378,15 @@ define(function (require, exports, module) { * @param {!CodeMirror} cm the CodeMirror instance for the active editor */ function onCursorActivity(cm) { - var state = cm.state.foldGutter; - var vp = cm.getViewport(); - window.clearTimeout(state.changeUpdate); - state.changeUpdate = window.setTimeout(function () { + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } + const vp = cm.getViewport(); + scheduleChangeUpdate(cm, state, 400, function () { //need to render the entire visible viewport to remove fold marks rendered from previous selections if any updateInViewport(cm, vp.from, vp.to); - }, 400); + }); } /** @@ -341,7 +396,10 @@ define(function (require, exports, module) { * @param {!Object} to the ch and line position that designates the end of the region */ function onFold(cm, from, to) { - var state = cm.state.foldGutter; + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } updateFoldInfo(cm, from.line, from.line + 1); } @@ -352,12 +410,35 @@ define(function (require, exports, module) { * @param {!{line:number, ch:number}} to the ch and line position that designates the end of the region */ function onUnFold(cm, from, to) { - var state = cm.state.foldGutter; - var vp = cm.getViewport(); + const state = cm.state.foldGutter; + if (!isActive(cm, state)) { + return; + } + const vp = cm.getViewport(); delete cm._lineFolds[from.line]; updateFoldInfo(cm, from.line, to.line || vp.to); } + function disableFoldGutter(cm) { + const state = cm.state.foldGutter; + if (!state) { + return; + } + state.active = false; + clearTimer(state, "changeUpdate"); + clearTimer(state, "viewportRefresh"); + cm.clearGutter(state.options.gutter); + if (typeof state.options.onGutterClick === "function") { + cm.off("gutterClick", state.options.onGutterClick); + } + cm.off("change", onChange); + cm.off("viewportChange", onViewportChange); + cm.off("cursorActivity", onCursorActivity); + cm.off("fold", onFold); + cm.off("unfold", onUnFold); + cm.state.foldGutter = null; + } + /** * Initialises the fold gutter and registers event handlers for changes to document, viewport * and user interactions. @@ -365,27 +446,23 @@ define(function (require, exports, module) { function init() { CodeMirror.defineOption("foldGutter", false, function (cm, val, old) { if (old && old !== CodeMirror.Init) { - cm.clearGutter(cm.state.foldGutter.options.gutter); - cm.state.foldGutter = null; - cm.off("gutterClick", old.onGutterClick); - cm.off("change", onChange); - cm.off("viewportChange", onViewportChange); - cm.off("cursorActivity", onCursorActivity); - - cm.off("fold", onFold); - cm.off("unfold", onUnFold); - cm.off("swapDoc", updateInViewport); + disableFoldGutter(cm); } if (val) { + cm._lineFolds = cm._lineFolds || {}; cm.state.foldGutter = new State(parseOptions(val)); updateInViewport(cm); - cm.on("gutterClick", val.onGutterClick); + if (typeof cm.state.foldGutter.options.onGutterClick === "function") { + cm.on( + "gutterClick", + cm.state.foldGutter.options.onGutterClick + ); + } cm.on("change", onChange); cm.on("viewportChange", onViewportChange); cm.on("cursorActivity", onCursorActivity); cm.on("fold", onFold); cm.on("unfold", onUnFold); - cm.on("swapDoc", updateInViewport); } }); } diff --git a/src/extensions/default/CodeFolding/foldhelpers/handlebarsFold.js b/src/extensions/default/CodeFolding/foldhelpers/handlebarsFold.js index 037db7b7bc..3ad7a8ccd4 100644 --- a/src/extensions/default/CodeFolding/foldhelpers/handlebarsFold.js +++ b/src/extensions/default/CodeFolding/foldhelpers/handlebarsFold.js @@ -27,7 +27,7 @@ define(function (require, exports, module) { - var CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), _ = brackets.getModule("thirdparty/lodash"), StringUtils = brackets.getModule("utils/StringUtils"); diff --git a/src/extensions/default/CodeFolding/foldhelpers/indentFold.js b/src/extensions/default/CodeFolding/foldhelpers/indentFold.js index 4983ca4aa0..2fbd894d44 100644 --- a/src/extensions/default/CodeFolding/foldhelpers/indentFold.js +++ b/src/extensions/default/CodeFolding/foldhelpers/indentFold.js @@ -6,7 +6,7 @@ define(function (require, exports, module) { - var CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), cols = CodeMirror.countColumn, pos = CodeMirror.Pos; diff --git a/src/extensions/default/CodeFolding/foldhelpers/languageFold.js b/src/extensions/default/CodeFolding/foldhelpers/languageFold.js new file mode 100644 index 0000000000..29f5b5270a --- /dev/null +++ b/src/extensions/default/CodeFolding/foldhelpers/languageFold.js @@ -0,0 +1,391 @@ +/* + * CodeMirror, copyright (c) by Marijn Haverbeke and others + * Distributed under an MIT license: https://codemirror.net/5/LICENSE + * + * Adapted for Phoenix's CodeMirror 6 compatibility layer from the CodeMirror + * 5 brace-fold, comment-fold, and markdown-fold addons. + */ + +/*! DONT_STRIP_MINIFY: CodeMirror 5-derived compatibility implementation. + * See thirdparty/licences/codemirror5-derived.markdown. + */ + +define(function (require, exports, module) { + + const CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), + CM6 = brackets.getModule("thirdparty/CodeMirror6/codemirror6"); + + let initialized = false; + + function clipPosition(cm, position) { + const line = Math.max(cm.firstLine(), Math.min(position.line, cm.lastLine())); + return CodeMirror.Pos( + line, + Math.max(0, Math.min(position.ch || 0, cm.getLine(line).length)) + ); + } + + function bracketFolding(pairs) { + return function (cm, start) { + if (!start || + start.line < cm.firstLine() || + start.line > cm.lastLine()) { + return null; + } + const line = start.line; + const lineText = cm.getLine(line); + if (typeof lineText !== "string") { + return null; + } + + function findOpening(pair) { + let tokenType; + let at = start.ch; + let pass = 0; + + while (true) { + const found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1); + if (found === -1) { + if (pass === 1) { + break; + } + pass = 1; + at = lineText.length; + continue; + } + if (pass === 1 && found < start.ch) { + break; + } + tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)); + if (!/^(comment|string)/.test(tokenType || "")) { + return { + ch: found + 1, + pair: pair, + tokenType: tokenType + }; + } + at = found - 1; + } + } + + function findRange(found) { + let count = 1; + let end; + let endCh; + + outer: + for (let lineNumber = line; lineNumber <= cm.lastLine(); lineNumber++) { + const text = cm.getLine(lineNumber); + let position = lineNumber === line ? found.ch : 0; + + while (true) { + let nextOpen = text.indexOf(found.pair[0], position); + let nextClose = text.indexOf(found.pair[1], position); + if (nextOpen < 0) { + nextOpen = text.length; + } + if (nextClose < 0) { + nextClose = text.length; + } + position = Math.min(nextOpen, nextClose); + if (position === text.length) { + break; + } + if (cm.getTokenTypeAt(CodeMirror.Pos(lineNumber, position + 1)) === + found.tokenType) { + if (position === nextOpen) { + count++; + } else if (!--count) { + end = lineNumber; + endCh = position; + break outer; + } + } + position++; + } + } + + if (end === undefined || line === end) { + return null; + } + return { + from: CodeMirror.Pos(line, found.ch), + to: CodeMirror.Pos(end, endCh) + }; + } + + const openings = []; + pairs.forEach(function (pair) { + const opening = findOpening(pair); + if (opening) { + openings.push(opening); + } + }); + openings.sort(function (left, right) { + return left.ch - right.ch; + }); + + for (let index = 0; index < openings.length; index++) { + const range = findRange(openings[index]); + if (range) { + return range; + } + } + return null; + }; + } + + function hasImport(cm, line) { + if (line < cm.firstLine() || line > cm.lastLine()) { + return null; + } + let start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) { + start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + } + if (start.type !== "keyword" || start.string !== "import") { + return null; + } + for (let lineNumber = line; + lineNumber <= Math.min(cm.lastLine(), line + 10); + lineNumber++) { + const semicolon = cm.getLine(lineNumber).indexOf(";"); + if (semicolon !== -1) { + return { + end: CodeMirror.Pos(lineNumber, semicolon), + startCh: start.end + }; + } + } + return null; + } + + function importFold(cm, start) { + const startLine = start.line; + const first = hasImport(cm, startLine); + const previous = hasImport(cm, startLine - 2); + if (!first || hasImport(cm, startLine - 1) || + (previous && previous.end.line === startLine - 1)) { + return null; + } + + let end = first.end; + while (true) { + const next = hasImport(cm, end.line + 1); + if (!next) { + break; + } + end = next.end; + } + return { + from: clipPosition(cm, CodeMirror.Pos(startLine, first.startCh + 1)), + to: end + }; + } + + function hasInclude(cm, line) { + if (line < cm.firstLine() || line > cm.lastLine()) { + return null; + } + let start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) { + start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + } + if (start.type === "meta" && start.string.slice(0, 8) === "#include") { + return start.start + 8; + } + return null; + } + + function includeFold(cm, start) { + const startLine = start.line; + const first = hasInclude(cm, startLine); + if (first === null || hasInclude(cm, startLine - 1) !== null) { + return null; + } + + let end = startLine; + while (hasInclude(cm, end + 1) !== null) { + end++; + } + return { + from: CodeMirror.Pos(startLine, first + 1), + to: clipPosition(cm, CodeMirror.Pos(end)) + }; + } + + function commentFold(cm, start) { + const mode = cm.getModeAt(start); + const startToken = mode.blockCommentStart; + const endToken = mode.blockCommentEnd; + if (!startToken || !endToken) { + return; + } + + const line = start.line; + const lineText = cm.getLine(line); + let startCh; + let at = start.ch; + let pass = 0; + + while (true) { + const found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1); + if (found === -1) { + if (pass === 1) { + return; + } + pass = 1; + at = lineText.length; + continue; + } + if (pass === 1 && found < start.ch) { + return; + } + if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)) || "") && + (found === 0 || + lineText.slice(found - endToken.length, found) === endToken || + !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found)) || ""))) { + startCh = found + startToken.length; + break; + } + at = found - 1; + } + + let depth = 1; + let end; + let endCh; + outer: + for (let lineNumber = line; lineNumber <= cm.lastLine(); lineNumber++) { + const text = cm.getLine(lineNumber); + let position = lineNumber === line ? startCh : 0; + while (true) { + let nextOpen = text.indexOf(startToken, position); + let nextClose = text.indexOf(endToken, position); + if (nextOpen < 0) { + nextOpen = text.length; + } + if (nextClose < 0) { + nextClose = text.length; + } + position = Math.min(nextOpen, nextClose); + if (position === text.length) { + break; + } + if (position === nextOpen) { + depth++; + } else if (!--depth) { + end = lineNumber; + endCh = position; + break outer; + } + position++; + } + } + + if (end === undefined || (line === end && endCh === startCh)) { + return; + } + return { + from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh) + }; + } + + function markdownFold(cm, start) { + const maxDepth = 100; + + function isHeader(lineNumber) { + const tokenType = cm.getTokenTypeAt(CodeMirror.Pos(lineNumber, 0)); + return tokenType && /\bheader\b/.test(tokenType); + } + + function headerLevel(lineNumber, line, nextLine) { + let match = line && line.match(/^#+/); + if (match && isHeader(lineNumber)) { + return match[0].length; + } + match = nextLine && nextLine.match(/^[=-]+\s*$/); + if (match && isHeader(lineNumber + 1)) { + return nextLine[0] === "=" ? 1 : 2; + } + return maxDepth; + } + + const firstLine = cm.getLine(start.line); + let nextLine = cm.getLine(start.line + 1); + const level = headerLevel(start.line, firstLine, nextLine); + if (level === maxDepth) { + return; + } + + const lastLine = cm.lastLine(); + let end = start.line; + let nextNextLine = cm.getLine(end + 2); + while (end < lastLine) { + if (headerLevel(end + 1, nextLine, nextNextLine) <= level) { + break; + } + end++; + nextLine = nextNextLine; + nextNextLine = cm.getLine(end + 2); + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, cm.getLine(end).length) + }; + } + + function syntaxFold(cm, start) { + if (!cm._view || !CM6.foldable) { + return; + } + const mode = cm.getModeAt(start); + if (CodeMirror.fold && CodeMirror.fold.xml && + (mode.name === "xml" || + mode.helperType === "xml" || + mode.helperType === "html")) { + return CodeMirror.fold.xml(cm, start); + } + const state = cm._view.state; + const localLine = start.line - cm.firstLine(); + if (localLine < 0 || localLine >= state.doc.lines) { + return; + } + const line = state.doc.line(localLine + 1); + const range = CM6.foldable(state, line.from, line.to); + if (!range) { + return; + } + return { + from: cm.posFromIndex(range.from), + to: cm.posFromIndex(range.to) + }; + } + + function init() { + if (initialized) { + return; + } + initialized = true; + + CodeMirror.registerHelper("fold", "brace", bracketFolding([ + ["{", "}"], + ["[", "]"] + ])); + CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ])); + CodeMirror.registerHelper("fold", "import", importFold); + CodeMirror.registerHelper("fold", "include", includeFold); + CodeMirror.registerHelper("fold", "markdown", markdownFold); + CodeMirror.registerGlobalHelper("fold", "comment", function (mode) { + return mode.blockCommentStart && mode.blockCommentEnd; + }, commentFold); + } + + exports.init = init; + exports.syntaxFold = syntaxFold; +}); diff --git a/src/extensions/default/CodeFolding/main.js b/src/extensions/default/CodeFolding/main.js index f60556ec69..2ec8f30a79 100644 --- a/src/extensions/default/CodeFolding/main.js +++ b/src/extensions/default/CodeFolding/main.js @@ -29,7 +29,7 @@ define(function (require, exports, module) { - var CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = brackets.getModule("editor/CodeMirrorCompat"), Strings = brackets.getModule("strings"), AppInit = brackets.getModule("utils/AppInit"), CommandManager = brackets.getModule("command/CommandManager"), @@ -51,14 +51,10 @@ define(function (require, exports, module) { codeFoldingMenuDivider = "codefolding.divider", collapseKey = "Ctrl-Shift-{", expandKey = "Ctrl-Shift-}"; + const GUTTER_EVENT_NAMESPACE = ".CodeFolding"; ExtensionUtils.loadStyleSheet(module, "main.less"); - // Load CodeMirror addons - brackets.getModule(["thirdparty/CodeMirror/addon/fold/brace-fold"]); - brackets.getModule(["thirdparty/CodeMirror/addon/fold/comment-fold"]); - brackets.getModule(["thirdparty/CodeMirror/addon/fold/markdown-fold"]); - // Still using slightly modified versions of the foldcode.js and foldgutter.js since we // need to modify the gutter click handler to take care of some collapse and expand features // e.g. collapsing all children when 'alt' key is pressed @@ -66,6 +62,7 @@ define(function (require, exports, module) { foldCode = require("foldhelpers/foldcode"), indentFold = require("foldhelpers/indentFold"), handlebarsFold = require("foldhelpers/handlebarsFold"), + languageFold = require("foldhelpers/languageFold"), selectionFold = require("foldhelpers/foldSelected"); @@ -122,9 +119,20 @@ define(function (require, exports, module) { } var cm = editor._codeMirror; + if (typeof cm.getValidFolds !== "function" && + typeof CodeMirror.installExtensions === "function") { + CodeMirror.installExtensions(cm); + } + if (typeof cm.getValidFolds !== "function") { + cm._lineFolds = {}; + return; + } var viewState = ViewStateManager.getViewState(editor.document.file); var path = editor.document.file.fullPath; - var folds = cm._lineFolds || prefs.getFolds(path) || {}; + const currentFolds = cm._lineFolds || {}; + const folds = Object.keys(currentFolds).length ? + currentFolds : + prefs.getFolds(path) || {}; //separate out selection folds from non-selection folds var nonSelectionFolds = {}, selectionFolds = {}, range; @@ -283,23 +291,23 @@ define(function (require, exports, module) { */ function setupGutterEventListeners(editor) { var cm = editor._codeMirror; + const $gutter = $(cm.getGutterElement()); $(editor.getRootElement()).addClass("folding-enabled"); cm.setOption("foldGutter", {onGutterClick: onGutterClick}); - $(cm.getGutterElement()).on({ - mouseenter: function () { - if (prefs.getSetting("hideUntilMouseover")) { - foldGutter.updateInViewport(cm); - } else { - $(editor.getRootElement()).addClass("over-gutter"); - } - }, - mouseleave: function () { - if (prefs.getSetting("hideUntilMouseover")) { - clearGutter(editor); - } else { - $(editor.getRootElement()).removeClass("over-gutter"); - } + $gutter.off(GUTTER_EVENT_NAMESPACE); + $gutter.on("mouseenter" + GUTTER_EVENT_NAMESPACE, function () { + if (prefs.getSetting("hideUntilMouseover")) { + foldGutter.updateInViewport(cm); + } else { + $(editor.getRootElement()).addClass("over-gutter"); + } + }); + $gutter.on("mouseleave" + GUTTER_EVENT_NAMESPACE, function () { + if (prefs.getSetting("hideUntilMouseover")) { + clearGutter(editor); + } else { + $(editor.getRootElement()).removeClass("over-gutter"); } }); } @@ -309,9 +317,10 @@ define(function (require, exports, module) { * @param {Editor} editor the editor instance whose gutter should be removed */ function removeGutters(editor) { - Editor.unregisterGutter(GUTTER_NAME); - $(editor.getRootElement()).removeClass("folding-enabled"); - CodeMirror.defineOption("foldGutter", false, null); + const cm = editor._codeMirror; + $(cm.getGutterElement()).off(GUTTER_EVENT_NAMESPACE); + $(editor.getRootElement()).removeClass("folding-enabled over-gutter"); + cm.setOption("foldGutter", false); } /** @@ -319,9 +328,14 @@ define(function (require, exports, module) { * @param {Editor} editor the editor instance where gutter should be added. */ function enableFoldingInEditor(editor) { + const cm = editor && editor._codeMirror; + if (!cm || cm._destroyed || + (Object.prototype.hasOwnProperty.call(cm, "_view") && !cm._view)) { + return; + } restoreLineFolds(editor); setupGutterEventListeners(editor); - editor._codeMirror.refresh(); + cm.refresh(); } /** @@ -332,7 +346,8 @@ define(function (require, exports, module) { * @param {Editor} previous the previous editor */ function onActiveEditorChanged(event, current, previous) { - if (current && !current._codeMirror._lineFolds) { + if (current && current._codeMirror && !current._codeMirror._destroyed && + !current._codeMirror.state.foldGutter) { enableFoldingInEditor(current); } if (previous) { @@ -371,8 +386,9 @@ define(function (require, exports, module) { // Remove gutter & revert collapsed sections in all currently open editors Editor.forEveryEditor(function (editor) { CodeMirror.commands.unfoldAll(editor._codeMirror); + removeGutters(editor); }); - removeGutters(); + Editor.unregisterGutter(GUTTER_NAME); } /** @@ -383,6 +399,7 @@ define(function (require, exports, module) { foldCode.init(); foldGutter.init(); + languageFold.init(); // Many CodeMirror modes specify which fold helper should be used for that language. For a few that // don't, we register helpers explicitly here. We also register a global helper for generic indent-based @@ -393,6 +410,9 @@ define(function (require, exports, module) { CodeMirror.registerGlobalHelper("fold", "indent", function (mode, cm) { return prefs.getSetting("alwaysUseIndentFold"); }, indentFold); + CodeMirror.registerGlobalHelper("fold", "cm6Syntax", function (mode, cm) { + return Boolean(cm && cm._view); + }, languageFold.syntaxFold); CodeMirror.registerHelper("fold", "handlebars", handlebarsFold); CodeMirror.registerHelper("fold", "htmlhandlebars", handlebarsFold); diff --git a/src/extensions/default/CodeFolding/main.less b/src/extensions/default/CodeFolding/main.less index 541fdcc913..bf3fd9dd79 100644 --- a/src/extensions/default/CodeFolding/main.less +++ b/src/extensions/default/CodeFolding/main.less @@ -19,7 +19,10 @@ } } -.CodeMirror.over-gutter, .CodeMirror-activeline { +.CodeMirror.over-gutter, +.CodeMirror-activeline, +.CodeMirror-activeline-gutter, +.cm-activeLineGutter { .CodeMirror-foldgutter-open:after { color: @color-triangle-mouseover; } @@ -39,11 +42,8 @@ padding-top: 2px; } -.CodeMirror-gutter-elt { - height: 100% !important; -} - -.CodeMirror.folding-enabled .CodeMirror-linenumber { +.CodeMirror.folding-enabled .CodeMirror-linenumber, +.CodeMirror.folding-enabled .cm-lineNumbers .cm-gutterElement { // Normally linenumber gutter has large right-padding to separate it from the code's text. But the // folding gutter provides that same separation, so we need much less padding when it's displayed. padding-right: 5px; @@ -52,7 +52,8 @@ // If line numbers are not shown and codefolding is enabled we remove the left padding. // We add the same padding below to the fold gutter .show-line-padding { - .folding-enabled.linenumber-disabled pre { + .folding-enabled.linenumber-disabled pre, + .folding-enabled.linenumber-disabled .cm-line { padding-left: 0; } } diff --git a/src/extensions/default/CodeFolding/unittests.js b/src/extensions/default/CodeFolding/unittests.js index 18ae99a0fa..6ef5b494f0 100644 --- a/src/extensions/default/CodeFolding/unittests.js +++ b/src/extensions/default/CodeFolding/unittests.js @@ -4,19 +4,21 @@ * @date 01/08/2015 18:34 */ -/*global describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, awaitsForDone, awaitsFor, awaits*/ +/*global describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, awaitsForDone, awaitsFor*/ define(function (require, exports, module) { var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"); - describe("individualrun:Code Folding", function () { + describe("integration:Code Folding", function () { var testWindow, testEditor, EditorManager, DocumentManager, CommandManager, + ExtensionLoader, PreferencesManager, + ViewStateManager, prefs, cm, gutterName = "CodeMirror-foldgutter", @@ -84,10 +86,29 @@ define(function (require, exports, module) { DocumentManager = testWindow.brackets.test.DocumentManager; PreferencesManager = testWindow.brackets.test.PreferencesManager; CommandManager = testWindow.brackets.test.CommandManager; + ExtensionLoader = testWindow.brackets.test.ExtensionLoader; + ViewStateManager = testWindow.require("view/ViewStateManager"); prefs = PreferencesManager.getExtensionPrefs("code-folding"); } + function getCodeFoldingModule(moduleName) { + const extensionRequire = ExtensionLoader.getRequireContextForExtension("CodeFolding"); + return extensionRequire(moduleName); + } + + function resetPreferences() { + setPreference("enabled", true); + setPreference("minFoldSize", 2); + setPreference("saveFoldStates", true); + setPreference("alwaysUseIndentFold", false); + setPreference("hideUntilMouseover", false); + setPreference("maxFoldLevel", 2); + setPreference("makeSelectionsFoldable", true); + getCodeFoldingModule("Prefs").clearAllFolds(); + ViewStateManager.reset(); + } + /** * Sets up the test window and loads the test project */ @@ -155,7 +176,11 @@ define(function (require, exports, module) { if (!lineInfo || !lineInfo.gutterMarkers) { return; } - var classes = lineInfo.gutterMarkers[gutterName].classList; + const marker = lineInfo.gutterMarkers[gutterName]; + if (!marker) { + return; + } + const classes = marker.classList; if (classes && classes.contains(foldMarkerClosed)) { return {line: lineInfo.line, type: folded}; } else if (classes && classes.contains(foldMarkerOpen)) { @@ -169,9 +194,16 @@ define(function (require, exports, module) { * * @returns {Array} An array of objects containing the line and the type of marker. */ - function getGutterFoldMarks() { + function getGutterFoldMarks(includeAllLines) { testEditor = EditorManager.getCurrentFullEditor(); cm = testEditor._codeMirror; + if (includeAllLines) { + getCodeFoldingModule("foldhelpers/foldgutter").updateInViewport( + cm, + cm.firstLine(), + cm.lastLine() + 1 + ); + } var marks = []; cm.eachLine(function (lineHandle) { var lineInfo = cm.lineInfo(lineHandle); @@ -226,8 +258,14 @@ define(function (require, exports, module) { */ async function selectTextInEditor(start, end) { cm.setSelection(start, end); - //wait for foldmarks to be rendered - await awaits(500); + await awaitsFor(function () { + return cm.state.foldGutter && + cm.state.foldGutter.changeUpdate !== null; + }, "fold gutter refresh to be scheduled"); + await awaitsFor(function () { + return cm.state.foldGutter && + cm.state.foldGutter.changeUpdate === null; + }, "fold gutter refresh to complete"); } beforeAll(async function () { @@ -240,6 +278,247 @@ define(function (require, exports, module) { await tearDown(); }); + it("supports standalone CM6 folding without Phoenix editor setup", function () { + const CodeMirror = testWindow.brackets.getModule("editor/CodeMirrorCompat"); + const holder = testWindow.document.createElement("div"); + testWindow.document.body.appendChild(holder); + const standalone = new CodeMirror(holder, { + value: "function answer() {\n return 42;\n}", + mode: "javascript", + foldGutter: true + }); + const range = { + from: {line: 0, ch: 19}, + to: {line: 2, ch: 0} + }; + + try { + expect(standalone._lineFolds).toEqual({}); + expect(function () { + standalone.foldCode(0, {range: range}); + }).not.toThrow(); + expect(standalone.isFolded(0)).toEqual(range); + standalone.unfoldCode(0, {range: range}); + expect(standalone.isFolded(0)).toBeFalsy(); + } finally { + standalone.destroy(); + holder.remove(); + } + }); + + it("does not reschedule a deferred fold-gutter refresh after destroy", function () { + const CodeMirror = testWindow.brackets.getModule("editor/CodeMirrorCompat"); + const foldGutter = getCodeFoldingModule("foldhelpers/foldgutter"); + const holder = testWindow.document.createElement("div"); + testWindow.document.body.appendChild(holder); + const standalone = new CodeMirror(holder, { + value: "function answer() {\n return 42;\n}", + mode: "javascript", + foldGutter: true + }); + const state = standalone.state.foldGutter; + testWindow.clearTimeout(state.viewportRefresh); + state.viewportRefresh = null; + + const originalSetTimeout = testWindow.setTimeout; + const scheduledCallbacks = []; + testWindow.setTimeout = function (callback) { + scheduledCallbacks.push(callback); + return scheduledCallbacks.length; + }; + try { + foldGutter.updateInViewport(standalone, 0, 0); + expect(scheduledCallbacks.length).toBe(1); + + standalone.destroy(); + scheduledCallbacks.shift()(); + + expect(scheduledCallbacks.length).toBe(0); + expect(state.viewportRefresh).toBeNull(); + } finally { + testWindow.setTimeout = originalSetTimeout; + standalone.destroy(); + holder.remove(); + } + }); + + it("uses global line coordinates when syntax-folding linked subviews", function () { + const CodeMirror = testWindow.brackets.getModule("editor/CodeMirrorCompat"); + const languageFold = getCodeFoldingModule("foldhelpers/languageFold"); + const rootDocument = new CodeMirror.Doc( + "prefix\nfunction answer() {\n return 42;\n}\nsuffix", + "javascript" + ); + const subview = rootDocument.linkedDoc({ + from: 1, + to: 4 + }); + + try { + const range = languageFold.syntaxFold( + subview._adapter, + {line: 1, ch: 0} + ); + + expect(range).toBeTruthy(); + expect(range.from.line).toBe(1); + expect(range.to.line).toBe(3); + } finally { + subview.unlinkDoc(rootDocument); + subview._adapter.destroy(); + rootDocument._adapter.destroy(); + } + }); + + it("ignores a stale fold-gutter line after the document shrinks", async function () { + const CodeMirror = testWindow.brackets.getModule("editor/CodeMirrorCompat"); + await openTestFile("test.js"); + const staleLine = cm.lastLine() + 1; + + expect(function () { + CodeMirror.fold.brace(cm, CodeMirror.Pos(staleLine, 0)); + }).not.toThrow(); + expect(CodeMirror.fold.brace( + cm, + CodeMirror.Pos(staleLine, 0) + )).toBeNull(); + }); + + it("restores folding extensions before enabling a CM6 editor", async function () { + await openTestFile("test.js"); + cm.setOption("foldGutter", false); + delete cm.getValidFolds; + + EditorManager.trigger("activeEditorChange", testEditor, testEditor); + + expect(typeof cm.getValidFolds).toBe("function"); + expect(cm.state.foldGutter).toBeTruthy(); + }); + + it("updates fold-gutter markers after vertically scrolling the CM6 viewport", async function () { + const CodeMirror = testWindow.brackets.getModule("editor/CodeMirrorCompat"); + const holder = testWindow.document.createElement("div"); + const targetLine = 210; + const lines = Array.from({length: 220}, function (_value, line) { + if (line === targetLine) { + return "function farAwayFold() {"; + } + if (line === targetLine + 3) { + return "}"; + } + return ` const value${line} = ${line};`; + }); + holder.style.display = "block"; + holder.style.width = "600px"; + holder.style.height = "120px"; + holder.style.position = "fixed"; + holder.style.left = "0"; + holder.style.top = "0"; + testWindow.document.body.appendChild(holder); + + const standalone = new CodeMirror(holder, { + value: lines.join("\n"), + mode: "javascript", + lineNumbers: true, + gutters: ["CodeMirror-linenumbers", gutterName], + foldGutter: { + rangeFinder: function (_codeMirror, position) { + if (position.line !== targetLine) { + return; + } + return { + from: CodeMirror.Pos(targetLine, lines[targetLine].length), + to: CodeMirror.Pos(targetLine + 3, 0) + }; + } + } + }); + + try { + standalone.setSize(600, 120); + standalone.refresh(); + + await awaitsFor(function () { + const viewport = standalone.getViewport(); + const scrollInfo = standalone.getScrollInfo(); + return scrollInfo.clientHeight > 0 && + scrollInfo.height > scrollInfo.clientHeight && + standalone.defaultTextHeight() > 0 && + viewport.from === 0 && + viewport.to > viewport.from && + viewport.to < targetLine; + }, "CM6 folding test editor to expose its initial viewport"); + + expect(gutterMarkState(standalone.lineInfo(targetLine))).toBeUndefined(); + + standalone.scrollTo( + 0, + standalone.getScrollInfo().height + ); + await awaitsFor(function () { + const viewport = standalone.getViewport(); + return standalone.getScrollInfo().top > 0 && + viewport.from <= targetLine && + viewport.to > targetLine; + }, "CM6 folding test editor to scroll to the target line"); + await awaitsFor(function () { + const markerState = gutterMarkState( + standalone.lineInfo(targetLine) + ); + return markerState && + markerState.line === targetLine && + markerState.type === open; + }, "fold gutter marker to update for the scrolled CM6 viewport"); + await awaitsFor(function () { + return Boolean( + standalone.getGutterElement() + .querySelector("." + foldMarkerOpen) + ); + }, "fold gutter marker to render in the scrolled CM6 viewport"); + } finally { + standalone.destroy(); + holder.remove(); + } + }); + + it("does not accumulate gutter hover handlers when folding is toggled", async function () { + resetPreferences(); + await openTestFile("test.js"); + + const foldGutter = getCodeFoldingModule("foldhelpers/foldgutter"); + const gutterElement = cm.getGutterElement(); + const originalUpdateInViewport = foldGutter.updateInViewport; + let updateCount = 0; + + async function setFoldingEnabled(enabled) { + setPreference("enabled", enabled); + await awaitsFor(function () { + return Boolean(cm.state.foldGutter) === enabled; + }, `code folding to be ${enabled ? "enabled" : "disabled"}`); + } + + try { + setPreference("hideUntilMouseover", true); + await setFoldingEnabled(false); + await setFoldingEnabled(true); + await setFoldingEnabled(false); + await setFoldingEnabled(true); + + expect(cm.getGutterElement()).toBe(gutterElement); + foldGutter.updateInViewport = function () { + updateCount++; + }; + testWindow.$(gutterElement).trigger("mouseenter"); + + expect(updateCount).toBe(1); + } finally { + foldGutter.updateInViewport = originalUpdateInViewport; + setPreference("hideUntilMouseover", false); + setPreference("enabled", true); + await testWindow.closeAllFiles(); + } + }); + Object.keys(testFilesSpec).forEach(function (file) { var testFilePath = testFilesSpec[file].filePath; var foldableLines = testFilesSpec[file].foldableLines; @@ -248,6 +527,7 @@ define(function (require, exports, module) { beforeEach(async function () { await setupWindow(); await setup(); + resetPreferences(); await openTestFile(testFilePath); @@ -260,7 +540,7 @@ define(function (require, exports, module) { }); it("renders fold marks on startup", async function () { - var marks = getGutterFoldMarks(); + var marks = getGutterFoldMarks(true); expect(marks.length).toBeGreaterThan(0); marks.map(getLineNumber).forEach(function (line) { expect(toZeroIndex(foldableLines)).toContain(line); @@ -318,7 +598,7 @@ define(function (require, exports, module) { it("indicates foldable lines in the gutter", async function () { var lineNumbers = foldableLines; - var marks = getGutterFoldMarks(); + var marks = getGutterFoldMarks(true); var gutterNumbers = marks.filter(filterOpen) .map(getLineNumber); expect(gutterNumbers).toEqual(toZeroIndex(lineNumbers)); @@ -352,7 +632,7 @@ define(function (require, exports, module) { expect(marks.length).toEqual(0); var lineNumbers = foldableLines; - var marks = getGutterFoldMarks(); + var marks = getGutterFoldMarks(true); var gutterNumbers = marks.filter(filterOpen) .map(getLineNumber); expect(gutterNumbers).toEqual(toZeroIndex(lineNumbers)); @@ -372,6 +652,11 @@ define(function (require, exports, module) { setPreference("enabled", false); var marks = getEditorFoldMarks(); expect(marks.length).toEqual(0); + expect(cm.getOption("foldGutter")).toBe(false); + expect(cm.state.foldGutter).toBeNull(); + expect(testEditor.getRootElement().classList.contains("folding-enabled")).toBe(false); + expect(testWindow.brackets.getModule("editor/Editor").Editor + .isGutterRegistered(gutterName)).toBe(false); }); describe("Fold selected region", function () { @@ -415,43 +700,50 @@ define(function (require, exports, module) { }); describe("Editor text changes", function () { - var foldableLine = foldableLines[1], - expandTimeoutElapsed = false; + var foldableLine = foldableLines[1]; // add a line after folding a region preserves the region and the region can be unfolded it("can unfold a folded region after a line has been added above it", async function () { await foldCodeOnLine(foldableLine); - cm.replaceRange("\r\n", {line: foldableLine - 1, ch: 0}); - - await expandCodeOnLine(foldableLine + 1); - setTimeout(function () { - expandTimeoutElapsed = true; - }, 400); + try { + cm.replaceRange("\r\n", {line: foldableLine - 1, ch: 0}); - await awaitsFor(function () { - return expandTimeoutElapsed; - }, "waiting a moment for gutter markerts to be re-rendered"); + await expandCodeOnLine(foldableLine + 1); + await awaitsFor(function () { + return getGutterFoldMarks().filter(filterFolded).length === 0; + }, "fold gutter markers to update after inserting a line"); - var marks = getGutterFoldMarks().filter(filterFolded); - expect(marks.length).toEqual(0); + var marks = getGutterFoldMarks().filter(filterFolded); + expect(marks.length).toEqual(0); + } finally { + if (testEditor.document.isDirty) { + cm.undo(); + } + } }); it("can unfold a folded region even after a line has been removed above it", async function () { await foldCodeOnLine(foldableLine); - cm.replaceRange("", {line: foldableLine - 1, ch: 0}, {line: foldableLine, ch: 0}); - - await expandCodeOnLine(foldableLine - 1); - setTimeout(function () { - expandTimeoutElapsed = true; - }, 400); - - await awaitsFor(function () { - return expandTimeoutElapsed; - }, "waiting a moment for gutter markerts to be re-rendered"); - - var marks = getGutterFoldMarks().filter(filterFolded); - expect(marks.length).toEqual(0); + try { + cm.replaceRange( + "", + {line: foldableLine - 2, ch: 0}, + {line: foldableLine - 1, ch: 0} + ); + + await expandCodeOnLine(foldableLine - 1); + await awaitsFor(function () { + return getGutterFoldMarks().filter(filterFolded).length === 0; + }, "fold gutter markers to update after removing a line"); + + var marks = getGutterFoldMarks().filter(filterFolded); + expect(marks.length).toEqual(0); + } finally { + if (testEditor.document.isDirty) { + cm.undo(); + } + } }); }); }); diff --git a/src/extensions/default/DarkTheme/main.less b/src/extensions/default/DarkTheme/main.less index 5f705a5a10..1eb921b4e1 100644 --- a/src/extensions/default/DarkTheme/main.less +++ b/src/extensions/default/DarkTheme/main.less @@ -87,7 +87,8 @@ } } -.CodeMirror-matchingbracket { +.CodeMirror-matchingbracket, +.cm-matchingBracket { /* Ensure visibility against gray inline editor background */ background-color: @matching-bracket; color: @foreground !important; @@ -101,7 +102,8 @@ border-bottom: var(--border-height) solid @matching-tags; } -.CodeMirror-overwrite .CodeMirror-cursor { +.CodeMirror-overwrite .CodeMirror-cursor, +.CodeMirror-overwrite .cm-cursor { border-left: none !important; border-bottom: 1px solid #fff; } @@ -113,7 +115,10 @@ color: #aaa; } -.CodeMirror.over-gutter, .CodeMirror-activeline { +.CodeMirror.over-gutter, +.CodeMirror-activeline, +.CodeMirror-activeline-gutter, +.cm-activeLineGutter { .CodeMirror-foldgutter-open:after { color: #ddd; } diff --git a/src/extensions/default/HandlebarsSupport/main.js b/src/extensions/default/HandlebarsSupport/main.js index b1c7f76cd9..63c6252838 100644 --- a/src/extensions/default/HandlebarsSupport/main.js +++ b/src/extensions/default/HandlebarsSupport/main.js @@ -23,27 +23,27 @@ define(function (require, exports, module) { var LanguageManager = brackets.getModule("language/LanguageManager"), - CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"); + CodeMirror = brackets.getModule("editor/CodeMirrorCompat"); - brackets.getModule(["thirdparty/CodeMirror/mode/handlebars/handlebars"], function () { + if (!CodeMirror.modes.htmlhandlebars) { CodeMirror.defineMode("htmlhandlebars", function (config) { return CodeMirror.multiplexingMode( CodeMirror.getMode(config, "text/html"), { open: "{{", - close: "}}", + close: /\}\}\}?/, mode: CodeMirror.getMode(config, "handlebars"), parseDelimiters: true } ); }); - CodeMirror.defineMIME("text/x-handlebars-template", "htmlhandlebars"); + } + CodeMirror.defineMIME("text/x-handlebars-template", "htmlhandlebars"); - LanguageManager.defineLanguage("handlebars", { - name: "Handlebars", - mode: ["htmlhandlebars", "text/x-handlebars-template"], - fileExtensions: ["hbs", "handlebars"], - blockComment: ["{{!", "}}"] - }); + LanguageManager.defineLanguage("handlebars", { + name: "Handlebars", + mode: ["htmlhandlebars", "text/x-handlebars-template"], + fileExtensions: ["hbs", "handlebars"], + blockComment: ["{{!", "}}"] }); }); diff --git a/src/extensions/default/QuickView/numberPreviewProvider.js b/src/extensions/default/QuickView/numberPreviewProvider.js index c5dee6a20f..e77ac21958 100644 --- a/src/extensions/default/QuickView/numberPreviewProvider.js +++ b/src/extensions/default/QuickView/numberPreviewProvider.js @@ -71,12 +71,17 @@ define(function (require, exports, module) { } function _getWordAfterPos(editor, pos) { + const lineText = editor.getLine(pos.line) || ""; + if (lineText.charAt(pos.ch) === "%") { + return { + text: "%", + startPos: { line: pos.line, ch: pos.ch }, + endPos: { line: pos.line, ch: pos.ch + 1 } + }; + } + // Find the word at the specified position const wordRange = editor.getWordAt(pos); - if(wordRange.text.startsWith('%')) { - wordRange.text = wordRange.text.slice(0, 1); - wordRange.endPos.ch = wordRange.startPos.ch + 1; - } const wordFull = editor.getTextBetween(wordRange.startPos, wordRange.endPos); // Calculate effective start position within the word, if startPos is within the word diff --git a/src/extensions/default/QuickView/unittests.js b/src/extensions/default/QuickView/unittests.js index 440b07d94a..20bc50609c 100644 --- a/src/extensions/default/QuickView/unittests.js +++ b/src/extensions/default/QuickView/unittests.js @@ -19,7 +19,7 @@ * */ -/*global describe, it, expect, beforeEach, awaitsFor, awaitsForDone, afterAll */ +/*global describe, it, expect, beforeEach, afterEach, awaitsFor, awaitsForDone, afterAll, spyOn */ define(function (require, exports, module) { @@ -38,6 +38,7 @@ define(function (require, exports, module) { Commands, MainViewManager, EditorManager, + FileViewController, QuickView, editor, testFile = "test.css"; @@ -55,6 +56,7 @@ define(function (require, exports, module) { CommandManager = brackets.test.CommandManager; Commands = brackets.test.Commands; EditorManager = brackets.test.EditorManager; + FileViewController = brackets.test.FileViewController; QuickView = brackets.test.QuickViewManager; MainViewManager = brackets.test.MainViewManager; @@ -69,6 +71,7 @@ define(function (require, exports, module) { CommandManager = null; Commands = null; EditorManager = null; + FileViewController = null; QuickView = null; MainViewManager = null; await SpecRunnerUtils.closeTestWindow(); @@ -330,6 +333,15 @@ define(function (require, exports, module) { editor = EditorManager.getCurrentFullEditor(); }, 30000); + + afterEach(async function () { + const currentFile = MainViewManager.getCurrentlyViewedFile(); + if (currentFile && !currentFile.fullPath.endsWith("/" + testFile)) { + await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }), + "close file opened from image preview"); + } + }, 30000); + it("Should show image preview for file path inside url()",async function () { await checkImagePathAtPos("img/grabber_color-well.png", 140, 26); await checkImagePathAtPos("img/Color.png", 141, 26); @@ -345,11 +357,14 @@ define(function (require, exports, module) { // Just check end of path - local drive location prefix unimportant expect(imagePath.substr(imagePath.length - expectedPathEnding.length)).toBe(expectedPathEnding); + const openSpy = spyOn(FileViewController, "openAndSelectDocument").and.callThrough(); imagePreview.click(); - await awaitsFor(()=>{ - let currentFile = MainViewManager.getCurrentlyViewedFile(); - return currentFile.fullPath.endsWith(expectedPathEnding); - }, "waits for image to open"); + await awaitsFor(() => openSpy.calls.count() === 1, + "image preview to request opening its file"); + await awaitsForDone(openSpy.calls.mostRecent().returnValue, + "image preview file to open"); + const currentFile = MainViewManager.getCurrentlyViewedFile(); + expect(currentFile.fullPath.endsWith(expectedPathEnding)).toBeTrue(); }); it("Should click on svg image preview open the corresponding file", async function () { @@ -362,11 +377,14 @@ define(function (require, exports, module) { // Just check end of path - local drive location prefix unimportant expect(imagePath.substr(imagePath.length - expectedPathEnding.length)).toBe(expectedPathEnding); + const openSpy = spyOn(FileViewController, "openAndSelectDocument").and.callThrough(); imagePreview.click(); - await awaitsFor(()=>{ - let currentFile = MainViewManager.getCurrentlyViewedFile(); - return currentFile.fullPath.endsWith(expectedPathEnding); - }, "waits for chinese sch image to open"); + await awaitsFor(() => openSpy.calls.count() === 1, + "SVG preview to request opening its file"); + await awaitsForDone(openSpy.calls.mostRecent().returnValue, + "SVG preview file to open"); + const currentFile = MainViewManager.getCurrentlyViewedFile(); + expect(currentFile.fullPath.endsWith(expectedPathEnding)).toBeTrue(); }); it("Should show image preview for urls with http/https",async function () { diff --git a/src/extensionsIntegrated/CSSColorPreview/main.js b/src/extensionsIntegrated/CSSColorPreview/main.js index 04a4ad9dbc..a77611513b 100644 --- a/src/extensionsIntegrated/CSSColorPreview/main.js +++ b/src/extensionsIntegrated/CSSColorPreview/main.js @@ -199,6 +199,10 @@ define(function (require, exports, module) { }); } }); + // CM6 batches gutter reconfiguration in a microtask. Flush once + // after the complete marker batch so callers can immediately + // inspect and interact with the newly installed gutter nodes. + editor.refresh(); } } @@ -482,4 +486,3 @@ define(function (require, exports, module) { registerHandlers(); }); }); - diff --git a/src/extensionsIntegrated/DisplayShortcuts/main.js b/src/extensionsIntegrated/DisplayShortcuts/main.js index 6f7a957ba2..b4c37008f8 100644 --- a/src/extensionsIntegrated/DisplayShortcuts/main.js +++ b/src/extensionsIntegrated/DisplayShortcuts/main.js @@ -27,7 +27,7 @@ define(function (require, exports, module) { // Brackets modules const _ = require("thirdparty/lodash"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror = require("editor/CodeMirrorCompat"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), KeyBindingManager = require("command/KeyBindingManager"), diff --git a/src/extensionsIntegrated/HtmlTagSyncEdit/main.js b/src/extensionsIntegrated/HtmlTagSyncEdit/main.js index d279e27200..60e781500d 100644 --- a/src/extensionsIntegrated/HtmlTagSyncEdit/main.js +++ b/src/extensionsIntegrated/HtmlTagSyncEdit/main.js @@ -27,7 +27,7 @@ define(function (require, exports, module) { const AppInit = require("utils/AppInit"), Editor = require("editor/Editor").Editor, LanguageManager = require("language/LanguageManager"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + CodeMirror = require("editor/CodeMirrorCompat"), Commands = require("command/Commands"), PreferencesManager = require("preferences/PreferencesManager"), WorkspaceManager = require("view/WorkspaceManager"), @@ -258,7 +258,8 @@ define(function (require, exports, module) { } return; } - const matchingTags = CodeMirror.findMatchingTag(activeEditor._codeMirror, cursor); + const matchingTags = CodeMirror.findMatchingTag && + CodeMirror.findMatchingTag(activeEditor._codeMirror, cursor); if(!matchingTags) { clearRenameMarkers(); return; diff --git a/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js b/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js index 5dacdc616d..1d95c806e2 100644 --- a/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js +++ b/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js @@ -313,6 +313,7 @@ define(function (require, exports, module) { /** * Function to actually navigate to the position(file,selections) captured in this frame + * @return {$.Promise} Resolves after the target file is open and its selection is restored */ NavigationFrame.prototype.goTo = function () { const self = this; @@ -326,7 +327,10 @@ define(function (require, exports, module) { this.paneId = thisDoc._masterEditor._paneId; } - CommandManager.execute(Commands.FILE_OPEN, {fullPath: this.filePath, paneId: this.paneId}).done(function () { + return CommandManager.execute( + Commands.FILE_OPEN, + {fullPath: this.filePath, paneId: this.paneId} + ).then(function () { if(!self.nonEditorView) { EditorManager.getCurrentFullEditor().setSelections(self.selections, true); } @@ -464,12 +468,15 @@ define(function (require, exports, module) { if(currentEditNavFrame) { jumpForwardStack.push(currentEditNavFrame); } - navFrame.goTo(); + navFrame.goTo().always(function () { + _validateNavigationCmds(); + deferred.resolve(); + }); }).fail(function () { - CommandManager.execute(NAVIGATION_JUMP_BACK); - }).always(function () { - _validateNavigationCmds(); - deferred.resolve(); + CommandManager.execute(NAVIGATION_JUMP_BACK).always(function () { + _validateNavigationCmds(); + deferred.resolve(); + }); }); } else { if(currentEditNavFrame){ @@ -509,18 +516,21 @@ define(function (require, exports, module) { if(currentEditNavFrame){ jumpBackwardStack.push(currentEditNavFrame); } - navFrame.goTo(); + navFrame.goTo().always(function () { + _validateNavigationCmds(); + deferred.resolve(); + }); }).fail(function () { _validateNavigationCmds(); - CommandManager.execute(NAVIGATION_JUMP_FWD); - }).always(function () { - _validateNavigationCmds(); - deferred.resolve(); + CommandManager.execute(NAVIGATION_JUMP_FWD).always(function () { + _validateNavigationCmds(); + deferred.resolve(); + }); }); } else { deferred.resolve(); } - return deferred.promise(); + return deferred.promise(); } /** @@ -627,13 +637,34 @@ define(function (require, exports, module) { } } + /** + * Returns true while an editor's CodeMirror surface can still be queried. + * CodeMirror 6 clears `_view` during destruction, while CodeMirror 5 does + * not define that property. + * @private + * @param {?Editor} editor + * @return {boolean} + */ + function _isEditorSurfaceUsable(editor) { + const codeMirror = editor && editor._codeMirror; + const hasCodeMirror6View = codeMirror && + Object.prototype.hasOwnProperty.call(codeMirror, "_view"); + + return Boolean(codeMirror && + !codeMirror._destroyed && + (!hasCodeMirror6View || codeMirror._view)); + } + /** * Function to request a navigation frame creation explicitly. Resets forward stack * @private */ function _captureBackFrame(editor) { + if (!_isEditorSurfaceUsable(editor)) { + return; + } _recordJumpDef({target: editor}, - {ranges: editor._codeMirror.listSelections()}, + {ranges: editor.getSelections()}, true); } diff --git a/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js b/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js index 97902ade7d..059e5d6b27 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. * - * Bidirectional sync between Phoenix's CM5 document editor and the mdviewr iframe. + * Bidirectional sync between Phoenix's document editor and the mdviewr iframe. * Handles content sync, theme sync, locale sync, and edit mode relay. */ @@ -103,7 +103,7 @@ define(function (require, exports, module) { * If the iframe is the same as the previous activation (e.g. switching between markdown files), * content is sent immediately without waiting for mdviewrReady. * - * @param {Document} doc - Phoenix CM5 Document + * @param {Document} doc - Phoenix Document * @param {jQuery} $iframe - The iframe jQuery element * @param {string} baseURL - Base URL for resolving relative image/resource paths */ @@ -261,7 +261,7 @@ define(function (require, exports, module) { }; window.addEventListener("message", _messageHandler); - // Listen for CM5 document changes (Phoenix → iframe) + // Listen for Phoenix document changes (Phoenix → iframe) let _lastChangeOrigin = null; _docChangeHandler = function () { if (_syncingFromIframe) { @@ -284,7 +284,8 @@ define(function (require, exports, module) { }; ThemeManager.on("themeChange", _themeChangeHandler); - // Listen for cursor activity in CM5 for scroll sync, selection sync, and toolbar state (CM5 → iframe) + // Listen for cursor activity in the Phoenix editor for scroll sync, + // selection sync, and toolbar state (Phoenix → iframe) _cursorHandler = function () { if (_syncingFromIframe || !_iframeReady) { return; @@ -362,7 +363,10 @@ define(function (require, exports, module) { clearTimeout(_scrollSyncTimer); clearTimeout(_selectionSyncTimer); - const cm = _getCM(); + // Detach from the exact editor instance used during activation. The + // document's master editor may already be gone, and EditorManager may + // point at a different file by the time deactivation runs. + const cm = _activeCM || _getCM(); if (cm) { if (_cursorHandler) { cm.off("cursorActivity", _cursorHandler); @@ -606,7 +610,7 @@ define(function (require, exports, module) { // --- iframe → Phoenix --- /** - * Apply new text to the CM5 editor using a minimal diff so that the undo stack + * Apply new text to the Phoenix editor using a minimal diff so that the undo stack * records only the changed region instead of a full-document replacement. */ function _applyDiffToEditor(newText) { @@ -775,7 +779,7 @@ define(function (require, exports, module) { // --- Scroll sync --- /** - * Send the current CM5 cursor line to the iframe so it can scroll to the + * Send the current Phoenix editor cursor line to the iframe so it can scroll to the * corresponding rendered element (only if it's not already visible). */ function _syncScrollToIframe() { @@ -790,7 +794,7 @@ define(function (require, exports, module) { if (!cm) { return; } - // CM5 cursor line is 0-based; source lines in markdown are 1-based + // Phoenix editor lines are 0-based; source lines in markdown are 1-based const cursor = cm.getCursor(); const line = cursor.line + 1; // For table rows, determine column by counting | before cursor @@ -930,7 +934,7 @@ define(function (require, exports, module) { } /** - * Move the CM5 cursor to the given source line (1-based) and scroll + * Move the Phoenix editor cursor to the given source line (1-based) and scroll * the editor to show it if it's not already visible. */ function _scrollCMToLine(sourceLine) { @@ -938,7 +942,7 @@ define(function (require, exports, module) { if (!cm) { return; } - // Convert 1-based source line to 0-based CM5 line + // Convert the 1-based source line to a 0-based editor line const cmLine = Math.max(0, sourceLine - 1); const lineCount = cm.lineCount(); if (cmLine >= lineCount) { @@ -982,7 +986,7 @@ define(function (require, exports, module) { // --- Selection sync --- /** - * Send the current CM5 selection range to the iframe so it can highlight + * Send the current Phoenix editor selection range to the iframe so it can highlight * the corresponding rendered elements. */ function _syncSelectionToIframe() { @@ -1021,7 +1025,7 @@ define(function (require, exports, module) { /** * Handle a selection coming from the iframe. Finds the corresponding text - * in CM5 and sets the selection there. + * in the Phoenix editor and sets the selection there. */ function _handleSelectionFromIframe(data) { const cm = _getCM(); @@ -1217,20 +1221,25 @@ define(function (require, exports, module) { function _getCM() { if (_doc && _doc._masterEditor) { - return _doc._masterEditor._codeMirror; + const masterCodeMirror = _doc._masterEditor._codeMirror; + if (masterCodeMirror && !masterCodeMirror._destroyed) { + return masterCodeMirror; + } } // Fallback: _masterEditor can be null when the editor pane doesn't have // focus (e.g. md viewer is focused). Try EditorManager lookups first, // then fall back to the CM reference captured during activation. const fullEditor = EditorManager.getCurrentFullEditor(); - if (fullEditor) { + if (fullEditor && fullEditor._codeMirror && + !fullEditor._codeMirror._destroyed) { return fullEditor._codeMirror; } const activeEditor = EditorManager.getActiveEditor(); - if (activeEditor) { + if (activeEditor && activeEditor._codeMirror && + !activeEditor._codeMirror._destroyed) { return activeEditor._codeMirror; } - return _activeCM; + return _activeCM && !_activeCM._destroyed ? _activeCM : null; } /** diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 1f8dc1e25b..a43fda8a48 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -94,9 +94,13 @@ define(function (require, exports, module) { const EVENT_EMBEDDED_IFRAME_WHO_AM_I = 'whoAmIframePhoenix'; const EVENT_EMBEDDED_IFRAME_FOCUS_EDITOR = 'embeddedIframeFocusEditor'; + const EVENT_LIVE_PREVIEW_SCROLL_POSITION = "PHOENIX_LIVE_PREVIEW_SCROLL_POSITION"; + const EVENT_LIVE_PREVIEW_SCROLL_READY = "PHOENIX_LIVE_PREVIEW_SCROLL_READY"; + const EVENT_LIVE_PREVIEW_RESTORE_SCROLL_POSITION = "PHOENIX_LIVE_PREVIEW_RESTORE_SCROLL_POSITION"; const PREVIEW_TRUSTED_PROJECT_KEY = "preview_trusted"; const PREVIEW_PROJECT_README_KEY = "preview_readme"; + const _livePreviewScrollPositions = new Map(); // holds the dropdown instance let $dropdown = null; @@ -166,7 +170,10 @@ define(function (require, exports, module) { // for integ tests window._livePreviewIntegTest = { urlLoadCount: 0, - STATE_CUSTOM_SERVER_BANNER_ACK + STATE_CUSTOM_SERVER_BANNER_ACK, + getSavedScrollPosition: function (url) { + return _livePreviewScrollPositions.get(url) || null; + } }; } @@ -194,6 +201,38 @@ define(function (require, exports, module) { let customLivePreviewBannerShown = false; + function _handleLivePreviewScrollMessage(event) { + if (!$iframe || !$iframe[0] || event.source !== $iframe[0].contentWindow) { + return; + } + const data = event.data; + if (!data || typeof data.url !== "string") { + return; + } + if (data.type === EVENT_LIVE_PREVIEW_SCROLL_POSITION) { + if (!Number.isFinite(data.scrollX) || !Number.isFinite(data.scrollY)) { + return; + } + _livePreviewScrollPositions.set(data.url, { + scrollX: data.scrollX, + scrollY: data.scrollY + }); + return; + } + if (data.type === EVENT_LIVE_PREVIEW_SCROLL_READY) { + const position = _livePreviewScrollPositions.get(data.url); + if (!position) { + return; + } + event.source.postMessage({ + type: EVENT_LIVE_PREVIEW_RESTORE_SCROLL_POSITION, + url: data.url, + scrollX: position.scrollX, + scrollY: position.scrollY + }, event.origin === "null" ? "*" : event.origin); + } + } + // live Preview overlay variables (overlays are shown when live preview is connecting or there's a syntax error) let $statusOverlay = null; // reference to the static overlay element let $statusOverlayMessage = null; // reference to the message span @@ -1201,6 +1240,7 @@ define(function (require, exports, module) { } async function _projectOpened() { + _livePreviewScrollPositions.clear(); // Deactivate mdviewr on project switch — keep iframe alive but clear cache if(_isMdviewrActive) { MarkdownSync.deactivate(); @@ -1274,6 +1314,7 @@ define(function (require, exports, module) { } function _projectClosed() { + _livePreviewScrollPositions.clear(); if(urlPinned) { _togglePinUrl(); } @@ -1488,6 +1529,7 @@ define(function (require, exports, module) { Metrics.countEvent(Metrics.EVENT_TYPE.LIVE_PREVIEW, "atStart", LivePreviewSettings.shouldShowLivePreviewAtStartup() ? "show" : "hide"); _createExtensionPanel(); + window.addEventListener("message", _handleLivePreviewScrollMessage); StaticServer.init(); LiveDevServerManager.registerServer({ create: _createStaticServer }, 5); ProjectManager.on(ProjectManager.EVENT_PROJECT_FILE_CHANGED, _projectFileChanges); @@ -1747,4 +1789,3 @@ define(function (require, exports, module) { exports.hideInterstitial = hideInterstitial; }); - diff --git a/src/extensionsIntegrated/indentGuides/main.js b/src/extensionsIntegrated/indentGuides/main.js index f0920f121d..b2b7cac258 100644 --- a/src/extensionsIntegrated/indentGuides/main.js +++ b/src/extensionsIntegrated/indentGuides/main.js @@ -132,6 +132,7 @@ define(function (require, exports, module) { cm.__indentGuidesOverlayAttached !== editor.__indentGuidesOverlay) { cm.removeOverlay(cm.__indentGuidesOverlayAttached); cm.addOverlay(editor.__indentGuidesOverlay); + cm.__indentGuidesOverlayAttached = editor.__indentGuidesOverlay; cm.__overlayEnabled = enabled; } else if (shouldRerender || cm.__overlayEnabled !== enabled) { cm.__overlayEnabled = enabled; @@ -170,4 +171,4 @@ define(function (require, exports, module) { // Apply preferences and draw indent guides preferenceChanged(); }); -}); \ No newline at end of file +}); diff --git a/src/language/CSSUtils.js b/src/language/CSSUtils.js index c217474942..ac88610f21 100644 --- a/src/language/CSSUtils.js +++ b/src/language/CSSUtils.js @@ -30,7 +30,7 @@ define(function (require, exports, module) { - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), Async = require("utils/Async"), DocumentManager = require("document/DocumentManager"), AppInit = require("utils/AppInit"), @@ -669,41 +669,48 @@ define(function (require, exports, module) { return createInfo(); } - // Context from the current editor will have htmlState if we are in css mode - // and in attribute value state of a tag with attribute name style - if (ctx.token.state.htmlState && (!ctx.token.state.localMode || ctx.token.state.localMode.name !== "css")) { - - // tagInfo is required to aquire the style attr value - var tagInfo = HTMLUtils.getTagInfo(editor, pos, true), - // To be used as relative character position - offset = tagInfo.position.offset; + let contextCM; + try { + // Context from the current editor will have htmlState if we are in css mode + // and in attribute value state of a tag with attribute name style + if (ctx.token.state.htmlState && + (!ctx.token.state.localMode || ctx.token.state.localMode.name !== "css")) { + + // tagInfo is required to aquire the style attr value + const tagInfo = HTMLUtils.getTagInfo(editor, pos, true), + // To be used as relative character position + offset = tagInfo.position.offset; + + /** + * Use a detached CM6-backed compatibility editor to compute CSS + * context for a style attribute without adding another visible editor. + */ + contextCM = new CodeMirror(function () { }, { + value: "{" + tagInfo.attr.value.replace(/(^")|("$)/g, ""), + mode: "css" + }); - /** - * We will use this CM to cook css context in case of style attribute value - * as CM in htmlmixed mode doesn't yet identify this as css context. We provide - * a no-op display function to run CM without a DOM head. - */ - var _contextCM = new CodeMirror(function () { }, { - value: "{" + tagInfo.attr.value.replace(/(^")|("$)/g, ""), - mode: "css" - }); + ctx = TokenUtils.getInitialContext(contextCM, { line: 0, ch: offset + 1 }); + } - ctx = TokenUtils.getInitialContext(_contextCM, { line: 0, ch: offset + 1 }); - } + if (_isInPropName(ctx)) { + return _getPropNameInfo(ctx); + } - if (_isInPropName(ctx)) { - return _getPropNameInfo(ctx); - } + if (_isInPropValue(ctx)) { + return _getRuleInfoStartingFromPropValue(ctx, ctx.editor); + } - if (_isInPropValue(ctx)) { - return _getRuleInfoStartingFromPropValue(ctx, ctx.editor); - } + if (_isInAtRule(ctx)) { + return _getImportUrlInfo(ctx, editor); + } - if (_isInAtRule(ctx)) { - return _getImportUrlInfo(ctx, editor); + return createInfo(); + } finally { + if (contextCM) { + contextCM.destroy(); + } } - - return createInfo(); } /** diff --git a/src/language/HTMLUtils.js b/src/language/HTMLUtils.js index 19fcf0a197..73bb79c60d 100644 --- a/src/language/HTMLUtils.js +++ b/src/language/HTMLUtils.js @@ -25,7 +25,7 @@ define(function (require, exports, module) { - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), TokenUtils = require("utils/TokenUtils"); diff --git a/src/language/JSUtils.js b/src/language/JSUtils.js index d7667322af..842b048985 100644 --- a/src/language/JSUtils.js +++ b/src/language/JSUtils.js @@ -33,7 +33,7 @@ define(function (require, exports, module) { ASTWalker = require("thirdparty/acorn/dist/walk"); // Load brackets modules - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), Async = require("utils/Async"), DocumentManager = require("document/DocumentManager"), ChangedDocumentTracker = require("document/ChangedDocumentTracker"), diff --git a/src/language/LanguageManager.js b/src/language/LanguageManager.js index c5ed872cb2..c04d48e803 100644 --- a/src/language/LanguageManager.js +++ b/src/language/LanguageManager.js @@ -76,7 +76,7 @@ * language.addFileExtension("lhs"); * * Some CodeMirror modes define variations of themselves. They are called MIME modes. - * To find existing MIME modes, search for "CodeMirror.defineMIME" in thirdparty/CodeMirror/mode + * To find existing MIME modes, inspect the registrations in editor/CodeMirrorCompat. * For instance, C++, C# and Java all use the clike (C-like) mode with different settings and a different MIME name. * You can refine the mode definition by specifying the MIME mode as well: * @@ -131,7 +131,7 @@ define(function (require, exports, module) { // Dependencies - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), EventDispatcher = require("utils/EventDispatcher"), Async = require("utils/Async"), FileUtils = require("file/FileUtils"), @@ -578,9 +578,9 @@ define(function (require, exports, module) { /** * Loads a mode and sets it for this language. * @private - * @param {(string|Array.)} mode CodeMirror mode (e.g. "htmlmixed"), optionally paired with a MIME mode defined by - * that mode (e.g. ["clike", "text/x-c++src"]). Unless the mode is located in thirdparty/CodeMirror/mode/"name"/"name".js, - * you need to first load it yourself. + * @param {(string|Array.)} mode CodeMirror-compatible mode (e.g. "htmlmixed"), optionally paired with a MIME mode + * defined by that mode (e.g. ["clike", "text/x-c++src"]). Custom modes must be registered with + * CodeMirrorCompat.defineMode() before defining the language. * @return {$.Promise} A promise object that will be resolved when the mode is loaded and set */ Language.prototype._loadAndSetMode = function (mode) { @@ -604,11 +604,6 @@ define(function (require, exports, module) { } var finish = function () { - if (!CodeMirror.modes[mode]) { - result.reject("CodeMirror mode \"" + mode + "\" is not loaded"); - return; - } - if (mimeMode) { var modeConfig = CodeMirror.mimeModes[mimeMode]; @@ -626,10 +621,10 @@ define(function (require, exports, module) { result.resolve(self); }; - if (CodeMirror.modes[mode]) { + if (CodeMirror.loadMode(mode)) { finish(); } else { - require(["thirdparty/CodeMirror/mode/" + mode + "/" + mode], finish); + result.reject("CodeMirror-compatible mode \"" + mode + "\" is not registered"); } return result.promise(); @@ -938,8 +933,9 @@ define(function (require, exports, module) { * @param {Array.} definition.fileNames List of exact file names (e.g. ["Makefile"] or ["package.json]). Higher precedence than file extension. * @param {Array.} definition.blockComment Array with two entries defining the block comment prefix and suffix (e.g. ["< !--", "-->"]) * @param {(string|Array.)} definition.lineComment Line comment prefixes (e.g. "//" or ["//", "#"]) - * @param {(string|Array.)} definition.mode CodeMirror mode (e.g. "htmlmixed"), optionally with a MIME mode defined by that mode ["clike", "text/x-c++src"] - * Unless the mode is located in thirdparty/CodeMirror/mode/"name"/"name".js, you need to first load it yourself. + * @param {(string|Array.)} definition.mode CodeMirror-compatible mode (e.g. "htmlmixed"), optionally with a + * MIME mode defined by that mode ["clike", "text/x-c++src"]. + * Custom modes must be registered before defining the language. * * @return {$.Promise} A promise object that will be resolved with a Language object **/ diff --git a/src/main.js b/src/main.js index 16ba27da5f..2d87cb89ae 100644 --- a/src/main.js +++ b/src/main.js @@ -130,7 +130,8 @@ if(!Phoenix.isNativeApp) { */ require.config({ paths: { - "text": "thirdparty/text/text", + "text-base": "thirdparty/text/text", + "text": "editor/CodeMirrorLegacyText", "i18n": "thirdparty/i18n/i18n", // The file system implementation. Change this value to use different @@ -141,7 +142,10 @@ require.config({ }, map: { "*": { - "thirdparty/CodeMirror2": "thirdparty/CodeMirror", + // Keep these aliases exact. CodeMirrorLegacyModuleLoader handles + // legacy root, addon, keymap, and mode IDs without a CM5 file tree. + "thirdparty/CodeMirror/lib/codemirror": "editor/CodeMirrorCompat", + "thirdparty/CodeMirror2/lib/codemirror": "editor/CodeMirrorCompat", "thirdparty/preact": "preact-compat", "view/PanelManager": "view/WorkspaceManager" // For extension compatibility } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 56f3b19b9a..02208993f8 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1703,7 +1703,7 @@ define({ "DESCRIPTION_LANGUAGE_FILE_EXTENSIONS": "Additional mappings from file extension to language name", "DESCRIPTION_LANGUAGE_FILE_NAMES": "Additional mappings from file name to language name", "DESCRIPTION_LINEWISE_COPY_CUT": "Doing copy and cut when there's no selection will copy or cut the whole lines that have cursors in them", - "DESCRIPTION_INPUT_STYLE": "Selects the way CodeMirror handles input and focus. It can be textarea, which is the default, or contenteditable which is better for screen readers (unstable)", + "DESCRIPTION_INPUT_STYLE": "Selects the way the editor handles input and focus. CodeMirror 6 uses contenteditable for editing and screen-reader access.", "DESCRIPTION_LINTING_ENABLED": "true to enable Code Inspection", "DESCRIPTION_ASYNC_TIMEOUT": "The time in milliseconds after which asynchronous linters time out", "DESCRIPTION_LINTING_PREFER": "Array of linters to run first", diff --git a/src/phoenix/virtual-server-loader.js b/src/phoenix/virtual-server-loader.js index 0678015994..62050de615 100644 --- a/src/phoenix/virtual-server-loader.js +++ b/src/phoenix/virtual-server-loader.js @@ -66,13 +66,17 @@ function _isServiceWorkerLoaderPage() { baseUrl = `${location.origin}/`, devURL = 'http://localhost:8000/src/', distTestURL = 'http://localhost:8000/dist-test/src/', - playwrightDevURL = 'http://localhost:5000/src/', - playwrightDistTestURL = 'http://localhost:5000/dist-test/src/', - currentURL = _getBaseURL(); + playwrightDevURL = `${location.origin}/src/`, + playwrightDistTestURL = `${location.origin}/dist-test/src/`, + currentURL = _getBaseURL(), + isLoopbackTestServer = location.port === "5000" && + ["localhost", "127.0.0.1", "::1", "[::1]"].includes(location.hostname); console.log("currentURL", currentURL, indexUrl, baseUrl, devURL); return (currentURL === baseUrl || currentURL === indexUrl || currentURL === devURL || currentURL === distTestURL || - (currentURL === playwrightDevURL && window.Phoenix.browser.desktop.isChromeBased) || - (currentURL === playwrightDistTestURL && window.Phoenix.browser.desktop.isChromeBased)); + (isLoopbackTestServer && currentURL === playwrightDevURL && + window.Phoenix.browser.desktop.isChromeBased) || + (isLoopbackTestServer && currentURL === playwrightDistTestURL && + window.Phoenix.browser.desktop.isChromeBased)); // we dont spawn virtual server in iframe playwright linux/safari as playwright linux/safari fails badly // we dont need virtual server for tests except for live preview and custom extension load tests, // which are disabled in playwright. We test in chrome atleast as chromium support is a baseline. diff --git a/src/preferences/PreferencesBase.js b/src/preferences/PreferencesBase.js index c2810b150c..94788dcbe2 100644 --- a/src/preferences/PreferencesBase.js +++ b/src/preferences/PreferencesBase.js @@ -849,7 +849,7 @@ define(function (require, exports, module) { * (switching to single line comments because the glob interferes with the multiline comment): */ // "path": { - // "src/thirdparty/CodeMirror/**/*.js": { + // "src/thirdparty/generated-vendor/**/*.js": { // "spaceUnits": 2, // "linting.enabled": false // } diff --git a/src/search/FindReplace.js b/src/search/FindReplace.js index adf3ef165c..13bd063ee9 100644 --- a/src/search/FindReplace.js +++ b/src/search/FindReplace.js @@ -41,7 +41,7 @@ define(function (require, exports, module) { FindInFilesUI = require("search/FindInFilesUI"), ScrollTrackMarkers = require("search/ScrollTrackMarkers"), _ = require("thirdparty/lodash"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); + CodeMirror = require("editor/CodeMirrorCompat"); /** * Maximum file size to search within (in chars) diff --git a/src/search/ScrollTrackMarkers.js b/src/search/ScrollTrackMarkers.js index 99bc5332c4..175eddf9db 100644 --- a/src/search/ScrollTrackMarkers.js +++ b/src/search/ScrollTrackMarkers.js @@ -105,12 +105,11 @@ define(function (require, exports, module) { /** * Return the scrollbar element for the given editor. - * (Select only the direct descendant so we don't get nested inline editors). * @param {!Editor} editor * @return {jQueryObject} */ function _getScrollbar(editor) { - return $(editor.getRootElement()).children(".CodeMirror-vscrollbar"); + return $(editor.getScrollerElement()); } /** @@ -120,17 +119,16 @@ define(function (require, exports, module) { function _calcScaling(editor) { const markerState = _getMarkerState(editor); const $sb = _getScrollbar(editor); + const scrollbar = $sb[0]; - const trackHeight = $sb[0].offsetHeight; + const trackHeight = scrollbar ? scrollbar.offsetHeight : 0; if (trackHeight > 0) { markerState.trackOffset = scrollbarTrackOffset; markerState.trackHt = trackHeight - markerState.trackOffset * 2; } else { - // No scrollbar: use the height of the entire code content - const codeContainer = $(editor.getRootElement()) - .find("> .CodeMirror-scroll > .CodeMirror-sizer > div > .CodeMirror-lines > div")[0]; - markerState.trackHt = codeContainer.offsetHeight; - markerState.trackOffset = codeContainer.offsetTop; + const scroller = editor.getScrollerElement(); + markerState.trackHt = scroller ? scroller.clientHeight : 0; + markerState.trackOffset = 0; } } @@ -208,39 +206,31 @@ define(function (require, exports, module) { }); }); - // Merge/condense overlapping or adjacent segments, same as before + // addTickmarks() has already merged adjacent source ranges. Keep each + // remaining logical mark distinct here. A second, pixel-based merge is + // unreliable when an editor pane has not completed layout yet because + // CM6 may temporarily report the same coordinates for unrelated lines. markPositions.sort(function (a, b) { return a.top - b.top; }); - const mergedLineMarks = []; - const mergedLeftMarks = []; - + const lineMarks = []; + const leftMarks = []; markPositions.forEach(function (mark) { - const mergedMarks = mark.isLine ? mergedLineMarks : mergedLeftMarks; - if (mergedMarks.length > 0) { - const last = mergedMarks[mergedMarks.length - 1]; - // If overlapping or adjacent, merge them - if (mark.top <= last.bottom + 1) { - last.bottom = Math.max(last.bottom, mark.bottom); - last.height = last.bottom - last.top; - return; - } - } mark.height = mark.bottom - mark.top; - mergedMarks.push(mark); + (mark.isLine ? lineMarks : leftMarks).push(mark); }); // Now render them into the DOM // (1) For the "line" style - let html = mergedLineMarks.map(function (m) { + let html = lineMarks.map(function (m) { return `
`; }).join(""); $track.append($(html)); // (2) For the "left" style - html = mergedLeftMarks.map(function (m) { + html = leftMarks.map(function (m) { return `
`; }).join(""); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index c85c9bbc36..d13749da36 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -4203,7 +4203,8 @@ label input { } // CodeMirror uses inline styles for active line number, so must use !important here to override -.live-preview-sync-error .CodeMirror-linenumber { +.live-preview-sync-error .CodeMirror-linenumber, +.CodeMirror.phoenix-codemirror-6 .CodeMirror-linenumber.live-preview-sync-error { background-color: @live-preview-sync-error-background !important; color: @live-preview-sync-error-color !important; } diff --git a/src/styles/brackets_codemirror6.less b/src/styles/brackets_codemirror6.less new file mode 100644 index 0000000000..25d20ce51b --- /dev/null +++ b/src/styles/brackets_codemirror6.less @@ -0,0 +1,828 @@ +// Copyright (c) 2026 - present core.ai. All rights reserved. +// +// CodeMirror 6 uses a different DOM structure from the previous editor surface. Keep these +// rules scoped to the CM6 root so legacy and inline editors retain their +// existing layout. + +.CodeMirror.phoenix-codemirror-6 { + position: relative; + height: 100%; + overflow: hidden; + background: @background; + + &.cm-focused { + outline: none; + } + + .cm-scroller { + flex-shrink: 0; + overflow: auto; + font-family: inherit; + line-height: var(--editor-line-height); + } + + .cm-scroller.CodeMirror-lines { + padding: 0; + pointer-events: auto; + } + + .phoenix-cm6-legacy-sizer, + .phoenix-cm6-legacy-vscrollbar { + position: absolute; + top: 0; + left: 0; + box-sizing: content-box; + margin: 0; + overflow: hidden; + pointer-events: none; + visibility: hidden; + contain: strict; + } + + .phoenix-cm6-legacy-sizer { + padding: 0; + + > .phoenix-cm6-legacy-content-width { + height: 1px; + } + + > .phoenix-cm6-legacy-lines { + position: absolute; + inset: 0; + padding: @code-padding 0; + pointer-events: none; + } + + > .phoenix-cm6-legacy-measure { + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 1px; + margin: 0; + overflow: hidden; + } + } + + .phoenix-cm6-legacy-vscrollbar { + width: 0; + height: 0; + } + + .cm-content { + min-height: 100%; + padding: + @code-padding + var(--phoenix-cm6-scrollbar-right, 0) + calc(@code-padding + var(--phoenix-cm6-scrollbar-bottom, 0)) + 0; + caret-color: @foreground !important; + } + + .cm-line { + padding: 0 @code-padding 0 0; + } + + .phoenix-cm6-rulers { + pointer-events: none; + z-index: 1; + } + + .phoenix-cm6-line-widget { + pointer-events: auto; + } + + &.CodeMirror-overwrite .cm-cursor { + border-left: none !important; + border-bottom: 1px solid black; + width: 1.2ex; + } + + .cm-gutters { + background-color: @background; + border-right: none; + color: @accent-comment; + } + + .cm-lineNumbers .cm-gutterElement { + color: @accent-comment; + min-width: 2.5em; + padding: 0 @code-padding 0 10px; + } + + .cm-activeLine { + background: transparent; + } + + &.cm-focused .cm-activeLine { + background: @activeline-bg; + } + + .cm-activeLineGutter { + background: transparent; + color: @accent-comment; + } + + &.cm-focused .cm-activeLineGutter { + background: @activeline-number-bg; + color: @activeline-number; + } + + .cm-selectionBackground { + background: @selection-color-unfocused; + } + + &.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground { + background: @selection-color-focused; + } + + .cm-cursor, + .cm-dropCursor { + border-left-color: black; + } + + &.cm-vimMode { + .cm-content { + caret-color: transparent !important; + } + + &.cm-fat-cursor .cm-cursor { + width: 1ch; + border-left: none !important; + background: fade(@foreground, 50%); + } + + &.cm-fat-cursor:not(.cm-focused) .cm-cursor { + box-sizing: border-box; + background: transparent; + outline: 1px solid fade(@foreground, 70%); + } + } + + .phoenix-cm6-vim-dialog, + .phoenix-cm6-vim-notification { + position: absolute; + right: 0; + left: 0; + z-index: 10; + box-sizing: border-box; + padding: 4px 8px; + overflow: hidden; + color: @foreground; + background: @background; + border-bottom: 1px solid @bc-panel-border; + + input { + width: 20em; + max-width: calc(100% - 2em); + margin: 0 4px; + color: inherit; + font: inherit; + background: transparent; + border: none; + outline: none; + } + } + + .CodeMirror-dialog-top { + top: 0; + } + + .CodeMirror-dialog-bottom { + bottom: 0; + border-top: 1px solid @bc-panel-border; + border-bottom: none; + } + + .CodeMirror-dialog { + position: absolute; + right: 0; + left: 0; + z-index: 15; + box-sizing: border-box; + padding: 0.1em 0.8em; + overflow: hidden; + color: inherit; + background: inherit; + + input { + width: 20em; + max-width: calc(100% - 2em); + color: inherit; + font-family: monospace; + background: transparent; + border: none; + outline: none; + } + + button { + font-size: 70%; + } + } + + &.CodeMirror-fullscreen { + position: fixed; + inset: 0; + z-index: 9; + height: auto; + } + + .CodeMirror-lint-markers { + width: 16px; + } + + .CodeMirror-lint-mark { + background-position: left bottom; + background-repeat: repeat-x; + } + + .CodeMirror-lint-mark-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); + } + + .CodeMirror-lint-mark-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); + } + + .CodeMirror-lint-marker { + position: relative; + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + cursor: pointer; + background-position: center center; + background-repeat: no-repeat; + } + + .CodeMirror-lint-marker-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); + } + + .CodeMirror-lint-marker-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAAElFTkSuQmCC"); + } + + .CodeMirror-lint-marker-multiple { + width: 100%; + height: 100%; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); + background-position: right bottom; + background-repeat: no-repeat; + } + + .CodeMirror-lint-line-error { + background-color: rgba(183, 76, 81, 0.08); + } + + .CodeMirror-lint-line-warning { + background-color: rgba(255, 211, 0, 0.1); + } + + &.phoenix-cm6-custom-scrollbars .cm-scroller { + scrollbar-width: none; + -ms-overflow-style: none; + } + + &.phoenix-cm6-custom-scrollbars .cm-scroller::-webkit-scrollbar { + width: 0; + height: 0; + } + + .CodeMirror-simplescroll-horizontal div, + .CodeMirror-simplescroll-vertical div { + position: absolute; + box-sizing: border-box; + background: #ccc; + border: 1px solid #bbb; + border-radius: 2px; + } + + .CodeMirror-simplescroll-horizontal, + .CodeMirror-simplescroll-vertical { + position: absolute; + z-index: 6; + background: #eee; + } + + .CodeMirror-simplescroll-horizontal { + right: 0; + bottom: 0; + left: 0; + height: 8px; + + div { + bottom: 0; + height: 100%; + } + } + + .CodeMirror-simplescroll-vertical { + top: 0; + right: 0; + bottom: 0; + width: 8px; + + div { + right: 0; + width: 100%; + } + } + + .CodeMirror-overlayscroll-horizontal div, + .CodeMirror-overlayscroll-vertical div { + position: absolute; + background: #bcd; + border-radius: 3px; + } + + .CodeMirror-overlayscroll-horizontal, + .CodeMirror-overlayscroll-vertical { + position: absolute; + z-index: 6; + } + + .CodeMirror-overlayscroll-horizontal { + right: 0; + bottom: 0; + left: 0; + height: 6px; + + div { + bottom: 0; + height: 100%; + } + } + + .CodeMirror-overlayscroll-vertical { + top: 0; + right: 0; + bottom: 0; + width: 6px; + + div { + right: 0; + width: 100%; + } + } + + span.cm-underlined, + .cm-tw-underline { + text-decoration: underline; + } + + span.cm-strikethrough, + .cm-tw-deleted { + text-decoration: line-through; + } + + span.cm-brace { + color: #170; + font-weight: bold; + } + + span.cm-table { + color: blue; + font-weight: bold; + } + + .cm-tw-syntaxerror { + color: #fff; + background-color: #900; + } + + .cm-tw-header5 { + font-weight: bold; + } + + .cm-tw-listitem:first-child { + padding-left: 10px; + } + + .cm-tw-box { + border: 1px solid; + border-top-width: 0 !important; + border-color: inherit; + } + + .cm-matchingBracket { + background-color: @matching-bracket; + color: @accent-bracket; + } + + .cm-nonmatchingBracket { + color: @accent-bracket; + } +} + +.CodeMirror-hints { + position: absolute; + z-index: 10; + box-sizing: border-box; + max-height: 20em; + margin: 0; + padding: 2px; + overflow: hidden auto; + color: black; + font-family: monospace; + font-size: 90%; + list-style: none; + background: white; + border: 1px solid silver; + border-radius: 3px; + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); +} + +.CodeMirror-hint { + margin: 0; + padding: 0 4px; + color: black; + white-space: pre; + cursor: pointer; + border-radius: 2px; +} + +li.CodeMirror-hint-active { + color: white; + background: #08f; +} + +.CodeMirror-lint-tooltip { + position: fixed; + z-index: 100; + max-width: 600px; + padding: 2px 5px; + overflow: hidden; + color: black; + font-family: monospace; + font-size: 10pt; + white-space: pre-wrap; + background-color: #ffd; + border: 1px solid black; + border-radius: 4px; + opacity: 0; + transition: opacity 0.4s; +} + +.CodeMirror-lint-message { + padding-left: 18px; + background-position: top left; + background-repeat: no-repeat; +} + +.CodeMirror-lint-message-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); +} + +.CodeMirror-lint-message-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAAElFTkSuQmCC"); +} + +.CodeMirror-merge { + position: relative; + height: 350px; + white-space: pre; + border: 1px solid #ddd; + + .CodeMirror { + height: 350px; + } +} + +.CodeMirror-merge-2pane { + .CodeMirror-merge-pane { + width: 47%; + } + + .CodeMirror-merge-gap { + width: 6%; + } +} + +.CodeMirror-merge-3pane { + .CodeMirror-merge-pane { + width: 31%; + } + + .CodeMirror-merge-gap { + width: 3.5%; + } +} + +.CodeMirror-merge-pane { + display: inline-block; + white-space: normal; + vertical-align: top; +} + +.CodeMirror-merge-pane-rightmost { + position: absolute; + right: 0; + z-index: 1; +} + +.CodeMirror-merge-gap { + position: relative; + z-index: 2; + display: inline-block; + box-sizing: border-box; + height: 100%; + overflow: hidden; + background: #f8f8f8; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; +} + +.CodeMirror-merge-scrolllock-wrap { + position: absolute; + bottom: 0; + left: 50%; +} + +.CodeMirror-merge-scrolllock { + position: relative; + left: -50%; + color: #555; + line-height: 1; + cursor: pointer; + + &::after { + content: "\21db\00a0\00a0\21da"; + } + + &.CodeMirror-merge-scrolllock-enabled::after { + content: "\21db\21da"; + } +} + +.CodeMirror-merge-copybuttons-left, +.CodeMirror-merge-copybuttons-right { + position: absolute; + inset: 0; + line-height: 1; +} + +.CodeMirror-merge-copy, +.CodeMirror-merge-copy-reverse { + position: absolute; + z-index: 3; + color: #44c; + cursor: pointer; +} + +.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { + left: 2px; +} + +.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { + right: 2px; +} + +.CodeMirror-merge-r-inserted, +.CodeMirror-merge-l-inserted { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg=="); + background-position: bottom left; + background-repeat: repeat-x; +} + +.CodeMirror-merge-r-deleted, +.CodeMirror-merge-l-deleted { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg=="); + background-position: bottom left; + background-repeat: repeat-x; +} + +.CodeMirror-merge-r-chunk { + background: #ffffe0; +} + +.CodeMirror-merge-r-chunk-start { + border-top: 1px solid #ee8; +} + +.CodeMirror-merge-r-chunk-end { + border-bottom: 1px solid #ee8; +} + +.CodeMirror-merge-r-connect { + fill: #ffffe0; + stroke: #ee8; + stroke-width: 1px; +} + +.CodeMirror-merge-l-chunk { + background: #eef; +} + +.CodeMirror-merge-l-chunk-start { + border-top: 1px solid #88e; +} + +.CodeMirror-merge-l-chunk-end { + border-bottom: 1px solid #88e; +} + +.CodeMirror-merge-l-connect { + fill: #eef; + stroke: #88e; + stroke-width: 1px; +} + +.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { + background: #dfd; +} + +.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { + border-top: 1px solid #4e4; +} + +.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { + border-bottom: 1px solid #4e4; +} + +.CodeMirror-merge-collapsed-widget { + padding: 0 3px; + color: #88b; + font-size: 90%; + cursor: pointer; + background: #eef; + border: 1px solid #ddf; + border-radius: 4px; + + &::before { + content: "(...)"; + } +} + +.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { + display: none; +} + +.CodeMirror-Tern-completion { + position: relative; + padding-left: 22px; + line-height: 1.5; + + &::before { + position: absolute; + bottom: 2px; + left: 2px; + box-sizing: border-box; + width: 15px; + height: 15px; + color: white; + font-weight: bold; + font-size: 12px; + line-height: 16px; + text-align: center; + border-radius: 50%; + } +} + +.CodeMirror-Tern-completion-unknown::before { + content: "?"; + background: #4bb; +} + +.CodeMirror-Tern-completion-object::before { + content: "O"; + background: #77c; +} + +.CodeMirror-Tern-completion-fn::before { + content: "F"; + background: #7c7; +} + +.CodeMirror-Tern-completion-array::before { + content: "A"; + background: #c66; +} + +.CodeMirror-Tern-completion-number::before { + content: "1"; + background: #999; +} + +.CodeMirror-Tern-completion-string::before { + content: "S"; + background: #999; +} + +.CodeMirror-Tern-completion-bool::before { + content: "B"; + background: #999; +} + +.CodeMirror-Tern-completion-guess { + color: #999; +} + +.CodeMirror-Tern-tooltip { + position: absolute; + z-index: 10; + max-width: 40em; + padding: 2px 5px; + color: #444; + font-family: monospace; + font-size: 90%; + white-space: pre-wrap; + background-color: white; + border: 1px solid silver; + border-radius: 3px; + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + transition: opacity 1s; +} + +.CodeMirror-Tern-hint-doc { + max-width: 25em; + margin-top: -3px; +} + +.CodeMirror-Tern-fname { + color: black; +} + +.CodeMirror-Tern-farg { + color: #70a; +} + +.CodeMirror-Tern-farg-current { + text-decoration: underline; +} + +.CodeMirror-Tern-type { + color: #07c; +} + +.CodeMirror-Tern-fhint-guess { + opacity: 0.7; +} + +// CM6's base theme uses `display: flex !important`. Phoenix switches open +// editors by writing an inline `display: none`, so explicitly preserve that +// existing visibility contract for inactive editor views. +.CodeMirror.phoenix-codemirror-6[style*="display: none"] { + display: none !important; +} + +.show-line-padding .CodeMirror.phoenix-codemirror-6 .cm-line { + padding-left: @code-padding; +} + +.dark #editor-holder, +.dark .editor-holder { + .CodeMirror.phoenix-codemirror-6 { + background-color: #1d1f21; + color: #ddd; + + .cm-content { + caret-color: #c5c8c6 !important; + } + + .cm-gutters { + background-color: #1d1f21; + color: #767676; + } + + .cm-lineNumbers .cm-gutterElement { + color: #767676; + } + + .cm-activeLine { + background: transparent; + } + + &.cm-focused .cm-activeLine { + background: #2f2f2f; + } + + .cm-activeLineGutter { + background: transparent; + color: #767676; + } + + &.cm-focused .cm-activeLineGutter { + background: rgba(0, 0, 0, 0.2); + color: #fff; + } + + .cm-selectionBackground { + background: #333f48; + } + + &.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground { + background: #0050a0; + } + + .cm-cursor, + .cm-dropCursor { + border-left-color: #c5c8c6; + } + + &.CodeMirror-overwrite .cm-cursor { + border-left: none !important; + border-bottom: 1px solid #fff; + } + } +} diff --git a/src/styles/brackets_codemirror6_legacy_themes.less b/src/styles/brackets_codemirror6_legacy_themes.less new file mode 100644 index 0000000000..597113de8e --- /dev/null +++ b/src/styles/brackets_codemirror6_legacy_themes.less @@ -0,0 +1,3571 @@ +/* DONT_STRIP_MINIFY + * Phoenix CodeMirror 6 compatibility styles derived from CodeMirror 5.65.16 themes. + * Third-party license notice: thirdparty/licences/codemirror5-derived.markdown + * Selectors are isolated to the CM6 compatibility root. Theme declarations are + * authoritative while a stock legacy theme is selected, matching CM5 loading. + */ + +/* CodeMirror 5.65.16 theme source: 3024-day.css */ +/* + + Name: 3024 day + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day.CodeMirror { background: #f7f7f7 !important; color: #3a3432 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-comment { color: #cdab53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-atom { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-number { color: #a16a94 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-attribute { color: #01a252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-keyword { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-string { color: #fded02 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-variable { color: #01a252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-variable-2 { color: #01a0e4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-def { color: #e8bbd0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-bracket { color: #3a3432 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-tag { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-link { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day span.cm-error { background: #db2d20 !important; color: #5c5855 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline !important; color: #a16a94 !important; } + +/* CodeMirror 5.65.16 theme source: 3024-night.css */ +/* + + Name: 3024 night + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night.CodeMirror { background: #090300 !important; color: #d6d5d4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night div.CodeMirror-selected { background: #3a3432 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-gutters { background: #090300 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-comment { color: #cdab53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-atom { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-number { color: #a16a94 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-attribute { color: #01a252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-keyword { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-string { color: #fded02 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-variable { color: #01a252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-variable-2 { color: #01a0e4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-def { color: #e8bbd0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-bracket { color: #d6d5d4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-tag { color: #db2d20 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-link { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night span.cm-error { background: #db2d20 !important; color: #807d7c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: abbott.css */ +/* + * abbott.css + * A warm, dark theme for prose and code, with pastels and pretty greens. + * + * Ported from abbott.vim (https://github.com/bcat/abbott.vim) version 2.1. + * Original design and CodeMirror port by Jonathan Rascher. + * + * This theme shares the following color palette with the Vim color scheme. + * + * Brown shades: + * bistre: #231c14 + * chocolate: #3c3022 + * cocoa: #745d42 + * vanilla_cream: #fef3b4 + * + * Red shades: + * crimson: #d80450 + * cinnabar: #f63f05 + * + * Green shades: + * dark_olive: #273900 + * forest_green: #24a507 + * chartreuse: #a0ea00 + * pastel_chartreuse: #d8ff84 + * + * Yellow shades: + * marigold: #fbb32f + * lemon_meringue: #fbec5d + * + * Blue shades: + * cornflower_blue: #3f91f1 + * periwinkle_blue: #8ccdf0 + * + * Magenta shades: + * french_pink: #ec6c99 + * lavender: #e6a2f3 + * + * Cyan shades: + * zomp: #39a78d + * seafoam_green: #00ff7f + */ + +/* Style the UI: */ + +/* Equivalent to Vim's Normal group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott.CodeMirror { + background: #231c14 /* bistre */ !important; + color: #d8ff84 /* pastel_chartreuse */ !important; +} + +/* Roughly equivalent to Vim's LineNr group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-gutters { + background: #231c14 /* bistre */ !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-linenumber { color: #fbec5d /* lemon_meringue */ !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-guttermarker { color: #f63f05 /* cinnabar */ !important; } + +/* Roughly equivalent to Vim's FoldColumn group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-guttermarker-subtle { color: #fbb32f /* marigold */ !important; } + +/* + * Roughly equivalent to Vim's CursorColumn group. (We use a brighter color + * since Vim's cursorcolumn option highlights a whole column, whereas + * CodeMirror's rule just highlights a thin line.) + */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-ruler { border-color: #745d42 /* cocoa */ !important; } + +/* Equivalent to Vim's Cursor group in insert mode. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-cursor { border-color: #a0ea00 /* chartreuse */ !important; } + +/* Equivalent to Vim's Cursor group in normal mode. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott.cm-fat-cursor .CodeMirror-cursor, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .cm-animate-fat-cursor { + /* + * CodeMirror doesn't allow changing the foreground color of the character + * under the cursor, so we can't use a reverse video effect for the cursor. + * Instead, make it semitransparent. + */ + background: rgba(160, 234, 0, 0.5) /* chartreuse */ !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-abbott.cm-fat-cursor .CodeMirror-cursors { + /* + * Boost the z-index so the fat cursor shows up on top of text and + * matchingbracket/matchingtag highlights. + */ + z-index: 3 !important; +} + +/* Equivalent to Vim's Cursor group in replace mode. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-overwrite .CodeMirror-cursor { + border-bottom: 1px solid #a0ea00 /* chartreuse */ !important; + border-left: none !important; + width: auto !important; +} + +/* Roughly equivalent to Vim's CursorIM group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-secondarycursor { + border-color: #00ff7f /* seafoam_green */ !important; +} + +/* Roughly equivalent to Vim's Visual group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-selected, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott.CodeMirror-focused .CodeMirror-selected { + background: #273900 /* dark_olive */ !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line > span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line > span > span::selection { + background: #273900 /* dark_olive */ !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line > span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-line > span > span::-moz-selection { + background: #273900 /* dark_olive */ !important; +} + +/* Roughly equivalent to Vim's SpecialKey group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .cm-tab { color: #00ff7f /* seafoam_green */ !important; } + +/* Equivalent to Vim's Search group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .cm-searching { + background: #fef3b4 /* vanilla_cream */ !important; + color: #231c14 /* bistre */ !important; +} + +/* Style syntax highlighting modes: */ + +/* Equivalent to Vim's Comment group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-comment { + color: #fbb32f /* marigold */ !important; + font-style: italic !important; +} + +/* Equivalent to Vim's String group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-string, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-string-2 { + color: #e6a2f3 /* lavender */ !important; +} + +/* Equivalent to Vim's Constant group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-number, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-string.cm-url { color: #f63f05 /* cinnabar */ !important; } + +/* Roughly equivalent to Vim's SpecialKey group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-invalidchar { color: #00ff7f /* seafoam_green */ !important; } + +/* Equivalent to Vim's Special group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-atom { color: #fef3b4 /* vanilla_cream */ !important; } + +/* Equivalent to Vim's Delimiter group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-bracket, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-punctuation { + color: #fef3b4 /* vanilla_cream */ !important; +} + +/* Equivalent Vim's Operator group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-operator { font-weight: bold !important; } + +/* Roughly equivalent to Vim's Identifier group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-def, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-variable, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-variable-2, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-variable-3 { + color: #8ccdf0 /* periwinkle_blue */ !important; +} + +/* Roughly equivalent to Vim's Function group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-builtin, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-property, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-qualifier { + color: #3f91f1 /* cornflower_blue */ !important; +} + +/* Equivalent to Vim's Type group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-type { color: #24a507 /* forest_green */ !important; } + +/* Equivalent to Vim's Keyword group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-keyword { + color: #d80450 /* crimson */ !important; + font-weight: bold !important; +} + +/* Equivalent to Vim's PreProc group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-meta { color: #ec6c99 /* french_pink */ !important; } + +/* Equivalent to Vim's htmlTagName group (linked to Statement). */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-tag { + color: #d80450 /* crimson */ !important; + font-weight: bold !important; +} + +/* Equivalent to Vim's htmlArg group (linked to Type). */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-attribute { color: #24a507 /* forest_green */ !important; } + +/* Equivalent to Vim's htmlH1, markdownH1, etc. groups (linked to Title). */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-header { + color: #d80450 /* crimson */ !important; + font-weight: bold !important; +} + +/* Equivalent to Vim's markdownRule group (linked to PreProc). */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-hr { color: #ec6c99 /* french_pink */ !important; } + +/* Roughly equivalent to Vim's Underlined group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-link { color: #e6a2f3 /* lavender */ !important; } + +/* Equivalent to Vim's diffRemoved group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-negative { + background: #d80450 /* crimson */ !important; + color: #231c14 /* bistre */ !important; +} + +/* Equivalent to Vim's diffAdded group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-positive { + background: #a0ea00 /* chartreuse */ !important; + color: #231c14 /* bistre */ !important; + font-weight: bold !important; +} + +/* Equivalent to Vim's Error group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.cm-error { + background: #d80450 /* crimson */ !important; + color: #231c14 /* bistre */ !important; +} + +/* Style addons: */ + +/* Equivalent to Vim's MatchParen group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.CodeMirror-matchingbracket { + background: #745d42 /* cocoa */ !important; + color: #231c14 /* bistre */ !important; + font-weight: bold !important; +} + +/* + * Roughly equivalent to Vim's Error group. (Vim doesn't seem to have a direct + * equivalent in its own matchparen plugin, but many syntax highlighting plugins + * mark mismatched brackets as Error.) + */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott span.CodeMirror-nonmatchingbracket { + background: #d80450 /* crimson */ !important; + color: #231c14 /* bistre */ !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-matchingtag, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .cm-matchhighlight { + outline: 1px solid #39a78d /* zomp */ !important; +} + +/* Equivalent to Vim's CursorLine group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-activeline-background, +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-activeline-gutter { + background: #3c3022 /* chocolate */ !important; +} + +/* Equivalent to Vim's CursorLineNr group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-activeline-gutter .CodeMirror-linenumber { + color: #d8ff84 /* pastel_chartreuse */ !important; + font-weight: bold !important; +} + +/* Roughly equivalent to Vim's Folded group. */ +.CodeMirror.phoenix-codemirror-6.cm-s-abbott .CodeMirror-foldmarker { + color: #f63f05 /* cinnabar */ !important; + text-shadow: none !important; +} + +/* CodeMirror 5.65.16 theme source: abcdef.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef.CodeMirror { background: #0f0f0f !important; color: #defdef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef div.CodeMirror-selected { background: #515151 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-gutters { background: #555 !important; border-right: 2px solid #314151 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-guttermarker { color: #222 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-keyword { color: darkgoldenrod !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-atom { color: #77F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-number { color: violet !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-def { color: #fffabc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-variable { color: #abcdef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-variable-2 { color: #cacbcc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-type { color: #def !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-property { color: #fedcba !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-operator { color: #ff0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-comment { color: #7a7b7c !important; font-style: italic !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-string { color: #2b4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-meta { color: #C9F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-qualifier { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-builtin { color: #30aabc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-bracket { color: #8a8a8a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-tag { color: #FFDD44 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-attribute { color: #DDFF00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-error { color: #FF0000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-header { color: aquamarine !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef span.cm-link { color: blueviolet !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-abcdef .CodeMirror-activeline-background { background: #314151 !important; } + +/* CodeMirror 5.65.16 theme source: ambiance-mobile.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance-mobile.CodeMirror { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +/* CodeMirror 5.65.16 theme source: ambiance.css */ +/* ambiance theme for codemirror */ + +/* Color scheme */ + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-header { color: blue !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-quote { color: #24C2C7 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-keyword { color: #cda869 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-atom { color: #CF7EA9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-number { color: #78CF8A !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-def { color: #aac6e3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-variable { color: #ffb795 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-variable-2 { color: #eed1b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-type { color: #faded3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-property { color: #eed1b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-operator { color: #fa8d6a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-comment { color: #555 !important; font-style:italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-string { color: #8f9d6a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-string-2 { color: #9d937c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-meta { color: #D2A8A1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-qualifier { color: yellow !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-builtin { color: #9999cc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-bracket { color: #24C2C7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-tag { color: #fee4ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-attribute { color: #9B859D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-hr { color: pink !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-link { color: #F4C20B !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-special { color: #FF9D00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .cm-error { color: #AF2018 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10) !important; } + +/* Editor styling */ + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance.CodeMirror { + line-height: 1.40em !important; + color: #E6E1DC !important; + background-color: #202020 !important; + -webkit-box-shadow: inset 0 0 10px black !important; + -moz-box-shadow: inset 0 0 10px black !important; + box-shadow: inset 0 0 10px black !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-gutters { + background: #3D3D3D !important; + border-right: 1px solid #4D4D4D !important; + box-shadow: 0 10px 20px black !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-linenumber { + text-shadow: 0px 1px 1px #4d4d4d !important; + color: #111 !important; + padding: 0 5px !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-activeline-background { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance.CodeMirror, +.CodeMirror.phoenix-codemirror-6.cm-s-ambiance .CodeMirror-gutters { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC") !important; +} + +/* CodeMirror 5.65.16 theme source: ayu-dark.css */ +/* Based on https://github.com/dempfi/ayu */ + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark.CodeMirror { background: #0a0e14 !important; color: #b3b1ad !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark div.CodeMirror-selected { background: #273747 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line > span > span::selection { background: rgba(39, 55, 71, 99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 55, 71, 99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-gutters { background: #0a0e14 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-guttermarker-subtle { color: #3d424d !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-linenumber { color: #3d424d !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-cursor { border-left: 1px solid #e6b450 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #a2a8a175 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-comment { color: #626a73 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-atom { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-number { color: #e6b450 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-comment.cm-attribute { color: #ffb454 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-comment.cm-def { color: rgba(57, 186, 230, 80) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-comment.cm-tag { color: #39bae6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-comment.cm-type { color: #5998a6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-attribute { color: #ffb454 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-keyword { color: #ff8f40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-builtin { color: #e6b450 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-string { color: #c2d94c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-variable { color: #b3b1ad !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-variable-2 { color: #f07178 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-variable-3 { color: #39bae6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-type { color: #ff8f40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-def { color: #ffee99 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-bracket { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-tag { color: rgba(57, 186, 230, 80) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-header { color: #c2d94c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-link { color: #39bae6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark span.cm-error { color: #ff3333 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-activeline-background { background: #01060e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-dark .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: ayu-mirage.css */ +/* Based on https://github.com/dempfi/ayu */ + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage.CodeMirror { background: #1f2430 !important; color: #cbccc6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage div.CodeMirror-selected { background: #34455a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line > span > span::selection { background: #34455a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-line > span > span::-moz-selection { background: rgba(25, 30, 42, 99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-gutters { background: #1f2430 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-guttermarker-subtle { color: rgba(112, 122, 140, 66) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-linenumber { color: rgba(61, 66, 77, 99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-cursor { border-left: 1px solid #ffcc66 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage.cm-fat-cursor .CodeMirror-cursor {background-color: #a2a8a175 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-comment { color: #5c6773 !important; font-style:italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-atom { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-number { color: #ffcc66 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-comment.cm-attribute { color: #ffd580 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-comment.cm-def { color: #d4bfff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-comment.cm-tag { color: #5ccfe6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-comment.cm-type { color: #5998a6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-property { color: #f29e74 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-attribute { color: #ffd580 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-keyword { color: #ffa759 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-builtin { color: #ffcc66 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-string { color: #bae67e !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-variable { color: #cbccc6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-variable-2 { color: #f28779 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-variable-3 { color: #5ccfe6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-type { color: #ffa759 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-def { color: #ffd580 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-bracket { color: rgba(92, 207, 230, 80) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-tag { color: #5ccfe6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-header { color: #bae67e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-link { color: #5ccfe6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage span.cm-error { color: #ff3333 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-activeline-background { background: #191e2a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ayu-mirage .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: base16-dark.css */ +/* + + Name: Base16 Default Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark.CodeMirror { background: #151515 !important; color: #e0e0e0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark div.CodeMirror-selected { background: #303030 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-gutters { background: #151515 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-linenumber { color: #505050 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #8e8d8875 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .cm-animate-fat-cursor { background-color: #8e8d8875 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-comment { color: #8f5536 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-atom { color: #aa759f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-number { color: #aa759f !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-attribute { color: #90a959 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-keyword { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-string { color: #f4bf75 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-variable { color: #90a959 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-def { color: #d28445 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-bracket { color: #e0e0e0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-tag { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-link { color: #aa759f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark span.cm-error { background: #ac4142 !important; color: #b0b0b0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-activeline-background { background: #202020 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: base16-light.css */ +/* + + Name: Base16 Default Light + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light.CodeMirror { background: #f5f5f5 !important; color: #202020 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-comment { color: #8f5536 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-atom { color: #aa759f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-number { color: #aa759f !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-attribute { color: #90a959 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-keyword { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-string { color: #f4bf75 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-variable { color: #90a959 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-variable-2 { color: #6a9fb5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-def { color: #d28445 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-bracket { color: #202020 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-tag { color: #ac4142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-link { color: #aa759f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light span.cm-error { background: #ac4142 !important; color: #505050 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-base16-light .CodeMirror-matchingbracket { color: #f5f5f5 !important; background-color: #6A9FB5 !important} + +/* CodeMirror 5.65.16 theme source: bespin.css */ +/* + + Name: Bespin + Author: Mozilla / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-bespin.CodeMirror {background: #28211c !important; color: #9d9b97 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin div.CodeMirror-selected {background: #59554f !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin .CodeMirror-gutters {background: #28211c !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin .CodeMirror-linenumber {color: #666666 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-comment {color: #937121 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-atom {color: #9b859d !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-number {color: #9b859d !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-attribute {color: #54be0d !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-keyword {color: #cf6a4c !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-string {color: #f9ee98 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-variable {color: #54be0d !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-variable-2 {color: #5ea6ea !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-def {color: #cf7d34 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-error {background: #cf6a4c !important; color: #797977 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-bracket {color: #9d9b97 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-tag {color: #cf6a4c !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin span.cm-link {color: #9b859d !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-bespin .CodeMirror-activeline-background { background: #404040 !important; } + +/* CodeMirror 5.65.16 theme source: blackboard.css */ +/* Port of TextMate's Blackboard theme */ + +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard.CodeMirror { background: #0C1021 !important; color: #F8F8F8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard div.CodeMirror-selected { background: #253B76 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-gutters { background: #0C1021 !important; border-right: 0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-linenumber { color: #888 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-keyword { color: #FBDE2D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-atom { color: #D8FA3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-number { color: #D8FA3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-def { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-variable { color: #FF6400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-operator { color: #FBDE2D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-comment { color: #AEAEAE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-string { color: #61CE3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-string-2 { color: #61CE3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-meta { color: #D8FA3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-builtin { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-tag { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-attribute { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-header { color: #FF6400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-hr { color: #AEAEAE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-link { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .cm-error { background: #9D1E15 !important; color: #F8F8F8 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey !important;color:white !important; } + +/* CodeMirror 5.65.16 theme source: cobalt.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt.CodeMirror { background: #002240 !important; color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-gutters { background: #002240 !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-comment { color: #08f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-atom { color: #845dc4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-attribute { color: #ff80e1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-keyword { color: #ffee80 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-string { color: #3ad900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-meta { color: #ff9d00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-tag { color: #9effff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt .cm-type { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-bracket { color: #d8d8d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-builtin, .CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-special { color: #ff9e59 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-link { color: #845dc4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt span.cm-error { color: #9d1e15 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-activeline-background { background: #002D57 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey !important;color:white !important; } + +/* CodeMirror 5.65.16 theme source: colorforth.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth.CodeMirror { background: #000000 !important; color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-gutters { background: #0a001f !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-linenumber { color: #bababa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-comment { color: #ededed !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-def { color: #ff1c1c !important; font-weight:bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-keyword { color: #ffd900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-builtin { color: #00d95a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-variable { color: #73ff00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-string { color: #007bff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-number { color: #00c4ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-atom { color: #606060 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-variable-2 { color: #EEE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-type { color: #DDD !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-property {} +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-operator {} + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-meta { color: yellow !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-qualifier { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-tag { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-attribute { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-error { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth div.CodeMirror-selected { background: #333d53 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-colorforth .CodeMirror-activeline-background { background: #253540 !important; } + +/* CodeMirror 5.65.16 theme source: darcula.css */ +/** + Name: IntelliJ IDEA darcula theme + From IntelliJ IDEA by JetBrains + */ + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula { font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-darcula.CodeMirror { background: #2B2B2B !important; color: #A9B7C6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-meta { color: #BBB529 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-number { color: #6897BB !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-keyword { color: #CC7832 !important; line-height: 1em !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-def { color: #A9B7C6 !important; font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-variable { color: #A9B7C6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-variable-2 { color: #A9B7C6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-variable-3 { color: #9876AA !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-type { color: #AABBCC !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-property { color: #FFC66D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-operator { color: #A9B7C6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-string { color: #6A8759 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-string-2 { color: #6A8759 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-comment { color: #61A151 !important; font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-link { color: #CC7832 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-atom { color: #CC7832 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-error { color: #BC3F3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-tag { color: #629755 !important; font-weight: bold !important; font-style: italic !important; text-decoration: underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-attribute { color: #6897bb !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-qualifier { color: #6A8759 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-bracket { color: #A9B7C6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-builtin { color: #FF9E59 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-special { color: #FF9E59 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-matchhighlight { color: #FFFFFF !important; background-color: rgba(50, 89, 48, .7) !important; font-weight: normal !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-darcula span.cm-searching { color: #FFFFFF !important; background-color: rgba(61, 115, 59, .7) !important; font-weight: normal !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-cursor { border-left: 1px solid #A9B7C6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-activeline-background { background: #323232 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-gutters { background: #313335 !important; border-right: 1px solid #313335 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-guttermarker { color: #FFEE80 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-guttermarker-subtle { color: #D0D0D0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirrir-linenumber { color: #606366 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-matchingbracket { background-color: #3B514D !important; color: #FFEF28 !important; font-weight: bold !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula div.CodeMirror-selected { background: #214283 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-hints.darcula { + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace !important; + color: #9C9E9E !important; + background-color: #3B3E3F !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-darcula .CodeMirror-hints.darcula .CodeMirror-hint-active { + background-color: #494D4E !important; + color: #9C9E9E !important; +} + +/* CodeMirror 5.65.16 theme source: dracula.css */ +/* + + Name: dracula + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) + +*/ + + +.CodeMirror.phoenix-codemirror-6.cm-s-dracula.CodeMirror, .CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-gutters { + background-color: #282a36 !important; + color: #f8f8f2 !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-gutters { color: #282a36 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-comment { color: #6272a4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-string-2 { color: #f1fa8c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-number { color: #bd93f9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-variable { color: #50fa7b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-variable-2 { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-def { color: #50fa7b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-operator { color: #ff79c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-keyword { color: #ff79c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-atom { color: #bd93f9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-meta { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-tag { color: #ff79c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-attribute { color: #50fa7b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-qualifier { color: #50fa7b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-property { color: #66d9ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-builtin { color: #50fa7b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-dracula span.cm-type { color: #ffb86c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: duotone-dark.css */ +/* +Name: DuoTone-Dark +Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + +CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark.CodeMirror { background: #2a2734 !important; color: #6c6783 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .CodeMirror-linenumber { color: #545167 !important; } + +/* begin cursor */ +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c !important; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c !important; /* border-right: .5em solid #ffad5c80; */ opacity: .5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342 !important; /* background: #36334280; */ opacity: .5 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c !important; /* background: #ffad5c80; */ opacity: .5 !important;} +/* end cursor */ + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-atom, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-keyword, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-variable, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-attribute, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-quote, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-hr, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-link { color: #ffcc99 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-property { color: #9a86fd !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-punctuation, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-unit, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-negative { color: #e09142 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-string { color: #ffb870 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-operator { color: #ffad5c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-positive { color: #6a51e6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-type, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-string-2, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-url { color: #7a63ee !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-tag, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-builtin, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-qualifier, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-header, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-em { color: #eeebff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-bracket, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-comment { color: #6c6783 !important; } + +/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-error, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-invalidchar { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark span.cm-header { font-weight: normal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline !important; color: #eeebff !important; } + +/* CodeMirror 5.65.16 theme source: duotone-light.css */ +/* +Name: DuoTone-Light +Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + +CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light.CodeMirror { background: #faf8f5 !important; color: #b29762 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light div.CodeMirror-selected { background: #e3dcce !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .CodeMirror-gutters { background: #faf8f5 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .CodeMirror-linenumber { color: #cdc4b1 !important; } + +/* begin cursor */ +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .CodeMirror-cursor { border-left: 1px solid #93abdc !important; /* border-left: 1px solid #93abdc80; */ border-right: .5em solid #93abdc !important; /* border-right: .5em solid #93abdc80; */ opacity: .5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .CodeMirror-activeline-background { background: #e3dcce !important; /* background: #e3dcce80; */ opacity: .5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .cm-fat-cursor .CodeMirror-cursor { background: #93abdc !important; /* #93abdc80; */ opacity: .5 !important; } +/* end cursor */ + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-atom, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-keyword, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-variable, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-attribute, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-quote, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-hr, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-link { color: #063289 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-property { color: #b29762 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-punctuation, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-unit, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-negative { color: #063289 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-operator { color: #1659df !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-positive { color: #896724 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-type, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-string-2, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-url { color: #896724 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-tag, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-builtin, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-qualifier, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-header, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-em { color: #2d2006 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-bracket, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-comment { color: #b6ad9a !important; } + +/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ +/* .cm-s-duotone-light span.cm-error { background: #896724; color: #728fcb; } */ +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-error, .CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-invalidchar { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light span.cm-header { font-weight: normal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-duotone-light .CodeMirror-matchingbracket { text-decoration: underline !important; color: #faf8f5 !important; } + +/* CodeMirror 5.65.16 theme source: eclipse.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-meta { color: #FF1717 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-keyword { line-height: 1em !important; font-weight: bold !important; color: #7F0055 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-atom { color: #219 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-number { color: #164 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-def { color: #00f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-variable-2 { color: #0000C0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-type { color: #0000C0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-property { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-operator { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-comment { color: #3F7F5F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-string { color: #2A00FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-string-2 { color: #f50 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-qualifier { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-builtin { color: #30a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-tag { color: #170 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-attribute { color: #00c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-link { color: #219 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse span.cm-error { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey !important; color:black !important; } + +/* CodeMirror 5.65.16 theme source: elegant.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-atom { color: #762 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-comment { color: #262 !important; font-style: italic !important; line-height: 1em !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-meta { color: #555 !important; font-style: italic !important; line-height: 1em !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-variable-2 { color: #b11 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-qualifier { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-keyword { color: #730 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-builtin { color: #30a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-link { color: #762 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant span.cm-error { background-color: #fdd !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey !important; color:black !important; } + +/* CodeMirror 5.65.16 theme source: erlang-dark.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark.CodeMirror { background: #002240 !important; color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-gutters { background: #002240 !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-quote { color: #ccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-atom { color: #f133f1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-attribute { color: #ff80e1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-bracket { color: #ff9d00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-builtin { color: #eaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-comment { color: #77f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-def { color: #e7a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-keyword { color: #ffee80 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-meta { color: #50fefe !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-number { color: #ffd0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-operator { color: #d55 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-property { color: #ccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-qualifier { color: #ccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-special { color: #ffbbbb !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-string { color: #3ad900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-string-2 { color: #ccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-tag { color: #9effff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-variable { color: #50fe50 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-variable-2 { color: #e0e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-type { color: #ccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark span.cm-error { color: #9d1e15 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: gruvbox-dark.css */ +/* + + Name: gruvbox-dark + Author: kRkk (https://github.com/krkk) + + Original gruvbox color scheme by Pavel Pertsev (https://github.com/morhetz/gruvbox) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark.CodeMirror, .CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-gutters { background-color: #282828 !important; color: #bdae93 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-gutters {background: #282828 !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-linenumber {color: #7c6f64 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-cursor { border-left: 1px solid #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #8e8d8875 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .cm-animate-fat-cursor { background-color: #8e8d8875 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark div.CodeMirror-selected { background: #928374 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-meta { color: #83a598 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-comment { color: #928374 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-atom { color: #d3869b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-keyword { color: #f84934 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-variable { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-variable-2 { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-type { color: #fabd2f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-operator { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-callee { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-def { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-property { color: #ebdbb2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-string { color: #b8bb26 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-string-2 { color: #8ec07c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-qualifier { color: #8ec07c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-attribute { color: #8ec07c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-activeline-background { background: #3c3836 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark .CodeMirror-matchingbracket { background: #928374 !important; color:#282828 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-builtin { color: #fe8019 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-gruvbox-dark span.cm-tag { color: #fe8019 !important; } + +/* CodeMirror 5.65.16 theme source: hopscotch.css */ +/* + + Name: Hopscotch + Author: Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch.CodeMirror {background: #322931 !important; color: #d5d3d5 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch .CodeMirror-gutters {background: #322931 !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch .CodeMirror-linenumber {color: #797379 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-comment {color: #b33508 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-atom {color: #c85e7c !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-number {color: #c85e7c !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-attribute {color: #8fc13e !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-keyword {color: #dd464c !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-string {color: #fdcc59 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-variable {color: #8fc13e !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-variable-2 {color: #1290bf !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-def {color: #fd8b19 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-error {background: #dd464c !important; color: #989498 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-bracket {color: #d5d3d5 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-tag {color: #dd464c !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch span.cm-link {color: #c85e7c !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-hopscotch .CodeMirror-activeline-background { background: #302020 !important; } + +/* CodeMirror 5.65.16 theme source: icecoder.css */ +/* +ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder { color: #666 !important; background: #1d1d1b !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-keyword { color: #eee !important; font-weight:bold !important; } /* off-white 1 */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-atom { color: #e1c76e !important; } /* yellow */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-number { color: #6cb5d9 !important; } /* blue */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-def { color: #b9ca4a !important; } /* green */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-variable { color: #6cb5d9 !important; } /* blue */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c !important; } /* pink */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-type { color: #f9602c !important; } /* orange */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-property { color: #eee !important; } /* off-white 1 */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-operator { color: #9179bb !important; } /* purple */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-comment { color: #97a3aa !important; } /* grey-blue */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-string { color: #b9ca4a !important; } /* green */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-string-2 { color: #6cb5d9 !important; } /* blue */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-meta { color: #555 !important; } /* grey */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-qualifier { color: #555 !important; } /* grey */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-builtin { color: #214e7b !important; } /* bright blue */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-bracket { color: #cc7 !important; } /* grey-yellow */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-tag { color: #e8e8e8 !important; } /* off-white 2 */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-attribute { color: #099 !important; } /* teal */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-header { color: #6a0d6a !important; } /* purple-pink */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-quote { color: #186718 !important; } /* dark green */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-hr { color: #888 !important; } /* mid-grey */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-link { color: #e1c76e !important; } /* yellow */ +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder span.cm-error { color: #d00 !important; } /* red */ + +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder div.CodeMirror-selected { color: #fff !important; background: #037 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b !important; min-width: 41px !important; border-right: 0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder .CodeMirror-linenumber { color: #555 !important; cursor: default !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-icecoder .CodeMirror-activeline-background { background: #000 !important; } + +/* CodeMirror 5.65.16 theme source: idea.css */ +/** + Name: IDEA default theme + From IntelliJ IDEA by JetBrains + */ + +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-meta { color: #808000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-number { color: #0000FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-keyword { line-height: 1em !important; font-weight: bold !important; color: #000080 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-atom { font-weight: bold !important; color: #000080 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-def { color: #000000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-variable-2 { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-type { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-property { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-operator { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-comment { color: #808080 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-string { color: #008000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-string-2 { color: #008000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-qualifier { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-error { color: #FF0000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-attribute { color: #0000FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-tag { color: #000080 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-link { color: #0000FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea .CodeMirror-activeline-background { background: #FFFAE3 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-builtin { color: #30a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-idea { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif !important;} + + +.CodeMirror.phoenix-codemirror-6.cm-s-idea .CodeMirror-matchingbracket { outline:1px solid grey !important; color:black !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-idea .CodeMirror-hints.idea { + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace !important; + color: #616569 !important; + background-color: #ebf3fd !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-idea .CodeMirror-hints.idea .CodeMirror-hint-active { + background-color: #a2b8c9 !important; + color: #5c6065 !important; +} + +/* CodeMirror 5.65.16 theme source: isotope.css */ +/* + + Name: Isotope + Author: David Desandro / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-isotope.CodeMirror {background: #000000 !important; color: #e0e0e0 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope div.CodeMirror-selected {background: #404040 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope .CodeMirror-gutters {background: #000000 !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope .CodeMirror-linenumber {color: #808080 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-comment {color: #3300ff !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-atom {color: #cc00ff !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-number {color: #cc00ff !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-attribute {color: #33ff00 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-keyword {color: #ff0000 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-string {color: #ff0099 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-variable {color: #33ff00 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-variable-2 {color: #0066ff !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-def {color: #ff9900 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-error {background: #ff0000 !important; color: #c0c0c0 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-bracket {color: #e0e0e0 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-tag {color: #ff0000 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope span.cm-link {color: #cc00ff !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-isotope .CodeMirror-activeline-background { background: #202020 !important; } + +/* CodeMirror 5.65.16 theme source: juejin.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-juejin.CodeMirror { + background: #f8f9fa !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-header, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-def { + color: #1ba2f0 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-comment { + color: #009e9d !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-quote, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-link, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-strong, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-attribute { + color: #fd7741 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-url, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-keyword, +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-builtin { + color: #bb51b8 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-hr { + color: #909090 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-tag { + color: #107000 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-juejin .cm-variable-2 { + color: #0050a0 !important; +} + +/* CodeMirror 5.65.16 theme source: lesser-dark.css */ +/* +http://lesscss.org/ dark theme +Ported to CodeMirror by Peter Kroon +*/ +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark { + line-height: 1.3em !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark.CodeMirror { background: #262626 !important; color: #EBEFE7 !important; text-shadow: 0 -1px 1px #262626 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B !important; } /* 33322B*/ +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark pre { padding: 0 8px !important; }/*editable code holder*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E !important; }/*65FC65*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-gutters { background: #262626 !important; border-right:1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-linenumber { color: #777 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-header { color: #a0a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-quote { color: #090 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-keyword { color: #599eff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-atom { color: #C2B470 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-number { color: #B35E4D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-def { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-variable { color:#D9BF8C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-variable-2 { color: #669199 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-type { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-property { color: #92A75C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-operator { color: #92A75C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-comment { color: #666 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-string { color: #BCD279 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-string-2 { color: #f50 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-meta { color: #738C73 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-qualifier { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-builtin { color: #ff9e59 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-tag { color: #669199 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-attribute { color: #81a4d5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-hr { color: #999 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-link { color: #7070E6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark span.cm-error { color: #9d1e15 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: liquibyte.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte.CodeMirror { + background-color: #000 !important; + color: #fff !important; + line-height: 1.2em !important; + font-size: 1em !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight { + text-decoration: underline !important; + text-decoration-color: #0f0 !important; + text-decoration-style: wavy !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .cm-trailingspace { + text-decoration: line-through !important; + text-decoration-color: #f00 !important; + text-decoration-style: dotted !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .cm-tab { + text-decoration: line-through !important; + text-decoration-color: #404040 !important; + text-decoration-style: dotted !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626 !important; border-right: 1px solid #505050 !important; padding-right: 0.8em !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-guttermarker { } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-guttermarker-subtle { } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-linenumber { color: #606060 !important; padding-left: 0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-comment { color: #008000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-def { color: #ffaf40 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-keyword { color: #c080ff !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-builtin { color: #ffaf40 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-variable { color: #5967ff !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-string { color: #ff8000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-number { color: #0f0 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-atom { color: #bf3030 !important; font-weight: bold !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-variable-2 { color: #007f7f !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-type { color: #c080ff !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-property { color: #999 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-operator { color: #fff !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-meta { color: #0f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-qualifier { color: #fff700 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-tag { color: #ff0 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-attribute { color: #c080ff !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-error { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15) !important; } + +/* Default styles for common addons */ +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte .CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3) !important; } +/* Scrollbars */ +/* Simple */ +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover { + background-color: rgba(80, 80, 80, .7) !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { + background-color: rgba(80, 80, 80, .3) !important; + border: 1px solid #404040 !important; + border-radius: 5px !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { + border-top: 1px solid #404040 !important; + border-bottom: 1px solid #404040 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div { + border-left: 1px solid #404040 !important; + border-right: 1px solid #404040 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-vertical { + background-color: #262626 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal { + background-color: #262626 !important; + border-top: 1px solid #404040 !important; +} +/* Overlay */ +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, .CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { + background-color: #404040 !important; + border-radius: 5px !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { + border: 1px solid #404040 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div { + border: 1px solid #404040 !important; +} + +/* CodeMirror 5.65.16 theme source: lucario.css */ +/* + Name: lucario + Author: Raphael Amorim + + Original Lucario color scheme (https://github.com/raphamorim/lucario) +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-lucario.CodeMirror, .CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-gutters { + background-color: #2b3e50 !important; + color: #f8f8f2 !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-gutters { color: #2b3e50 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-cursor { border-left: solid thin #E6C845 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-linenumber { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-selected { background: #243443 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line > span > span::selection { background: #243443 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-line > span > span::-moz-selection { background: #243443 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-comment { color: #5c98cd !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-string-2 { color: #E6DB74 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-number { color: #ca94ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-variable { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-variable-2 { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-def { color: #72C05D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-operator { color: #66D9EF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-keyword { color: #ff6541 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-atom { color: #bd93f9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-meta { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-tag { color: #ff6541 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-attribute { color: #66D9EF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-qualifier { color: #72C05D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-property { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-builtin { color: #72C05D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-lucario span.cm-type { color: #ffb86c !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-activeline-background { background: #243443 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-lucario .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: material-darker.css */ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker.CodeMirror { + background-color: #212121 !important; + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-gutters { + background: #212121 !important; + color: #545454 !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-guttermarker, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-guttermarker-subtle, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-linenumber { + color: #545454 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-cursor { + border-left: 1px solid #FFCC00 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker div.CodeMirror-selected { + background: rgba(97, 97, 97, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker.CodeMirror-focused div.CodeMirror-selected { + background: rgba(97, 97, 97, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line>span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line>span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-keyword { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-operator { + color: #89DDFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-variable-2 { + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-type { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-builtin { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-atom { + color: #F78C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-number { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-def { + color: #82AAFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-string { + color: #C3E88D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-string-2 { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-comment { + color: #545454 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-variable { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-tag { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-meta { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-attribute { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-property { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-qualifier { + color: #DECB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-type { + color: #DECB6B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .cm-error { + color: rgba(255, 255, 255, 1.0) !important; + background-color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-darker .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: material-ocean.css */ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean.CodeMirror { + background-color: #0F111A !important; + color: #8F93A2 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-gutters { + background: #0F111A !important; + color: #464B5D !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-guttermarker, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-guttermarker-subtle, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-linenumber { + color: #464B5D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-cursor { + border-left: 1px solid #FFCC00 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean.cm-fat-cursor .CodeMirror-cursor { + background-color: #a2a8a175 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-animate-fat-cursor { + background-color: #a2a8a175 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean.CodeMirror-focused div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line>span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line>span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-keyword { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-operator { + color: #89DDFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-variable-2 { + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-type { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-builtin { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-atom { + color: #F78C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-number { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-def { + color: #82AAFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-string { + color: #C3E88D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-string-2 { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-comment { + color: #464B5D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-variable { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-tag { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-meta { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-attribute { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-property { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-qualifier { + color: #DECB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-type { + color: #DECB6B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .cm-error { + color: rgba(255, 255, 255, 1.0) !important; + background-color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-ocean .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: material-palenight.css */ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight.CodeMirror { + background-color: #292D3E !important; + color: #A6ACCD !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-gutters { + background: #292D3E !important; + color: #676E95 !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-guttermarker, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-guttermarker-subtle, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-linenumber { + color: #676E95 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-cursor { + border-left: 1px solid #FFCC00 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight.cm-fat-cursor .CodeMirror-cursor { + background-color: #607c8b80 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-animate-fat-cursor { + background-color: #607c8b80 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight.CodeMirror-focused div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line>span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line>span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-keyword { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-operator { + color: #89DDFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-variable-2 { + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-type { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-builtin { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-atom { + color: #F78C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-number { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-def { + color: #82AAFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-string { + color: #C3E88D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-string-2 { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-comment { + color: #676E95 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-variable { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-tag { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-meta { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-attribute { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-property { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-qualifier { + color: #DECB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-type { + color: #DECB6B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .cm-error { + color: rgba(255, 255, 255, 1.0) !important; + background-color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material-palenight .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: material.css */ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-material.CodeMirror { + background-color: #263238 !important; + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-gutters { + background: #263238 !important; + color: #546E7A !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-guttermarker, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-guttermarker-subtle, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-linenumber { + color: #546E7A !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-cursor { + border-left: 1px solid #FFCC00 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material.cm-fat-cursor .CodeMirror-cursor { + background-color: #5d6d5c80 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-animate-fat-cursor { + background-color: #5d6d5c80 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material.CodeMirror-focused div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line>span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line>span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-keyword { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-operator { + color: #89DDFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-variable-2 { + color: #EEFFFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-type { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-builtin { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-atom { + color: #F78C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-number { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-def { + color: #82AAFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-string { + color: #C3E88D !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-string-2 { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-comment { + color: #546E7A !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-variable { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-tag { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-meta { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-attribute { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-property { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-qualifier { + color: #DECB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-type { + color: #DECB6B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-material .cm-error { + color: rgba(255, 255, 255, 1.0) !important; + background-color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-material .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: mbo.css */ +/****************************************************************/ +/* Based on mbonaci's Brackets mbo theme */ +/* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ +/* Create your own: http://tmtheme-editor.herokuapp.com */ +/****************************************************************/ + +.CodeMirror.phoenix-codemirror-6.cm-s-mbo.CodeMirror { background: #2c2c2c !important; color: #ffffec !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo div.CodeMirror-selected { background: #716C62 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-gutters { background: #4e4e4e !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-linenumber { color: #dadada !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-comment { color: #95958a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-atom { color: #00a8c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-number { color: #00a8c6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-attribute { color: #9ddfe9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-keyword { color: #ffb928 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-string { color: #ffcf6c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-string.cm-property { color: #ffffec !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-variable { color: #ffffec !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-variable-2 { color: #00a8c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-def { color: #ffffec !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-bracket { color: #fffffc !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-tag { color: #9ddfe9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-link { color: #f54b07 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-error { border-bottom: #636363 !important; color: #ffffec !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo span.cm-qualifier { color: #ffffec !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-activeline-background { background: #494b41 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37) !important; } + +/* CodeMirror 5.65.16 theme source: mdn-like.css */ +/* + MDN-LIKE Theme - Mozilla + Ported to CodeMirror by Peter Kroon + Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues + GitHub: @peterkroon + + The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation + +*/ +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like.CodeMirror { color: #999 !important; background-color: #fff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like div.CodeMirror-selected { background: #cfc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #cfc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #cfc !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8 !important; border-left: 6px solid rgba(0,83,159,0.65) !important; color: #333 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa !important; padding-left: 8px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-keyword { color: #6262FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-atom { color: #F90 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-number { color: #ca7841 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-def { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.cm-tag { color: #690 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.cm-type { color: #07a !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-variable { color: #07a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-property { color: #905 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-qualifier { color: #690 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-operator { color: #cda869 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-comment { color:#777 !important; font-weight:normal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-string { color:#07a !important; font-style:italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-string-2 { color:#bd6b18 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-meta { color: #000 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-builtin { color: #9B7536 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-tag { color: #997643 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-attribute { color: #d6bb6d !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-header { color: #FF6400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-hr { color: #AEAEAE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-link { color:#ad9361 !important; font-style:italic !important; text-decoration:none !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .cm-error { border-bottom: 1px solid red !important; } + +div.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff !important; } +div.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey !important; color: inherit !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAyCAYAAAAp8UeFAAAHvklEQVR42s2b63bcNgyEQZCSHCdt2vd/0tWF7I+Q6XgMXiTtuvU5Pl57ZQKkKHzEAOtF5KeIJBGJ8uvL599FRFREZhFx8DeXv8trn68RuGaC8TRfo3SNp9dlDDHedyLyTUTeRWStXKPZrjtpZxaRw5hPqozRs1N8/enzIiQRWcCgy4MUA0f+XWliDhyL8Lfyvx7ei/Ae3iQFHyw7U/59pQVIMEEPEz0G7XiwdRjzSfC3UTtz9vchIntxvry5iMgfIhJoEflOz2CQr3F5h/HfeFe+GTdLaKcu9L8LTeQb/R/7GgbsfKedyNdoHsN31uRPWrfZ5wsj/NzzRQHuToIdU3ahwnsKPxXCjJITuOsi7XLc7SG/v5GdALs7wf8JjTFiB5+QvTEfRyGOfX3Lrx8wxyQi3sNq46O7QahQiCsRFgqddjBouVEHOKDgXAQHD9gJCr5sMKkEdjwsarG/ww3BMHBU7OBjXnzdyY7SfCxf5/z6ATccrwlKuwC/jhznnPF4CgVzhhVf4xp2EixcBActO75iZ8/fM9zAs2OMzKdslgXWJ9XG8PQoOAMA5fGcsvORgv0doBXyHrCwfLJAOwo71QLNkb8n2Pl6EWiR7OCibtkPaz4Kc/0NNAze2gju3zOwekALDaCFPI5vjPFmgGY5AZqyGEvH1x7QfIb8YtxMnA/b+QQ0aQDAwc6JMFg8CbQZ4qoYEEHbRwNojuK3EHwd7VALSgq+MNDKzfT58T8qdpADrgW0GmgcAS1lhzztJmkAzcPNOQbsWEALBDSlMKUG0Eq4CLAQWvEVQ9WU57gZJwZtgPO3r9oBTQ9WO8TjqXINx8R0EYpiZEUWOF3FxkbJkgU9B2f41YBrIj5ZfsQa0M5kTgiAAqM3ShXLgu8XMqcrQBvJ0CL5pnTsfMB13oB8athpAq2XOQmcGmoACCLydx7nToa23ATaSIY2ichfOdPTGxlasXMLaL0MLZAOwAKIM+y8CmicobGdCcbbK9DzN+yYGVoNNI5iUKTMyYOjPse4A8SM1MmcXgU0toOq1yO/v8FOxlASyc7TgeYaAMBJHcY1CcCwGI/TK4AmDbDyKYBBtFUkRwto8gygiQEaByFgJ00BH2M8JWwQS1nafDXQCidWyOI8AcjDCSjCLk8ngObuAm3JAHAdubAmOaK06V8MNEsKPJOhobSprwQa6gD7DclRQdqcwL4zxqgBrQcabUiBLclRDKAlWp+etPkBaNMA0AKlrHwTdEByZAA4GM+SNluSY6wAzcMNewxmgig5Ks0nkrSpBvSaQHMdKTBAnLojOdYyGpQ254602ZILPdTD1hdlggdIm74jbTp8vDwF5ZYUeLWGJpWsh6XNyXgcYwVoJQTEhhTYkxzZjiU5npU2TaB979TQehlaAVq4kaGpiPwwwLkYUuBbQwocyQTv1tA0+1UFWoJF3iv1oq+qoSk8EQdJmwHkziIF7oOZk14EGitibAdjLYYK78H5vZOhtWpoI0ATGHs0Q8OMb4Ey+2bU2UYztCtA0wFAs7TplGLRVQCcqaFdGSPCeTI1QNIC52iWNzof6Uib7xjEp07mNNoUYmVosVItHrHzRlLgBn9LFyRHaQCtVUMbtTNhoXWiTOO9k/V8BdAc1Oq0ArSQs6/5SU0hckNy9NnXqQY0PGYo5dWJ7nINaN6o958FWin27aBaWRka1r5myvLOAm0j30eBJqCxHLReVclxhxOEN2JfDWjxBtAC7MIH1fVaGdoOp4qJYDgKtKPSFNID2gSnGldrCqkFZ+5UeQXQBIRrSwocbdZYQT/2LwRahBPBXoHrB8nxaGROST62DKUbQOMMzZIC9abkuELfQzQALWTnDNAm8KHWFOJgJ5+SHIvTPcmx1xQyZRhNL5Qci689aXMEaN/uNIWkEwDAvFpOZmgsBaaGnbs1NPa1Jm32gBZAIh1pCtG7TSH4aE0y1uVY4uqoFPisGlpP2rSA5qTecWn5agK6BzSpgAyD+wFaqhnYoSZ1Vwr8CmlTQbrcO3ZaX0NAEyMbYaAlyquFoLKK3SPby9CeVUPThrSJmkCAE0CrKUQadi4DrdSlWhmah0YL9z9vClH59YGbHx1J8VZTyAjQepJjmXwAKTDQI3omc3p1U4gDUf6RfcdYfrUp5ClAi2J3Ba6UOXGo+K+bQrjjssitG2SJzshaLwMtXgRagUNpYYoVkMSBLM+9GGiJZMvduG6DRZ4qc04DMPtQQxOjEtACmhO7K1AbNbQDEggZyJwscFpAGwENhoBeUwh3bWolhe8BTYVKxQEWrSUn/uhcM5KhvUu/+eQu0Lzhi+VrK0PrZZNDQKs9cpYUuFYgMVpD4/NxenJTiMCNqdUEUf1qZWjppLT5qSkkUZbCwkbZMSuVnu80hfSkzRbQeqCZSAh6huR4VtoM2gHAlLf72smuWgE+VV7XpE25Ab2WFDgyhnSuKbs4GuGzCjR+tIoUuMFg3kgcWKLTwRqanJQ2W00hAsenfaApRC42hbCvK1SlE0HtE9BGgneJO+ELamitD1YjjOYnNYVcraGhtKkW0EqVVeDx733I2NH581k1NNxNLG0i0IJ8/NjVaOZ0tYZ2Vtr0Xv7tPV3hkWp9EFkgS/J0vosngTaSoaG06WHi+xObQkaAdlbanP8B2+2l0f90LmUAAAAASUVORK5CYII=) !important; } + +/* CodeMirror 5.65.16 theme source: midnight.css */ +/* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ + +/**/ +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-activeline-background { background: #253540 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight.CodeMirror { + background: #0F192A !important; + color: #D1EDFF !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight div.CodeMirror-selected { background: #314D67 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-gutters { background: #0F192A !important; border-right: 1px solid !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-comment { color: #428BDD !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-atom { color: #AE81FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-number { color: #D1EDFF !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-attribute { color: #A6E22E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-keyword { color: #E83737 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-string { color: #1DC116 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-variable { color: #FFAA3E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-variable-2 { color: #FFAA3E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-def { color: #4DD !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-bracket { color: #D1EDFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-tag { color: #449 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-link { color: #AE81FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-midnight span.cm-error { background: #F92672 !important; color: #F8F8F0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-midnight .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: monokai.css */ +/* Based on Sublime Text's Monokai theme */ + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai.CodeMirror { background: #272822 !important; color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai div.CodeMirror-selected { background: #49483E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-gutters { background: #272822 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-comment { color: #75715e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-atom { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-number { color: #ae81ff !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-comment.cm-attribute { color: #97b757 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-comment.cm-def { color: #bc9262 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-comment.cm-tag { color: #bc6283 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-comment.cm-type { color: #5998a6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-attribute { color: #a6e22e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-keyword { color: #f92672 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-builtin { color: #66d9ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-string { color: #e6db74 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-variable { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-variable-2 { color: #9effff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-type { color: #66d9ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-def { color: #fd971f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-bracket { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-tag { color: #f92672 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-header { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-link { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai span.cm-error { background: #f92672 !important; color: #f8f8f0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-activeline-background { background: #373831 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-monokai .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: moxer.css */ +/* + Name: Moxer Theme + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://github.com/moxer-theme/moxer-code +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer.CodeMirror { + background-color: #090A0F !important; + color: #8E95B4 !important; + line-height: 1.8 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-gutters { + background: #090A0F !important; + color: #35394B !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-guttermarker, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-guttermarker-subtle, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-linenumber { + color: #35394B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-cursor { + border-left: 1px solid #FFCC00 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer.CodeMirror-focused div.CodeMirror-selected { + background: #212431 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line>span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line>span>span::selection { + background: #212431 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line>span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line>span>span::-moz-selection { + background: #212431 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-activeline-background, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-activeline-gutter .CodeMirror-linenumber { + background: rgba(33, 36, 49, 0.5) !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-keyword { + color: #D46C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-operator { + color: #D46C6C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-variable-2 { + color: #81C5DA !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-type { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-builtin { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-atom { + color: #A99BE2 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-number { + color: #7CA4C0 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-def { + color: #F5DFA5 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-line .cm-def ~ .cm-def { + color: #81C5DA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-string { + color: #B2E4AE !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-string-2 { + color: #f07178 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-comment { + color: #3F445A !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-variable { + color: #8E95B4 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-tag { + color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-meta { + color: #FFCB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-attribute { + color: #C792EA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-property { + color: #81C5DA !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-qualifier { + color: #DECB6B !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-variable-3, +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-type { + color: #DECB6B !important; +} + + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .cm-error { + color: rgba(255, 255, 255, 1.0) !important; + background-color: #FF5370 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-moxer .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: neat.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-comment { color: #a86 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-keyword { line-height: 1em !important; font-weight: bold !important; color: blue !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-string { color: #a22 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-builtin { line-height: 1em !important; font-weight: bold !important; color: #077 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-special { line-height: 1em !important; font-weight: bold !important; color: #0aa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-atom { color: #3a3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-meta { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat span.cm-link { color: #3a3 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey !important; color:black !important; } + +/* CodeMirror 5.65.16 theme source: neo.css */ +/* neo theme for codemirror */ + +/* Color scheme */ + +.CodeMirror.phoenix-codemirror-6.cm-s-neo.CodeMirror { + background-color:#ffffff !important; + color:#2e383c !important; + line-height:1.4375 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-comment { color:#75787b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-keyword, .CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-property { color:#1d75b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-atom,.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-number { color:#75438a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-node,.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-tag { color:#9c3328 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-string { color:#b35e14 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-variable,.CodeMirror.phoenix-codemirror-6.cm-s-neo .cm-qualifier { color:#047d65 !important; } + + +/* Editor styling */ + +.CodeMirror.phoenix-codemirror-6.cm-s-neo pre { + padding:0 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-neo .CodeMirror-gutters { + border:none !important; + border-right:10px solid transparent !important; + background-color:transparent !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-neo .CodeMirror-linenumber { + padding:0 !important; + color:#e0e2e5 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-neo .CodeMirror-cursor { + width: auto !important; + border: 0 !important; + background: rgba(155,157,162,0.37) !important; + z-index: 1 !important; +} + +/* CodeMirror 5.65.16 theme source: night.css */ +/* Loosely based on the Midnight Textmate theme */ + +.CodeMirror.phoenix-codemirror-6.cm-s-night.CodeMirror { background: #0a001f !important; color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night div.CodeMirror-selected { background: #447 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-gutters { background: #0a001f !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-linenumber { color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-comment { color: #8900d1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-atom { color: #845dc4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-attribute { color: #ffd500 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-keyword { color: #599eff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-string { color: #37f14a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-meta { color: #7678e2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-tag { color: #99b2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-type { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-bracket { color: #8da6ce !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-builtin, .CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-special { color: #ff9e59 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-link { color: #845dc4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night span.cm-error { color: #9d1e15 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-activeline-background { background: #1C005A !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: nord.css */ +/* Based on arcticicestudio's Nord theme */ +/* https://github.com/arcticicestudio/nord */ + +.CodeMirror.phoenix-codemirror-6.cm-s-nord.CodeMirror { background: #2e3440 !important; color: #d8dee9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord div.CodeMirror-selected { background: #434c5e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line > span > span::selection { background: #3b4252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-line > span > span::-moz-selection { background: #3b4252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-gutters { background: #2e3440 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-guttermarker { color: #4c566a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-guttermarker-subtle { color: #4c566a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-linenumber { color: #4c566a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-cursor { border-left: 1px solid #f8f8f0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-comment { color: #4c566a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-atom { color: #b48ead !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-number { color: #b48ead !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-comment.cm-attribute { color: #97b757 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-comment.cm-def { color: #bc9262 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-comment.cm-tag { color: #bc6283 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-comment.cm-type { color: #5998a6 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-attribute { color: #8FBCBB !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-keyword { color: #81A1C1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-builtin { color: #81A1C1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-string { color: #A3BE8C !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-variable { color: #d8dee9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-variable-2 { color: #d8dee9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-type { color: #d8dee9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-def { color: #8FBCBB !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-bracket { color: #81A1C1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-tag { color: #bf616a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-header { color: #b48ead !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-link { color: #b48ead !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord span.cm-error { background: #bf616a !important; color: #f8f8f0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-activeline-background { background: #3b4252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-nord .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: oceanic-next.css */ +/* + + Name: oceanic-next + Author: Filype Pereira (https://github.com/fpereira1) + + Original oceanic-next color scheme by Dmitri Voronianski (https://github.com/voronianski/oceanic-next-color-scheme) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next.CodeMirror { background: #304148 !important; color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next div.CodeMirror-selected { background: rgba(101, 115, 126, 0.33) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line > span > span::selection { background: rgba(101, 115, 126, 0.33) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-line > span > span::-moz-selection { background: rgba(101, 115, 126, 0.33) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-gutters { background: #304148 !important; border-right: 10px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-linenumber { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-cursor { border-left: 1px solid #f8f8f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next.cm-fat-cursor .CodeMirror-cursor { background-color: #a2a8a175 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-comment { color: #65737E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-atom { color: #C594C5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-number { color: #F99157 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-property { color: #99C794 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-attribute, +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-keyword { color: #C594C5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-builtin { color: #66d9ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-string { color: #99C794 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-variable, +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-variable-2, +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-variable-3 { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-def { color: #6699CC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-bracket { color: #5FB3B3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-tag { color: #C594C5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-header { color: #C594C5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-link { color: #C594C5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next span.cm-error { background: #C594C5 !important; color: #f8f8f0 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-activeline-background { background: rgba(101, 115, 126, 0.33) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-oceanic-next .CodeMirror-matchingbracket { + text-decoration: underline !important; + color: white !important; +} + +/* CodeMirror 5.65.16 theme source: panda-syntax.css */ +/* + Name: Panda Syntax + Author: Siamak Mokhtari (http://github.com/siamak/) + CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax) +*/ +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax { + background: #292A2B !important; + color: #E6E6E6 !important; + line-height: 1.5 !important; + font-family: 'Operator Mono', 'Source Code Pro', Menlo, Monaco, Consolas, Courier New, monospace !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-activeline-background { + background: rgba(99, 123, 156, 0.1) !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-selected { + background: #FFF !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-comment { + font-style: italic !important; + color: #676B79 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-operator { + color: #f3f3f3 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-string { + color: #19F9D8 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-string-2 { + color: #FFB86C !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-tag { + color: #ff2c6d !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-meta { + color: #b084eb !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-number { + color: #FFB86C !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-atom { + color: #ff2c6d !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-keyword { + color: #FF75B5 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-variable { + color: #ffb86c !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-variable-2 { + color: #ff9ac1 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-type { + color: #ff9ac1 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-def { + color: #e6e6e6 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-property { + color: #f3f3f3 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-unit { + color: #ffb86c !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .cm-attribute { + color: #ffb86c !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-matchingbracket { + border-bottom: 1px dotted #19F9D8 !important; + padding-bottom: 2px !important; + color: #e6e6e6 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-gutters { + background: #292a2b !important; + border-right-color: rgba(255, 255, 255, 0.1) !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-panda-syntax .CodeMirror-linenumber { + color: #e6e6e6 !important; + opacity: 0.6 !important; +} + +/* CodeMirror 5.65.16 theme source: paraiso-dark.css */ +/* + + Name: Paraíso (Dark) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark.CodeMirror { background: #2f1e2e !important; color: #b9b6b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-comment { color: #e96ba8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-atom { color: #815ba4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-number { color: #815ba4 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-attribute { color: #48b685 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-keyword { color: #ef6155 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-string { color: #fec418 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-variable { color: #48b685 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-def { color: #f99b15 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-tag { color: #ef6155 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-link { color: #815ba4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark span.cm-error { background: #ef6155 !important; color: #8d8687 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: paraiso-light.css */ +/* + + Name: Paraíso (Light) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light.CodeMirror { background: #e7e9db !important; color: #41323f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light div.CodeMirror-selected { background: #b9b6b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line > span > span::selection { background: #b9b6b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-line > span > span::-moz-selection { background: #b9b6b0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-gutters { background: #e7e9db !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-guttermarker { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-linenumber { color: #8d8687 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-cursor { border-left: 1px solid #776e71 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-comment { color: #e96ba8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-atom { color: #815ba4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-number { color: #815ba4 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-attribute { color: #48b685 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-keyword { color: #ef6155 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-string { color: #fec418 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-variable { color: #48b685 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-variable-2 { color: #06b6ef !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-def { color: #f99b15 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-bracket { color: #41323f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-tag { color: #ef6155 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-link { color: #815ba4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light span.cm-error { background: #ef6155 !important; color: #776e71 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-activeline-background { background: #CFD1C4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: pastel-on-dark.css */ +/** + * Pastel On Dark theme ported from ACE editor + * @license MIT + * @copyright AtomicPages LLC 2014 + * @author Dennis Thompson, AtomicPages LLC + * @version 1.1 + * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme + */ + +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark.CodeMirror { + background: #2c2827 !important; + color: #8F938F !important; + line-height: 1.5 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(221,240,255,0.2) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-gutters { + background: #34302f !important; + border-right: 0px !important; + padding: 0 3px !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-atom { color: #DE8E30 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-number { color: #CCCCCC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-property { color: #8F938F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-string { color: #66A968 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-type { color: #DE8E30 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-def { color: #757aD8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-tag { color: #C1C144 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-link { color: #ae81ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-qualifier,.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark span.cm-error { + background: #757aD8 !important; + color: #f8f8f0 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-pastel-on-dark .CodeMirror-matchingbracket { + border: 1px solid rgba(255,255,255,0.25) !important; + color: #8F938F !important; + margin: -1px -1px 0 -1px !important; +} + +/* CodeMirror 5.65.16 theme source: railscasts.css */ +/* + + Name: Railscasts + Author: Ryan Bates (http://railscasts.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts.CodeMirror {background: #2b2b2b !important; color: #f4f1ed !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts div.CodeMirror-selected {background: #272935 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts .CodeMirror-gutters {background: #2b2b2b !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts .CodeMirror-linenumber {color: #5a647e !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts .CodeMirror-cursor {border-left: 1px solid #d4cfc9 !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-comment {color: #bc9458 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-atom {color: #b6b3eb !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-number {color: #b6b3eb !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-attribute {color: #a5c261 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-keyword {color: #da4939 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-string {color: #ffc66d !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-variable {color: #a5c261 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-variable-2 {color: #6d9cbe !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-def {color: #cc7833 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-error {background: #da4939 !important; color: #d4cfc9 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-bracket {color: #f4f1ed !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-tag {color: #da4939 !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts span.cm-link {color: #b6b3eb !important;} + +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-railscasts .CodeMirror-activeline-background { background: #303040 !important; } + +/* CodeMirror 5.65.16 theme source: rubyblue.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue.CodeMirror { background: #112435 !important; color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line > span > span::selection { background: rgba(56, 86, 111, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 86, 111, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-gutters { background: #1F4661 !important; border-right: 7px solid #3E7087 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-linenumber { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-comment { color: #999 !important; font-style:italic !important; line-height: 1em !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-atom { color: #F4C20B !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-attribute { color: #82C6E0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-keyword { color: #F0F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-string { color: #F08047 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-meta { color: #F0F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-tag { color: #7BD827 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-type { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-bracket { color: #F0F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-link { color: #F4C20B !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-builtin, .CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-special { color: #FF9D00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue span.cm-error { color: #AF2018 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-rubyblue .CodeMirror-activeline-background { background: #173047 !important; } + +/* CodeMirror 5.65.16 theme source: seti.css */ +/* + + Name: seti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original seti color scheme by Jesse Weed (https://github.com/jesseweed/seti-syntax) + +*/ + + +.CodeMirror.phoenix-codemirror-6.cm-s-seti.CodeMirror { + background-color: #151718 !important; + color: #CFD2D1 !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-gutters { + color: #404b53 !important; + background-color: #0E1112 !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-cursor { border-left: solid thin #f8f8f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-linenumber { color: #6D8A88 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-comment { color: #41535b !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-string-2 { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-number { color: #cd3f45 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-variable { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-variable-2 { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-def { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-keyword { color: #ff79c6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-operator { color: #9fca56 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-keyword { color: #e6cd69 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-atom { color: #cd3f45 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-meta { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-tag { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-attribute { color: #9fca56 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-qualifier { color: #9fca56 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-property { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-type { color: #9fca56 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti span.cm-builtin { color: #9fca56 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-activeline-background { background: #101213 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-seti .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: shadowfox.css */ +/* + + Name: shadowfox + Author: overdodactyl (http://github.com/overdodactyl) + + Original shadowfox color scheme by Firefox + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox.CodeMirror { background: #2a2a2e !important; color: #b1b1b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox div.CodeMirror-selected { background: #353B48 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line > span > span::selection { background: #353B48 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-line > span > span::-moz-selection { background: #353B48 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-gutters { background: #0c0c0d !important; border-right: 1px solid #0c0c0d !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-guttermarker { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-linenumber { color: #939393 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-cursor { border-left: 1px solid #fff !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-comment { color: #939393 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-atom { color: #FF7DE9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-quote { color: #FF7DE9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-builtin { color: #FF7DE9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-attribute { color: #FF7DE9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-keyword { color: #FF7DE9 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-error { color: #FF7DE9 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-number { color: #6B89FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-string { color: #6B89FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-string-2 { color: #6B89FF !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-meta { color: #939393 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-hr { color: #939393 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-header { color: #75BFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-qualifier { color: #75BFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-variable-2 { color: #75BFFF !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-property { color: #86DE74 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-def { color: #75BFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-bracket { color: #75BFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-tag { color: #75BFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-link:visited { color: #75BFFF !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-variable { color: #B98EFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-variable-3 { color: #d7d7db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-link { color: #737373 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-operator { color: #b1b1b3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox span.cm-special { color: #d7d7db !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-activeline-background { background: rgba(185, 215, 253, .15) !important } +.CodeMirror.phoenix-codemirror-6.cm-s-shadowfox .CodeMirror-matchingbracket { outline: solid 1px rgba(255, 255, 255, .25) !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: solarized.css */ +/* +Solarized theme for code-mirror +http://ethanschoonover.com/solarized +*/ + +/* +Solarized color palette +http://ethanschoonover.com/solarized/img/solarized-palette.png +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base03 { color: #002b36 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base02 { color: #073642 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base01 { color: #586e75 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base00 { color: #657b83 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base0 { color: #839496 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base1 { color: #93a1a1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base2 { color: #eee8d5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.base3 { color: #fdf6e3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-yellow { color: #b58900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-orange { color: #cb4b16 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-red { color: #dc322f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-magenta { color: #d33682 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-violet { color: #6c71c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-blue { color: #268bd2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-cyan { color: #2aa198 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .solarized.solar-green { color: #859900 !important; } + +/* Color scheme for code-mirror */ + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized { + line-height: 1.45em !important; + color-profile: sRGB !important; + rendering-intent: auto !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark { + color: #839496 !important; + background-color: #002b36 !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light { + background-color: #fdf6e3 !important; + color: #657b83 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-widget { + text-shadow: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-header { color: #586e75 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-quote { color: #93a1a1 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-keyword { color: #cb4b16 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-atom { color: #d33682 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-number { color: #d33682 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-def { color: #2aa198 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-variable { color: #839496 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-variable-2 { color: #b58900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-type { color: #6c71c4 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-property { color: #2aa198 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-operator { color: #6c71c4 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-comment { color: #586e75 !important; font-style:italic !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-string { color: #859900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-string-2 { color: #b58900 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-meta { color: #859900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-qualifier { color: #b58900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-builtin { color: #d33682 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-bracket { color: #cb4b16 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-matchingbracket { color: #859900 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-tag { color: #93a1a1 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-attribute { color: #2aa198 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-hr { + color: transparent !important; + border-top: 1px solid #586e75 !important; + display: block !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-link { color: #93a1a1 !important; cursor: pointer !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-special { color: #6c71c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-em { + color: #999 !important; + text-decoration: underline !important; + text-decoration-style: dotted !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-error, +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .cm-invalidchar { + color: #586e75 !important; + border-bottom: 1px dotted #dc322f !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-light .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-light .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-light .CodeMirror-line > span > span::-moz-selection { background: #eee8d5 !important; } + +/* Editor styling */ + + + +/* Little shadow on the view-port of the buffer view */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.CodeMirror { + -moz-box-shadow: inset 7px 0 12px -6px #000 !important; + -webkit-box-shadow: inset 7px 0 12px -6px #000 !important; + box-shadow: inset 7px 0 12px -6px #000 !important; +} + +/* Remove gutter border */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-gutters { + border-right: 0 !important; +} + +/* Gutter colors and line number styling based of color scheme (dark / light) */ + +/* Dark */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .CodeMirror-gutters { + background-color: #073642 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .CodeMirror-linenumber { + color: #586e75 !important; +} + +/* Light */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-gutters { + background-color: #eee8d5 !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-linenumber { + color: #839496 !important; +} + +/* Common */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-linenumber { + padding: 0 5px !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { + color: #586e75 !important; +} + +/* Cursor */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090 !important; } + +/* Fat cursor */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75 !important; } + +/* Active line */ +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background { + background: rgba(255, 255, 255, 0.06) !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-solarized.cm-s-light .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.06) !important; +} + +/* CodeMirror 5.65.16 theme source: ssms.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-keyword { color: blue !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-comment { color: darkgreen !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-string { color: red !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-def { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-variable-2 { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-atom { color: darkgray !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms .CodeMirror-linenumber { color: teal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms .CodeMirror-activeline-background { background: #ffffff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-string-2 { color: #FF00FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-operator, +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-bracket, +.CodeMirror.phoenix-codemirror-6.cm-s-ssms span.cm-punctuation { color: darkgray !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms .CodeMirror-gutters { border-right: 3px solid #ffee62 !important; background-color: #ffffff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ssms div.CodeMirror-selected { background: #ADD6FF !important; } + +/* CodeMirror 5.65.16 theme source: the-matrix.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix.CodeMirror { background: #000000 !important; color: #00FF00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-gutters { background: #060 !important; border-right: 2px solid #00FF00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-keyword { color: #008803 !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-atom { color: #3FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-number { color: #FFB94F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-def { color: #99C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-variable { color: #F6C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-variable-2 { color: #C6F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-type { color: #96F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-property { color: #62FFA0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-operator { color: #999 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-comment { color: #CCCCCC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-string { color: #39C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-meta { color: #C9F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-qualifier { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-builtin { color: #30a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-tag { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-attribute { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix span.cm-error { color: #FF0000 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-the-matrix .CodeMirror-activeline-background { background: #040 !important; } + +/* CodeMirror 5.65.16 theme source: tomorrow-night-bright.css */ +/* + + Name: Tomorrow Night - Bright + Author: Chris Kempson + + Port done by Gerard Braad + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright.CodeMirror { background: #000000 !important; color: #eaeaea !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright div.CodeMirror-selected { background: #424242 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-gutters { background: #000000 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-linenumber { color: #424242 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-cursor { border-left: 1px solid #6A6A6A !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-comment { color: #d27b53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-atom { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-number { color: #a16a94 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-attribute { color: #99cc99 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-keyword { color: #d54e53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-string { color: #e7c547 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-variable { color: #b9ca4a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-variable-2 { color: #7aa6da !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-def { color: #e78c45 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-bracket { color: #eaeaea !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-tag { color: #d54e53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-link { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright span.cm-error { background: #d54e53 !important; color: #6A6A6A !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-activeline-background { background: #2a2a2a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: tomorrow-night-eighties.css */ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties.CodeMirror { background: #000000 !important; color: #CCCCCC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties div.CodeMirror-selected { background: #2D2D2D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-gutters { background: #000000 !important; border-right: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-linenumber { color: #515151 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-cursor { border-left: 1px solid #6A6A6A !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-comment { color: #d27b53 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-atom { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-number { color: #a16a94 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-property, .CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-attribute { color: #99cc99 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-keyword { color: #f2777a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-string { color: #ffcc66 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-variable { color: #99cc99 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-variable-2 { color: #6699cc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-def { color: #f99157 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-bracket { color: #CCCCCC !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-tag { color: #f2777a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-link { color: #a16a94 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties span.cm-error { background: #f2777a !important; color: #6A6A6A !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { background: #343600 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline !important; color: white !important; } + +/* CodeMirror 5.65.16 theme source: ttcn.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-quote { color: #090 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-negative { color: #d44 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-positive { color: #292 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-header, .CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-strong { font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-em { font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-link { text-decoration: underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-strikethrough { text-decoration: line-through !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-header { color: #00f !important; font-weight: bold !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-atom { color: #219 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-attribute { color: #00c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-bracket { color: #997 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-comment { color: #333333 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-def { color: #00f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-em { font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-error { color: #f00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-hr { color: #999 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-invalidchar { color: #f00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-keyword { font-weight:bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-link { color: #00c !important; text-decoration: underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-meta { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-negative { color: #d44 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-positive { color: #292 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-qualifier { color: #555 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-strikethrough { text-decoration: line-through !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-string { color: #006400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-string-2 { color: #f50 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-strong { font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-tag { color: #170 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-variable { color: #8B2252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-variable-2 { color: #05a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-type { color: #085 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-invalidchar { color: #f00 !important; } + +/* ASN */ +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-accessTypes, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-compareTypes { color: #27408B !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-cmipVerbs { color: #8B2252 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-modifier { color:#D2691E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-status { color:#8B4545 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-storage { color:#A020F0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-tags { color:#006400 !important; } + +/* CFG */ +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-externalCommands { color: #8B4545 !important; font-weight:bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-fileNCtrlMaskOptions, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-sectionTitle { color: #2E8B57 !important; font-weight:bold !important; } + +/* TTCN */ +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-booleanConsts, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-otherConsts, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-verdictConsts { color: #006400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-configOps, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-functionOps, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-portOps, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-sutOps, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-timerOps, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-verdictOps { color: #0000FF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-preprocessor, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-templateMatch, +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-ttcn3Macros { color: #27408B !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-types { color: #A52A2A !important; font-weight:bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-ttcn .cm-visibilityModifiers { font-weight:bold !important; } + +/* CodeMirror 5.65.16 theme source: twilight.css */ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight.CodeMirror { background: #141414 !important; color: #f7f7f7 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight div.CodeMirror-selected { background: #323232 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line > span > span::selection { background: rgba(50, 50, 50, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-line > span > span::-moz-selection { background: rgba(50, 50, 50, 0.99) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-gutters { background: #222 !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-linenumber { color: #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-keyword { color: #f9ee98 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-atom { color: #FC0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-number { color: #ca7841 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-def { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-twilight span.cm-tag { color: #607392 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-twilight span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-twilight span.cm-type { color: #607392 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-operator { color: #cda869 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-comment { color:#777 !important; font-style:italic !important; font-weight:normal !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-string { color:#8f9d6a !important; font-style:italic !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-string-2 { color:#bd6b18 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-meta { background-color:#141414 !important; color:#f7f7f7 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-builtin { color: #cda869 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-tag { color: #997643 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-attribute { color: #d6bb6d !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-header { color: #FF6400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-hr { color: #AEAEAE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-link { color:#ad9361 !important; font-style:italic !important; text-decoration:none !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .cm-error { border-bottom: 1px solid red !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-activeline-background { background: #27282E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-twilight .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: vibrant-ink.css */ +/* Taken from the popular Visual Studio Vibrant Ink Schema */ + +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink.CodeMirror { background: black !important; color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink div.CodeMirror-selected { background: #35493c !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line > span > span::selection { background: rgba(53, 73, 60, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-line > span > span::-moz-selection { background: rgba(53, 73, 60, 0.99) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-gutters { background: #002240 !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-guttermarker { color: white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-keyword { color: #CC7832 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-atom { color: #FC0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-number { color: #FFEE98 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-def { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink span.cm-variable-2, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink span.cm-tag { color: #FFC66D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink span.cm-def, .CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink span.cm-type { color: #FFC66D !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-operator { color: #888 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-comment { color: gray !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-string { color: #A5C25C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-string-2 { color: red !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-meta { color: #D8FA3C !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-builtin { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-tag { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-attribute { color: #8DA6CE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-header { color: #FF6400 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-hr { color: #AEAEAE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-link { color: #5656F3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-activeline-background { background: #27282E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-vibrant-ink .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: xq-dark.css */ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark.CodeMirror { background: #0a001f !important; color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark div.CodeMirror-selected { background: #27007A !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99) !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-gutters { background: #0a001f !important; border-right: 1px solid #aaa !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-keyword { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-atom { color: #6C8CD5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-number { color: #164 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-def { color: #FFF !important; text-decoration:underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-variable { color: #FFF !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-variable-2 { color: #EEE !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-type { color: #DDD !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-property {} +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-operator {} +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-comment { color: gray !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-string { color: #9FEE00 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-meta { color: yellow !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-qualifier { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-builtin { color: #30a !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-tag { color: #FFBD40 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-attribute { color: #FFF700 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark span.cm-error { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-activeline-background { background: #27282E !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-dark .CodeMirror-matchingbracket { outline:1px solid grey !important; color:white !important; } + +/* CodeMirror 5.65.16 theme source: xq-light.css */ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-keyword { line-height: 1em !important; font-weight: bold !important; color: #5A5CAD !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-atom { color: #6C8CD5 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-number { color: #164 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-def { text-decoration:underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-variable { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-variable-2 { color:black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-type { color: black !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-property {} +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-operator {} +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-comment { color: #0080FF !important; font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-string { color: red !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-meta { color: yellow !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-qualifier { color: grey !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-builtin { color: #7EA656 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-bracket { color: #cc7 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-tag { color: #3F7F7F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-attribute { color: #7F007F !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light span.cm-error { color: #f00 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light .CodeMirror-activeline-background { background: #e8f2ff !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-xq-light .CodeMirror-matchingbracket { outline:1px solid grey !important;color:black !important;background:yellow !important; } + +/* CodeMirror 5.65.16 theme source: yeti.css */ +/* + + Name: yeti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original yeti color scheme by Jesse Weed (https://github.com/jesseweed/yeti-syntax) + +*/ + + +.CodeMirror.phoenix-codemirror-6.cm-s-yeti.CodeMirror { + background-color: #ECEAE8 !important; + color: #d1c9c0 !important; + border: none !important; +} + +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-gutters { + color: #adaba6 !important; + background-color: #E5E1DB !important; + border: none !important; +} +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-cursor { border-left: solid thin #d1c9c0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-linenumber { color: #adaba6 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti.CodeMirror-focused div.CodeMirror-selected { background: #DCD8D2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line::selection, .CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line > span::selection, .CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line > span > span::selection { background: #DCD8D2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line > span::-moz-selection, .CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-line > span > span::-moz-selection { background: #DCD8D2 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-comment { color: #d4c8be !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-string, .CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-string-2 { color: #96c0d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-number { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-variable { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-variable-2 { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-def { color: #55b5db !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-operator { color: #9fb96e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-keyword { color: #9fb96e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-atom { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-meta { color: #96c0d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-tag { color: #96c0d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-attribute { color: #9fb96e !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-qualifier { color: #96c0d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-property { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-builtin { color: #a074c4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-variable-3, .CodeMirror.phoenix-codemirror-6.cm-s-yeti span.cm-type { color: #96c0d8 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-activeline-background { background: #E7E4E0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yeti .CodeMirror-matchingbracket { text-decoration: underline !important; } + +/* CodeMirror 5.65.16 theme source: yonce.css */ +/* + + Name: yoncé + Author: Thomas MacLean (http://github.com/thomasmaclean) + + Original yoncé color scheme by Mina Markham (https://github.com/minamarkham) + +*/ + +.CodeMirror.phoenix-codemirror-6.cm-s-yonce.CodeMirror { background: #1C1C1C !important; color: #d4d4d4 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce div.CodeMirror-selected { background: rgba(252, 69, 133, 0.478) !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-selectedtext, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-selected, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line > span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line > span > span::selection, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line > span::-moz-selection, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-line > span > span::-moz-selection { background: rgba(252, 67, 132, 0.47) !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-yonce.CodeMirror pre { padding-left: 0px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-gutters {background: #1C1C1C !important; border-right: 0px !important;} +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-linenumber {color: #777777 !important; padding-right: 10px !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-activeline .CodeMirror-linenumber.CodeMirror-gutter-elt { background: #1C1C1C !important; color: #fc4384 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-linenumber { color: #777 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-cursor { border-left: 2px solid #FC4384 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-searching { background: rgba(243, 155, 53, .3) !important; outline: 1px solid #F39B35 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-searching.CodeMirror-selectedtext { background: rgba(243, 155, 53, .7) !important; color: white !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-keyword { color: #00A7AA !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-atom { color: #F39B35 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-number, .CodeMirror.phoenix-codemirror-6.cm-s-yonce span.cm-type { color: #A06FCA !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-def { color: #98E342 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-property, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce span.cm-variable { color: #D4D4D4 !important; font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce span.cm-variable-2 { color: #da7dae !important; font-style: italic !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce span.cm-variable-3 { color: #A06FCA !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-type.cm-def { color: #FC4384 !important; font-style: normal !important; text-decoration: underline !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-property.cm-def { color: #FC4384 !important; font-style: normal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-callee { color: #FC4384 !important; font-style: normal !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-operator { color: #FC4384 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-qualifier, +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-tag { color: #FC4384 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-tag.cm-bracket { color: #D4D4D4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-attribute { color: #A06FCA !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-comment { color:#696d70 !important; font-style:italic !important; font-weight:normal !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-comment.cm-tag { color: #FC4384 !important } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-comment.cm-attribute { color: #D4D4D4 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-string { color:#E6DB74 !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-string-2 { color:#F39B35 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-meta { color: #D4D4D4 !important; background: inherit !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-builtin { color: #FC4384 !important; } /*?*/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-header { color: #da7dae !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-hr { color: #98E342 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-link { color:#696d70 !important; font-style:italic !important; text-decoration:none !important; } /**/ +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .cm-error { border-bottom: 1px solid #C42412 !important; } + +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-activeline-background { background: #272727 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-yonce .CodeMirror-matchingbracket { outline:1px solid grey !important; color:#D4D4D4 !important; } + +/* CodeMirror 5.65.16 theme source: zenburn.css */ +/** + * " + * Using Zenburn color palette from the Emacs Zenburn Theme + * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el + * + * Also using parts of https://github.com/xavi/coderay-lighttable-theme + * " + * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css + */ + +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-foldgutter-folded { color: #999 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn.CodeMirror { background-color: #3f3f3f !important; color: #dcdccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-builtin { color: #dcdccc !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-comment { color: #7f9f7f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-keyword { color: #f0dfaf !important; font-weight: bold !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-atom { color: #bfebbf !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-def { color: #dcdccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-variable { color: #dfaf8f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-variable-2 { color: #dcdccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-string { color: #cc9393 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-string-2 { color: #cc9393 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-number { color: #dcdccc !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-tag { color: #93e0e3 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-property { color: #dfaf8f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-attribute { color: #dfaf8f !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-qualifier { color: #7cb8bb !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-meta { color: #f0dfaf !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-header { color: #f0efd0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.cm-operator { color: #f0efd0 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box !important; background: transparent !important; border-bottom: 1px solid !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid !important; background: none !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-activeline { background: #000000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-activeline-background { background: #000000 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn div.CodeMirror-selected { background: #545454 !important; } +.CodeMirror.phoenix-codemirror-6.cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f !important; } diff --git a/src/styles/brackets_codemirror_override.less b/src/styles/brackets_codemirror_override.less index 524939cc60..94a8e74ec2 100644 --- a/src/styles/brackets_codemirror_override.less +++ b/src/styles/brackets_codemirror_override.less @@ -76,6 +76,13 @@ border-bottom: 2px solid #78B2F2; } +.CodeMirror .CodeMirror-search-match, +.CodeMirror .CodeMirror-selection-highlight-scrollbar { + background: #78B2F2; + box-sizing: border-box; + opacity: 0.65; +} + span.cm-keyword {color: @accent-keyword;} span.cm-atom {color: @accent-atom;} span.cm-number {color: @accent-number;} @@ -192,7 +199,7 @@ div.CodeMirror-cursors { TODO (issue #324): We'll still have problems if editors can be nested more than one level deep, or if any other descendant-selector-driven CM styles can differ between inner & outer editors - (potential problem areas include line wrap and coloring theme: basically, anything in codemirror.css + (potential problem areas include line wrap and editor-surface coloring: that uses a descandant selector where the CSS class name to the left of the space is something other than a vanilla .CodeMirror) */ @@ -303,4 +310,4 @@ span.cm-emstrong { display: inline-block; position: absolute; box-shadow: none; -} \ No newline at end of file +} diff --git a/src/styles/brackets_shared.less b/src/styles/brackets_shared.less index 471486175e..028b9caf61 100644 --- a/src/styles/brackets_shared.less +++ b/src/styles/brackets_shared.less @@ -33,9 +33,6 @@ */ /* LESS imports */ -// codemirror -@import (less) "../thirdparty/CodeMirror/lib/codemirror.css"; - // Bootstrap @ v.2.3.1 @import url("bootstrap/bootstrap.less"); @@ -54,6 +51,8 @@ // Codemirror styling overrides @import url("brackets_codemirror_override.less"); +@import url("brackets_codemirror6.less"); +@import (inline) "brackets_codemirror6_legacy_themes.less"; // Styling for file tree @import url("jsTreeTheme.less"); diff --git a/src/styles/brackets_theme_default.less b/src/styles/brackets_theme_default.less index 1e4472cbfb..6a3daf1532 100644 --- a/src/styles/brackets_theme_default.less +++ b/src/styles/brackets_theme_default.less @@ -186,7 +186,7 @@ TODO (issue #324): We'll still have problems if editors can be nested more than one level deep, or if any other descendant-selector-driven CM styles can differ between inner & outer editors - (potential problem areas include line wrap and coloring theme: basically, anything in codemirror.css + (potential problem areas include line wrap and editor-surface coloring: that uses a descandant selector where the CSS class name to the left of the space is something other than a vanilla .CodeMirror) */ @@ -347,4 +347,3 @@ } /* Variables and Mixins for non-code UI elements that can be styled */ - diff --git a/src/thirdparty/licences/codemirror.markdown b/src/thirdparty/licences/codemirror-vim-derived.markdown similarity index 73% rename from src/thirdparty/licences/codemirror.markdown rename to src/thirdparty/licences/codemirror-vim-derived.markdown index 9018d33e8f..14580a47a1 100644 --- a/src/thirdparty/licences/codemirror.markdown +++ b/src/thirdparty/licences/codemirror-vim-derived.markdown @@ -1,6 +1,13 @@ -MIT License +# @replit CodeMirror Vim-derived compatibility code -Copyright (C) 2017 by Marijn Haverbeke and others +Phoenix bundles `@replit/codemirror-vim-core` and adapts the CodeMirror 5 +integration layer from `@replit/codemirror-vim` in: + +- `src/editor/CodeMirrorVimCompat.js` + +The adapted integration code is distributed under the following MIT license: + +Copyright (C) 2018-2021 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/thirdparty/licences/codemirror5-derived.markdown b/src/thirdparty/licences/codemirror5-derived.markdown new file mode 100644 index 0000000000..cd7db0524f --- /dev/null +++ b/src/thirdparty/licences/codemirror5-derived.markdown @@ -0,0 +1,39 @@ +# CodeMirror 5-derived compatibility code + +Phoenix does not distribute the CodeMirror 5 runtime. The following retained +compatibility implementations are based in part on CodeMirror 5 source code: + +- `src/editor/CodeMirrorCompat.js` +- `src/editor/CodeMirrorLegacyAddons.js` +- `src/editor/CodeMirrorLegacyExtendedAddons.js` +- `src/editor/CodeMirrorLegacyModeMeta.js` +- `src/editor/CodeMirrorLegacyModesCompat.js` +- `src/editor/CodeMirrorLegacyRSTSlimCompat.js` +- `src/editor/CodeMirrorSublimeCompat.js` +- `src/editor/CodeMirrorTwigCompat.js` +- `src/extensions/default/CodeFolding/foldhelpers/foldcode.js` +- `src/extensions/default/CodeFolding/foldhelpers/foldgutter.js` +- `src/extensions/default/CodeFolding/foldhelpers/languageFold.js` +- `src/styles/brackets_codemirror6_legacy_themes.less` + +CodeMirror is distributed under the following MIT license: + +Copyright (C) 2017 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/thirdparty/licences/codemirror6.markdown b/src/thirdparty/licences/codemirror6.markdown new file mode 100644 index 0000000000..c0f26f891f --- /dev/null +++ b/src/thirdparty/licences/codemirror6.markdown @@ -0,0 +1,719 @@ +# CodeMirror 6 bundle licenses + +This file is generated by `build/build-codemirror6.mjs` from the packages included in +`src/thirdparty/CodeMirror6/codemirror6.js`. Each package's license text is reproduced +below. + +## @codemirror/autocomplete 6.20.3 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/commands 6.11.0 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-css 6.3.1 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-html 6.4.12 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-javascript 6.2.5 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-json 6.0.2 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-markdown 6.5.2 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-php 6.0.2 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lang-xml 6.1.0 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/language 6.12.4 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/legacy-modes 6.5.3 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/lint 6.9.7 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/search 6.7.1 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/state 6.7.1 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @codemirror/view 6.43.9 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/common 1.5.2 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/css 1.3.6 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/highlight 1.2.3 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/html 1.3.13 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/javascript 1.5.4 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/json 1.0.3 + +MIT License + +Copyright (C) 2020 by Marijn Haverbeke , Arun Srinivasan , and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/lr 1.4.10 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/markdown 1.7.2 + +MIT License + +Copyright (C) 2020 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/php 1.0.5 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @lezer/xml 1.0.6 + +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @marijn/find-cluster-break 1.0.4 + +MIT License + +Copyright (C) 2024 by Marijn Haverbeke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## @replit/codemirror-vim-core 0.1.0 + +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## crelt 1.0.7 + +Copyright (C) 2020 by Marijn Haverbeke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## style-mod 4.1.3 + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## w3c-keyname 2.2.8 + +Copyright (C) 2016 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index 01f5c2e4cd..e89e6bdd94 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -47,6 +47,8 @@ define(function (require, exports, module) { const _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), FileSystem = require("filesystem/FileSystem"), + CodeMirrorLegacyFileSystem = + require("editor/CodeMirrorLegacyFileSystem"), FileUtils = require("file/FileUtils"), Async = require("utils/Async"), ExtensionUtils = require("utils/ExtensionUtils"), @@ -64,6 +66,8 @@ define(function (require, exports, module) { DeprecatedExtensionsTemplate = require("text!htmlContent/deprecated-extensions-dialog.html"), CommandManager = require("command/CommandManager"); + CodeMirrorLegacyFileSystem.install(); + // takedown/dont load extensions that are compromised at app start - start const EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY = "PH_EXTENSION_TAKEDOWN_LIST"; @@ -387,6 +391,14 @@ define(function (require, exports, module) { * (Note: if extension contains a JS syntax error, promise is resolved not rejected). */ function loadExtensionModule(name, config, entryPoint, metadata) { + const textPluginConfig = { + useXhr: function(_url, _protocol, _hostname, _port) { + // as we load extensions in cross domain fashion, we have to use xhr + // https://github.com/requirejs/text#xhr-restrictions + // else user installed extension require will fail in tauri + return true; + } + }; let extensionConfig = { context: name, baseUrl: config.baseUrl, @@ -394,14 +406,8 @@ define(function (require, exports, module) { locale: brackets.getLocale(), waitSeconds: EXTENSION_LOAD_TIMOUT_SECONDS, config: { - text: { - useXhr: function(_url, _protocol, _hostname, _port) { - // as we load extensions in cross domain fashion, we have to use xhr - // https://github.com/requirejs/text#xhr-restrictions - // else user installed extension require will fail in tauri - return true; - } - } + text: textPluginConfig, + "text-base": textPluginConfig } }; const isDefaultExtensionModule =( extensionConfig.baseUrl diff --git a/src/utils/Global.js b/src/utils/Global.js index e4fb5ed120..a3e8621fc8 100644 --- a/src/utils/Global.js +++ b/src/utils/Global.js @@ -28,7 +28,8 @@ define(function (require, exports, module) { - const UrlParams = require("utils/UrlParams").UrlParams; + const UrlParams = require("utils/UrlParams").UrlParams, + CodeMirrorLegacyModuleLoader = require("editor/CodeMirrorLegacyModuleLoader"); // Define core brackets namespace if it isn't already defined // @@ -98,8 +99,14 @@ define(function (require, exports, module) { // core modules) so that extensions can use it. // Note: we change the name to "getModule" because this won't do exactly // the same thing as 'require' in AMD-wrapped modules. The extension will - // only be able to load modules that have already been loaded once. - global.brackets.getModule = require; + // only be able to load modules that have already been loaded once, except + // for historical CodeMirror IDs resolved by the CM6 compatibility loader. + global.brackets.getModule = function (moduleName) { + if (CodeMirrorLegacyModuleLoader.isLegacyModule(moduleName)) { + return CodeMirrorLegacyModuleLoader.resolveLegacyModule(moduleName); + } + return require.apply(null, arguments); + }; /* API for retrieving the global RequireJS config * For internal use only diff --git a/src/utils/TokenUtils.js b/src/utils/TokenUtils.js index c111d31cfb..7368ce7dd4 100644 --- a/src/utils/TokenUtils.js +++ b/src/utils/TokenUtils.js @@ -30,7 +30,7 @@ define(function (require, exports, module) { var _ = require("thirdparty/lodash"), - CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); + CodeMirror = require("editor/CodeMirrorCompat"); var cache; diff --git a/src/view/ThemeView.js b/src/view/ThemeView.js index 11b9db4dde..6fbd9d8404 100644 --- a/src/view/ThemeView.js +++ b/src/view/ThemeView.js @@ -24,7 +24,7 @@ define(function (require, exports, module) { - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), PreferencesManager = require("preferences/PreferencesManager"), prefs = PreferencesManager.getExtensionPrefs("themes"); diff --git a/src/widgets/NotificationUI.js b/src/widgets/NotificationUI.js index 121aa45024..e3eccf3d7b 100644 --- a/src/widgets/NotificationUI.js +++ b/src/widgets/NotificationUI.js @@ -123,7 +123,12 @@ define(function (require, exports, module) { function _closeToastNotification($NotificationPopup, endCB) { // Animate out + let cleaned = false; function cleanup() { + if (cleaned) { + return; + } + cleaned = true; $NotificationPopup.removeClass("animateClose"); $NotificationPopup.remove(); endCB && endCB(); @@ -134,6 +139,9 @@ define(function (require, exports, module) { .addClass("animateClose") .one("transitionend", cleanup) .one("transitioncancel", cleanup); + // A transition event is not guaranteed when a toast is closed before + // its deferred opening transition starts. + setTimeout(cleanup, 600); } function _closeArrowNotification($NotificationPopup, endCB) { @@ -383,7 +391,9 @@ define(function (require, exports, module) { // Animate in // Must wait a cycle for the "display: none" to drop out before CSS transitions will work setTimeout(function () { - $NotificationPopup.addClass( options.instantOpen ? "instantOpen" : "animateOpen"); + if (notification.$notification) { + $NotificationPopup.addClass(options.instantOpen ? "instantOpen" : "animateOpen"); + } }, 0); if(options.autoCloseTimeS){ diff --git a/test/SpecRunner.js b/test/SpecRunner.js index a4d428986a..f01022fe70 100644 --- a/test/SpecRunner.js +++ b/test/SpecRunner.js @@ -29,7 +29,8 @@ require.config({ "test": "../test", "perf": "../test/perf", "spec": "../test/spec", - "text": "thirdparty/text/text", + "text-base": "thirdparty/text/text", + "text": "editor/CodeMirrorLegacyText", "i18n": "thirdparty/i18n/i18n", "fileSystemImpl": "filesystem/impls/appshell/AppshellFileSystem", "preferences/PreferencesImpl": "../test/TestPreferencesImpl", @@ -41,6 +42,10 @@ require.config({ }, map: { "*": { + // Keep these aliases exact. CodeMirrorLegacyModuleLoader handles + // legacy root, addon, keymap, and mode IDs without a CM5 file tree. + "thirdparty/CodeMirror/lib/codemirror": "editor/CodeMirrorCompat", + "thirdparty/CodeMirror2/lib/codemirror": "editor/CodeMirrorCompat", "thirdparty/preact": "preact-compat", "thirdparty/preact-test-utils": "preact-test-utils" } @@ -266,18 +271,11 @@ define(function (require, exports, module) { require("thirdparty/jquery.knob.modified"); require('thirdparty/marked.min'); - // Load CodeMirror add-ons--these attach themselves to the CodeMirror module - require("thirdparty/CodeMirror/addon/fold/xml-fold"); - require("thirdparty/CodeMirror/addon/edit/matchtags"); - require("thirdparty/CodeMirror/addon/edit/matchbrackets"); - require("thirdparty/CodeMirror/addon/edit/closebrackets"); - require("thirdparty/CodeMirror/addon/edit/closetag"); - require("thirdparty/CodeMirror/addon/selection/active-line"); - require("thirdparty/CodeMirror/addon/mode/multiplex"); - require("thirdparty/CodeMirror/addon/mode/overlay"); - require("thirdparty/CodeMirror/addon/search/searchcursor"); - require("thirdparty/CodeMirror/addon/comment/comment"); - require("thirdparty/CodeMirror/keymap/sublime"); + // Preserve the eagerly available CodeMirror 5-era addon surface with + // implementations backed entirely by the CodeMirror 6 adapter. + const CodeMirror = require("editor/CodeMirrorCompat"); + require("editor/CodeMirrorLegacyAddons").installAll(CodeMirror); + require("editor/CodeMirrorSublimeCompat").install(CodeMirror); //load Language Tools Module require("languageTools/PathConverters"); diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index b7a159286a..f3c81546a9 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -33,6 +33,14 @@ define(function (require, exports, module) { require("spec/Document-integ-test"); require("spec/DocumentSync-test"); require("spec/Editor-test"); + require("spec/EditorSurfaceConformance-test"); + require("spec/CodeMirrorCompatParity-test"); + require("spec/CodeMirrorLegacyModesCompat-test"); + require("spec/CodeMirrorLegacyAddons-test"); + require("spec/CodeMirrorLegacyExtendedAddons-test"); + require("spec/CodeMirrorTwigCompat-test"); + require("spec/CodeMirrorVimCompat-test"); + require("spec/MarkdownSync-test"); require("spec/EditorRedraw-test"); require("spec/EditorCommandHandlers-test"); require("spec/EditorCommandHandlers-integ-test"); diff --git a/test/phoenix-test-runner-mcp.js b/test/phoenix-test-runner-mcp.js index be19d8f42e..e49fa4e19b 100644 --- a/test/phoenix-test-runner-mcp.js +++ b/test/phoenix-test-runner-mcp.js @@ -89,6 +89,11 @@ var qs = "category=" + encodeURIComponent(category) + "&spec=" + encodeURIComponent(spec || "all"); + // MCP-driven runs must see the current repository fixtures. The test + // assets live in a persistent virtual filesystem, so a normal page + // reload can otherwise reuse an older extracted test_folders.zip. + window.localStorage.setItem("EXTRACT_TEST_ASSETS_KEY", "EXTRACT"); + setTimeout(function () { window.location.href = base + "?" + qs; }, 100); diff --git a/test/spec/CodeMirrorCompatParity-test.js b/test/spec/CodeMirrorCompatParity-test.js new file mode 100644 index 0000000000..59dbb168f6 --- /dev/null +++ b/test/spec/CodeMirrorCompatParity-test.js @@ -0,0 +1,1435 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect, afterEach, awaitsFor */ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + CM6 = require("thirdparty/CodeMirror6/codemirror6"), + CodeMirrorSublimeCompat = require("editor/CodeMirrorSublimeCompat"); + + CodeMirrorSublimeCompat.install(CodeMirror); + + function readToken(mode, stream, state) { + for (let attempt = 0; attempt < 10; attempt++) { + const type = mode.token(stream, state); + if (stream.pos > stream.start) { + return type; + } + } + throw new Error(`Mode ${mode.name || "unknown"} failed to advance.`); + } + + function tokenizeLines(modeSpecification, source, options) { + const mode = CodeMirror.getMode(options || {indentUnit: 4}, modeSpecification); + const state = CodeMirror.startState(mode); + const lines = source.split("\n"); + return { + mode: mode, + lines: lines.map(function (line, lineNumber) { + if (!line.length) { + if (mode.blankLine) { + mode.blankLine(state); + } + return { + tokens: [], + state: CodeMirror.copyState(mode, state), + innerMode: CodeMirror.innerMode(mode, state).mode.name + }; + } + + const stream = new CodeMirror.StringStream(line, 4, { + lookAhead: function (distance) { + return lines[lineNumber + distance]; + }, + baseToken: function () { + return null; + } + }); + const tokens = []; + while (!stream.eol()) { + stream.start = stream.pos; + tokens.push({ + string: stream.current(), + type: readToken(mode, stream, state) + }); + tokens[tokens.length - 1].string = stream.current(); + } + return { + tokens: tokens, + state: CodeMirror.copyState(mode, state), + innerMode: CodeMirror.innerMode(mode, state).mode.name + }; + }) + }; + } + + function tokenFor(line, text) { + return line.tokens.find(function (token) { + return token.string === text; + }); + } + + function modeInfoFingerprint(modeInfo) { + const serialized = JSON.stringify(modeInfo, function (key, value) { + if (value instanceof RegExp) { + return { + source: value.source, + flags: value.flags + }; + } + return value; + }); + let hashA = 0; + let hashB = 0; + for (let i = 0; i < serialized.length; i++) { + const code = serialized.charCodeAt(i); + hashA = (hashA * 31 + code) % 1000000007; + hashB = (hashB * 131 + code) % 1000000009; + } + return `${serialized.length}:${hashA}:${hashB}`; + } + + describe("CodeMirror compatibility parity", function () { + const editors = []; + const documents = []; + const fixtures = []; + + afterEach(function () { + editors.forEach(function (editor) { + if (editor && typeof editor.destroy === "function") { + editor.destroy(); + } + }); + editors.length = 0; + documents.forEach(function (doc) { + if (doc && doc._adapter && !doc._adapter._destroyed) { + doc._adapter.destroy(); + } + }); + documents.length = 0; + fixtures.forEach(function (fixture) { + fixture.remove(); + }); + fixtures.length = 0; + }); + + function createEditor(value, options) { + const holder = window.document.createElement("div"); + window.document.body.appendChild(holder); + fixtures.push(holder); + const editor = new CodeMirror( + holder, + Object.assign({value: value}, options || {}) + ); + editors.push(editor); + return editor; + } + + function trackDoc(doc) { + documents.push(doc); + return doc; + } + + it("matches CodeMirror 5.65.16 mode metadata and lookup semantics", function () { + expect(CodeMirror.modeInfo.length).toBe(157); + expect(modeInfoFingerprint(CodeMirror.modeInfo)) + .toBe("13339:731755378:285343183"); + expect(CodeMirror.modeInfo[0]).toEqual({ + name: "APL", + mime: "text/apl", + mode: "apl", + ext: ["dyalog", "apl"] + }); + expect(CodeMirror.modeInfo[CodeMirror.modeInfo.length - 1]).toEqual({ + name: "WebAssembly", + mime: "text/webassembly", + mode: "wast", + ext: ["wat", "wast"] + }); + + const javaScript = CodeMirror.findModeByMIME("TEXT/JAVASCRIPT"); + const json = CodeMirror.findModeByMIME("application/problem+json"); + const xml = CodeMirror.findModeByMIME("image/svg+xml"); + expect(javaScript.name).toBe("JavaScript"); + expect(javaScript.mime).toBe("text/javascript"); + expect(javaScript.mimes).toEqual([ + "text/javascript", + "text/ecmascript", + "application/javascript", + "application/x-javascript", + "application/ecmascript" + ]); + expect(json.name).toBe("JSON"); + expect(xml.name).toBe("XML"); + expect(CodeMirror.findModeByMIME("application/not-real")) + .toBeUndefined(); + + expect(CodeMirror.findModeByExtension("CPP").name).toBe("C++"); + expect(CodeMirror.findModeByExtension("m").name).toBe("Mathematica"); + expect(CodeMirror.findModeByExtension("BUILD")).toBeUndefined(); + expect(CodeMirror.findModeByExtension("not-real")).toBeUndefined(); + + expect(CodeMirror.findModeByFileName("README.md").name) + .toBe("GitHub Flavored Markdown"); + expect(CodeMirror.findModeByFileName("CMakeLists.txt").name) + .toBe("CMake"); + expect(CodeMirror.findModeByFileName("component.TSX").name) + .toBe("TypeScript-JSX"); + expect(CodeMirror.findModeByFileName("not-real")).toBeUndefined(); + + expect(CodeMirror.findModeByName("NODE")).toBe(javaScript); + expect(CodeMirror.findModeByName("diff").name).toBe("diff"); + expect(CodeMirror.findModeByName("not-real")).toBeUndefined(); + }); + + it("rejects unsupported input styles before attaching an editor", function () { + const holder = window.document.createElement("div"); + window.document.body.appendChild(holder); + fixtures.push(holder); + + expect(function () { + return new CodeMirror(holder, { + value: "alpha", + inputStyle: "unsupported" + }); + }).toThrowError( + 'Unsupported CodeMirror inputStyle "unsupported"' + ); + expect(holder.childNodes.length).toBe(0); + }); + + it("preserves PHP heredoc, nowdoc, and interpolated string tokens", function () { + const heredoc = tokenizeLines( + "application/x-httpd-php-open", + "<<id}\nTXT;\n" + + "\"value $name {$user->id}\";" + ); + + expect(tokenFor(heredoc.lines[0], "<<\nhello\n" + ); + + expect(result.lines[0].state.fencedCode).toBe(true); + expect(result.lines[0].innerMode).toBe("javascript"); + expect(tokenFor(result.lines[1], "const").type).toBe("keyword"); + expect(result.lines[1].innerMode).toBe("javascript"); + expect(result.lines[2].state.fencedCode).toBe(false); + expect(result.lines[2].innerMode).toBe("markdown"); + + expect(tokenFor(result.lines[3], "<").type).toBe("tag bracket"); + expect(tokenFor(result.lines[3], "div").type).toBe("tag"); + expect(tokenFor(result.lines[3], "class").type).toBe("attribute"); + expect(result.lines[3].innerMode).toBe("xml"); + expect(tokenFor(result.lines[4], "span").type).toBe("tag"); + expect(result.lines[4].innerMode).toBe("xml"); + expect(result.lines[5].innerMode).toBe("markdown"); + }); + + it("preserves GFM task, emoji, issue, SHA, and URL token semantics", function () { + const result = tokenizeLines( + "gfm", + "- [ ] open\n- [x] done\n" + + ":smile: owner/repo#123 deadbe1 https://example.com" + ); + + expect(tokenFor(result.lines[0], "[ ]").type).toContain("meta"); + expect(tokenFor(result.lines[1], "[x]").type).toContain("property"); + expect(tokenFor(result.lines[2], ":smile:").type).toBe("builtin"); + expect(tokenFor(result.lines[2], "owner/repo#123").type).toBe("link"); + expect(tokenFor(result.lines[2], "deadbe1").type).toBe("link"); + expect(tokenFor(result.lines[2], "https://example.com").type).toBe("link"); + }); + + it("configures native CM6 fenced languages and GFM syntax separately", function () { + const source = "```javascript\nconst value = 1;\n```\n- [x] ~~done~~"; + const holder = window.document.createElement("div"); + const markdownHolder = window.document.createElement("div"); + window.document.body.appendChild(holder); + window.document.body.appendChild(markdownHolder); + fixtures.push(holder, markdownHolder); + + const gfmEditor = new CodeMirror(holder, { + value: source, + mode: "gfm" + }); + const markdownEditor = new CodeMirror(markdownHolder, { + value: source, + mode: "markdown" + }); + editors.push(gfmEditor, markdownEditor); + + const gfmTree = CM6.syntaxTree(gfmEditor._view.state).toString(); + const markdownTree = CM6.syntaxTree(markdownEditor._view.state).toString(); + const gfmCodeNode = CM6.syntaxTree(gfmEditor._view.state).resolveInner( + source.indexOf("const") + 1, + 1 + ); + const markdownCodeNode = CM6.syntaxTree( + markdownEditor._view.state + ).resolveInner(source.indexOf("const") + 1, 1); + expect(gfmCodeNode.parent.name).toBe("VariableDeclaration"); + expect(gfmTree).toContain("TaskMarker"); + expect(gfmTree).toContain("Strikethrough"); + expect(markdownCodeNode.parent.name).toBe("VariableDeclaration"); + expect(markdownTree).not.toContain("TaskMarker"); + expect(markdownTree).not.toContain("Strikethrough"); + }); + + it("resolves custom MIME aliases to their native CM6 language", function () { + const mime = "application/x-phoenix-cm6-javascript"; + CodeMirror.defineMIME(mime, "javascript"); + const editor = createEditor("const answer = 42;", { + mode: mime + }); + + expect(CM6.syntaxTree(editor._view.state).toString()) + .toContain("VariableDeclaration"); + }); + + it("uses overridden legacy modes inside native HTML script regions", async function () { + const originalJavaScriptMode = CodeMirror.modes.javascript; + CodeMirror.modes.javascript = function () { + return { + token: function (stream) { + stream.skipToEnd(); + return "string"; + } + }; + }; + + try { + const editor = createEditor( + "", + { + mode: { + name: "htmlmixed", + scriptTypes: [{ + matches: /^text\/custom-js$/i, + mode: "javascript" + }] + } + } + ); + + await awaitsFor(function () { + return Array.from( + editor.getWrapperElement().querySelectorAll(".cm-string") + ).some(function (element) { + return element.textContent.includes("const answer"); + }); + }, "overridden JavaScript mode should render in the script region"); + } finally { + CodeMirror.modes.javascript = originalJavaScriptMode; + } + }); + + it("provides safe CM5 static helpers and type checks for extensions", function () { + expect(CodeMirror.findColumn("a\tb", 4, 4)).toBe(2); + expect(CodeMirror.wheelEventPixels({ + deltaX: 2, + deltaY: 3, + deltaMode: 1 + })).toEqual({x: 32, y: 48}); + + const parent = window.document.createElement("div"); + const child = window.document.createElement("span"); + parent.appendChild(child); + CodeMirror.addClass(child, "one two"); + expect(child.classList.contains("one")).toBe(true); + expect(child.classList.contains("two")).toBe(true); + CodeMirror.rmClass(child, "one"); + expect(child.classList.contains("one")).toBe(false); + expect(CodeMirror.contains(parent, child)).toBe(true); + + const event = { + prevented: false, + stopped: false, + preventDefault: function () { + this.prevented = true; + }, + stopPropagation: function () { + this.stopped = true; + } + }; + CodeMirror.e_stop(event); + expect(event.prevented).toBe(true); + expect(event.stopped).toBe(true); + + CodeMirror.defineInitHook(function (editor) { + editor._compatInitHookObserved = true; + }); + const holder = window.document.createElement("div"); + window.document.body.appendChild(holder); + fixtures.push(holder); + const editor = new CodeMirror(holder, { + value: "alpha" + }); + editors.push(editor); + expect(editor._compatInitHookObserved).toBe(true); + expect(editor._lineFolds).toEqual({}); + expect(Object.keys(CodeMirror.inputStyles).sort()).toEqual([ + "contenteditable", + "textarea" + ]); + expect(Object.keys(CodeMirror.scrollbarModel).sort()).toEqual([ + "native", + "null", + "overlay", + "simple" + ]); + expect(new CodeMirror.inputStyles.textarea(editor).getField()) + .toBe(editor.getInputField()); + expect(new CodeMirror.inputStyles.textarea(editor).supportsTouch()) + .toBe(false); + expect(new CodeMirror.inputStyles.contenteditable(editor).supportsTouch()) + .toBe(true); + expect(new CodeMirror.scrollbarModel.native(null, null, editor).update()) + .toEqual({right: 0, bottom: 0}); + expect(new CodeMirror.scrollbarModel.null().update()) + .toEqual({right: 0, bottom: 0}); + expect(CodeMirror.defaults.autoCloseBrackets).toBe(false); + expect(CodeMirror.defaults.matchBrackets).toBe(false); + expect(CodeMirror.defaults.styleActiveLine).toBe(false); + + const marker = editor.markText( + {line: 0, ch: 0}, + {line: 0, ch: 1} + ); + const line = editor.getLineHandle(0); + const widgetNode = window.document.createElement("div"); + const widget = editor.addLineWidget(0, widgetNode); + expect(marker instanceof CodeMirror.TextMarker).toBe(true); + expect(line instanceof CodeMirror.Line).toBe(true); + expect(widget instanceof CodeMirror.LineWidget).toBe(true); + expect(marker.doc).toBe(editor.getDoc()); + expect(marker.widgetNode).toBeUndefined(); + expect(widget.doc).toBe(editor.getDoc()); + expect(typeof widget.on).toBe("function"); + expect(typeof widget.off).toBe("function"); + + const shared = new CodeMirror.SharedTextMarker([marker], marker); + expect(shared.find()).toEqual(marker.find()); + shared.clear(); + expect(marker.find()).toBeUndefined(); + }); + + it("preserves CM5 overlay state and getTokenTypeAt semantics", function () { + const modeName = "cm6-overlay-token-parity"; + CodeMirror.defineMode(modeName, function () { + return { + token: function (stream) { + if (stream.match("alpha")) { + return "keyword"; + } + if (stream.match("beta")) { + return "string"; + } + stream.next(); + return null; + } + }; + }); + + const editor = createEditor("alpha beta", { + mode: modeName + }); + const observedBaseTokens = []; + const transparentOverlay = { + token: function (stream) { + const baseToken = stream.baseToken(); + if (stream.sol()) { + observedBaseTokens.push(baseToken); + } + if (stream.match("alpha")) { + return "transparent-overlay"; + } + stream.next(); + return null; + } + }; + const opaqueOverlay = { + token: function (stream) { + if (stream.match("alpha")) { + return "opaque-overlay"; + } + stream.next(); + return null; + } + }; + + expect(editor.state.overlays).toBe(editor._overlays); + expect(editor.state.overlays).toEqual([]); + expect(editor.getTokenTypeAt({line: 0, ch: 1})).toBe("keyword"); + + editor.addOverlay(transparentOverlay, {priority: 10}); + expect(editor.state.overlays).toBe(editor._overlays); + expect(editor.state.overlays.length).toBe(1); + expect(editor.state.overlays[0].mode).toBe(transparentOverlay); + expect(editor.state.overlays[0].modeSpec).toBe(transparentOverlay); + expect(editor.state.overlays[0].opaque).toBeUndefined(); + expect(editor.state.overlays[0].priority).toBe(10); + expect(editor.getTokenTypeAt({line: 0, ch: 1})).toBe("keyword"); + expect(observedBaseTokens.some(function (token) { + return token && token.type === "keyword" && token.size === 5; + })).toBe(true); + + editor.addOverlay(opaqueOverlay, { + opaque: true, + priority: -1 + }); + expect(editor.state.overlays.map(function (overlay) { + return overlay.modeSpec; + })).toEqual([opaqueOverlay, transparentOverlay]); + expect(editor.getTokenTypeAt({line: 0, ch: 1})).toBeNull(); + expect(editor.getTokenTypeAt({line: 0, ch: 7})).toBe("string"); + + editor.removeOverlay(opaqueOverlay); + expect(editor.state.overlays.length).toBe(1); + expect(editor.state.overlays[0].modeSpec).toBe(transparentOverlay); + expect(editor.state.overlays[0].priority).toBe(10); + expect(editor.getTokenTypeAt({line: 0, ch: 1})).toBe("keyword"); + + editor.removeOverlay(transparentOverlay); + expect(editor.state.overlays).toEqual([]); + expect(editor.getTokenTypeAt({line: 0, ch: 1})).toBe("keyword"); + }); + + it("provides CM5-compatible bracket search results from CM6 state", function () { + const editor = createEditor( + "(\n \")\"\n [value]\n)\n(]\n\n(", + { + mode: "javascript", + matchBrackets: false + } + ); + const openingStyle = editor.getTokenTypeAt( + CodeMirror.Pos(0, 1) + ); + + [ + "findMatchingBracket", + "matchBrackets", + "scanForBracket" + ].forEach(function (methodName) { + expect(typeof CodeMirror[methodName]).toBe("function"); + expect(typeof editor[methodName]).toBe("function"); + }); + + const forwardMatch = editor.findMatchingBracket( + CodeMirror.Pos(0, 1) + ); + expect(forwardMatch).toEqual({ + from: CodeMirror.Pos(0, 0), + to: CodeMirror.Pos(3, 0), + match: true, + forward: true + }); + expect(CodeMirror.findMatchingBracket( + editor, + CodeMirror.Pos(3, 1) + )).toEqual({ + from: CodeMirror.Pos(3, 0), + to: CodeMirror.Pos(0, 0), + match: true, + forward: false + }); + expect(editor.findMatchingBracket( + CodeMirror.Pos(0, 1), + true + )).toBeNull(); + expect(editor.findMatchingBracket( + CodeMirror.Pos(0, 1), + false + )).toEqual(forwardMatch); + + expect(editor.scanForBracket( + CodeMirror.Pos(0, 1), + 1, + openingStyle + )).toEqual({ + pos: CodeMirror.Pos(3, 0), + ch: ")" + }); + expect(CodeMirror.scanForBracket( + editor, + CodeMirror.Pos(0, 1), + 1, + openingStyle, + {maxScanLines: 2} + )).toBeNull(); + + expect(editor.findMatchingBracket( + CodeMirror.Pos(4, 1) + )).toEqual({ + from: CodeMirror.Pos(4, 0), + to: CodeMirror.Pos(4, 1), + match: false, + forward: true + }); + expect(editor.findMatchingBracket( + CodeMirror.Pos(5, 1), + {bracketRegex: /[<>]/} + )).toEqual({ + from: CodeMirror.Pos(5, 0), + to: CodeMirror.Pos(5, 2), + match: true, + forward: true + }); + + const unmatched = editor.findMatchingBracket( + CodeMirror.Pos(6, 1) + ); + expect(unmatched.to).toBe(false); + expect(unmatched.match).toBe(false); + }); + + it("highlights and clears bracket matches through CM6-backed markers", function () { + const editor = createEditor("([value])\n(]", { + mode: "javascript", + matchBrackets: false + }); + + editor.setCursor(CodeMirror.Pos(0, 1)); + const clearMatch = CodeMirror.matchBrackets(editor, false); + expect(typeof clearMatch).toBe("function"); + expect(editor.getAllMarks().map(function (marker) { + return marker.className; + })).toEqual([ + "CodeMirror-matchingbracket", + "CodeMirror-matchingbracket" + ]); + clearMatch(); + expect(editor.getAllMarks()).toEqual([]); + + editor.setCursor(CodeMirror.Pos(1, 1)); + const clearMismatch = CodeMirror.matchBrackets(editor, false); + expect(typeof clearMismatch).toBe("function"); + expect(editor.getAllMarks().map(function (marker) { + return marker.className; + })).toEqual([ + "CodeMirror-nonmatchingbracket", + "CodeMirror-nonmatchingbracket" + ]); + clearMismatch(); + expect(editor.getAllMarks()).toEqual([]); + + expect(CodeMirror.matchBrackets(editor, false, { + highlightNonMatching: false + })).toBeUndefined(); + expect(editor.getAllMarks()).toEqual([]); + }); + + it("preserves public marker and line-widget identities across document swaps", function () { + const firstDoc = trackDoc(new CodeMirror.Doc( + "alpha\nbeta", + "javascript" + )); + const replacementNode = window.document.createElement("strong"); + replacementNode.textContent = "replacement"; + const marker = firstDoc.markText( + {line: 0, ch: 0}, + {line: 0, ch: 2}, + { + replacedWith: replacementNode, + doc: {}, + handleMouseEvents: false + } + ); + const bookmarkNode = window.document.createElement("em"); + bookmarkNode.textContent = "bookmark"; + const bookmark = firstDoc.setBookmark( + {line: 0, ch: 3}, + { + widget: bookmarkNode, + insertLeft: true, + handleMouseEvents: true + } + ); + const lineWidgetNode = window.document.createElement("div"); + const spoofedLine = {}; + const lineWidget = firstDoc.addLineWidget(1, lineWidgetNode, { + doc: {}, + node: window.document.createElement("div"), + line: spoofedLine + }); + const firstLineHandle = firstDoc.getLineHandle(1); + + expect(marker.doc).toBe(firstDoc); + expect(marker.replacedWith).toBe(replacementNode); + expect(marker.widgetNode).not.toBe(replacementNode); + expect(marker.widgetNode.tagName).toBe("SPAN"); + expect(marker.widgetNode.className).toBe("CodeMirror-widget"); + expect(marker.widgetNode.getAttribute("role")).toBe("presentation"); + expect(marker.widgetNode.getAttribute("cm-ignore-events")).toBe("true"); + expect(marker.widgetNode.firstChild).toBe(replacementNode); + expect(bookmark.doc).toBe(firstDoc); + expect(bookmark.replacedWith).toBe(bookmarkNode); + expect(bookmark.widgetNode.firstChild).toBe(bookmarkNode); + expect(bookmark.widgetNode.hasAttribute("cm-ignore-events")).toBe(false); + expect(bookmark.widgetNode.insertLeft).toBe(true); + expect(lineWidget.doc).toBe(firstDoc); + expect(lineWidget.node).toBe(lineWidgetNode); + expect(lineWidget.line).toBe(firstLineHandle); + + let probeCount = 0; + const probe = function () { + probeCount++; + }; + lineWidget.on("probe", probe); + CodeMirror.signal(lineWidget, "probe"); + lineWidget.off("probe", probe); + CodeMirror.signal(lineWidget, "probe"); + expect(probeCount).toBe(1); + + const editor = createEditor(firstDoc); + const secondDoc = trackDoc(new CodeMirror.Doc( + "gamma\ndelta", + "javascript" + )); + const secondMarker = secondDoc.markText( + {line: 0, ch: 1}, + {line: 0, ch: 3} + ); + const secondWidgetNode = window.document.createElement("div"); + const secondWidget = secondDoc.addLineWidget(1, secondWidgetNode); + firstDoc._adapter._lineFolds.firstDocumentFold = { + from: 0, + to: 1 + }; + secondDoc._adapter._lineFolds.secondDocumentFold = { + from: 1, + to: 2 + }; + + expect(editor.swapDoc(secondDoc)).toBe(firstDoc); + expect(editor._lineFolds).toEqual({ + secondDocumentFold: { + from: 1, + to: 2 + } + }); + expect(firstDoc._adapter._lineFolds).toEqual({ + firstDocumentFold: { + from: 0, + to: 1 + } + }); + expect(marker.doc).toBe(firstDoc); + expect(bookmark.doc).toBe(firstDoc); + expect(lineWidget.doc).toBe(firstDoc); + expect(firstDoc.getAllMarks()).toContain(marker); + expect(firstDoc.getAllMarks()).toContain(bookmark); + expect(firstDoc.lineInfo(1).widgets).toContain(lineWidget); + expect(secondMarker.doc).toBe(secondDoc); + expect(secondWidget.doc).toBe(secondDoc); + expect(secondDoc.getAllMarks()).toEqual([secondMarker]); + expect(secondDoc.lineInfo(1).widgets).toContain(secondWidget); + + expect(editor.swapDoc(firstDoc)).toBe(secondDoc); + expect(editor._lineFolds).toEqual({ + firstDocumentFold: { + from: 0, + to: 1 + } + }); + expect(secondDoc._adapter._lineFolds).toEqual({ + secondDocumentFold: { + from: 1, + to: 2 + } + }); + expect(marker.doc).toBe(firstDoc); + expect(marker.widgetNode.firstChild).toBe(replacementNode); + expect(lineWidget.doc).toBe(firstDoc); + expect(lineWidget.node).toBe(lineWidgetNode); + marker.clear(); + lineWidget.clear(); + expect(marker.doc).toBe(firstDoc); + expect(marker.widgetNode.firstChild).toBe(replacementNode); + expect(lineWidget.doc).toBe(firstDoc); + expect(lineWidget.node).toBe(lineWidgetNode); + expect(lineWidget.line).toBe(firstLineHandle); + }); + + it("preserves CM5 marker ordering across document queries", function () { + const holder = window.document.createElement("div"); + window.document.body.appendChild(holder); + fixtures.push(holder); + + const editor = new CodeMirror(holder, { + value: "zero line\none line text\ntwo line" + }); + editors.push(editor); + + const lateOnLineOne = editor.markText( + {line: 1, ch: 8}, + {line: 1, ch: 10} + ); + lateOnLineOne.compatLabel = "line-one-late"; + const earlyOnLineOne = editor.markText( + {line: 1, ch: 1}, + {line: 1, ch: 3} + ); + earlyOnLineOne.compatLabel = "line-one-early"; + const lineZero = editor.markText( + {line: 0, ch: 1}, + {line: 0, ch: 3} + ); + lineZero.compatLabel = "line-zero"; + const spanning = editor.markText( + {line: 0, ch: 4}, + {line: 2, ch: 1} + ); + spanning.compatLabel = "spanning"; + + function labels(markers) { + return markers.map(function (marker) { + return marker.compatLabel; + }); + } + + expect(labels(editor.getAllMarks())).toEqual([ + "line-zero", + "spanning", + "line-one-late", + "line-one-early" + ]); + expect(labels(editor.findMarks( + {line: 0, ch: 0}, + {line: 2, ch: 2} + ))).toEqual([ + "line-zero", + "spanning", + "line-one-late", + "line-one-early" + ]); + expect(labels(editor.findMarks( + {line: 1, ch: 0}, + {line: 2, ch: 2} + ))).toEqual([ + "line-one-late", + "line-one-early", + "spanning" + ]); + expect(labels(editor.findMarksAt({line: 1, ch: 9}))).toEqual([ + "line-one-late", + "spanning" + ]); + }); + + it("provides detached Doc identity, copying, attachment, and swapDoc", function () { + const doc = trackDoc(new CodeMirror.Doc( + "one", + "javascript", + 0, + "\n", + "ltr" + )); + expect(doc instanceof CodeMirror.Doc).toBe(true); + expect(doc.getEditor()).toBeNull(); + expect(doc.getValue()).toBe("one"); + + doc.replaceRange("!", {line: 0, ch: 3}); + const copy = trackDoc(doc.copy(true)); + expect(copy).not.toBe(doc); + expect(copy.getValue()).toBe("one!"); + expect(copy.historySize().undo).toBe(1); + copy.undo(); + expect(copy.getValue()).toBe("one"); + expect(doc.getValue()).toBe("one!"); + expect(trackDoc(doc.copy(false)).historySize().undo).toBe(0); + + const editor = createEditor(doc); + expect(editor instanceof CodeMirror).toBe(true); + expect(editor.getDoc()).toBe(doc); + expect(doc.getEditor()).toBe(editor); + expect(function () { + createEditor(doc); + }).toThrow(); + + const replacement = trackDoc(new CodeMirror.Doc( + "# title", + "markdown" + )); + const oldDoc = editor.swapDoc(replacement); + expect(oldDoc).toBe(doc); + expect(oldDoc.getEditor()).toBeNull(); + expect(editor.getDoc()).toBe(replacement); + expect(replacement.getEditor()).toBe(editor); + expect(editor.getValue()).toBe("# title"); + expect(editor.getMode().name).toBe("markdown"); + + oldDoc.setValue("detached"); + expect(editor.getValue()).toBe("# title"); + editor.swapDoc(oldDoc); + expect(editor.getValue()).toBe("detached"); + }); + + it("propagates linked documents transitively and partitions on unlink", function () { + const editor = createEditor("x"); + const rootDoc = editor.getDoc(); + [ + "iterLinkedDocs", + "linkedDoc", + "unlinkDoc" + ].forEach(function (methodName) { + expect(typeof editor[methodName]).toBe("function"); + }); + const linked = trackDoc(editor.linkedDoc()); + const descendant = trackDoc(linked.linkedDoc()); + + editor.setValue("hello"); + expect(linked.getValue()).toBe("hello"); + expect(descendant.getValue()).toBe("hello"); + + descendant.replaceRange("!", {line: 0, ch: 5}); + expect(editor.getValue()).toBe("hello!"); + expect(linked.getValue()).toBe("hello!"); + + const editorVisited = []; + editor.iterLinkedDocs(function (doc) { + editorVisited.push(doc); + }); + expect(editorVisited).toEqual([linked, descendant]); + + editor.unlinkDoc(linked); + linked.setValue("detached branch"); + expect(descendant.getValue()).toBe("detached branch"); + expect(editor.getValue()).toBe("hello!"); + + const visited = []; + linked.iterLinkedDocs(function (doc, sharedHistory) { + visited.push({ + doc: doc, + sharedHistory: sharedHistory + }); + }); + expect(visited.length).toBe(1); + expect(visited[0].doc).toBe(descendant); + expect(visited[0].sharedHistory).toBe(false); + }); + + it("shares history across linked docs and keeps separate history usable", function () { + const editor = createEditor("ab\ncd\nef"); + const shared = trackDoc(editor.getDoc().linkedDoc({ + sharedHist: true + })); + + editor.replaceRange("x", {line: 0, ch: 2}); + shared.replaceRange("y", {line: 1, ch: 2}); + editor.replaceRange("z", {line: 2, ch: 2}); + expect(shared.getValue()).toBe("abx\ncdy\nefz"); + editor.undo(); + shared.undo(); + expect(editor.getValue()).toBe("abx\ncd\nef"); + shared.redo(); + editor.redo(); + expect(editor.getValue()).toBe("abx\ncdy\nefz"); + + const separate = trackDoc(editor.getDoc().linkedDoc()); + separate.replaceRange("!", {line: 2, ch: 3}); + editor.replaceRange("prefix\n", {line: 0, ch: 0}); + separate.undo(); + expect(editor.getValue()).toBe("prefix\nabx\ncdy\nefz"); + }); + + it("preserves global line coordinates for linked subviews", function () { + const editor = createEditor("1\n2\n3\n4\n5"); + const subview = trackDoc(editor.getDoc().linkedDoc({ + from: 1, + to: 3 + })); + expect(subview.getValue()).toBe("2\n3"); + expect(subview.firstLine()).toBe(1); + expect(subview.lastLine()).toBe(2); + + subview.setCursor({line: 4, ch: 0}); + expect(subview.getCursor()).toEqual({line: 2, ch: 1}); + editor.replaceRange("-1\n0\n", {line: 0, ch: 0}); + expect(subview.firstLine()).toBe(3); + expect(subview.getCursor()).toEqual({line: 4, ch: 1}); + editor.undo(); + expect(subview.firstLine()).toBe(1); + expect(subview.getCursor()).toEqual({line: 2, ch: 1}); + + subview.replaceRange("new\n", {line: 2, ch: 0}); + expect(editor.getValue()).toBe("1\n2\nnew\n3\n4\n5"); + subview.undo(); + expect(editor.getValue()).toBe("1\n2\n3\n4\n5"); + }); + + it("shares and partitions shared markers across linked documents", function () { + const editor = createEditor("abcde"); + const linked = trackDoc(editor.getDoc().linkedDoc()); + const descendant = trackDoc(linked.linkedDoc()); + const sharedMarker = linked.markText( + {line: 0, ch: 1}, + {line: 0, ch: 3}, + { + className: "cm-searching", + shared: true + } + ); + + expect(sharedMarker.doc).toBeUndefined(); + expect(sharedMarker.primary.doc).toBe(editor.getDoc()); + expect(linked.findMarksAt({line: 0, ch: 2})[0]).toBe(sharedMarker); + expect(descendant.findMarksAt({line: 0, ch: 2})[0]).toBe(sharedMarker); + const rootMarker = editor.getAllMarks()[0]; + const linkedMarker = linked.getAllMarks()[0]; + const descendantMarker = descendant.getAllMarks()[0]; + expect(rootMarker.doc).toBe(editor.getDoc()); + expect(linkedMarker.doc).toBe(linked); + expect(descendantMarker.doc).toBe(descendant); + expect(rootMarker.parent).toBe(sharedMarker); + expect(linkedMarker.parent).toBe(sharedMarker); + expect(descendantMarker.parent).toBe(sharedMarker); + + editor.getDoc().unlinkDoc(linked); + const detachedMarker = linked.findMarksAt({line: 0, ch: 2})[0]; + const detachedDescendantMarker = + descendant.findMarksAt({line: 0, ch: 2})[0]; + expect(detachedMarker).not.toBe(sharedMarker); + expect(detachedDescendantMarker).not.toBe(sharedMarker); + expect(detachedDescendantMarker).not.toBe(detachedMarker); + expect(detachedMarker.doc).toBe(linked); + expect(detachedDescendantMarker.doc).toBe(descendant); + expect(detachedMarker.parent).toBeNull(); + expect(detachedDescendantMarker.parent).toBeNull(); + + detachedMarker.clear(); + expect(linked.findMarksAt({line: 0, ch: 2}).length).toBe(0); + expect(descendant.findMarksAt({line: 0, ch: 2})[0]) + .toBe(detachedDescendantMarker); + expect(editor.findMarksAt({line: 0, ch: 2})[0]).toBe(sharedMarker); + detachedDescendantMarker.clear(); + sharedMarker.clear(); + expect(editor.findMarksAt({line: 0, ch: 2}).length).toBe(0); + }); + + it("supports legacy instance selection, operation, movement, and widget APIs", async function () { + const holder = window.document.createElement("div"); + holder.style.display = "block"; + holder.style.width = "600px"; + holder.style.height = "180px"; + window.document.body.appendChild(holder); + fixtures.push(holder); + + const editor = new CodeMirror(holder, { + value: "alpha\nbeta\ngamma", + mode: "javascript", + phrases: { + Greeting: "Hello" + } + }); + editors.push(editor); + editor.setSize(600, 180); + editor.refresh(); + + editor.setOption("lineNumbers", true); + editor.setOption("styleActiveLine", true); + editor.setCursor({line: 0, ch: 0}); + expect(editor.getGutterElement().querySelector(".CodeMirror-linenumbers")) + .not.toBeNull(); + expect(editor.lineInfo(0).wrapClass).toContain("CodeMirror-activeline"); + expect(editor.lineInfo(0).bgClass).toContain( + "CodeMirror-activeline-background" + ); + + [ + "addSelection", + "addWidget", + "annotateScrollbar", + "clipPos", + "endOperation", + "extendSelections", + "findPosH", + "findPosV", + "getExtending", + "getLineHandleVisualStart", + "hasFocus", + "isReadOnly", + "phrase", + "setDirection", + "setExtending", + "showMatchesOnScrollbar", + "splitLines", + "startOperation", + "triggerElectric", + "triggerOnKeyDown", + "triggerOnKeyPress", + "triggerOnKeyUp", + "triggerOnMouseDown" + ].forEach(function (methodName) { + expect(typeof editor[methodName]).toBe("function"); + }); + + expect(editor.clipPos({line: 99, ch: 99})).toEqual({ + line: 2, + ch: 5 + }); + editor.setOption("lineSeparator", "\r\n"); + expect(editor.splitLines("one\r\ntwo")).toEqual(["one", "two"]); + expect(editor.phrase("Greeting")).toBe("Hello"); + + editor.setCursor({line: 0, ch: 0}); + editor.addSelection( + {line: 1, ch: 0}, + {line: 1, ch: 2} + ); + expect(editor.listSelections().length).toBe(2); + expect(editor.getSelection()).toBe("be"); + + editor.setExtending(true); + editor.extendSelections([ + {line: 0, ch: 2}, + {line: 1, ch: 4} + ]); + expect(editor.getExtending()).toBe(true); + expect(editor.listSelections()[1].to().ch).toBe(4); + editor.setExtending(false); + + let changesEventCount = 0; + let operationChangeCount = 0; + editor.on("changes", function (_codeMirror, changes) { + changesEventCount++; + operationChangeCount = changes.length; + }); + editor.startOperation(); + editor.replaceRange("A", {line: 0, ch: 0}, {line: 0, ch: 1}, "+input"); + editor.replaceRange("B", {line: 1, ch: 0}, {line: 1, ch: 1}, "+input"); + expect(changesEventCount).toBe(0); + editor.endOperation(); + expect(changesEventCount).toBe(1); + expect(operationChangeCount).toBe(2); + + expect(editor.findPosH({line: 0, ch: 0}, 1, "char")).toEqual({ + line: 0, + ch: 1 + }); + expect(editor.getLineHandleVisualStart(1)).toBe(editor.getLineHandle(1)); + + editor.setValue(Array.from({length: 80}, function (_value, index) { + return `line ${index}`; + }).join("\n")); + editor.setCursor({line: 0, ch: 0}); + await awaitsFor(function () { + return holder.getBoundingClientRect().height > 0 && + editor.defaultTextHeight() > 0; + }, "CM6 compatibility editor should be measurable"); + const pagePosition = editor.findPosV( + {line: 0, ch: 0}, + 1, + "page" + ); + expect(pagePosition.line).toBeGreaterThan(0); + + const widget = window.document.createElement("span"); + widget.textContent = "widget"; + editor.addWidget({line: 0, ch: 0}, widget); + expect(widget.parentElement).toBe(editor.getScrollerElement()); + + let keyUpObserved = false; + editor.on("keyup", function (_codeMirror, event) { + keyUpObserved = event.key === "F12"; + }); + editor.triggerOnKeyUp(new window.KeyboardEvent("keyup", { + key: "F12", + bubbles: true + })); + expect(keyUpObserved).toBe(true); + + editor.setDirection("rtl"); + expect(editor.getWrapperElement().classList.contains("CodeMirror-rtl")) + .toBe(true); + editor.setOption("readOnly", true); + expect(editor.isReadOnly()).toBe(true); + editor.setOption("readOnly", false); + editor.focus(); + await awaitsFor(function () { + return editor.hasFocus(); + }, "CM6 compatibility editor should receive focus"); + }); + + it("streams string search cursors with CodeMirror 5 line semantics", function () { + const editor = createEditor([ + "Alpha", + "middle", + "Omega", + "alpha", + "middle", + "omega", + "café" + ].join("\n")); + let cursor = editor.getSearchCursor( + "ALPHA\nMIDDLE\nOMEGA", + {line: 0, ch: 0}, + {caseFold: true} + ); + + expect(cursor.findNext()).toBe(true); + expect(cursor.from()).toEqual({line: 0, ch: 0}); + expect(cursor.to()).toEqual({line: 2, ch: 5}); + expect(cursor.findNext()).toBe(true); + expect(cursor.from()).toEqual({line: 3, ch: 0}); + expect(cursor.to()).toEqual({line: 5, ch: 5}); + expect(cursor.findNext()).toBe(false); + + cursor = editor.getSearchCursor( + "ALPHA\nMIDDLE\nOMEGA", + {line: editor.lastLine(), ch: null}, + {caseFold: true} + ); + expect(cursor.findPrevious()).toBe(true); + expect(cursor.from()).toEqual({line: 3, ch: 0}); + expect(cursor.to()).toEqual({line: 5, ch: 5}); + + cursor = editor.getSearchCursor( + "cafe\u0301", + {line: 0, ch: 0} + ); + expect(cursor.findNext()).toBe(true); + expect(cursor.from()).toEqual({line: 6, ch: 0}); + expect(cursor.to()).toEqual({line: 6, ch: 4}); + }); + + it("does not rebuild the whole document for every string match", function () { + const lineCount = 5000; + const editor = createEditor( + Array.from({length: lineCount}, function (_value, index) { + return `line ${index} needle value`; + }).join("\n") + ); + const originalGetValue = editor.getValue.bind(editor); + let wholeDocumentReads = 0; + editor.getValue = function (separator) { + wholeDocumentReads++; + return originalGetValue(separator); + }; + + const cursor = editor.getSearchCursor("needle"); + let matchCount = 0; + while (cursor.findNext()) { + matchCount++; + } + + expect(matchCount).toBe(lineCount); + expect(wholeDocumentReads).toBe(0); + }); + + it("installs idempotent Sublime keymaps and their CM6-backed commands", function () { + const selectNextOccurrence = CodeMirror.commands.selectNextOccurrence; + + expect(CodeMirrorSublimeCompat.install(CodeMirror)).toBe(CodeMirror); + expect(CodeMirrorSublimeCompat.install(CodeMirror)).toBe(CodeMirror); + expect(CodeMirror.commands.selectNextOccurrence) + .toBe(selectNextOccurrence); + expect(CodeMirror.keyMap.macSublime["Cmd-D"]) + .toBe("selectNextOccurrence"); + expect(CodeMirror.keyMap.pcSublime["Ctrl-D"]) + .toBe("selectNextOccurrence"); + expect(CodeMirror.keyMap.pcSublime["Ctrl-K"]) + .toBe("..."); + expect(CodeMirror.keyMap.pcSublime["Ctrl-K Ctrl-U"]) + .toBe("upcaseAtCursor"); + expect(CodeMirror.keyMap.macSublime.fallthrough) + .toBe("macDefault"); + expect(CodeMirror.keyMap.pcSublime.fallthrough) + .toBe("pcDefault"); + expect([ + CodeMirror.keyMap.macSublime, + CodeMirror.keyMap.pcSublime + ]).toContain(CodeMirror.keyMap.sublime); + + [ + "addCursorToNextLine", + "clearBookmarks", + "duplicateLine", + "findAllUnder", + "goSubwordRight", + "insertLineAfter", + "joinLines", + "selectBetweenBrackets", + "selectNextOccurrence", + "smartBackspace", + "sortLinesInsensitive", + "swapLineUp", + "toggleBookmark" + ].forEach(function (commandName) { + expect(typeof CodeMirror.commands[commandName]).toBe("function"); + }); + }); + + it("dispatches Sublime single-key and chord bindings through the adapter", function () { + const editor = createEditor("alpha beta alpha alphabet", { + keyMap: "pcSublime" + }); + + function keyDown(keyCode, key, options) { + const event = Object.assign({ + altKey: false, + ctrlKey: false, + defaultPrevented: false, + key: key, + keyCode: keyCode, + metaKey: false, + shiftKey: false, + preventDefault: function () { + this.defaultPrevented = true; + } + }, options || {}); + const handled = editor.triggerOnKeyDown(event); + expect(event.defaultPrevented).toBe(true); + expect(handled).toBe(true); + } + + editor.setCursor({line: 0, ch: 2}); + keyDown(68, "d", {ctrlKey: true}); + expect(editor.getSelection()).toBe("alpha"); + keyDown(68, "d", {ctrlKey: true}); + expect(editor.getSelections()).toEqual(["alpha", "alpha"]); + + editor.setSelection( + {line: 0, ch: 6}, + {line: 0, ch: 10} + ); + keyDown(75, "k", {ctrlKey: true}); + keyDown(85, "u", {ctrlKey: true}); + expect(editor.getValue()).toBe("alpha BETA alpha alphabet"); + }); + + it("executes Sublime line editing commands against the CM6 document", function () { + const editor = createEditor("one\ntwo\nthree"); + + editor.setCursor({line: 1, ch: 1}); + CodeMirror.commands.swapLineUp(editor); + expect(editor.getValue()).toBe("two\none\nthree"); + expect(editor.getCursor().line).toBe(0); + + CodeMirror.commands.swapLineDown(editor); + expect(editor.getValue()).toBe("one\ntwo\nthree"); + expect(editor.getCursor().line).toBe(1); + + CodeMirror.commands.duplicateLine(editor); + expect(editor.getValue()).toBe("one\ntwo\ntwo\nthree"); + + editor.setCursor({line: 1, ch: 0}); + CodeMirror.commands.joinLines(editor); + expect(editor.getValue()).toBe("one\ntwo two\nthree"); + + editor.setValue("beta\nAlpha\ncharlie"); + editor.setSelection( + {line: 0, ch: 0}, + {line: 2, ch: 7} + ); + CodeMirror.commands.sortLinesInsensitive(editor); + expect(editor.getValue()).toBe("Alpha\nbeta\ncharlie"); + }); + + it("supports Sublime subword, indentation, bracket, and bookmark actions", function () { + const editor = createEditor("fooBar\n value\n(alpha)", { + indentUnit: 4 + }); + + editor.setCursor({line: 0, ch: 0}); + CodeMirror.commands.goSubwordRight(editor); + expect(editor.getCursor()).toEqual({line: 0, ch: 3}); + CodeMirror.commands.goSubwordRight(editor); + expect(editor.getCursor()).toEqual({line: 0, ch: 6}); + + editor.setCursor({line: 1, ch: 4}); + CodeMirror.commands.smartBackspace(editor); + expect(editor.getLine(1)).toBe("value"); + + editor.setCursor({line: 2, ch: 3}); + CodeMirror.commands.selectBetweenBrackets(editor); + expect(editor.getSelection()).toBe("alpha"); + + editor.setCursor({line: 0, ch: 1}); + CodeMirror.commands.toggleBookmark(editor); + editor.setCursor({line: 1, ch: 1}); + CodeMirror.commands.toggleBookmark(editor); + expect(editor.state.sublimeBookmarks.length).toBe(2); + CodeMirror.commands.nextBookmark(editor); + expect(editor.getCursor()).toEqual({line: 0, ch: 1}); + CodeMirror.commands.clearBookmarks(editor); + expect(editor.state.sublimeBookmarks.length).toBe(0); + }); + + it("creates and restores editors through fromTextArea", function () { + const form = window.document.createElement("form"); + const textArea = window.document.createElement("textarea"); + textArea.value = "before"; + textArea.style.display = "inline-block"; + textArea.tabIndex = 7; + textArea.placeholder = "placeholder"; + form.appendChild(textArea); + window.document.body.appendChild(form); + fixtures.push(form); + + const editor = CodeMirror.fromTextArea(textArea, { + mode: "javascript", + leaveSubmitMethodAlone: true + }); + editors.push(editor); + + expect(textArea.style.display).toBe("none"); + expect(editor.getValue()).toBe("before"); + expect(editor.getOption("tabindex")).toBe(7); + expect(editor.getTextArea()).toBe(textArea); + + editor.setValue("after"); + editor.save(); + expect(textArea.value).toBe("after"); + editor.toTextArea(); + expect(textArea.style.display).toBe("inline-block"); + expect(textArea.value).toBe("after"); + }); + }); +}); diff --git a/test/spec/CodeMirrorLegacyAddons-test.js b/test/spec/CodeMirrorLegacyAddons-test.js new file mode 100644 index 0000000000..badbe90222 --- /dev/null +++ b/test/spec/CodeMirrorLegacyAddons-test.js @@ -0,0 +1,304 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global describe, it, expect, afterEach */ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + LegacyAddons = require("editor/CodeMirrorLegacyAddons"); + + LegacyAddons.installAll(CodeMirror); + + describe("CodeMirror legacy addon compatibility", function () { + const editors = []; + const fixtures = []; + + function createEditor(value, options) { + const fixture = window.document.createElement("div"); + window.document.body.appendChild(fixture); + fixtures.push(fixture); + const editor = new CodeMirror( + fixture, + Object.assign({ + value: value + }, options || {}) + ); + editors.push(editor); + return editor; + } + + afterEach(function () { + editors.forEach(function (editor) { + editor.destroy(); + }); + editors.length = 0; + fixtures.forEach(function (fixture) { + fixture.remove(); + }); + fixtures.length = 0; + }); + + it("installs supported addon paths idempotently", function () { + const lineComment = CodeMirror.prototype.lineComment; + + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/comment/comment.js" + )).toBe(true); + expect(CodeMirror.prototype.lineComment).toBe(lineComment); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/unsupported/example" + )).toBe(false); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/fold/brace-fold" + )).toBe(true); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/fold/comment-fold" + )).toBe(true); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/fold/markdown-fold" + )).toBe(true); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/runmode/runmode" + )).toBe(true); + expect(LegacyAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/edit/trailingspace" + )).toBe(true); + }); + + it("comments, uncomments, and block-comments through facade APIs", function () { + const editor = createEditor( + " const first = 1;\n const second = 2;", + {mode: "javascript"} + ); + const from = CodeMirror.Pos(0, 0); + const to = CodeMirror.Pos(1, editor.getLine(1).length); + + editor.lineComment(from, to, {indent: true}); + expect(editor.getValue()).toBe( + " // const first = 1;\n // const second = 2;" + ); + expect(editor.uncomment(from, CodeMirror.Pos(1, editor.getLine(1).length))) + .toBe(true); + expect(editor.getValue()).toBe( + " const first = 1;\n const second = 2;" + ); + + editor.setValue("value"); + editor.setSelection(CodeMirror.Pos(0, 0), CodeMirror.Pos(0, 5)); + editor.blockComment( + CodeMirror.Pos(0, 0), + CodeMirror.Pos(0, 5), + {fullLines: false} + ); + expect(editor.getValue()).toBe("/*value*/"); + expect(editor.uncomment( + CodeMirror.Pos(0, 0), + CodeMirror.Pos(0, editor.getLine(0).length) + )).toBe(true); + expect(editor.getValue()).toBe("value"); + }); + + it("selects search matches within the current selection", function () { + const editor = createEditor("one two one", {mode: "text/plain"}); + editor.setSelection(CodeMirror.Pos(0, 0), CodeMirror.Pos(0, 11)); + + editor.selectMatches("one"); + + expect(editor.listSelections().length).toBe(2); + expect(editor.getSelections()).toEqual(["one", "one"]); + }); + + it("finds, highlights, and navigates matching tags", function () { + const editor = createEditor( + "
x
", + {mode: "text/html"} + ); + const enclosing = CodeMirror.findEnclosingTag( + editor, + CodeMirror.Pos(0, 13) + ); + expect(enclosing.open.tag).toBe("span"); + expect(enclosing.close.tag).toBe("span"); + + const closing = CodeMirror.scanForClosingTag( + editor, + CodeMirror.Pos(0, 6), + "main", + 1 + ); + expect(closing.tag).toBe("main"); + + editor.setCursor(CodeMirror.Pos(0, 7)); + editor.setOption("matchTags", {bothTags: true}); + expect(editor.state.tagHit).toBeTruthy(); + expect(editor.state.tagOther).toBeTruthy(); + + CodeMirror.commands.toMatchingTag(editor); + expect(editor.getSelection()).toBe(""); + }); + + it("inserts an explicit closing tag for the active HTML context", function () { + const editor = createEditor( + "
\n content", + {mode: "text/html"} + ); + editor.setCursor(CodeMirror.Pos(1, editor.getLine(1).length)); + + expect(CodeMirror.commands.closeTag(editor)).toBe(true); + expect(editor.getValue()).toBe("
\n content
"); + }); + + it("continues line and block comments through the legacy command", function () { + const editor = createEditor("// note", {mode: "javascript"}); + editor.setCursor(CodeMirror.Pos(0, 5)); + + expect(CodeMirror.commands.continueComment(editor)).toBe(true); + expect(editor.getValue()).toBe("// no\n// te"); + + editor.setValue("/* hello */"); + editor.setCursor(CodeMirror.Pos(0, 8)); + expect(CodeMirror.commands.continueComment(editor)).toBe(true); + expect(editor.getValue()).toBe("/* hello\n * */"); + }); + + it("marks selected text with the configured compatibility class", function () { + const editor = createEditor("selected text", {mode: "text/plain"}); + editor.setSelection(CodeMirror.Pos(0, 0), CodeMirror.Pos(0, 8)); + editor.setOption("styleSelectedText", "extension-selection"); + + expect(editor.state.markedSelection.length).toBe(1); + expect(editor.state.markedSelection[0].className) + .toBe("extension-selection"); + const markedRange = editor.state.markedSelection[0].find(); + expect(markedRange.from.line).toBe(0); + expect(markedRange.from.ch).toBe(0); + expect(markedRange.to.line).toBe(0); + expect(markedRange.to.ch).toBe(8); + + editor.setOption("styleSelectedText", false); + expect(editor.state.markedSelection).toBe(null); + }); + + it("provides brace, comment, and Markdown fold helpers", function () { + const javascriptEditor = createEditor( + "function answer() {\n /* detail\n line */\n return 42;\n}", + {mode: "javascript"} + ); + const braceRange = CodeMirror.fold.brace( + javascriptEditor, + CodeMirror.Pos(0, 0) + ); + const commentRange = CodeMirror.fold.comment( + javascriptEditor, + CodeMirror.Pos(1, 0) + ); + + expect(braceRange.from.line).toBe(0); + expect(braceRange.to.line).toBe(4); + expect(commentRange.from).toEqual(CodeMirror.Pos(1, 6)); + expect(commentRange.to).toEqual(CodeMirror.Pos(2, 12)); + + const markdownEditor = createEditor( + "# Heading\nbody\n## Nested\nnested body\n# Next", + {mode: "markdown"} + ); + const markdownRange = CodeMirror.fold.markdown( + markdownEditor, + CodeMirror.Pos(0, 0) + ); + expect(markdownRange.from).toEqual(CodeMirror.Pos(0, 9)); + expect(markdownRange.to).toEqual(CodeMirror.Pos(3, 11)); + }); + + it("tokenizes source through runMode callbacks and DOM output", function () { + const tokens = []; + CodeMirror.runMode( + "const answer = 42;\nanswer;", + "javascript", + function (text, style, line, start, state, mode) { + tokens.push({ + line: line, + mode: mode && mode.name, + start: start, + state: state, + style: style, + text: text + }); + } + ); + + expect(tokens.map(function (token) { + return token.text; + }).join("")).toBe("const answer = 42;\nanswer;"); + expect(tokens.some(function (token) { + return token.text === "const" && + token.style === "keyword" && + token.line === 0 && + token.start === 0 && + token.mode === "javascript" && + token.state; + })).toBe(true); + + const output = window.document.createElement("pre"); + CodeMirror.runMode( + "\tconst value = 1;", + "javascript", + output, + {tabSize: 4} + ); + expect(output.textContent).toBe(" const value = 1;"); + expect(output.querySelector(".cm-keyword").textContent) + .toBe("const"); + }); + + it("renders and removes trailing-space decorations", function () { + const editor = createEditor( + "const value = 1; \nclean", + { + mode: "javascript", + showTrailingSpace: true + } + ); + + expect(editor.state.overlays.length).toBe(1); + expect(editor.state.overlays[0].mode.name).toBe("trailingspace"); + expect( + editor.getWrapperElement() + .querySelector(".cm-trailingspace") + .textContent + ).toBe(" "); + + editor.setOption("showTrailingSpace", false); + expect(editor.state.overlays).toEqual([]); + expect( + editor.getWrapperElement() + .querySelector(".cm-trailingspace") + ).toBeNull(); + }); + }); +}); diff --git a/test/spec/CodeMirrorLegacyExtendedAddons-test.js b/test/spec/CodeMirrorLegacyExtendedAddons-test.js new file mode 100644 index 0000000000..c0690c0cb9 --- /dev/null +++ b/test/spec/CodeMirrorLegacyExtendedAddons-test.js @@ -0,0 +1,1472 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect, afterEach, spyOn */ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + LegacyAddons = require("editor/CodeMirrorLegacyAddons"), + ExtendedAddons = require("editor/CodeMirrorLegacyExtendedAddons"); + + LegacyAddons.installAll(CodeMirror); + ExtendedAddons.installAll(CodeMirror); + + describe("CodeMirror extended legacy addon compatibility", function () { + const editors = []; + const fixtures = []; + const mergeViews = []; + + function createFixture() { + const fixture = window.document.createElement("div"); + window.document.body.appendChild(fixture); + fixtures.push(fixture); + return fixture; + } + + function createEditor(value, options) { + const editor = new CodeMirror( + createFixture(), + Object.assign({value: value}, options || {}) + ); + editors.push(editor); + return editor; + } + + afterEach(function () { + mergeViews.forEach(function (mergeView) { + mergeView.destroy(); + }); + mergeViews.length = 0; + editors.forEach(function (editor) { + editor.setOption("autoRefresh", false); + editor.setOption("lint", false); + editor.setOption("selectionPointer", false); + if (editor.getOption("fullScreen")) { + editor.setOption("fullScreen", false); + } + editor.destroy(); + }); + editors.length = 0; + fixtures.forEach(function (fixture) { + fixture.remove(); + }); + fixtures.length = 0; + }); + + it("recognizes all remaining canonical paths and normalizes aliases", function () { + expect(ExtendedAddons.supportedPaths.length).toBe(32); + ExtendedAddons.supportedPaths.forEach(function (path) { + expect(ExtendedAddons.isSupported(path)).toBe(true); + expect(ExtendedAddons.isSupported( + `thirdparty/CodeMirror/${path}.js?cache=1#test` + )).toBe(true); + expect(ExtendedAddons.isSupported( + `thirdparty/CodeMirror2/${path}` + )).toBe(true); + expect(ExtendedAddons.install(CodeMirror, path)).toBe(true); + }); + expect(ExtendedAddons.isSupported( + "thirdparty/CodeMirror/addon/not-real" + )).toBe(false); + + const openDialog = CodeMirror.prototype.openDialog; + expect(ExtendedAddons.install( + CodeMirror, + "thirdparty/CodeMirror/addon/dialog/dialog.js?again=1" + )).toBe(true); + expect(CodeMirror.prototype.openDialog).toBe(openDialog); + }); + + it("preserves richer Phoenix folding APIs when legacy addons load", function () { + function FoldingFacade() {} + + const existing = { + auto: function () {}, + combine: function () {}, + fold: function () {}, + foldAll: function () {}, + foldCode: function () {}, + foldGutter: function () {}, + foldOptions: function () {}, + getValidFolds: function () {}, + indent: function () {}, + isFolded: function () {}, + newFoldFunction: function () {}, + unfold: function () {}, + unfoldAll: function () {}, + unfoldCode: function () {} + }; + + FoldingFacade.prototype.foldCode = existing.foldCode; + FoldingFacade.prototype.getValidFolds = existing.getValidFolds; + FoldingFacade.prototype.isFolded = existing.isFolded; + FoldingFacade.prototype.unfoldCode = existing.unfoldCode; + FoldingFacade.commands = { + fold: existing.fold, + foldAll: existing.foldAll, + unfold: existing.unfold, + unfoldAll: existing.unfoldAll + }; + FoldingFacade.fold = { + auto: existing.auto, + combine: existing.combine, + indent: existing.indent + }; + FoldingFacade.helpers = { + fold: FoldingFacade.fold + }; + FoldingFacade.newFoldFunction = existing.newFoldFunction; + FoldingFacade.optionHandlers = { + foldGutter: existing.foldGutter, + foldOptions: existing.foldOptions + }; + FoldingFacade.defineExtension = function (name, extension) { + FoldingFacade.prototype[name] = extension; + }; + FoldingFacade.defineOption = function (name, defaultValue, handler) { + FoldingFacade.optionHandlers[name] = { + defaultValue: defaultValue, + handler: handler + }; + }; + FoldingFacade.registerHelper = function (type, name, helper) { + FoldingFacade.helpers[type] = + FoldingFacade.helpers[type] || {}; + FoldingFacade.helpers[type][name] = helper; + FoldingFacade[type] = FoldingFacade.helpers[type]; + }; + + expect(ExtendedAddons.install( + FoldingFacade, + "addon/fold/foldcode" + )).toBe(true); + expect(ExtendedAddons.install( + FoldingFacade, + "addon/fold/foldgutter" + )).toBe(true); + expect(ExtendedAddons.install( + FoldingFacade, + "addon/fold/indent-fold" + )).toBe(true); + + expect(FoldingFacade.prototype.foldCode) + .toBe(existing.foldCode); + expect(FoldingFacade.prototype.isFolded) + .toBe(existing.isFolded); + expect(FoldingFacade.prototype.unfoldCode) + .toBe(existing.unfoldCode); + expect(FoldingFacade.prototype.getValidFolds) + .toBe(existing.getValidFolds); + expect(FoldingFacade.commands.fold).toBe(existing.fold); + expect(FoldingFacade.commands.unfold).toBe(existing.unfold); + expect(FoldingFacade.commands.foldAll).toBe(existing.foldAll); + expect(FoldingFacade.commands.unfoldAll) + .toBe(existing.unfoldAll); + expect(FoldingFacade.fold.auto).toBe(existing.auto); + expect(FoldingFacade.fold.combine).toBe(existing.combine); + expect(FoldingFacade.fold.indent).toBe(existing.indent); + expect(FoldingFacade.newFoldFunction) + .toBe(existing.newFoldFunction); + expect(FoldingFacade.optionHandlers.foldOptions) + .toBe(existing.foldOptions); + expect(FoldingFacade.optionHandlers.foldGutter) + .toBe(existing.foldGutter); + expect(typeof FoldingFacade.prototype.foldOption) + .toBe("function"); + expect(typeof FoldingFacade.commands.toggleFold) + .toBe("function"); + }); + + it("exposes the expected extended API surface", function () { + [ + "openConfirm", + "openDialog", + "openNotification", + "addPanel", + "foldCode", + "foldOption", + "isFolded", + "performLint", + "wrapParagraph", + "wrapParagraphsInRange", + "wrapRange" + ].forEach(function (methodName) { + expect(typeof CodeMirror.prototype[methodName]) + .toBe("function"); + }); + [ + "autoRefresh", + "foldGutter", + "fullScreen", + "lint", + "selectionPointer" + ].forEach(function (optionName) { + expect(CodeMirror.optionHandlers[optionName]).toBeTruthy(); + }); + [ + "coffeescript", + "css", + "html", + "javascript", + "sql", + "xml" + ].forEach(function (helperName) { + expect(typeof CodeMirror.hint[helperName]).toBe("function"); + }); + [ + "coffeescript", + "css", + "html", + "javascript", + "json", + "yaml" + ].forEach(function (helperName) { + expect(typeof CodeMirror.lint[helperName]).toBe("function"); + }); + expect(typeof CodeMirror.fold.indent).toBe("function"); + expect(typeof CodeMirror.MergeView).toBe("function"); + expect(typeof CodeMirror.TernServer).toBe("function"); + expect(typeof CodeMirror.colorize).toBe("function"); + expect(typeof CodeMirror.requireMode).toBe("function"); + expect(typeof CodeMirror.autoLoadMode).toBe("function"); + expect(typeof CodeMirror.scrollbarModel.simple).toBe("function"); + expect(typeof CodeMirror.scrollbarModel.overlay).toBe("function"); + expect(CodeMirror.emacs).toBeTruthy(); + expect(CodeMirror.keyMap.emacs).toBeTruthy(); + }); + + it("opens and closes dialogs, confirms, and notifications", function () { + const editor = createEditor("", {mode: "text/plain"}); + let submitted = null; + const close = editor.openDialog( + "", + function (value) { + submitted = value; + }, + {closeOnBlur: false, value: "answer"} + ); + const wrapper = editor.getWrapperElement(); + const input = wrapper.querySelector(".CodeMirror-dialog input"); + + expect(wrapper.querySelector(".CodeMirror-dialog")).not.toBeNull(); + expect(input.value).toBe("answer"); + close("updated"); + expect(input.value).toBe("updated"); + input.dispatchEvent(new window.KeyboardEvent("keydown", { + bubbles: true, + key: "Enter", + keyCode: 13 + })); + expect(submitted).toBe("updated"); + expect(wrapper.querySelector(".CodeMirror-dialog")).toBeNull(); + + let confirmed = false; + editor.openConfirm( + "", + [function () { + confirmed = true; + }] + ); + wrapper.querySelector(".CodeMirror-dialog button").click(); + expect(confirmed).toBe(true); + + const closeNotification = editor.openNotification( + "Ready", + {duration: 0} + ); + expect(wrapper.textContent).toContain("Ready"); + const closeReplacementNotification = editor.openNotification( + "Still ready", + {duration: 0} + ); + expect(wrapper.querySelectorAll(".CodeMirror-dialog").length) + .toBe(1); + expect(wrapper.textContent).toContain("Still ready"); + expect(wrapper.classList.contains("dialog-opened")).toBe(true); + closeNotification(); + expect(wrapper.classList.contains("dialog-opened")).toBe(true); + closeReplacementNotification(); + closeReplacementNotification(); + expect(wrapper.classList.contains("dialog-opened")).toBe(false); + }); + + it("adds panels in order and restores the editor wrapper", function () { + const editor = createEditor("", {mode: "text/plain"}); + const wrapper = editor.getWrapperElement(); + const originalParent = wrapper.parentNode; + const firstNode = window.document.createElement("div"); + const secondNode = window.document.createElement("div"); + firstNode.textContent = "first"; + secondNode.textContent = "second"; + + const first = editor.addPanel(firstNode); + const second = editor.addPanel(secondNode, {after: first}); + + expect(editor.state.panels.panels.length).toBe(2); + expect(firstNode.nextSibling).toBe(secondNode); + first.changed(); + first.clear(); + expect(editor.state.panels.panels).toEqual([second]); + second.clear(); + expect(editor.state.panels).toBeNull(); + expect(wrapper.parentNode).toBe(originalParent); + }); + + it("toggles display compatibility options without retaining state", function () { + const editor = createEditor("", {mode: "text/plain"}); + const wrapper = editor.getWrapperElement(); + const oldOverflow = + window.document.documentElement.style.overflow; + + editor.setOption("fullScreen", true); + expect(wrapper.classList.contains("CodeMirror-fullscreen")) + .toBe(true); + expect(window.document.documentElement.style.overflow) + .toBe("hidden"); + editor.setOption("fullScreen", false); + expect(wrapper.classList.contains("CodeMirror-fullscreen")) + .toBe(false); + expect(window.document.documentElement.style.overflow) + .toBe(oldOverflow); + + editor.setOption("autoRefresh", {delay: 1}); + editor.setOption("autoRefresh", false); + expect(editor.state.autoRefresh).toBeNull(); + + editor.setOption("selectionPointer", "pointer"); + expect(editor.state.selectionPointer.value).toBe("pointer"); + editor.setOption("selectionPointer", false); + expect(editor.state.selectionPointer).toBeNull(); + }); + + it("continues and renumbers Markdown lists", function () { + const editor = createEditor( + "1. one\n2. two", + {mode: "markdown"} + ); + editor.setCursor(CodeMirror.Pos(0, 6)); + CodeMirror.commands.newlineAndIndentContinueMarkdownList(editor); + expect(editor.getValue()).toBe("1. one\n2. \n3. two"); + + editor.setValue("- [x] done"); + editor.setCursor(CodeMirror.Pos(0, 10)); + CodeMirror.commands.newlineAndIndentContinueMarkdownList(editor); + expect(editor.getValue()).toBe("- [x] done\n- [ ] "); + }); + + it("does not continue list-shaped text outside Markdown list state", function () { + const editor = createEditor( + " 1. indented code", + {mode: "markdown"} + ); + const fallback = spyOn(editor, "execCommand").and.callThrough(); + + editor.setCursor(CodeMirror.Pos(0, 20)); + CodeMirror.commands.newlineAndIndentContinueMarkdownList(editor); + + expect(fallback).toHaveBeenCalledWith("newlineAndIndent"); + expect(editor.getValue()).not.toContain("\n 2. "); + }); + + it("folds custom ranges and finds indentation folds", function () { + const editor = createEditor( + "root\n child\n\n child two\nnext", + {mode: "text/plain"} + ); + const rangeFinder = function () { + return { + from: CodeMirror.Pos(0, 4), + to: CodeMirror.Pos(3, 11) + }; + }; + let folded = 0; + let unfolded = 0; + editor.on("fold", function () { + folded++; + }); + editor.on("unfold", function () { + unfolded++; + }); + + const marker = editor.foldCode( + CodeMirror.Pos(0, 0), + rangeFinder + ); + expect(marker.__isFold).toBe(true); + expect(editor.isFolded(CodeMirror.Pos(0, 4))).toBe(true); + editor.foldCode(CodeMirror.Pos(0, 0), rangeFinder); + expect(editor.isFolded(CodeMirror.Pos(0, 4))).toBe(false); + expect(folded).toBe(1); + expect(unfolded).toBe(1); + + const indentRange = CodeMirror.fold.indent( + editor, + CodeMirror.Pos(0, 0) + ); + expect(indentRange.from).toEqual(CodeMirror.Pos(0, 4)); + expect(indentRange.to).toEqual(CodeMirror.Pos(3, 11)); + }); + + it("provides useful CSS, HTML, JavaScript, SQL, and XML hints", function () { + const cssEditor = createEditor( + ".sample { colo", + {mode: "css"} + ); + cssEditor.setCursor(CodeMirror.Pos(0, 14)); + expect(CodeMirror.hint.css(cssEditor).list) + .toContain("color"); + + const htmlEditor = createEditor( + "\n", {mode: "text/html"}); + htmlEditor.setCursor(CodeMirror.Pos(1, 0)); + expect(CodeMirror.hint.html(htmlEditor).list).toEqual([ + "" + ]); + + const languageSource = ""); + expect(hints).not.toContain(""); + }); + + it("uses XML parser context across lines", function () { + const source = "\n stream.start) { + break; + } + } + if (stream.pos <= stream.start) { + throw new Error( + `Mode ${mode.name} did not advance at ${stream.pos}.` + ); + } + tokens.push({ + string: stream.current(), + style: style + }); + } + }); + + return { + mode: mode, + state: state, + tokens: tokens + }; + } + + function tokenWithText(result, text) { + return result.tokens.find(function (token) { + return token.string === text; + }); + } + + describe("CodeMirror legacy mode compatibility", function () { + it("resolves every historical CodeMirror 5 mode module to the CM6 facade", function () { + expect(HISTORICAL_MODE_DIRECTORIES.length).toBe(121); + HISTORICAL_MODE_DIRECTORIES.forEach(function (modeName) { + const moduleName = [ + "thirdparty/CodeMirror/mode", + modeName, + modeName + ].join("/"); + expect( + LegacyModuleLoader.resolveLegacyModule(moduleName) + ).withContext(moduleName).toBe(CodeMirror); + expect(CodeMirror.hasMode(modeName)) + .withContext(modeName) + .toBeTrue(); + }); + }); + + it("resolves historical MIME side effects without loading CM5", function () { + expect(HISTORICAL_MIME_ALIASES.length).toBe(36); + HISTORICAL_MIME_ALIASES.forEach(function (mime) { + const resolved = CodeMirror.resolveMode(mime); + expect(resolved).withContext(mime).toBeDefined(); + expect(CodeMirror.getMode({indentUnit: 4}, mime).name) + .withContext(mime) + .not.toBe("null"); + }); + }); + + it("preserves Django, HAML, Smarty, Soy, and Tornado token behavior", function () { + const django = tokenize( + "django", + "

{{ user.name|upper }}

" + ); + expect(tokenWithText(django, "user").style).toContain("variable"); + expect(tokenWithText(django, "name").style).toContain("property"); + expect(tokenWithText(django, "upper").style) + .toContain("variable-2"); + + const haml = tokenize( + "haml", + "%div#main.card\n= user.name" + ); + expect(tokenWithText(haml, "%div").style).toBe("tag"); + expect(tokenWithText(haml, "#main.card").style) + .toBe("attribute"); + + const smarty = tokenize( + "smarty", + "{$user.name|escape}" + ); + expect(tokenWithText(smarty, "$user").style).toBe("variable-2"); + expect(tokenWithText(smarty, "name").style).toBe("property"); + expect(tokenWithText(smarty, "escape").style).toBe("qualifier"); + + const soy = tokenize( + "soy", + "{template .hello}\n{@param name: string}\nHello {$name}\n" + + "{/template}" + ); + expect(tokenWithText(soy, ".hello").style).toBe("def"); + expect(tokenWithText(soy, "name").style).toBe("def"); + expect(tokenWithText(soy, "$name").style).toBe("variable-2"); + + const tornado = tokenize( + "tornado", + "

{{ escape(title) }}

" + ); + expect(tornado.tokens.find(function (token) { + return token.string.trim() === "escape"; + }).style) + .toContain("keyword"); + }); + + it("preserves literate Haskell and YAML front-matter inner modes", function () { + const literate = tokenize( + "haskell-literate", + "Documentation\n> main = putStrLn \"hello\"" + ); + expect(literate.tokens[0].style).toBe("comment"); + expect(literate.tokens.some(function (token) { + return token.string === ">" && token.style === "meta"; + })).toBeTrue(); + expect(CodeMirror.innerMode(literate.mode, literate.state).mode.name) + .toBe("haskell"); + + const frontmatter = tokenize( + "yaml-frontmatter", + "---\ntitle: Phoenix\n---\n# Heading" + ); + expect(tokenWithText(frontmatter, "title").style) + .toContain("atom"); + expect(frontmatter.state.state).toBe(2); + expect(CodeMirror.innerMode( + frontmatter.mode, + frontmatter.state + ).mode.name).toBe("gfm"); + }); + + it("preserves RST and Slim parser behavior", function () { + const rst = tokenize( + "rst", + "Heading\n=======\n\n**strong** and ``literal``" + ); + expect(rst.tokens.some(function (token) { + return token.style && token.style.indexOf("header") !== -1; + })).toBeTrue(); + expect(rst.tokens.filter(function (token) { + return token.style && + token.style.indexOf("strong") !== -1; + }).map(function (token) { + return token.string; + }).join("")).toContain("**strong**"); + expect(rst.tokens.filter(function (token) { + return token.style && + token.style.indexOf("string-2") !== -1; + }).map(function (token) { + return token.string; + }).join("")).toContain("``literal``"); + + const slim = tokenize( + "slim", + "div#main.card\n = user.name" + ); + expect(tokenWithText(slim, "div").style).toBe("tag"); + expect(tokenWithText(slim, "#main").style).toContain("attribute"); + expect(tokenWithText(slim, ".card").style).toContain("attribute"); + expect(slim.tokens.some(function (token) { + return token.style && token.style.indexOf("variable") !== -1; + })).toBeTrue(); + }); + }); +}); diff --git a/test/spec/CodeMirrorTwigCompat-test.js b/test/spec/CodeMirrorTwigCompat-test.js new file mode 100644 index 0000000000..71669a6959 --- /dev/null +++ b/test/spec/CodeMirrorTwigCompat-test.js @@ -0,0 +1,179 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect*/ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + LegacyModuleLoader = require("editor/CodeMirrorLegacyModuleLoader"); + + function tokenize(mode, state, line) { + const stream = new CodeMirror.StringStream(line, 4); + const tokens = []; + while (!stream.eol()) { + stream.start = stream.pos; + const type = mode.token(stream, state); + if (stream.pos <= stream.start) { + throw new Error("Twig mode failed to advance the stream."); + } + tokens.push({ + string: stream.current(), + type: type + }); + } + return tokens; + } + + describe("CodeMirror Twig compatibility", function () { + it("resolves the historical Twig module to the CM6-backed facade", function () { + const moduleId = "thirdparty/CodeMirror/mode/twig/twig"; + + expect(LegacyModuleLoader.getModuleType(moduleId)).toBe("mode"); + expect(LegacyModuleLoader.resolveLegacyModule(moduleId)).toBe(CodeMirror); + expect(CodeMirror.loadMode("twig")).toBe(true); + expect(typeof CodeMirror.modes["twig:inner"]).toBe("function"); + expect(typeof CodeMirror.modes.twig).toBe("function"); + expect(CodeMirror.resolveMode("text/x-twig")).toEqual({name: "twig"}); + }); + + it("preserves Twig keyword, atom, string, operator, and tag tokens", function () { + const mode = CodeMirror.getMode({indentUnit: 4}, "twig:inner"); + const state = CodeMirror.startState(mode); + const tokens = tokenize( + mode, + state, + "{% if user.active and true %}{{ \"value\"|upper }}" + ); + + expect(tokens.some(function (token) { + return token.type === "tag" && token.string === "{%"; + })).toBe(true); + expect(tokens.some(function (token) { + return token.type === "keyword" && /\bif$/.test(token.string); + })).toBe(true); + expect(tokens.some(function (token) { + return token.type === "keyword" && /\band$/.test(token.string); + })).toBe(true); + expect(tokens.some(function (token) { + return token.type === "atom" && /\btrue$/.test(token.string); + })).toBe(true); + expect(tokens.some(function (token) { + return token.type === "string"; + })).toBe(true); + expect(tokens.some(function (token) { + return token.type === "operator"; + })).toBe(true); + expect(tokens.filter(function (token) { + return token.type === "tag"; + }).length).toBe(4); + expect(state.intag).toBe(false); + }); + + it("retains multiline Twig comment state and closes it at the delimiter", function () { + const mode = CodeMirror.getMode({indentUnit: 4}, "twig:inner"); + const state = CodeMirror.startState(mode); + const firstLine = tokenize(mode, state, "{# first line"); + + expect(firstLine.length).toBe(1); + expect(firstLine[0].type).toBe("comment"); + expect(state.incomment).toBe(true); + + const secondLine = tokenize(mode, state, "second line #}"); + expect(secondLine.length).toBe(1); + expect(secondLine[0].type).toBe("comment"); + expect(state.incomment).toBe(false); + }); + + it("multiplexes Twig expressions with an HTML base mode", function () { + const mode = CodeMirror.getMode( + {indentUnit: 4}, + { + name: "twig", + base: "htmlmixed" + } + ); + const state = CodeMirror.startState(mode); + const tokens = tokenize( + mode, + state, + "
{{ user.name }}
" + ); + + expect(tokens.some(function (token) { + return token.type && token.type.indexOf("tag") !== -1; + })).toBe(true); + const variableText = tokens.filter(function (token) { + return token.type === "variable"; + }).map(function (token) { + return token.string; + }).join(""); + expect(variableText).toContain("user.name"); + }); + + it("consumes parse-delimiter openers in the first token call", function () { + const mode = CodeMirror.getMode( + {indentUnit: 4}, + { + name: "twig", + base: "htmlmixed" + } + ); + const state = CodeMirror.startState(mode); + const stream = new CodeMirror.StringStream("{{ value }}", 4); + + stream.start = stream.pos; + expect(mode.token(stream, state)).toBe("tag"); + expect(stream.current()).toBe("{{"); + expect(stream.pos).toBeGreaterThan(stream.start); + expect(CodeMirror.innerMode(mode, state).mode.name).toBe("twig:inner"); + }); + + it("handles zero-width parse-delimiter openers without recursion", function () { + const mode = CodeMirror.multiplexingMode( + { + name: "outer", + token: function (stream) { + stream.next(); + return "outer"; + } + }, + { + open: /(?=x)/, + close: /$^/, + mode: { + name: "inner", + token: function (stream) { + stream.next(); + return "inner"; + } + }, + parseDelimiters: true + } + ); + const state = CodeMirror.startState(mode); + const stream = new CodeMirror.StringStream("x", 4); + + stream.start = stream.pos; + expect(mode.token(stream, state)).toBe("inner"); + expect(stream.current()).toBe("x"); + expect(stream.pos).toBe(1); + }); + }); +}); diff --git a/test/spec/CodeMirrorVimCompat-test.js b/test/spec/CodeMirrorVimCompat-test.js new file mode 100644 index 0000000000..3989ca6460 --- /dev/null +++ b/test/spec/CodeMirrorVimCompat-test.js @@ -0,0 +1,193 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect, afterEach*/ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + VimCompat = require("editor/CodeMirrorVimCompat"); + + VimCompat.install(CodeMirror); + + describe("CodeMirror Vim compatibility", function () { + const editors = []; + const fixtures = []; + + function createEditor(value, options) { + const fixture = window.document.createElement("div"); + window.document.body.appendChild(fixture); + fixtures.push(fixture); + const editor = new CodeMirror( + fixture, + Object.assign({ + keyMap: "vim", + value: value + }, options || {}) + ); + editors.push(editor); + CodeMirror.Vim.resetVimGlobalState_(); + return editor; + } + + function sendVimKey(editor, key) { + const handled = CodeMirror.Vim.multiSelectHandleKey( + editor, + key, + "user" + ); + if (!handled && key.length === 1 && + editor.state.vim && editor.state.vim.insertMode) { + if (editor.state.overwrite) { + editor.overWriteSelection(key); + } else { + editor.replaceSelection(key, "end", "+input"); + } + } + return handled; + } + + function sendVimKeys(editor) { + Array.prototype.slice.call(arguments, 1).forEach(function (key) { + sendVimKey(editor, key); + }); + } + + function characterEvent(character) { + return { + altKey: false, + charCode: character.charCodeAt(0), + ctrlKey: false, + defaultPrevented: false, + key: character, + keyCode: character.toUpperCase().charCodeAt(0), + metaKey: false, + preventDefault: function () { + this.defaultPrevented = true; + }, + shiftKey: character !== character.toLowerCase(), + stopPropagation: function () {} + }; + } + + afterEach(function () { + editors.forEach(function (editor) { + editor.destroy(); + }); + editors.length = 0; + fixtures.forEach(function (fixture) { + fixture.remove(); + }); + fixtures.length = 0; + CodeMirror.Vim.resetVimGlobalState_(); + }); + + it("dispatches normal-mode edits through the configured keymap", function () { + const editor = createEditor("abc"); + const event = characterEvent("x"); + + expect(editor.triggerOnKeyPress(event)).toBe(true); + expect(event.defaultPrevented).toBe(true); + expect(editor.getValue()).toBe("bc"); + + sendVimKey(editor, "u"); + expect(editor.getValue()).toBe("abc"); + sendVimKey(editor, ""); + expect(editor.getValue()).toBe("bc"); + }); + + it("switches between normal, insert, and replace modes", function () { + const editor = createEditor("abc"); + + expect(editor.state.vim.insertMode).toBe(false); + expect(editor.getOption("disableInput")).toBe(true); + expect(editor.getWrapperElement().classList.contains("cm-vimMode")) + .toBe(true); + + sendVimKey(editor, "i"); + expect(editor.state.vim.insertMode).toBe(true); + expect(editor.getOption("keyMap")).toBe("vim-insert"); + sendVimKey(editor, "Z"); + sendVimKey(editor, ""); + expect(editor.getValue()).toBe("Zabc"); + expect(editor.state.vim.insertMode).toBe(false); + expect(editor.getOption("keyMap")).toBe("vim"); + + editor.setCursor(CodeMirror.Pos(0, 0)); + sendVimKey(editor, "R"); + expect(editor.state.overwrite).toBe(true); + expect(editor.getOption("keyMap")).toBe("vim-replace"); + sendVimKey(editor, "Q"); + sendVimKey(editor, ""); + expect(editor.getValue()).toBe("Qabc"); + expect(editor.state.overwrite).toBe(false); + expect(editor.getOption("keyMap")).toBe("vim"); + }); + + it("preserves marks and recorded macros on the CM6 document", function () { + const editor = createEditor(" "); + + editor.setCursor(CodeMirror.Pos(0, 2)); + sendVimKeys(editor, "m", "a", "l", "l", "`", "a"); + expect(editor.getCursor()).toEqual({line: 0, ch: 2}); + + editor.setCursor(CodeMirror.Pos(0, 0)); + sendVimKeys(editor, "q", "q", "l", "l", "q"); + expect(editor.getCursor()).toEqual({line: 0, ch: 2}); + sendVimKeys(editor, "@", "q"); + expect(editor.getCursor()).toEqual({line: 0, ch: 4}); + }); + + it("opens and completes Vim search dialogs", function () { + const editor = createEditor("alpha beta alpha"); + + sendVimKey(editor, "/"); + const input = editor.getWrapperElement().querySelector( + ".CodeMirror-dialog input" + ); + expect(input).not.toBeNull(); + input.value = "beta"; + input.dispatchEvent(new window.KeyboardEvent("keydown", { + bubbles: true, + key: "Enter", + keyCode: 13, + which: 13 + })); + + expect(editor.getWrapperElement().querySelector(".CodeMirror-dialog")) + .toBeNull(); + expect(editor.getCursor()).toEqual({line: 0, ch: 6}); + }); + + it("leaves Vim mode cleanly when the keymap changes or editor is destroyed", function () { + const editor = createEditor("text"); + + editor.setOption("keyMap", "default"); + expect(editor.state.vim).toBeNull(); + expect(editor.getOption("disableInput")).toBe(false); + expect(editor.getWrapperElement().classList.contains("cm-vimMode")) + .toBe(false); + + editor.setOption("keyMap", "vim"); + expect(editor.state.vim).toBeTruthy(); + editor.destroy(); + expect(editor.state.vim).toBeNull(); + }); + }); +}); diff --git a/test/spec/Editor-test.js b/test/spec/Editor-test.js index e48e3d7a51..12802318ac 100644 --- a/test/spec/Editor-test.js +++ b/test/spec/Editor-test.js @@ -193,6 +193,15 @@ define(function (require, exports, module) { expect(myEditor.getModeForDocument()).toBe(htmlLanguage.getMode()); }); + it("should forward document deletion as a lostContent event", function () { + const lostContentHandler = jasmine.createSpy(); + myEditor.on("lostContent", lostContentHandler); + + myDocument.trigger("deleted"); + + expect(lostContentHandler).toHaveBeenCalled(); + }); + }); describe("Focus", function () { @@ -554,6 +563,25 @@ define(function (require, exports, module) { }); describe("setCursorPos", function () { + it("should use centering as the only scroll path when requested", function () { + spyOn(myEditor._codeMirror, "setCursor").and.callThrough(); + spyOn(myEditor, "centerOnCursor"); + + myEditor.setCursorPos(1, 3, true); + + expect(myEditor._codeMirror.setCursor) + .toHaveBeenCalledWith(1, 3, {scroll: false}); + expect(myEditor.centerOnCursor).toHaveBeenCalled(); + + myEditor._codeMirror.setCursor.calls.reset(); + myEditor.centerOnCursor.calls.reset(); + myEditor.setCursorPos(0, 2, false); + + expect(myEditor._codeMirror.setCursor) + .toHaveBeenCalledWith(0, 2, undefined); + expect(myEditor.centerOnCursor).not.toHaveBeenCalled(); + }); + it("should replace an existing single cursor", function () { myEditor._codeMirror.setCursor(0, 2); myEditor.setCursorPos(1, 3); @@ -2525,6 +2553,19 @@ define(function (require, exports, module) { expect(myEditor.getHistory().undone.length).toBe(0); }); + it("should not add orphan history when committing an unchanged live hint", function () { + const initialHistoryLength = myEditor.getHistory().done.length; + myEditor.createHistoryRestorePoint("liveHint"); + myEditor.setSelection({line: 0, ch: 0}, {line: 0, ch: 5}); + myEditor.replaceSelection("hello", "around", "liveHints"); + const liveHintHistoryLength = myEditor.getHistory().done.length; + + expect(liveHintHistoryLength).toBe(initialHistoryLength + 3); + myEditor.replaceSelection("hello", "end"); + expect(myEditor.getHistory().done.length).toBe(liveHintHistoryLength); + expect(myEditor.getSelectedText()).toBe(""); + }); + it("should be able to create a history restore point and restore to that point", function () { expect(myEditor.getHistory().done.length).toBe(1); expect(myEditor.getHistory().undone.length).toBe(0); diff --git a/test/spec/EditorSurfaceConformance-test.js b/test/spec/EditorSurfaceConformance-test.js new file mode 100644 index 0000000000..52f65a8122 --- /dev/null +++ b/test/spec/EditorSurfaceConformance-test.js @@ -0,0 +1,3881 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect, afterEach, awaitsFor, spyOn */ + +define(function (require, exports, module) { + + const CodeMirror = require("editor/CodeMirrorCompat"), + CM6 = require("thirdparty/CodeMirror6/codemirror6"), + DocumentModule = require("document/Document"), + Editor = require("editor/Editor").Editor, + SpecRunnerUtils = require("spec/SpecRunnerUtils"); + + const ENGINE_LABEL = "CodeMirror 6"; + const CM6_GUTTER_MARKER_WRAPPER_CLASS = "phoenix-cm6-gutter-marker-wrapper"; + const EVENT_NAMESPACE = ".editorSurfaceConformance"; + const LEGACY_VISIBLE_GUTTER_CLASS = "legacy-option-visible-gutter"; + const LEGACY_VIEWPORT_GUTTER_OPTION = "editorSurfaceLegacyViewportGutter"; + const LEGACY_VIEWPORT_MARKER_CLASS = "legacy-option-gutter-marker"; + const LINE_NUMBER_GUTTER = "CodeMirror-linenumbers"; + const TEST_GUTTER = "editor-surface-conformance-gutter"; + + CodeMirror.defineOption(LEGACY_VIEWPORT_GUTTER_OPTION, false, function (codeMirror, enabled) { + const previousRefresh = codeMirror.state.editorSurfaceLegacyViewportRefresh; + if (previousRefresh) { + codeMirror.off("viewportChange", previousRefresh); + delete codeMirror.state.editorSurfaceLegacyViewportRefresh; + } + + const gutterElement = codeMirror.getGutterElement(); + gutterElement.classList.remove(LEGACY_VISIBLE_GUTTER_CLASS); + codeMirror.clearGutter(TEST_GUTTER); + if (!enabled) { + return; + } + + gutterElement.classList.add(LEGACY_VISIBLE_GUTTER_CLASS); + const refresh = function () { + const viewport = codeMirror.getViewport(); + codeMirror.clearGutter(TEST_GUTTER); + codeMirror.operation(function () { + for (let line = viewport.from; line < viewport.to; line++) { + const marker = window.document.createElement("span"); + marker.className = LEGACY_VIEWPORT_MARKER_CLASS; + marker.dataset.line = String(line); + codeMirror.setGutterMarker(line, TEST_GUTTER, marker); + } + }); + }; + codeMirror.state.editorSurfaceLegacyViewportRefresh = refresh; + codeMirror.on("viewportChange", refresh); + refresh(); + }, true); + + function plainPosition(position) { + return { + line: position.line, + ch: position.ch + }; + } + + function comparableSelections(editor) { + return editor.getSelections().map(function (selection) { + return { + start: { + line: selection.start.line, + ch: selection.start.ch + }, + end: { + line: selection.end.line, + ch: selection.end.ch + }, + reversed: selection.reversed, + primary: selection.primary + }; + }); + } + + function tokenTypes(mode, text) { + const state = CodeMirror.startState(mode); + const stream = new CodeMirror.StringStream(text, 4); + const tokens = []; + while (!stream.eol()) { + stream.start = stream.pos; + let type; + for (let attempt = 0; attempt < 10; attempt++) { + type = mode.token(stream, state); + if (stream.pos > stream.start) { + break; + } + } + expect(stream.pos).toBeGreaterThan(stream.start); + tokens.push({ + string: stream.current(), + type: type, + state: CodeMirror.copyState(mode, state) + }); + } + return tokens; + } + + describe("Editor Surface Conformance", function () { + describe(ENGINE_LABEL, function () { + let editor; + let testDocument; + let secondaryEditor; + let secondaryHolder; + let standaloneCodeMirror; + let compatibilityStyle; + + function createEditor(content, languageId = "javascript") { + const mocks = SpecRunnerUtils.createMockEditor( + content, + languageId + ); + editor = mocks.editor; + testDocument = mocks.doc; + return mocks; + } + + function showEditor(width = 600, height = 180) { + const root = editor.getRootElement(); + root.parentElement.style.display = "block"; + root.parentElement.style.height = `${height}px`; + root.parentElement.style.left = "0"; + root.parentElement.style.top = "0"; + editor.setSize(width, height); + editor.refresh(); + return root; + } + + afterEach(function () { + DocumentModule.off(EVENT_NAMESPACE); + if (secondaryEditor) { + secondaryEditor.destroy(); + secondaryEditor = null; + } + if (standaloneCodeMirror) { + standaloneCodeMirror.destroy(); + standaloneCodeMirror = null; + } + if (secondaryHolder) { + secondaryHolder.remove(); + secondaryHolder = null; + } + if (compatibilityStyle) { + compatibilityStyle.remove(); + compatibilityStyle = null; + } + if (editor) { + SpecRunnerUtils.destroyMockEditor(testDocument); + editor = null; + testDocument = null; + } + if (Editor.isGutterRegistered(TEST_GUTTER)) { + Editor.unregisterGutter(TEST_GUTTER); + } + }); + + it("creates the CodeMirror 6 backend with a usable editor surface", function () { + createEditor("const answer = 42;\n"); + + const root = editor.getRootElement(); + const scroller = editor.getScrollerElement(); + const codeMirror = editor._codeMirror; + const input = codeMirror.getInputField(); + + expect(editor.getEditorEngine()).toBe("codemirror6"); + expect(editor.document.getText()).toBe("const answer = 42;\n"); + expect(codeMirror.getOption("inputStyle")).toBe("contenteditable"); + expect(input.getAttribute("contenteditable")).toBe("true"); + expect(root).toBeTruthy(); + expect(scroller).toBeTruthy(); + expect(root === scroller || root.contains(scroller)).toBe(true); + + expect(root.classList.contains("cm-editor")).toBe(true); + expect(root.dataset.editorEngine).toBe("codemirror6"); + expect(scroller.classList.contains("CodeMirror-scroll")).toBe(true); + expect(scroller.classList.contains("CodeMirror-lines")).toBe(true); + expect(scroller.querySelector(".CodeMirror-code")) + .toBe(editor._codeMirror.getLineSpaceElement()); + + showEditor(); + expect(window.getComputedStyle(scroller).pointerEvents).toBe("auto"); + expect(window.getComputedStyle(scroller).paddingTop).toBe("0px"); + const rootBounds = root.getBoundingClientRect(); + const scrollerBounds = scroller.getBoundingClientRect(); + expect(scroller.clientHeight).toBeGreaterThan(0); + expect(Math.abs(scrollerBounds.height - rootBounds.height)) + .toBeLessThan(2); + }); + + it("keeps legacy minimap and ruler DOM selectors safe", async function () { + createEditor( + "const first = 1;\n" + + "const longestEditorSurfaceLine = first + 2;\n" + + "const last = longestEditorSurfaceLine;\n" + ); + + const root = showEditor(); + const scroller = editor.getScrollerElement(); + await awaitsFor(function () { + const sizer = root.querySelector( + '[data-phoenix-cm6-legacy-proxy="sizer"]' + ); + return sizer && + parseFloat(sizer.style.height) > 0 && + parseFloat(sizer.style.width) > 0; + }, `${ENGINE_LABEL} legacy geometry proxy should be measured`); + + const sizer = root.querySelector( + '[data-phoenix-cm6-legacy-proxy="sizer"]' + ); + const lines = sizer.querySelector(".CodeMirror-lines"); + const measurement = root.querySelector(".CodeMirror pre"); + const verticalScrollbar = root.querySelector( + '[data-phoenix-cm6-legacy-proxy="vertical-scrollbar"]' + ); + + expect(root.querySelector(".CodeMirror-sizer")).toBe(sizer); + expect(lines).toBeTruthy(); + expect(Number.isFinite(parseFloat( + window.getComputedStyle(lines).paddingBottom + ))).toBe(true); + expect(measurement).toBeTruthy(); + expect(Number.isFinite(parseFloat( + window.getComputedStyle(measurement).paddingLeft + ))).toBe(true); + expect(verticalScrollbar).toBeTruthy(); + expect(verticalScrollbar).not.toBe(scroller); + expect(root.querySelectorAll(".CodeMirror-vscrollbar").length) + .toBe(1); + + verticalScrollbar.classList.add("minimap-scrollbar-hide"); + expect(scroller.classList.contains("minimap-scrollbar-hide")) + .toBe(false); + }); + + it("preserves legacy cursor and selection classes across CM6 redraws", async function () { + createEditor("alpha\nbeta\ngamma"); + const root = showEditor(); + compatibilityStyle = window.document.createElement("style"); + compatibilityStyle.textContent = [ + ".CodeMirror-cursor { --legacy-cursor-style: applied; }", + ".CodeMirror-selected { --legacy-selection-style: applied; }" + ].join("\n"); + window.document.head.appendChild(compatibilityStyle); + + editor.focus(); + editor.setCursorPos({ line: 0, ch: 1 }); + await awaitsFor(function () { + const cursor = root.querySelector(".cm-cursor"); + return cursor && + cursor.classList.contains("CodeMirror-cursor"); + }, `${ENGINE_LABEL} cursor should receive its legacy class`); + + let cursor = root.querySelector(".cm-cursor"); + expect(window.getComputedStyle(cursor) + .getPropertyValue("--legacy-cursor-style").trim()) + .toBe("applied"); + + editor.setCursorPos({ line: 1, ch: 2 }); + await awaitsFor(function () { + const currentCursors = Array.from( + root.querySelectorAll(".cm-cursor") + ); + return currentCursors.length > 0 && + currentCursors.every(function (element) { + return element.classList.contains("CodeMirror-cursor"); + }); + }, `${ENGINE_LABEL} redrawn cursors should retain the legacy class`); + + editor.setSelection( + { line: 0, ch: 1 }, + { line: 2, ch: 3 } + ); + await awaitsFor(function () { + const selections = Array.from( + root.querySelectorAll(".cm-selectionBackground") + ); + return selections.length > 0 && + selections.every(function (element) { + return element.classList.contains("CodeMirror-selected"); + }); + }, `${ENGINE_LABEL} selections should receive their legacy class`); + + const selection = root.querySelector(".cm-selectionBackground"); + expect(window.getComputedStyle(selection) + .getPropertyValue("--legacy-selection-style").trim()) + .toBe("applied"); + }); + + it("preserves the callable CodeMirror constructor for detached editors", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + let placedWrapper; + + standaloneCodeMirror = new CodeMirror(function (wrapper) { + placedWrapper = wrapper; + secondaryHolder.get(0).appendChild(wrapper); + }, { + value: "a { color: red; }", + mode: "css" + }); + + expect(standaloneCodeMirror.isCodeMirror6).toBe(true); + expect(placedWrapper).toBe(standaloneCodeMirror.getWrapperElement()); + expect(standaloneCodeMirror.getValue()).toBe("a { color: red; }"); + expect(standaloneCodeMirror.getTokenAt({ line: 0, ch: 6 }).type) + .toContain("property"); + }); + + it("maps legacy tab indentation widths to CM6 indent units", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + + standaloneCodeMirror = new CodeMirror(function (wrapper) { + secondaryHolder.get(0).appendChild(wrapper); + }, { + value: "\t\tconsole.log();", + mode: "javascript", + tabSize: 4, + indentUnit: 8, + indentWithTabs: true + }); + + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe("\t\t"); + standaloneCodeMirror.setOption("indentUnit", 6); + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe(" "); + standaloneCodeMirror.setOption("tabSize", 6); + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe("\t"); + standaloneCodeMirror.setOption("tabSize", 4); + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe(" "); + standaloneCodeMirror.setOption("indentUnit", 16); + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe("\t\t\t\t"); + standaloneCodeMirror.setOption("tabSize", 8); + expect(standaloneCodeMirror._view.state.facet(CM6.indentUnit)) + .toBe("\t\t"); + }); + + it("matches CM5 option handler and optionChange semantics", function () { + const optionName = "editorSurfaceOptionChangeParity"; + let handlerCalls = 0; + const changedOptions = []; + + CodeMirror.defineOption(optionName, 1, function () { + handlerCalls++; + }, true); + + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror(function (wrapper) { + secondaryHolder.get(0).appendChild(wrapper); + }, { + value: "option parity", + mode: "javascript" + }); + standaloneCodeMirror.on("optionChange", function (_codeMirror, changedOption) { + changedOptions.push(changedOption); + }); + + standaloneCodeMirror.setOption(optionName, 1); + standaloneCodeMirror.setOption(optionName, "1"); + expect(handlerCalls).toBe(0); + expect(changedOptions).toEqual([]); + expect(standaloneCodeMirror.getOption(optionName)).toBe(1); + + standaloneCodeMirror.setOption(optionName, 2); + expect(handlerCalls).toBe(1); + expect(changedOptions).toEqual([optionName]); + expect(standaloneCodeMirror.getOption(optionName)).toBe(2); + + standaloneCodeMirror.setOption("mode", "javascript"); + expect(changedOptions).toEqual([optionName, "mode"]); + }); + + it("requests one scroll for cursor and selection setters", function () { + createEditor("one\ntwo\nthree"); + + const codeMirror = editor._codeMirror; + spyOn(codeMirror, "scrollIntoView"); + + codeMirror.setCursor({ line: 1, ch: 1 }); + expect(codeMirror.scrollIntoView.calls.count()).toBe(1); + + codeMirror.scrollIntoView.calls.reset(); + codeMirror.setSelection( + { line: 0, ch: 1 }, + { line: 2, ch: 2 } + ); + expect(codeMirror.scrollIntoView.calls.count()).toBe(1); + + codeMirror.scrollIntoView.calls.reset(); + codeMirror.setCursor( + { line: 0, ch: 0 }, + { scroll: false } + ); + expect(codeMirror.scrollIntoView).not.toHaveBeenCalled(); + }); + + it("snapshots absolute scroll positions for pending CM6 layout changes", async function () { + createEditor( + Array.from({length: 80}, function (_value, index) { + return `line ${index}`; + }).join("\n") + ); + showEditor(300, 90); + + const codeMirror = editor._codeMirror; + const scrollSnapshot = spyOn( + codeMirror._view, + "scrollSnapshot" + ).and.callThrough(); + + codeMirror.scrollTo(null, 240); + + expect(scrollSnapshot.calls.count()).toBe(1); + await awaitsFor(function () { + return Math.abs(codeMirror.getScrollInfo().top - 240) < 1; + }, `${ENGINE_LABEL} absolute scroll should survive pending layout`); + }); + + it("honors CM5 auto-close bracket enablement and configurations", async function () { + createEditor("()"); + + const codeMirror = editor._codeMirror; + showEditor(); + codeMirror.focus(); + codeMirror.setCursor({ line: 0, ch: 1 }); + codeMirror.setOption("autoCloseBrackets", false); + codeMirror.getInputField().dispatchEvent( + new window.KeyboardEvent("keydown", { + bubbles: true, + cancelable: true, + code: "Backspace", + key: "Backspace", + keyCode: 8 + }) + ); + await awaitsFor(function () { + return codeMirror.getValue() === ")"; + }, `${ENGINE_LABEL} disabled auto-close should delete one bracket`); + + codeMirror.setOption("mode", null); + codeMirror.setValue("x"); + codeMirror.setCursor({ line: 0, ch: 0 }); + codeMirror.setOption("autoCloseBrackets", { + closeBefore: "", + override: true, + pairs: "<>" + }); + const blockedEvent = { + altKey: false, + charCode: "<".charCodeAt(0), + ctrlKey: false, + defaultPrevented: false, + keyCode: "<".charCodeAt(0), + metaKey: false, + preventDefault: function () { + this.defaultPrevented = true; + }, + shiftKey: false + }; + expect(codeMirror.triggerOnKeyPress(blockedEvent)).toBe(false); + expect(codeMirror.getValue()).toBe("x"); + + codeMirror.setOption("autoCloseBrackets", { + closeBefore: "x", + override: true, + pairs: "<>" + }); + expect(codeMirror.triggerOnKeyPress(blockedEvent)).toBe(true); + expect(codeMirror.getValue()).toBe("<>x"); + + codeMirror.setValue(""); + codeMirror.setCursor({ line: 0, ch: 0 }); + codeMirror.setOption("autoCloseBrackets", "()"); + const parenthesisEvent = Object.assign({}, blockedEvent, { + charCode: "(".charCodeAt(0), + defaultPrevented: false, + keyCode: "(".charCodeAt(0) + }); + expect(codeMirror.triggerOnKeyPress(parenthesisEvent)).toBe(true); + expect(codeMirror.getValue()).toBe("()"); + + codeMirror.setValue("{}"); + codeMirror.setCursor({ line: 0, ch: 1 }); + codeMirror.setOption("autoCloseBrackets", { + explode: "{}", + override: true, + pairs: "{}" + }); + const enterEvent = { + altKey: false, + ctrlKey: false, + defaultPrevented: false, + key: "Enter", + keyCode: 13, + metaKey: false, + preventDefault: function () { + this.defaultPrevented = true; + }, + shiftKey: false + }; + expect(codeMirror.triggerOnKeyDown(enterEvent)).toBe(true); + expect(codeMirror.getValue()).toBe("{\n\n}"); + expect(codeMirror.getCursor()).toEqual({ + line: 1, + ch: 0 + }); + }); + + it("supports arbitrary CM5 auto-close bracket pair mappings", function () { + createEditor("x"); + + const codeMirror = editor._codeMirror; + const keyPress = function (character) { + return { + altKey: false, + charCode: character.charCodeAt(0), + ctrlKey: false, + defaultPrevented: false, + keyCode: character.charCodeAt(0), + metaKey: false, + preventDefault: function () { + this.defaultPrevented = true; + }, + shiftKey: false + }; + }; + codeMirror.setOption("mode", null); + codeMirror.setOption("autoCloseBrackets", { + closeBefore: "x", + override: true, + pairs: "ab" + }); + codeMirror.setCursor({ line: 0, ch: 0 }); + + expect(codeMirror.triggerOnKeyPress(keyPress("a"))).toBe(true); + expect(codeMirror.getValue()).toBe("abx"); + expect(codeMirror.getCursor()).toEqual({ + line: 0, + ch: 1 + }); + + expect(codeMirror.triggerOnKeyPress(keyPress("b"))).toBe(true); + expect(codeMirror.getValue()).toBe("abx"); + expect(codeMirror.getCursor()).toEqual({ + line: 0, + ch: 2 + }); + + codeMirror.setValue("word"); + codeMirror.setSelection( + { line: 0, ch: 0 }, + { line: 0, ch: 4 } + ); + expect(codeMirror.triggerOnKeyPress(keyPress("a"))).toBe(true); + expect(codeMirror.getValue()).toBe("awordb"); + expect(codeMirror.getSelection()).toBe("word"); + + codeMirror.setValue("ab"); + codeMirror.setCursor({ line: 0, ch: 1 }); + const backspace = { + altKey: false, + ctrlKey: false, + defaultPrevented: false, + key: "Backspace", + keyCode: 8, + metaKey: false, + preventDefault: function () { + this.defaultPrevented = true; + }, + shiftKey: false + }; + expect(codeMirror.triggerOnKeyDown(backspace)).toBe(true); + expect(codeMirror.getValue()).toBe(""); + + codeMirror.setValue("ab"); + codeMirror.setCursor({ line: 0, ch: 1 }); + let keydownCount = 0; + const onKeydown = function () { + keydownCount++; + }; + codeMirror.on("keydown", onKeydown); + codeMirror.getInputField().dispatchEvent( + new window.KeyboardEvent("keydown", { + bubbles: true, + cancelable: true, + code: "Backspace", + key: "Backspace", + keyCode: 8 + }) + ); + codeMirror.off("keydown", onKeydown); + expect(codeMirror.getValue()).toBe(""); + expect(keydownCount).toBe(1); + }); + + it("uses CM5 bracket matching limits and option lifecycle", async function () { + createEditor("(\n)"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.focus(); + codeMirror.setCursor({ line: 0, ch: 1 }); + codeMirror.setOption("matchBrackets", { + maxScanLineLength: 50000, + maxScanLines: 1 + }); + expect(codeMirror.state.matchBrackets.maxScanLines).toBe(1); + expect(root.querySelector(".CodeMirror-matchingbracket")).toBeNull(); + + codeMirror.setOption("matchBrackets", { + maxScanLineLength: 50000, + maxScanLines: 2 + }); + await awaitsFor(function () { + return root.querySelectorAll( + ".CodeMirror-matchingbracket" + ).length === 2; + }, `${ENGINE_LABEL} bracket matcher should honor CM5 scan limits`); + + codeMirror.getInputField().blur(); + await awaitsFor(function () { + return !root.querySelector(".CodeMirror-matchingbracket"); + }, `${ENGINE_LABEL} bracket matcher should clear on blur`); + codeMirror.focus(); + await awaitsFor(function () { + return root.querySelectorAll( + ".CodeMirror-matchingbracket" + ).length === 2; + }, `${ENGINE_LABEL} bracket matcher should restore on focus`); + + codeMirror.setOption("matchBrackets", false); + expect(codeMirror.state.matchBrackets).toBeNull(); + await awaitsFor(function () { + return !root.querySelector(".CodeMirror-matchingbracket"); + }, `${ENGINE_LABEL} disabled bracket matcher should clear highlights`); + }); + + it("matches CM5 selection highlighting option semantics and class names", async function () { + createEditor("foo food foo\nbar bar"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.focus(); + codeMirror.setOption("highlightSelectionMatches", true); + codeMirror.setSelection( + { line: 0, ch: 0 }, + { line: 0, ch: 1 } + ); + expect(root.querySelector(".cm-matchhighlight")).toBeNull(); + + codeMirror.setSelection( + { line: 0, ch: 0 }, + { line: 0, ch: 3 } + ); + await awaitsFor(function () { + return root.querySelectorAll(".cm-matchhighlight").length === 3; + }, `${ENGINE_LABEL} selected text matches should use the legacy class`); + + codeMirror.setOption("highlightSelectionMatches", { + minChars: 1, + wordsOnly: true + }); + codeMirror.setSelection( + { line: 0, ch: 4 }, + { line: 0, ch: 7 } + ); + await awaitsFor(function () { + return !root.querySelector(".cm-matchhighlight"); + }, `${ENGINE_LABEL} wordsOnly should reject a partial word`); + + codeMirror.setOption("highlightSelectionMatches", { + showToken: true + }); + codeMirror.setCursor({ line: 1, ch: 1 }); + await awaitsFor(function () { + return root.querySelectorAll(".cm-matchhighlight").length === 2; + }, `${ENGINE_LABEL} showToken should highlight the token at the cursor`); + }); + + it("supports CM5 scrollbar annotations for selection matches", async function () { + const lines = []; + for (let line = 0; line < 60; line++) { + lines.push(line % 10 === 0 ? "needle" : `line ${line}`); + } + createEditor(lines.join("\n")); + + const root = showEditor(600, 100); + const codeMirror = editor._codeMirror; + const directAnnotation = codeMirror.showMatchesOnScrollbar( + "needle", + false, + {className: "direct-scrollbar-match"} + ); + expect(directAnnotation.matches.length).toBe(6); + await awaitsFor(function () { + return Boolean( + root.querySelector(".direct-scrollbar-match") + ); + }, `${ENGINE_LABEL} direct scrollbar annotations should render`); + + codeMirror.replaceRange( + "needle\n", + {line: 1, ch: 0} + ); + await awaitsFor(function () { + return directAnnotation.matches.length === 7; + }, `${ENGINE_LABEL} scrollbar searches should refresh after changes`); + directAnnotation.clear(); + expect(root.querySelector(".direct-scrollbar-match")).toBeNull(); + + codeMirror.focus(); + codeMirror.setOption("highlightSelectionMatches", { + annotateScrollbar: true, + delay: 0, + minChars: 1 + }); + codeMirror.setSelection( + {line: 0, ch: 0}, + {line: 0, ch: 6} + ); + await awaitsFor(function () { + return Boolean(root.querySelector( + ".CodeMirror-selection-highlight-scrollbar" + )); + }, `${ENGINE_LABEL} selection matches should annotate the scrollbar`); + + codeMirror.setOption("highlightSelectionMatches", false); + expect(root.querySelector( + ".CodeMirror-selection-highlight-scrollbar" + )).toBeNull(); + }); + + it("matches CM5 active-line rules for non-empty selections", async function () { + createEditor("alpha\nbeta"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.setOption("lineNumbers", true); + codeMirror.setOption("styleActiveLine", false); + codeMirror.setSelection( + { line: 0, ch: 0 }, + { line: 0, ch: 3 } + ); + codeMirror.setOption("styleActiveLine", true); + expect(root.querySelector(".cm-activeLine")).toBeNull(); + expect(root.querySelector(".cm-activeLineGutter")).toBeNull(); + + codeMirror.setOption("styleActiveLine", { + nonEmpty: true + }); + await awaitsFor(function () { + return Boolean( + root.querySelector(".cm-activeLine") && + root.querySelector(".cm-activeLineGutter") + ); + }, `${ENGINE_LABEL} same-line selections should activate line and gutter`); + expect(codeMirror.lineInfo(0).wrapClass) + .toContain("CodeMirror-activeline"); + + codeMirror.setSelection( + { line: 0, ch: 1 }, + { line: 1, ch: 1 } + ); + await awaitsFor(function () { + return !root.querySelector(".cm-activeLine") && + !root.querySelector(".cm-activeLineGutter"); + }, `${ENGINE_LABEL} multi-line selections should not activate a line`); + }); + + it("keeps deferred compatibility reads safe after destroy", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + + standaloneCodeMirror = new CodeMirror(function (wrapper) { + secondaryHolder.get(0).appendChild(wrapper); + }, { + value: "const answer = 42;", + mode: "javascript" + }); + const destroyedCodeMirror = standaloneCodeMirror; + destroyedCodeMirror.destroy(); + standaloneCodeMirror = null; + + expect(destroyedCodeMirror.lineCount()).toBe(0); + expect(destroyedCodeMirror.indexFromPos({ line: 0, ch: 1 })).toBe(0); + expect(destroyedCodeMirror.getTokenAt({ line: 0, ch: 1 }).type).toBeNull(); + expect(function () { + destroyedCodeMirror.getHelpers({ line: 0, ch: 0 }, "fold"); + destroyedCodeMirror.charCoords({ line: 0, ch: 0 }, "local"); + destroyedCodeMirror.getScrollInfo(); + }).not.toThrow(); + }); + + it("normalizes CM6 stream parsers to the CM5 mode contract", function () { + const javascriptMode = CodeMirror.getMode( + { indentUnit: 4 }, + "javascript" + ); + const javascriptTokens = tokenTypes( + javascriptMode, + "/pattern/; const fn = (local) => local;" + ); + expect(javascriptMode.lineComment).toBe("//"); + expect(javascriptMode.blockCommentStart).toBe("/*"); + expect(javascriptMode.blockCommentEnd).toBe("*/"); + expect(javascriptMode.helperType).toBe("javascript"); + expect(javascriptTokens.find(function (token) { + return token.string === "/pattern/"; + }).type).toBe("string-2"); + expect(javascriptTokens.filter(function (token) { + return token.string === "local"; + })[1].type).toBe("variable-2"); + + const cssMode = CodeMirror.getMode( + { indentUnit: 4 }, + "css" + ); + const cssTokens = tokenTypes( + cssMode, + "a { color: var(--accent); }" + ); + expect(cssMode.blockCommentStart).toBe("/*"); + expect(cssMode.blockCommentEnd).toBe("*/"); + expect(cssTokens.find(function (token) { + return token.string === "var"; + }).type).toBe("variable callee"); + expect(cssTokens.find(function (token) { + return token.string === "--accent"; + }).type).toBe("variable-2"); + + const xmlMode = CodeMirror.getMode( + { indentUnit: 4 }, + "application/xml" + ); + const xmlTokens = tokenTypes(xmlMode, ""); + expect(xmlMode.blockCommentStart).toBe(""); + expect(xmlTokens[0].type).toBe("tag bracket"); + expect(xmlTokens[1].type).toBe("tag"); + expect(xmlTokens[2].type).toBe("tag bracket"); + + const markdownMode = CodeMirror.getMode({}, "markdown"); + const markdownTokens = tokenTypes( + markdownMode, + "## Heading" + ); + expect(markdownMode.fold).toBe("markdown"); + expect(markdownMode.helperType).toBe("markdown"); + expect(markdownTokens[0].type).toContain("header"); + expect(markdownTokens[0].type).toContain("header-2"); + + const gfmMode = CodeMirror.getMode({}, "gfm"); + const gfmTokens = tokenTypes( + gfmMode, + "~~removed~~ https://example.com" + ); + expect(gfmTokens.some(function (token) { + return token.type === "strikethrough"; + })).toBe(true); + expect(gfmTokens.some(function (token) { + return token.type === "link"; + })).toBe(true); + }); + + it("preserves JSX, TSX, and embedded script token semantics", async function () { + const jsxMode = CodeMirror.getMode({}, "jsx"); + const jsxTokens = tokenTypes( + jsxMode, + "const view = Hi;" + ); + expect(jsxTokens.filter(function (token) { + return token.string === "Panel"; + }).every(function (token) { + return token.type === "tag"; + })).toBe(true); + expect(jsxTokens.find(function (token) { + return token.string === "title"; + }).type).toBe("attribute"); + + const tsxMode = CodeMirror.getMode({}, "text/typescript-jsx"); + const tsxTokens = tokenTypes( + tsxMode, + "interface Props { value: string }" + ); + expect(tsxTokens.find(function (token) { + return token.string === "interface"; + }).type).toBe("keyword"); + + const mixedMime = "text/x-editor-surface-html"; + CodeMirror.defineMIME(mixedMime, { + name: "htmlmixed", + scriptTypes: [{ + matches: /^text\/jsx$/i, + mode: "jsx" + }] + }); + const mixedMode = CodeMirror.getMode({}, mixedMime); + const mixedTokens = tokenTypes( + mixedMode, + "" + ); + expect(mixedTokens.find(function (token) { + return token.string === "Panel"; + }).type).toBe("tag"); + + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror(secondaryHolder.get(0), { + value: "", + mode: mixedMime + }); + standaloneCodeMirror.refresh(); + await awaitsFor(function () { + return Array.from( + standaloneCodeMirror.getWrapperElement() + .querySelectorAll(".cm-tag") + ).some(function (element) { + return element.textContent === "Panel"; + }); + }, `${ENGINE_LABEL} should render configured JSX script regions`); + }); + + it("preserves EJS and ERB embedded-language regions", async function () { + const ejsMode = CodeMirror.getMode({}, "application/x-ejs"); + const ejsTokens = tokenTypes( + ejsMode, + "
<% const total = 1; %><%= total %>
" + ); + const ejsTotal = ejsTokens.filter(function (token) { + return token.string === "total"; + }).pop(); + expect(ejsTotal.type).toContain("variable"); + expect(CodeMirror.innerMode( + ejsMode, + ejsTotal.state + ).mode.name).toBe("javascript"); + + const erbMode = CodeMirror.getMode({}, "application/x-erb"); + const erbTokens = tokenTypes( + erbMode, + "
<% if ready %>shown<% end %>
" + ); + const erbValue = erbTokens.find(function (token) { + return token.string === "ready"; + }); + expect(erbValue.type).toContain("variable"); + expect(CodeMirror.innerMode( + erbMode, + erbValue.state + ).mode.name).toBe("ruby"); + + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror(secondaryHolder.get(0), { + value: "
<% const total = 1; %>
", + mode: "application/x-ejs" + }); + standaloneCodeMirror.refresh(); + await awaitsFor(function () { + return Array.from( + standaloneCodeMirror.getWrapperElement() + .querySelectorAll(".cm-keyword") + ).some(function (element) { + return element.textContent === "const"; + }); + }, `${ENGINE_LABEL} should render EJS script regions`); + }); + + it("preserves PHP mixed-language regions on the CM6 surface", function () { + createEditor( + " stays PHP\";\n" + + "?>\n" + + "
html
\n" + + "\n" + + "\n", + "php" + ); + + function expectLanguageAt(position, modeName, languageId) { + editor.setCursorPos(position); + const mode = editor.getModeForSelection(); + expect(typeof mode === "string" ? mode : mode.name).toBe(modeName); + expect(editor.getLanguageForSelection().getId()).toBe(languageId); + } + + expect(editor.getEditorEngine()).toBe("codemirror6"); + expectLanguageAt({ line: 1, ch: 2 }, "clike", "php"); + expectLanguageAt({ line: 1, ch: 14 }, "clike", "php"); + expectLanguageAt({ line: 3, ch: 6 }, "html", "html"); + expectLanguageAt({ line: 4, ch: 5 }, "clike", "php"); + expectLanguageAt({ line: 5, ch: 5 }, "clike", "php"); + }); + + it("preserves CM5 token boundaries, parser state, and custom-mode rendering", async function () { + const modeName = "editor-surface-token-state"; + const observedStreamMethods = {}; + spyOn(window.console, "warn").and.callThrough(); + CodeMirror.defineMode(modeName, function () { + return { + startState: function () { + return { + blankLines: 0 + }; + }, + copyState: function (state) { + return { + blankLines: state.blankLines + }; + }, + token: function (stream) { + observedStreamMethods.hideFirstChars = + typeof stream.hideFirstChars === "function"; + observedStreamMethods.lookAhead = + typeof stream.lookAhead === "function"; + observedStreamMethods.baseToken = + typeof stream.baseToken === "function"; + stream.skipToEnd(); + return stream.string === "alpha" ? + "editor-surface-custom-token keyword" : + "keyword"; + }, + blankLine: function (state) { + state.blankLines++; + } + }; + }); + + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror(secondaryHolder.get(0), { + value: "alpha\n\nomega", + mode: modeName + }); + + const lineStartToken = standaloneCodeMirror.getTokenAt({ + line: 0, + ch: 0 + }, true); + expect(lineStartToken.start).toBe(0); + expect(lineStartToken.end).toBe(0); + expect(lineStartToken.string).toBe(""); + expect(lineStartToken.type).toBeNull(); + expect(standaloneCodeMirror.getTokenTypeAt({ + line: 0, + ch: 0 + })).toBe("editor-surface-custom-token keyword"); + + expect(standaloneCodeMirror.getLineTokens(1, true)).toEqual([]); + expect(standaloneCodeMirror.getTokenTypeAt({ + line: 1, + ch: 0 + })).toBeNull(); + expect(standaloneCodeMirror.getStateAfter(1, true).blankLines).toBe(1); + expect(standaloneCodeMirror.getStateBefore(2, true).blankLines).toBe(1); + expect(standaloneCodeMirror.getTokenAt({ + line: 2, + ch: 0 + }, true).state.blankLines).toBe(1); + + standaloneCodeMirror.refresh(); + await awaitsFor(function () { + const wrapper = + standaloneCodeMirror.getWrapperElement(); + return Boolean(wrapper.querySelector(".cm-keyword")) && + Boolean(wrapper.querySelector( + ".cm-editor-surface-custom-token" + )); + }, `${ENGINE_LABEL} custom stream mode should render syntax highlighting`); + expect(observedStreamMethods).toEqual({ + hideFirstChars: true, + lookAhead: true, + baseToken: true + }); + expect(window.console.warn).not.toHaveBeenCalledWith( + "Unknown highlighting tag editor-surface-custom-token" + ); + }); + + it("supports lookAhead and incrementally retreats the legacy mode cache", function () { + const modeName = "editor-surface-look-ahead-cache"; + CodeMirror.defineMode(modeName, function () { + return { + startState: function () { + return { + nextLine: null + }; + }, + copyState: function (state) { + return { + nextLine: state.nextLine + }; + }, + token: function (stream, state) { + if (stream.sol()) { + state.nextLine = stream.lookAhead(1); + } + stream.skipToEnd(); + return state.nextLine === "sentinel" ? + "keyword" : + null; + } + }; + }); + + const lines = ["before", "sentinel"]; + for (let line = 2; line < 80; line++) { + lines.push(`line ${line}`); + } + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: lines.join("\n"), + mode: modeName + } + ); + + expect(standaloneCodeMirror.getTokenAt({ + line: 0, + ch: 1 + }, true).type).toBe("keyword"); + + const lastLine = lines.length - 1; + standaloneCodeMirror.getTokenAt({ + line: lastLine, + ch: 1 + }, true); + const warmParseCount = + standaloneCodeMirror._legacyModeParseCount; + standaloneCodeMirror.getTokenAt({ + line: lastLine, + ch: 1 + }, true); + expect(standaloneCodeMirror._legacyModeParseCount) + .toBe(warmParseCount); + + standaloneCodeMirror.replaceRange( + "changed tail", + { line: lastLine - 1, ch: 0 }, + { line: lastLine - 1, ch: lines[lastLine - 1].length }, + "+cache-retreat" + ); + const beforeIncrementalParse = + standaloneCodeMirror._legacyModeParseCount; + standaloneCodeMirror.getTokenAt({ + line: lastLine, + ch: 1 + }, true); + expect( + standaloneCodeMirror._legacyModeParseCount - + beforeIncrementalParse + ).toBe(1); + }); + + it("exposes Markdown tokens and block state to legacy extensions", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror(secondaryHolder.get(0), { + value: "# Heading\n```js\nconst value = 1;\n```\n", + mode: "markdown" + }); + + expect(standaloneCodeMirror.getTokenTypeAt({ + line: 0, + ch: 0 + })).toContain("header"); + expect(standaloneCodeMirror.getTokenAt({ + line: 2, + ch: 5 + }, true).type).toBe("keyword"); + expect(standaloneCodeMirror.getStateAfter( + 1, + true + ).fencedCode).toBe(true); + expect(standaloneCodeMirror.getStateAfter( + 3, + true + ).fencedCode).toBe(false); + }); + + it("provides CodeMirror-compatible HTML tag matching for extensions", function () { + createEditor("
text
", "html"); + + const openingMatch = CodeMirror.findMatchingTag( + editor._codeMirror, + { line: 0, ch: 8 } + ); + expect(openingMatch.at).toBe("open"); + expect(openingMatch.open).toEqual({ + tag: "section", + from: { line: 0, ch: 6 }, + to: { line: 0, ch: 15 } + }); + expect(openingMatch.close).toEqual({ + tag: "section", + from: { line: 0, ch: 19 }, + to: { line: 0, ch: 29 } + }); + + const closingMatch = CodeMirror.findMatchingTag( + editor._codeMirror, + { line: 0, ch: 21 } + ); + expect(closingMatch.at).toBe("close"); + expect(closingMatch.open).toEqual(openingMatch.open); + expect(closingMatch.close).toEqual(openingMatch.close); + + expect(CodeMirror.findMatchingTag( + editor._codeMirror, + { line: 0, ch: 16 } + )).toBeUndefined(); + }); + + it("preserves change payloads and synchronous notification order", function () { + createEditor("abc\ndef"); + + const eventOrder = []; + let observedChanges; + editor.on(`editorChange${EVENT_NAMESPACE}`, function (_event, changedEditor, changeList) { + eventOrder.push("editorChange"); + expect(changedEditor).toBe(editor); + observedChanges = changeList; + }); + testDocument.on(`change${EVENT_NAMESPACE}`, function (_event, changedDocument) { + eventOrder.push("document.change"); + expect(changedDocument).toBe(testDocument); + }); + DocumentModule.on(`documentChange${EVENT_NAMESPACE}`, function (_event, changedDocument) { + if (changedDocument === testDocument) { + eventOrder.push("Document.documentChange"); + } + }); + + editor.replaceRange("X", { line: 0, ch: 1 }, { line: 0, ch: 2 }, "+input"); + + expect(editor.document.getText()).toBe("aXc\ndef"); + expect(eventOrder).toEqual([ + "editorChange", + "document.change", + "Document.documentChange" + ]); + expect(observedChanges.length).toBe(1); + expect(observedChanges[0].from).toEqual({ line: 0, ch: 1 }); + expect(observedChanges[0].to).toEqual({ line: 0, ch: 2 }); + expect(observedChanges[0].text).toEqual(["X"]); + expect(observedChanges[0].removed).toEqual(["b"]); + expect(observedChanges[0].origin).toBe("+input"); + }); + + it("keeps rendering stable when a document replacement removes lines", function () { + createEditor([ + " first line", + " second line", + " third line", + " fourth line" + ].join("\n")); + + expect(function () { + testDocument.setText(" replacement"); + }).not.toThrow(); + expect(editor.document.getText()).toBe(" replacement"); + expect(editor.lineCount()).toBe(1); + }); + + it("batches public edit operations into one ordered change list", function () { + createEditor("abc\ndef"); + + const changeLists = []; + editor.on(`editorChange${EVENT_NAMESPACE}`, function (_event, _changedEditor, changeList) { + changeLists.push(changeList); + }); + + editor.operation(function () { + editor.replaceRange("X", { line: 1, ch: 1 }, { line: 1, ch: 2 }, "+input"); + editor.replaceRange("Y", { line: 0, ch: 1 }, { line: 0, ch: 2 }, "+input"); + }); + + expect(editor.document.getText()).toBe("aYc\ndXf"); + expect(changeLists.length).toBe(1); + expect(changeLists[0].map(function (change) { + return change.from.line; + })).toEqual([1, 0]); + }); + + it("emits per-edit CM5 change events before one aggregated changes event", function () { + createEditor("abc\ndef"); + + const codeMirror = editor._codeMirror; + const firstLine = codeMirror.getLineHandle(0); + const secondLine = codeMirror.getLineHandle(1); + const order = []; + const lineChanges = []; + const documentChanges = []; + const editorChanges = []; + let aggregatedChanges; + + CodeMirror.on(firstLine, "change", function (_line, change) { + order.push("line.0"); + lineChanges.push(change); + }); + CodeMirror.on(secondLine, "change", function (_line, change) { + order.push("line.1"); + lineChanges.push(change); + }); + codeMirror.getDoc().on("change", function (_doc, change) { + order.push(`doc.${change.from.line}`); + documentChanges.push(change); + }); + codeMirror.on("change", function (_instance, change) { + order.push(`editor.${change.from.line}`); + editorChanges.push(change); + }); + codeMirror.on("changes", function (_instance, changes) { + order.push("changes"); + aggregatedChanges = changes; + }); + + codeMirror.operation(function () { + codeMirror.replaceRange( + "X", + { line: 1, ch: 1 }, + { line: 1, ch: 2 }, + "+operation-order" + ); + codeMirror.replaceRange( + "Y", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "+operation-order" + ); + }); + + expect(order).toEqual([ + "line.1", + "doc.1", + "editor.1", + "line.0", + "doc.0", + "editor.0", + "changes" + ]); + expect(lineChanges[0]).toBe(documentChanges[0]); + expect(lineChanges[1]).toBe(documentChanges[1]); + expect(editorChanges[0]).not.toBe(documentChanges[0]); + expect(editorChanges[1]).not.toBe(documentChanges[1]); + expect(editorChanges[0].next).toBeUndefined(); + expect(editorChanges[1].next).toBeUndefined(); + expect(aggregatedChanges[0]).toBe(editorChanges[0]); + expect(aggregatedChanges[1]).toBe(editorChanges[1]); + }); + + it("preserves multiple selections and descending multi-edit changes", function () { + createEditor("abcd\nefgh\nijkl"); + + let observedChanges; + editor.on(`editorChange${EVENT_NAMESPACE}`, function (_event, _changedEditor, changeList) { + observedChanges = changeList; + }); + + editor.setSelections([ + { + start: { line: 0, ch: 1 }, + end: { line: 0, ch: 3 }, + reversed: true + }, + { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 3 }, + primary: true + }, + { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 3 } + } + ]); + + expect(comparableSelections(editor)).toEqual([ + { + start: { line: 0, ch: 1 }, + end: { line: 0, ch: 3 }, + reversed: true, + primary: false + }, + { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 3 }, + reversed: false, + primary: true + }, + { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 3 }, + reversed: false, + primary: false + } + ]); + + editor.replaceSelections(["X", "Y", "Z"], "around"); + + expect(editor.document.getText()).toBe("aXd\neYh\niZl"); + expect(observedChanges.map(function (change) { + return change.from.line; + })).toEqual([2, 1, 0]); + expect(comparableSelections(editor)).toEqual([ + { + start: { line: 0, ch: 1 }, + end: { line: 0, ch: 2 }, + reversed: true, + primary: false + }, + { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 2 }, + reversed: false, + primary: true + }, + { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 2 }, + reversed: false, + primary: false + } + ]); + }); + + it("tracks clean state through undo and redo", function () { + createEditor("abc"); + + expect(editor.isClean()).toBe(true); + expect(testDocument.isDirty).toBe(false); + + editor.replaceRange("X", { line: 0, ch: 1 }, { line: 0, ch: 2 }, "+input"); + expect(editor.document.getText()).toBe("aXc"); + expect(editor.isClean()).toBe(false); + expect(testDocument.isDirty).toBe(true); + + editor.undo(); + expect(editor.document.getText()).toBe("abc"); + expect(editor.isClean()).toBe(true); + expect(testDocument.isDirty).toBe(false); + + editor.redo(); + expect(editor.document.getText()).toBe("aXc"); + expect(editor.isClean()).toBe(false); + expect(testDocument.isDirty).toBe(true); + }); + + it("treats identical setValue calls as CM5-compatible changes", async function () { + const content = Array.from({ length: 40 }, function (_value, index) { + return `line ${index}: ${"x".repeat(160)}`; + }).join("\n"); + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "240px", height: "90px" }); + standaloneCodeMirror = new CodeMirror(function (wrapper) { + secondaryHolder.get(0).appendChild(wrapper); + }, { + value: content, + mode: "javascript" + }); + standaloneCodeMirror.setSize(240, 90); + standaloneCodeMirror.refresh(); + + const codeMirror = standaloneCodeMirror; + const originalSelection = { + anchor: { line: 20, ch: 12 }, + head: { line: 21, ch: 24 } + }; + codeMirror.setSelection(originalSelection.anchor, originalSelection.head); + codeMirror.scrollTo(120, 240); + await awaitsFor(function () { + const scrollInfo = codeMirror.getScrollInfo(); + return scrollInfo.left > 0 && scrollInfo.top > 0; + }, `${ENGINE_LABEL} editor should scroll before setValue parity checks`); + + codeMirror.clearHistory(); + const cleanGeneration = codeMirror.markClean(); + const firstLineHandle = codeMirror.getLineHandle(0); + const middleLineHandle = codeMirror.getLineHandle(20); + const lastLineHandle = codeMirror.getLineHandle(39); + const lineHandleDeletes = []; + [firstLineHandle, middleLineHandle, lastLineHandle] + .forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + lineHandleDeletes.push(index); + }); + }); + const marker = codeMirror.markText( + { line: 10, ch: 2 }, + { line: 11, ch: 8 } + ); + const bookmark = codeMirror.setBookmark({ line: 25, ch: 4 }); + const eventOrder = []; + const beforeChanges = []; + const beforeSelections = []; + const changes = []; + const changeLists = []; + marker.on("hide", function () { + eventOrder.push("marker.hide"); + }); + marker.on("unhide", function () { + eventOrder.push("marker.unhide"); + }); + bookmark.on("hide", function () { + eventOrder.push("bookmark.hide"); + }); + bookmark.on("unhide", function () { + eventOrder.push("bookmark.unhide"); + }); + + codeMirror.on("beforeChange", function (instance, change) { + expect(instance).toBe(codeMirror); + eventOrder.push("beforeChange"); + beforeChanges.push(change); + }); + codeMirror.on("beforeSelectionChange", function (instance, selection) { + expect(instance).toBe(codeMirror); + eventOrder.push("beforeSelectionChange"); + beforeSelections.push(selection.ranges.map(function (range) { + return { + anchor: plainPosition(range.anchor), + head: plainPosition(range.head) + }; + })); + }); + codeMirror.on("change", function (instance, change) { + expect(instance).toBe(codeMirror); + eventOrder.push("change"); + changes.push(change); + }); + codeMirror.on("cursorActivity", function (instance) { + expect(instance).toBe(codeMirror); + eventOrder.push("cursorActivity"); + }); + codeMirror.on("changes", function (instance, changeList) { + expect(instance).toBe(codeMirror); + eventOrder.push("changes"); + changeLists.push(changeList); + }); + codeMirror.on("update", function (instance) { + expect(instance).toBe(codeMirror); + eventOrder.push("update"); + }); + + codeMirror.setValue(content); + + const expectedChange = { + from: { line: 0, ch: 0 }, + to: { line: 39, ch: content.split("\n")[39].length }, + text: content.split("\n"), + removed: content.split("\n"), + origin: "setValue" + }; + function comparableChange(change) { + return { + from: plainPosition(change.from), + to: plainPosition(change.to), + text: change.text, + removed: change.removed, + origin: change.origin + }; + } + + expect(eventOrder.filter(function (eventName) { + return eventName.indexOf(".") === -1; + })).toEqual([ + "beforeChange", + "beforeSelectionChange", + "beforeSelectionChange", + "change", + "cursorActivity", + "changes", + "update" + ]); + expect(eventOrder.indexOf("marker.hide")) + .toBeGreaterThan(eventOrder.indexOf("cursorActivity")); + expect(eventOrder.indexOf("bookmark.hide")) + .toBeGreaterThan(eventOrder.indexOf("cursorActivity")); + expect(eventOrder.indexOf("marker.hide")) + .toBeLessThan(eventOrder.indexOf("changes")); + expect(eventOrder.indexOf("bookmark.hide")) + .toBeLessThan(eventOrder.indexOf("changes")); + expect(beforeChanges.length).toBe(1); + expect(beforeSelections).toEqual([ + [{ + anchor: expectedChange.to, + head: expectedChange.to + }], + [{ + anchor: { line: 0, ch: 0 }, + head: { line: 0, ch: 0 } + }] + ]); + expect(changes.length).toBe(1); + expect(changeLists.length).toBe(1); + expect(changeLists[0].length).toBe(1); + expect(comparableChange(beforeChanges[0])).toEqual(expectedChange); + expect(comparableChange(changes[0])).toEqual(expectedChange); + expect(comparableChange(changeLists[0][0])).toEqual(expectedChange); + expect(codeMirror.getValue()).toBe(content); + expect(plainPosition(codeMirror.getCursor())).toEqual({ + line: 0, + ch: 0 + }); + expect(codeMirror.getScrollInfo().left).toBe(0); + expect(codeMirror.getScrollInfo().top).toBe(0); + expect(codeMirror.changeGeneration()).not.toBe(cleanGeneration); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + expect(codeMirror.isClean()).toBe(false); + expect(codeMirror.getLineHandle(0)).toBe(firstLineHandle); + expect(codeMirror.getLineNumber(middleLineHandle)).toBeNull(); + expect(codeMirror.getLineHandle(39)).toBe(lastLineHandle); + expect(lineHandleDeletes).toEqual([1]); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + + eventOrder.length = 0; + codeMirror.undo(); + + expect(eventOrder.filter(function (eventName) { + return eventName.indexOf(".") === -1; + })).toEqual([ + "beforeChange", + "beforeSelectionChange", + "change", + "cursorActivity", + "changes", + "update" + ]); + expect(changes[1].origin).toBe("undo"); + expect(codeMirror.getValue()).toBe(content); + expect(codeMirror.isClean()).toBe(true); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 1 }); + expect(plainPosition(codeMirror.getCursor("anchor"))) + .toEqual(originalSelection.anchor); + expect(plainPosition(codeMirror.getCursor("head"))) + .toEqual(originalSelection.head); + expect(marker.find()).toEqual({ + from: { line: 10, ch: 2 }, + to: { line: 11, ch: 8 } + }); + expect(plainPosition(bookmark.find())).toEqual({ + line: 25, + ch: 4 + }); + + eventOrder.length = 0; + codeMirror.redo(); + + expect(eventOrder.filter(function (eventName) { + return eventName.indexOf(".") === -1; + })).toEqual([ + "beforeChange", + "beforeSelectionChange", + "change", + "cursorActivity", + "changes", + "update" + ]); + expect(changes[2].origin).toBe("redo"); + expect(codeMirror.getValue()).toBe(content); + expect(codeMirror.isClean()).toBe(false); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + expect(plainPosition(codeMirror.getCursor())).toEqual({ + line: 0, + ch: 0 + }); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + }); + + it("keeps identical setValue edits visible to Document dirty tracking", function () { + createEditor("dirty document"); + + const codeMirror = editor._codeMirror; + codeMirror.clearHistory(); + const cleanGeneration = codeMirror.markClean(); + + codeMirror.setValue("dirty document"); + + expect(codeMirror.changeGeneration()).not.toBe(cleanGeneration); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + expect(editor.isClean()).toBe(false); + expect(testDocument.isDirty).toBe(true); + + codeMirror.undo(); + expect(editor.isClean()).toBe(true); + expect(testDocument.isDirty).toBe(false); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 1 }); + + codeMirror.redo(); + expect(editor.isClean()).toBe(false); + expect(testDocument.isDirty).toBe(true); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + }); + + it("keeps empty and cancelled setValue calls as CM5 no-ops", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "", + mode: "javascript" + } + ); + + const codeMirror = standaloneCodeMirror; + const events = []; + const recordBeforeChange = function () { + events.push("beforeChange"); + }; + codeMirror.on("beforeChange", recordBeforeChange); + codeMirror.on("beforeSelectionChange", function () { + events.push("beforeSelectionChange"); + }); + codeMirror.on("change", function () { + events.push("change"); + }); + codeMirror.on("cursorActivity", function () { + events.push("cursorActivity"); + }); + codeMirror.on("changes", function () { + events.push("changes"); + }); + codeMirror.on("update", function () { + events.push("update"); + }); + + codeMirror.clearHistory(); + codeMirror.setValue(""); + + expect(events).toEqual([ + "beforeChange", + "beforeSelectionChange" + ]); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 0 }); + + events.length = 0; + codeMirror.off("beforeChange", recordBeforeChange); + codeMirror.on("beforeChange", function (_instance, change) { + events.push("beforeChange"); + change.cancel(); + }); + codeMirror.setValue(""); + + expect(events).toEqual([ + "beforeChange", + "beforeSelectionChange" + ]); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 0 }); + }); + + it("matches cancelled setValue selection and scroll update events", async function () { + const content = Array.from({ length: 40 }, function (_value, index) { + return `line ${index}: ${"x".repeat(160)}`; + }).join("\n"); + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "240px", height: "90px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: content, + mode: "javascript" + } + ); + standaloneCodeMirror.setSize(240, 90); + standaloneCodeMirror.refresh(); + + const codeMirror = standaloneCodeMirror; + const events = []; + codeMirror.on("beforeChange", function (_instance, change) { + events.push("beforeChange"); + change.cancel(); + }); + codeMirror.on("beforeSelectionChange", function () { + events.push("beforeSelectionChange"); + }); + codeMirror.on("change", function () { + events.push("change"); + }); + codeMirror.on("cursorActivity", function () { + events.push("cursorActivity"); + }); + codeMirror.on("changes", function () { + events.push("changes"); + }); + codeMirror.on("update", function () { + events.push("update"); + }); + codeMirror.on("scroll", function () { + events.push("scroll"); + }); + + codeMirror.setCursor({ line: 1, ch: 2 }); + events.length = 0; + codeMirror.clearHistory(); + codeMirror.setValue("cancelled replacement"); + + expect(events).toEqual([ + "beforeChange", + "beforeSelectionChange", + "cursorActivity" + ]); + expect(codeMirror.getValue()).toBe(content); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 0 }); + + codeMirror.setCursor({ line: 20, ch: 30 }); + codeMirror.scrollTo(120, 240); + await awaitsFor(function () { + const scrollInfo = codeMirror.getScrollInfo(); + return scrollInfo.left > 0 && + scrollInfo.top > 0 && + events.indexOf("scroll") !== -1; + }, `${ENGINE_LABEL} editor should scroll before cancelled setValue`); + + events.length = 0; + codeMirror.setValue("cancelled replacement"); + await awaitsFor(function () { + const scrollInfo = codeMirror.getScrollInfo(); + return scrollInfo.left === 0 && + scrollInfo.top === 0 && + events.indexOf("scroll") !== -1; + }, `${ENGINE_LABEL} cancelled setValue should reset scroll`); + + expect(events.filter(function (eventName) { + return eventName !== "scroll"; + })).toEqual([ + "beforeChange", + "beforeSelectionChange", + "cursorActivity" + ]); + expect(codeMirror.getValue()).toBe(content); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 0 }); + }); + + it("emits legacy updates only for visible CM5 update operations", async function () { + createEditor(Array.from({ length: 120 }, function (_value, index) { + return `line ${index}: ${"x".repeat(80)}`; + }).join("\n")); + showEditor(240, 90); + + const codeMirror = editor._codeMirror; + const updates = []; + codeMirror.on("update", function () { + updates.push("update"); + }); + await awaitsFor(function () { + const scrollInfo = codeMirror.getScrollInfo(); + return scrollInfo.clientHeight > 0 && + scrollInfo.height > scrollInfo.clientHeight; + }, `${ENGINE_LABEL} editor should have scrollable content`); + + const internalEffect = CM6.StateEffect["define"](); + codeMirror._view.dispatch({ + effects: internalEffect.of("internal") + }); + expect(updates).toEqual([]); + + codeMirror.scrollIntoView({ line: 0, ch: 0 }); + expect(updates).toEqual([]); + + codeMirror.scrollIntoView({ line: 119, ch: 80 }); + expect(updates).toEqual(["update"]); + + updates.length = 0; + codeMirror.setOption( + "lineWrapping", + !codeMirror.getOption("lineWrapping") + ); + expect(updates).toEqual(["update"]); + + updates.length = 0; + codeMirror.setOption( + "readOnly", + !codeMirror.getOption("readOnly") + ); + expect(updates).toEqual([]); + }); + + it("applies CM5 input attributes, placeholders, autofocus, and nocursor", async function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "", + mode: "javascript", + spellcheck: true, + autocorrect: true, + autocapitalize: true, + placeholder: "Start typing", + autofocus: true, + inputStyle: "textarea" + } + ); + + const codeMirror = standaloneCodeMirror; + const input = codeMirror.getInputField(); + const wrapper = codeMirror.getWrapperElement(); + expect(codeMirror.getOption("inputStyle")).toBe("contenteditable"); + expect(codeMirror.display.wrapper).toBe(wrapper); + expect(codeMirror.display.sizer) + .toBe(codeMirror.getLineSpaceElement()); + expect(input.getAttribute("spellcheck")).toBe("true"); + expect(input.getAttribute("autocorrect")).toBe("on"); + expect(input.getAttribute("autocapitalize")).toBe("on"); + expect(wrapper.classList.contains("CodeMirror-empty")).toBe(true); + expect(wrapper.querySelector(".CodeMirror-placeholder").textContent) + .toBe("Start typing"); + await awaitsFor(function () { + return codeMirror.hasFocus(); + }, `${ENGINE_LABEL} autofocus should focus the editor`); + + codeMirror.setValue("value"); + expect(wrapper.classList.contains("CodeMirror-empty")).toBe(false); + expect(wrapper.querySelector(".CodeMirror-placeholder")).toBeNull(); + + codeMirror.setOption("spellcheck", false); + codeMirror.setOption("autocorrect", false); + codeMirror.setOption("autocapitalize", false); + expect(input.getAttribute("spellcheck")).toBe("false"); + expect(input.getAttribute("autocorrect")).toBe("off"); + expect(input.getAttribute("autocapitalize")).toBe("off"); + + codeMirror.setOption("readOnly", true); + codeMirror.focus(); + await awaitsFor(function () { + return codeMirror.hasFocus(); + }, `${ENGINE_LABEL} readOnly editor should remain focusable`); + + codeMirror.setOption("readOnly", "nocursor"); + expect(codeMirror.hasFocus()).toBe(false); + expect(input.getAttribute("contenteditable")).toBe("false"); + codeMirror.focus(); + expect(codeMirror.hasFocus()).toBe(false); + + codeMirror.setOption("readOnly", false); + codeMirror.focus(); + await awaitsFor(function () { + return codeMirror.hasFocus(); + }, `${ENGINE_LABEL} editor should refocus after nocursor is cleared`); + expect(input.getAttribute("contenteditable")).toBe("true"); + }); + + it("tracks the longest document line through the legacy display facade", function () { + createEditor("tiny\nlongest-line\nmid"); + + const codeMirror = editor._codeMirror; + const sizer = codeMirror.display.sizer; + expect(sizer).toBe(codeMirror.getLineSpaceElement()); + expect(codeMirror.display.maxLineLength).toBe(12); + + codeMirror.replaceRange("-extended", { line: 0, ch: 4 }); + expect(codeMirror.display.sizer).toBe(sizer); + expect(codeMirror.display.maxLineLength).toBe(13); + + codeMirror.replaceRange( + "x", + { line: 0, ch: 0 }, + { line: 2, ch: 3 } + ); + expect(codeMirror.display.maxLineLength).toBe(1); + }); + + it("emits attached document events once with CM5 argument ordering", function () { + createEditor("abc\ndef"); + + const codeMirror = editor._codeMirror; + const compatDoc = codeMirror.getDoc(); + const eventOrder = []; + let documentBeforeChange; + let editorBeforeChange; + let documentBeforeSelection; + let editorBeforeSelection; + let documentChange; + let editorChange; + let historyAddedCount = 0; + let documentCursorActivityCount = 0; + let editorCursorActivityCount = 0; + + compatDoc.on("beforeChange", function (doc, change) { + expect(doc).toBe(compatDoc); + documentBeforeChange = change; + eventOrder.push("doc.beforeChange"); + }); + codeMirror.on("beforeChange", function (instance, change) { + expect(instance).toBe(codeMirror); + editorBeforeChange = change; + eventOrder.push("editor.beforeChange"); + }); + compatDoc.on("beforeSelectionChange", function (doc, selection) { + expect(doc).toBe(compatDoc); + documentBeforeSelection = selection; + eventOrder.push("doc.beforeSelectionChange"); + }); + codeMirror.on("beforeSelectionChange", function (instance, selection) { + expect(instance).toBe(codeMirror); + editorBeforeSelection = selection; + eventOrder.push("editor.beforeSelectionChange"); + }); + compatDoc.on("historyAdded", function () { + historyAddedCount++; + eventOrder.push("doc.historyAdded"); + }); + compatDoc.on("change", function (doc, change) { + expect(doc).toBe(compatDoc); + documentChange = change; + eventOrder.push("doc.change"); + }); + codeMirror.on("change", function (instance, change) { + expect(instance).toBe(codeMirror); + editorChange = change; + eventOrder.push("editor.change"); + }); + compatDoc.on("cursorActivity", function (doc) { + expect(doc).toBe(compatDoc); + documentCursorActivityCount++; + }); + codeMirror.on("cursorActivity", function () { + editorCursorActivityCount++; + }); + + codeMirror.clearHistory(); + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "+doc-events" + ); + + expect(eventOrder.indexOf("doc.beforeChange")) + .toBeLessThan(eventOrder.indexOf("editor.beforeChange")); + expect(eventOrder.indexOf("doc.beforeSelectionChange")) + .toBeLessThan(eventOrder.indexOf("editor.beforeSelectionChange")); + expect(eventOrder.indexOf("editor.beforeChange")) + .toBeLessThan(eventOrder.indexOf("doc.historyAdded")); + expect(eventOrder.indexOf("doc.historyAdded")) + .toBeLessThan(eventOrder.indexOf("doc.beforeSelectionChange")); + expect(eventOrder.indexOf("doc.change")) + .toBeLessThan(eventOrder.indexOf("editor.change")); + expect(documentBeforeChange).toBe(editorBeforeChange); + expect(documentBeforeSelection).toBe(editorBeforeSelection); + expect(documentChange).not.toBe(editorChange); + expect(documentChange.from).toEqual({ line: 0, ch: 1 }); + expect(documentChange.to).toEqual({ line: 0, ch: 2 }); + expect(documentChange.text).toEqual(["X"]); + expect(documentChange.removed).toEqual(["b"]); + expect(documentChange.origin).toBe("+doc-events"); + expect(historyAddedCount).toBe(1); + expect(documentCursorActivityCount).toBe(1); + expect(editorCursorActivityCount).toBe(1); + + codeMirror.replaceRange("Y", { line: 0, ch: 2 }, null, "+doc-events"); + expect(historyAddedCount).toBe(1); + codeMirror.changeGeneration(true); + codeMirror.replaceRange("Z", { line: 0, ch: 3 }, null, "+doc-events"); + expect(historyAddedCount).toBe(2); + }); + + it("emits document events for detached CM6-backed Docs", function () { + const compatDoc = new CodeMirror.Doc( + "alpha\nbeta", + "javascript" + ); + standaloneCodeMirror = compatDoc._adapter; + const events = []; + compatDoc.on("beforeChange", function (doc) { + expect(doc).toBe(compatDoc); + events.push("beforeChange"); + }); + compatDoc.on("beforeSelectionChange", function (doc) { + expect(doc).toBe(compatDoc); + events.push("beforeSelectionChange"); + }); + compatDoc.on("historyAdded", function () { + events.push("historyAdded"); + }); + compatDoc.on("change", function (doc) { + expect(doc).toBe(compatDoc); + events.push("change"); + }); + compatDoc.on("cursorActivity", function (doc) { + expect(doc).toBe(compatDoc); + events.push("cursorActivity"); + }); + + compatDoc.replaceRange( + "BETA", + { line: 1, ch: 0 }, + { line: 1, ch: 4 }, + "+detached-doc" + ); + + expect(compatDoc.getEditor()).toBeNull(); + expect(compatDoc.getValue()).toBe("alpha\nBETA"); + expect(events).toEqual([ + "beforeChange", + "historyAdded", + "beforeSelectionChange", + "change", + "cursorActivity" + ]); + }); + + it("signals surviving line handles for edits, multiline changes, undo, and redo", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "aa\nbb\ncc", + mode: "javascript" + } + ); + + const codeMirror = standaloneCodeMirror; + const handles = [0, 1, 2].map(function (lineNumber) { + return codeMirror.getLineHandle(lineNumber); + }); + const labels = ["first", "middle", "last"]; + const changed = []; + const deleted = []; + handles.forEach(function (handle, index) { + CodeMirror.on(handle, "change", function (lineHandle, change) { + expect(lineHandle).toBe(handle); + changed.push({ + label: labels[index], + origin: change.origin + }); + }); + CodeMirror.on(handle, "delete", function () { + deleted.push(labels[index]); + }); + }); + + codeMirror.replaceRange( + "X", + { line: 1, ch: 1 }, + { line: 1, ch: 2 }, + "+single-line" + ); + expect(changed).toEqual([{ + label: "middle", + origin: "+single-line" + }]); + + changed.length = 0; + codeMirror.replaceRange( + "X\nY", + { line: 0, ch: 1 }, + { line: 2, ch: 1 }, + "+multiline" + ); + expect(changed).toEqual([ + { label: "first", origin: "+multiline" }, + { label: "last", origin: "+multiline" } + ]); + expect(deleted).toEqual(["middle"]); + + changed.length = 0; + codeMirror.undo(); + expect(changed).toEqual([ + { label: "first", origin: "undo" }, + { label: "last", origin: "undo" } + ]); + + changed.length = 0; + codeMirror.redo(); + expect(changed).toEqual([ + { label: "first", origin: "redo" }, + { label: "last", origin: "redo" } + ]); + }); + + it("matches CM5 line-handle identity across structural replacements", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "aa\nbb\n", + mode: "javascript" + } + ); + + const codeMirror = standaloneCodeMirror; + const trailingNewlineHandles = [0, 1, 2].map(function (lineNumber) { + return codeMirror.getLineHandle(lineNumber); + }); + const trailingNewlineDeletes = []; + trailingNewlineHandles.forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + trailingNewlineDeletes.push(index); + }); + }); + codeMirror.on("beforeChange", function () {}); + + codeMirror.setValue(codeMirror.getValue()); + + expect(trailingNewlineDeletes).toEqual([0, 1]); + expect(codeMirror.getLineNumber(trailingNewlineHandles[0])).toBeNull(); + expect(codeMirror.getLineNumber(trailingNewlineHandles[1])).toBeNull(); + expect(codeMirror.getLineHandle(2)).toBe(trailingNewlineHandles[2]); + + codeMirror.setValue("aa\nbb\ncc"); + const multilineHandles = [0, 1, 2].map(function (lineNumber) { + return codeMirror.getLineHandle(lineNumber); + }); + const multilineDeletes = []; + multilineHandles.forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + multilineDeletes.push(index); + }); + }); + + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + { line: 2, ch: 1 }, + "+line-handle-parity" + ); + + expect(codeMirror.getValue()).toBe("aXc"); + expect(multilineDeletes).toEqual([1, 2]); + expect(codeMirror.getLineHandle(0)).toBe(multilineHandles[0]); + expect(codeMirror.getLineNumber(multilineHandles[1])).toBeNull(); + expect(codeMirror.getLineNumber(multilineHandles[2])).toBeNull(); + }); + + it("keeps bookmark sides compatible at insertion and replacement boundaries", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "abcdef", + mode: "javascript" + } + ); + + const codeMirror = standaloneCodeMirror; + const replacementStartBefore = codeMirror.setBookmark( + { line: 0, ch: 1 }, + { insertLeft: false } + ); + const replacementStartAfter = codeMirror.setBookmark( + { line: 0, ch: 1 }, + { insertLeft: true } + ); + const replacementEndBefore = codeMirror.setBookmark( + { line: 0, ch: 4 }, + { insertLeft: false } + ); + const replacementEndAfter = codeMirror.setBookmark( + { line: 0, ch: 4 }, + { insertLeft: true } + ); + + codeMirror.replaceRange( + "Q", + { line: 0, ch: 1 }, + { line: 0, ch: 4 } + ); + + expect(plainPosition(replacementStartBefore.find())) + .toEqual({ line: 0, ch: 1 }); + expect(plainPosition(replacementStartAfter.find())) + .toEqual({ line: 0, ch: 1 }); + expect(plainPosition(replacementEndBefore.find())) + .toEqual({ line: 0, ch: 2 }); + expect(plainPosition(replacementEndAfter.find())) + .toEqual({ line: 0, ch: 2 }); + + const insertionBefore = codeMirror.setBookmark( + { line: 0, ch: 1 }, + { insertLeft: false } + ); + const insertionAfter = codeMirror.setBookmark( + { line: 0, ch: 1 }, + { insertLeft: true } + ); + + codeMirror.replaceRange("XY", { line: 0, ch: 1 }); + + expect(plainPosition(insertionBefore.find())) + .toEqual({ line: 0, ch: 1 }); + expect(plainPosition(insertionAfter.find())) + .toEqual({ line: 0, ch: 3 }); + }); + + it("keeps legacy decorations valid through post-change selection updates", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "0123456789abcdefg", + mode: "javascript" + } + ); + + const codeMirror = standaloneCodeMirror; + const widgetNode = document.createElement("div"); + widgetNode.className = "legacy-decoration-at-old-end"; + codeMirror.addLineWidget( + 0, + widgetNode, + { above: false } + ); + codeMirror.on("beforeSelectionChange", function (_instance, selection) { + selection.update([{ + anchor: { line: 0, ch: 1 }, + head: { line: 0, ch: 1 } + }]); + }); + + expect(function () { + codeMirror.setValue("short content"); + }).not.toThrow(); + expect(codeMirror.getCursor()).toEqual({ line: 0, ch: 1 }); + }); + + it("uses CM5 full-replacement metadata semantics without beforeChange hooks", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "zero\none\ntwo\nthree", + mode: "javascript" + } + ); + + const lineHandles = [0, 1, 2, 3].map(function (lineNumber) { + return standaloneCodeMirror.getLineHandle(lineNumber); + }); + const deletedHandles = []; + lineHandles.forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + deletedHandles.push(index); + }); + }); + const marker = standaloneCodeMirror.markText( + { line: 1, ch: 0 }, + { line: 2, ch: 2 } + ); + const bookmark = standaloneCodeMirror.setBookmark({ + line: 2, + ch: 1 + }); + + standaloneCodeMirror.clearHistory(); + standaloneCodeMirror.markClean(); + standaloneCodeMirror.setValue(standaloneCodeMirror.getValue()); + + expect(deletedHandles).toEqual([0, 1, 2, 3]); + lineHandles.forEach(function (lineHandle, lineNumber) { + expect(standaloneCodeMirror.getLineHandle(lineNumber)) + .not.toBe(lineHandle); + expect(standaloneCodeMirror.getLineNumber(lineHandle)).toBeNull(); + }); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + + const replacementLineHandles = [0, 1, 2, 3].map(function (lineNumber) { + return standaloneCodeMirror.getLineHandle(lineNumber); + }); + const replacementHandleDeletes = []; + replacementLineHandles.forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + replacementHandleDeletes.push(index); + }); + }); + + standaloneCodeMirror.undo(); + expect(replacementHandleDeletes).toEqual([1, 2]); + expect(standaloneCodeMirror.getLineHandle(0)) + .toBe(replacementLineHandles[0]); + expect(standaloneCodeMirror.getLineNumber(replacementLineHandles[1])) + .toBeNull(); + expect(standaloneCodeMirror.getLineNumber(replacementLineHandles[2])) + .toBeNull(); + expect(standaloneCodeMirror.getLineHandle(3)) + .toBe(replacementLineHandles[3]); + expect(marker.find()).toEqual({ + from: { line: 1, ch: 0 }, + to: { line: 2, ch: 2 } + }); + expect(plainPosition(bookmark.find())).toEqual({ + line: 2, + ch: 1 + }); + + const undoLineHandles = [0, 1, 2, 3].map(function (lineNumber) { + return standaloneCodeMirror.getLineHandle(lineNumber); + }); + const undoHandleDeletes = []; + undoLineHandles.forEach(function (lineHandle, index) { + CodeMirror.on(lineHandle, "delete", function () { + undoHandleDeletes.push(index); + }); + }); + + standaloneCodeMirror.redo(); + expect(undoHandleDeletes).toEqual([1, 2]); + expect(standaloneCodeMirror.getLineHandle(0)) + .toBe(replacementLineHandles[0]); + expect(standaloneCodeMirror.getLineHandle(3)) + .toBe(replacementLineHandles[3]); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + }); + + it("keeps hidden markers detached until their deleting change is undone", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "a".repeat(600), + mode: "javascript" + } + ); + + const marker = standaloneCodeMirror.markText( + { line: 0, ch: 340 }, + { line: 0, ch: 350 } + ); + const bookmark = standaloneCodeMirror.setBookmark({ + line: 0, + ch: 596 + }); + const visibilityEvents = []; + marker.on("hide", function () { + visibilityEvents.push("marker.hide"); + }); + marker.on("unhide", function () { + visibilityEvents.push("marker.unhide"); + }); + bookmark.on("hide", function () { + visibilityEvents.push("bookmark.hide"); + }); + bookmark.on("unhide", function () { + visibilityEvents.push("bookmark.unhide"); + }); + + standaloneCodeMirror.clearHistory(); + standaloneCodeMirror.setValue("b".repeat(330)); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + expect(visibilityEvents).toEqual([ + "marker.hide", + "bookmark.hide" + ]); + + expect(function () { + standaloneCodeMirror.replaceRange( + "X", + { line: 0, ch: 0 }, + undefined, + "+hidden-marker-edit" + ); + }).not.toThrow(); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + + standaloneCodeMirror.undo(); + expect(standaloneCodeMirror.getValue()).toBe("b".repeat(330)); + expect(marker.find()).toBeUndefined(); + expect(bookmark.find()).toBeUndefined(); + expect(visibilityEvents).toEqual([ + "marker.hide", + "bookmark.hide" + ]); + + standaloneCodeMirror.undo(); + expect(standaloneCodeMirror.getValue()).toBe("a".repeat(600)); + expect(marker.find()).toEqual({ + from: { line: 0, ch: 340 }, + to: { line: 0, ch: 350 } + }); + expect(plainPosition(bookmark.find())).toEqual({ + line: 0, + ch: 596 + }); + expect(visibilityEvents).toEqual([ + "marker.hide", + "bookmark.hide", + "marker.unhide", + "bookmark.unhide" + ]); + }); + + it("round-trips undo history through the CodeMirror history API", function () { + createEditor("abc"); + + const codeMirror = editor._codeMirror; + codeMirror.clearHistory(); + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "+history-round-trip" + ); + + const savedHistory = editor.getHistory(); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + + codeMirror.clearHistory(); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 0 }); + editor.setHistory(savedHistory); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + + codeMirror.undo(); + expect(editor.document.getText()).toBe("abc"); + expect(codeMirror.historySize()).toEqual({ undo: 0, redo: 1 }); + + codeMirror.redo(); + expect(editor.document.getText()).toBe("aXc"); + expect(codeMirror.historySize()).toEqual({ undo: 1, redo: 0 }); + }); + + it("keeps selection history metadata live while isolating change payloads", function () { + createEditor("abc"); + + const codeMirror = editor._codeMirror; + codeMirror.clearHistory(); + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "+history-copy" + ); + + const exportedHistory = codeMirror.getHistory(); + const exportedChange = exportedHistory.done.find(function (entry) { + return entry.type === "change"; + }); + const internalChange = codeMirror.getDoc().history.done.find(function (entry) { + return entry.type === "change"; + }); + const exportedSelection = + exportedHistory.done[exportedHistory.done.length - 1]; + + expect(exportedChange).not.toBe(internalChange); + exportedChange.steps[0].undoChanges[0].insert = "corrupt"; + exportedSelection.restorePointName = "live-restore-point"; + expect( + codeMirror.getHistory().done[ + codeMirror.getHistory().done.length - 1 + ].restorePointName + ).toBe("live-restore-point"); + expect(function () { + JSON.stringify(exportedHistory); + }).not.toThrow(); + + codeMirror.undo(); + expect(codeMirror.getValue()).toBe("abc"); + codeMirror.redo(); + expect(codeMirror.getValue()).toBe("aXc"); + + const serializedHistory = JSON.parse( + JSON.stringify(codeMirror.getHistory()) + ); + codeMirror.setHistory(serializedHistory); + const serializedChange = serializedHistory.done.find(function (entry) { + return entry.type === "change"; + }); + serializedChange.steps[0].undoChanges[0].insert = "corrupt"; + codeMirror.undo(); + expect(codeMirror.getValue()).toBe("abc"); + }); + + it("honors disableInput and undoDepth without blocking programmatic edits", function () { + createEditor("abc"); + + const codeMirror = editor._codeMirror; + codeMirror.setOption("disableInput", true); + codeMirror._view.dispatch({ + changes: { + from: 0, + insert: "user" + }, + annotations: CM6.Transaction.userEvent.of("input.type") + }); + expect(codeMirror.getValue()).toBe("abc"); + + codeMirror.replaceRange( + "P", + { line: 0, ch: 0 }, + null, + "+programmatic" + ); + expect(codeMirror.getValue()).toBe("Pabc"); + + codeMirror.clearHistory(); + codeMirror.setOption("undoDepth", 1); + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + null, + "+depth-one" + ); + codeMirror.changeGeneration(true); + codeMirror.replaceRange( + "Y", + { line: 0, ch: 2 }, + null, + "+depth-two" + ); + expect(codeMirror.historySize()).toEqual({ + undo: 1, + redo: 0 + }); + + codeMirror.undo(); + expect(codeMirror.getValue()).toBe("PXabc"); + expect(codeMirror.historySize()).toEqual({ + undo: 0, + redo: 1 + }); + codeMirror.undo(); + expect(codeMirror.getValue()).toBe("PXabc"); + }); + + it("exposes live history stacks with stable change-event identity", function () { + createEditor("abc"); + + const codeMirror = editor._codeMirror; + const history = codeMirror.getDoc().history; + const initialDone = history.done; + codeMirror.clearHistory(); + + expect(codeMirror.getDoc().history).toBe(history); + expect(history.done).not.toBe(initialDone); + expect(history.done[0].changes).toBeUndefined(); + + codeMirror.replaceRange( + "X", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "*live-history" + ); + const changeEvent = history.done.slice().reverse().find(function (entry) { + return entry.changes; + }); + + expect(changeEvent).toBeDefined(); + expect(changeEvent.changes).toBe(changeEvent.steps); + + codeMirror.replaceRange( + "Y", + { line: 0, ch: 1 }, + { line: 0, ch: 2 }, + "*live-history" + ); + expect(history.done.indexOf(changeEvent)).not.toBe(-1); + + codeMirror.undo(); + expect(history.done.indexOf(changeEvent)).toBe(-1); + expect(history.undone.indexOf(changeEvent)).not.toBe(-1); + + codeMirror.redo(); + expect(history.done.indexOf(changeEvent)).not.toBe(-1); + expect(history.undone.indexOf(changeEvent)).toBe(-1); + + const savedHistory = codeMirror.getHistory(); + codeMirror.clearHistory(); + codeMirror.setHistory(savedHistory); + expect(codeMirror.getDoc().history).toBe(history); + const restoredChangeEvent = history.done.slice().reverse() + .find(function (entry) { + return entry.changes; + }); + expect(restoredChangeEvent).toBeDefined(); + expect(restoredChangeEvent).not.toBe(changeEvent); + expect(restoredChangeEvent.steps).not.toBe(changeEvent.steps); + }); + + it("preserves custom origins for single and multiple selection replacements", function () { + createEditor("abcd\nefgh"); + + const codeMirror = editor._codeMirror; + const observedOrigins = []; + codeMirror.on("changes", function (_instance, changeList) { + observedOrigins.push(changeList.map(function (change) { + return change.origin; + })); + }); + + editor.setSelection( + { line: 0, ch: 1 }, + { line: 0, ch: 2 } + ); + editor.replaceSelection("X", "around", "+single-selection-origin"); + + expect(editor.document.getText()).toBe("aXcd\nefgh"); + expect(observedOrigins[0]).toEqual(["+single-selection-origin"]); + + editor.setSelections([ + { + start: { line: 0, ch: 0 }, + end: { line: 0, ch: 1 } + }, + { + start: { line: 1, ch: 0 }, + end: { line: 1, ch: 1 }, + primary: true + } + ]); + editor.replaceSelections( + ["Y", "Z"], + "around", + "+multiple-selection-origin" + ); + + expect(editor.document.getText()).toBe("YXcd\nZfgh"); + expect(observedOrigins[1].length).toBe(2); + expect(observedOrigins[1].every(function (origin) { + return origin === "+multiple-selection-origin"; + })).toBe(true); + }); + + it("supports cancelling and updating selection replacements before change", function () { + createEditor("abcdef"); + + const codeMirror = editor._codeMirror; + const observedOrigins = []; + const appliedOrigins = []; + let action = "cancel"; + + function beforeChange(instance, change) { + expect(instance).toBe(codeMirror); + observedOrigins.push(change.origin); + if (action === "cancel") { + change.cancel(); + return; + } + change.update( + change.from, + change.to, + ["LONGER"], + "+updated-before-change" + ); + } + + codeMirror.on("beforeChange", beforeChange); + codeMirror.on("changes", function (_instance, changeList) { + appliedOrigins.push(changeList.map(function (change) { + return change.origin; + })); + }); + + editor.setSelection( + { line: 0, ch: 1 }, + { line: 0, ch: 4 } + ); + editor.replaceSelection("XXX", "around", "+cancel-before-change"); + + expect(editor.document.getText()).toBe("abcdef"); + expect(editor.getSelectedText()).toBe("bcd"); + expect(observedOrigins).toEqual(["+cancel-before-change"]); + expect(appliedOrigins.length).toBe(0); + + action = "update"; + editor.replaceSelection("XXX", "around", "+update-before-change"); + + expect(editor.document.getText()).toBe("aLONGERef"); + expect(editor.getSelectedText()).toBe("LONGER"); + expect(observedOrigins).toEqual([ + "+cancel-before-change", + "+update-before-change" + ]); + expect(appliedOrigins).toEqual([["+updated-before-change"]]); + + codeMirror.off("beforeChange", beforeChange); + }); + + it("renders and removes legacy overlay token classes", async function () { + createEditor("const TODO = true;"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + const overlay = { + token: function (stream) { + if (stream.match("TODO")) { + return "editor-surface-overlay"; + } + while (stream.next() !== undefined) { + if (stream.match("TODO", false)) { + break; + } + } + return null; + } + }; + + codeMirror.addOverlay(overlay); + await awaitsFor(function () { + return Boolean(root.querySelector(".cm-editor-surface-overlay")); + }, `${ENGINE_LABEL} overlay class should be visible`); + + codeMirror.removeOverlay(overlay); + await awaitsFor(function () { + return !root.querySelector(".cm-editor-surface-overlay"); + }, `${ENGINE_LABEL} overlay class should be removed`); + }); + + it("preserves legacy overlay validation, ordering, and named removal", async function () { + createEditor("TODO"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + const lowPriorityOverlay = { + token: function (stream) { + stream.skipToEnd(); + return "editor-surface-overlay-low"; + } + }; + CodeMirror.defineMode("editor-surface-overlay-mode", function () { + return { + token: function (stream) { + stream.skipToEnd(); + return "editor-surface-overlay-high"; + } + }; + }); + + codeMirror.addOverlay("editor-surface-overlay-mode", { + opaque: true, + priority: 10 + }); + codeMirror.addOverlay(lowPriorityOverlay, { priority: -1 }); + + expect(codeMirror._overlays.map(function (overlay) { + return overlay.priority; + })).toEqual([-1, 10]); + await awaitsFor(function () { + return Boolean(root.querySelector( + ".cm-editor-surface-overlay-high.cm-overlay-opaque" + )); + }, `${ENGINE_LABEL} opaque named overlay should be visible`); + + codeMirror.removeOverlay("editor-surface-overlay-mode"); + await awaitsFor(function () { + return !root.querySelector(".cm-editor-surface-overlay-high"); + }, `${ENGINE_LABEL} named overlay should be removed`); + expect(function () { + codeMirror.addOverlay({ + startState: function () { + return {}; + }, + token: function (stream) { + stream.skipToEnd(); + } + }); + }).toThrowError("Overlays may not be stateful."); + }); + + it("resolves registered helpers through the editor instance", function () { + createEditor("body {};", "css"); + + const helper = function () { + return "helper"; + }; + CodeMirror.registerHelper("editorSurfaceHelper", "css", helper); + + expect(editor._codeMirror.getHelpers( + { line: 0, ch: 1 }, + "editorSurfaceHelper" + )).toEqual([helper]); + expect(editor._codeMirror.getHelper( + { line: 0, ch: 1 }, + "editorSurfaceHelper" + )).toBe(helper); + }); + + it("renders, updates, and removes rulers", async function () { + createEditor("const answer = 42;"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.setOption("rulers", [{ + column: 4, + className: "editor-surface-ruler-first", + color: "rgb(255, 0, 0)" + }, { + column: 8, + className: "editor-surface-ruler-second" + }]); + + await awaitsFor(function () { + return root.querySelectorAll(".CodeMirror-ruler").length === 2; + }, `${ENGINE_LABEL} rulers should be visible`); + expect(root.querySelector(".editor-surface-ruler-first")).toBeTruthy(); + expect(root.querySelector(".editor-surface-ruler-second")).toBeTruthy(); + + codeMirror.setOption("rulers", [{ + column: 2, + className: "editor-surface-ruler-updated" + }]); + await awaitsFor(function () { + return root.querySelectorAll(".CodeMirror-ruler").length === 1 && + Boolean(root.querySelector(".editor-surface-ruler-updated")); + }, `${ENGINE_LABEL} rulers should update`); + expect(root.querySelector(".editor-surface-ruler-first")).toBeFalsy(); + expect(root.querySelector(".editor-surface-ruler-second")).toBeFalsy(); + + codeMirror.setOption("rulers", null); + await awaitsFor(function () { + return !root.querySelector(".CodeMirror-rulers") && + !root.querySelector(".CodeMirror-ruler"); + }, `${ENGINE_LABEL} rulers should be removed`); + }); + + it("renders and removes legacy mode-name token classes", async function () { + createEditor("const answer = 42;"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.setOption("addModeClass", true); + codeMirror.refresh(); + + await awaitsFor(function () { + return Boolean(root.querySelector(".cm-m-javascript")); + }, `${ENGINE_LABEL} mode-name classes should be visible`); + + codeMirror.setOption("addModeClass", false); + codeMirror.refresh(); + await awaitsFor(function () { + return !root.querySelector(".cm-m-javascript"); + }, `${ENGINE_LABEL} mode-name classes should be removed`); + }); + + it("preserves line widget nodes, classes, ordering, and clear handles", async function () { + createEditor("first\nsecond\nthird"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.setOption("styleActiveLine", false); + const untouchedLineInfo = codeMirror.lineInfo(0); + expect(untouchedLineInfo.gutterMarkers).toBeUndefined(); + expect(untouchedLineInfo.textClass).toBeUndefined(); + expect(untouchedLineInfo.bgClass).toBeUndefined(); + expect(untouchedLineInfo.wrapClass).toBeUndefined(); + expect(untouchedLineInfo.widgets).toBeUndefined(); + const aboveNode = window.document.createElement("div"); + const belowNode = window.document.createElement("div"); + aboveNode.textContent = "above widget"; + belowNode.textContent = "below widget"; + + const aboveWidget = codeMirror.addLineWidget(1, aboveNode, { + above: true, + className: "editor-surface-widget-above", + insertAt: 0 + }); + const belowWidget = codeMirror.addLineWidget(1, belowNode, { + className: "editor-surface-widget-below", + insertAt: 1 + }); + let redrawCount = 0; + let changedLine; + let clearedCount = 0; + CodeMirror.on(aboveWidget, "redraw", function () { + redrawCount++; + }); + codeMirror.on("lineWidgetChanged", function (_instance, widget, line) { + if (widget === aboveWidget) { + changedLine = line; + } + }); + codeMirror.on("lineWidgetCleared", function (_instance, widget) { + if (widget === aboveWidget) { + clearedCount++; + } + }); + + expect(aboveWidget.node).toBe(aboveNode); + expect(belowWidget.node).toBe(belowNode); + expect(aboveWidget.doc).toBe(codeMirror.getDoc()); + expect(aboveWidget.above).toBe(true); + expect(aboveWidget.className).toBe("editor-surface-widget-above"); + expect(aboveWidget.insertAt).toBe(0); + await awaitsFor(function () { + return aboveNode.isConnected && belowNode.isConnected; + }, `${ENGINE_LABEL} line widget nodes should be rendered`); + + const aboveWrapper = aboveNode.closest( + ".CodeMirror-linewidget.editor-surface-widget-above" + ); + const belowWrapper = belowNode.closest( + ".CodeMirror-linewidget.editor-surface-widget-below" + ); + const textLine = Array.from(root.querySelectorAll( + ".CodeMirror-line, .cm-line" + )).find(function (lineElement) { + return lineElement.textContent === "second"; + }); + + expect(aboveWrapper).toBeTruthy(); + expect(belowWrapper).toBeTruthy(); + expect(textLine).toBeTruthy(); + if (aboveWrapper && belowWrapper && textLine) { + const orderedElements = Array.from(root.querySelectorAll( + ".CodeMirror-linewidget, .CodeMirror-line, .cm-line" + )); + expect( + orderedElements.indexOf(aboveWrapper) < + orderedElements.indexOf(textLine) + ).toBe(true); + expect( + orderedElements.indexOf(textLine) < + orderedElements.indexOf(belowWrapper) + ).toBe(true); + } + + aboveWidget.changed(); + await awaitsFor(function () { + return redrawCount > 0; + }, `${ENGINE_LABEL} line widget redraw should be signaled`); + expect(changedLine).toBe(1); + + aboveWidget.clear(); + aboveWidget.clear(); + await awaitsFor(function () { + return !aboveNode.isConnected; + }, `${ENGINE_LABEL} line widget clear should remove the original node`); + expect(clearedCount).toBe(1); + expect(belowNode.isConnected).toBe(true); + + codeMirror.removeLineWidget(belowWidget); + await awaitsFor(function () { + return !belowNode.isConnected; + }, `${ENGINE_LABEL} remaining line widget should be clearable`); + }); + + it("maps markers and bookmarks through edits", function () { + createEditor("abcdef"); + + const marker = editor.markText( + "conformance-range", + { line: 0, ch: 1 }, + { line: 0, ch: 3 } + ); + const bookmark = editor.setBookmark( + "conformance-bookmark", + { line: 0, ch: 4 } + ); + expect(typeof marker.id).toBe("number"); + expect(marker.id).toBe(marker._id); + expect(typeof bookmark.id).toBe("number"); + expect(bookmark.id).toBe(bookmark._id); + expect(bookmark.id).not.toBe(marker.id); + let clearCount = 0; + marker.on("clear", function () { + clearCount++; + }); + + editor.replaceRange("X", { line: 0, ch: 0 }); + + const markerRange = marker.find(); + expect({ + from: plainPosition(markerRange.from), + to: plainPosition(markerRange.to) + }).toEqual({ + from: { line: 0, ch: 2 }, + to: { line: 0, ch: 4 } + }); + expect(plainPosition(bookmark.find())).toEqual({ line: 0, ch: 5 }); + expect(editor.getAllMarks("conformance-range")).toEqual([marker]); + expect(editor.getAllMarks("conformance-bookmark")).toEqual([bookmark]); + + marker.clear(); + expect(clearCount).toBe(1); + expect(marker.find()).toBeUndefined(); + }); + + it("clips non-inclusive markers at replacement boundaries", function () { + createEditor("0123456789"); + + const exactMarker = editor.markText( + "conformance-exact-replacement", + { line: 0, ch: 2 }, + { line: 0, ch: 5 } + ); + const leftMarker = editor.markText( + "conformance-left-replacement", + { line: 0, ch: 0 }, + { line: 0, ch: 5 } + ); + const rightMarker = editor.markText( + "conformance-right-replacement", + { line: 0, ch: 2 }, + { line: 0, ch: 8 } + ); + const inclusiveMarker = editor.markText( + "conformance-inclusive-replacement", + { line: 0, ch: 2 }, + { line: 0, ch: 5 }, + { + inclusiveLeft: true, + inclusiveRight: true + } + ); + + editor.replaceRange( + "WXYZ", + { line: 0, ch: 2 }, + { line: 0, ch: 5 } + ); + + expect(exactMarker.find()).toBeUndefined(); + expect({ + from: plainPosition(leftMarker.find().from), + to: plainPosition(leftMarker.find().to) + }).toEqual({ + from: { line: 0, ch: 0 }, + to: { line: 0, ch: 2 } + }); + expect({ + from: plainPosition(rightMarker.find().from), + to: plainPosition(rightMarker.find().to) + }).toEqual({ + from: { line: 0, ch: 6 }, + to: { line: 0, ch: 9 } + }); + expect({ + from: plainPosition(inclusiveMarker.find().from), + to: plainPosition(inclusiveMarker.find().to) + }).toEqual({ + from: { line: 0, ch: 2 }, + to: { line: 0, ch: 6 } + }); + }); + + it("clips selection endpoints around inclusive collapsed ranges", function () { + createEditor("hidden\nvisible\nhidden"); + + editor._codeMirror.markText( + { line: 2, ch: 0 }, + { line: 2, ch: null }, + { + collapsed: true, + inclusiveLeft: true, + inclusiveRight: true, + clearWhenEmpty: false + } + ); + editor.setSelection( + { line: 1, ch: 0 }, + { line: 2, ch: 0 } + ); + + expect(comparableSelections(editor)).toEqual([{ + start: { line: 1, ch: 0 }, + end: { line: 1, ch: 7 }, + reversed: false, + primary: true + }]); + }); + + it("keeps selection transactions current when an atomic marker clears on entry", function () { + createEditor("abcdef"); + + const marker = editor.markText( + "clear-on-enter", + { line: 0, ch: 1 }, + { line: 0, ch: 4 }, + { + atomic: true, + clearOnEnter: true + } + ); + + expect(function () { + editor.setCursorPos({ line: 0, ch: 2 }); + }).not.toThrow(); + expect(marker.find()).toBeUndefined(); + expect(plainPosition(editor.getCursorPos())).toEqual({ + line: 0, + ch: 2 + }); + }); + + it("reports CM5-compatible cursor positions and programmatic focus", function () { + createEditor("abcdef"); + showEditor(); + + let focusCount = 0; + editor.on(`focus${EVENT_NAMESPACE}`, function () { + focusCount++; + }); + editor.setCursorPos(0, 3); + editor.focus(); + + expect(editor.getCursorPos()).toEqual({ + line: 0, + ch: 3, + sticky: null + }); + expect(editor.hasFocus()).toBe(true); + expect(focusCount).toBe(1); + }); + + it("tracks focus independently for an editor nested in a line widget", async function () { + createEditor("outer"); + showEditor(); + + const nestedHolder = window.document.createElement("div"); + const lineWidget = editor._codeMirror.addLineWidget(0, nestedHolder, { + handleMouseEvents: true + }); + secondaryEditor = new Editor( + testDocument, + false, + nestedHolder + ); + secondaryEditor.setSize(300, 80); + secondaryEditor.refresh(); + + editor.focus(); + expect(editor.hasFocus()).toBe(true); + + secondaryEditor.focus(); + await awaitsFor(function () { + return secondaryEditor.hasFocus() && !editor.hasFocus(); + }, `${ENGINE_LABEL} nested editor focus should replace host focus`); + + secondaryEditor.replaceRange( + "X", + { line: 0, ch: 0 }, + { line: 0, ch: 1 }, + "+input" + ); + lineWidget.changed(); + await awaitsFor(function () { + return secondaryEditor.hasFocus() && !editor.hasFocus(); + }, `${ENGINE_LABEL} nested editor focus should survive edits`); + + editor.replaceRange( + "prefix\n", + { line: 0, ch: 0 }, + { line: 0, ch: 0 }, + "+input" + ); + await awaitsFor(function () { + return secondaryEditor.hasFocus() && !editor.hasFocus(); + }, `${ENGINE_LABEL} nested editor focus should survive host edits`); + }); + + it("scrolls long lines into view synchronously", function () { + createEditor(`short\n${"x".repeat(300)}`); + + showEditor(180, 120); + expect(editor.getScrollPos().x).toBe(0); + + editor.setCursorPos(1, 300); + + expect(editor.getScrollPos().x).toBeGreaterThan(0); + const scroller = editor.getScrollerElement(); + expect(scroller.scrollLeft) + .toBe(scroller.scrollWidth - scroller.clientWidth); + }); + + it("forwards visible gutter mouse events with CodeMirror-compatible arguments", async function () { + createEditor("first\nsecond\nthird"); + Editor.registerGutter(TEST_GUTTER, 10); + + const marker = window.document.createElement("span"); + marker.textContent = "!"; + editor.setGutterMarker(1, TEST_GUTTER, marker); + let markerClickCount = 0; + marker.addEventListener("click", function () { + markerClickCount++; + }); + expect(editor.getGutterMarker(1, TEST_GUTTER)).toBe(marker); + + const observedEvents = []; + function recordEvent(eventName) { + return function (codeMirror, lineNumber, gutterName, event) { + observedEvents.push({ + eventName: eventName, + codeMirror: codeMirror, + lineNumber: lineNumber, + gutterName: gutterName, + event: event + }); + if (eventName === "gutterContextMenu") { + event.preventDefault(); + } + }; + } + + editor._codeMirror.on("gutterClick", recordEvent("gutterClick")); + editor._codeMirror.on("gutterContextMenu", recordEvent("gutterContextMenu")); + + const root = editor.getRootElement(); + root.parentElement.style.display = "block"; + root.parentElement.style.height = "180px"; + root.parentElement.style.left = "0"; + root.parentElement.style.top = "0"; + editor.setSize(600, 180); + editor.refresh(); + + await awaitsFor(function () { + return marker.isConnected && root.querySelector(`.${TEST_GUTTER}`); + }, `${ENGINE_LABEL} gutter should be rendered`); + + marker.dispatchEvent(new window.MouseEvent("click", { bubbles: true })); + expect(markerClickCount).toBe(1); + + const gutter = root.querySelector(`.${TEST_GUTTER}`); + const gutterRect = gutter.getBoundingClientRect(); + const lineCoordinates = editor.charCoords({ line: 1, ch: 0 }, "window"); + const mouseDownEvent = new window.MouseEvent("mousedown", { + bubbles: true, + cancelable: true, + clientX: gutterRect.left + (gutterRect.width / 2), + clientY: lineCoordinates.top + + ((lineCoordinates.bottom - lineCoordinates.top) / 2) + }); + const contextMenuEvent = new window.MouseEvent("contextmenu", { + bubbles: true, + cancelable: true, + clientX: gutterRect.left + (gutterRect.width / 2), + clientY: lineCoordinates.top + + ((lineCoordinates.bottom - lineCoordinates.top) / 2) + }); + + gutter.dispatchEvent(mouseDownEvent); + gutter.dispatchEvent(contextMenuEvent); + + const lineNumberGutter = root.querySelector( + ".CodeMirror-linenumbers, .cm-lineNumbers" + ); + expect(lineNumberGutter).toBeTruthy(); + const lineNumberGutterRect = lineNumberGutter.getBoundingClientRect(); + const lineNumberMouseDownEvent = new window.MouseEvent("mousedown", { + bubbles: true, + cancelable: true, + clientX: lineNumberGutterRect.left + (lineNumberGutterRect.width / 2), + clientY: lineCoordinates.top + + ((lineCoordinates.bottom - lineCoordinates.top) / 2) + }); + lineNumberGutter.dispatchEvent(lineNumberMouseDownEvent); + + expect(observedEvents.length).toBe(3); + expect(observedEvents[0]).toEqual({ + eventName: "gutterClick", + codeMirror: editor._codeMirror, + lineNumber: 1, + gutterName: TEST_GUTTER, + event: mouseDownEvent + }); + expect(observedEvents[1]).toEqual({ + eventName: "gutterContextMenu", + codeMirror: editor._codeMirror, + lineNumber: 1, + gutterName: TEST_GUTTER, + event: contextMenuEvent + }); + expect(observedEvents[2]).toEqual({ + eventName: "gutterClick", + codeMirror: editor._codeMirror, + lineNumber: 1, + gutterName: LINE_NUMBER_GUTTER, + event: lineNumberMouseDownEvent + }); + expect(mouseDownEvent.defaultPrevented).toBe(true); + expect(contextMenuEvent.defaultPrevented).toBe(true); + expect(lineNumberMouseDownEvent.defaultPrevented).toBe(true); + }); + + it("keeps original gutter nodes live while replacing shared markers", async function () { + createEditor("first\nsecond\nthird\nfourth"); + Editor.registerGutter(TEST_GUTTER, 10); + + const root = editor.getRootElement(); + root.parentElement.style.display = "block"; + root.parentElement.style.height = "180px"; + root.parentElement.style.left = "0"; + root.parentElement.style.top = "0"; + editor.setSize(600, 180); + editor.refresh(); + + const openMarker = window.document.createElement("span"); + openMarker.className = "conformance-gutter-open"; + const sharedBlankMarker = window.document.createElement("span"); + sharedBlankMarker.className = "conformance-gutter-blank"; + + editor.operation(function () { + editor.setGutterMarker(0, TEST_GUTTER, openMarker); + editor.setGutterMarker(1, TEST_GUTTER, sharedBlankMarker); + editor.setGutterMarker(2, TEST_GUTTER, sharedBlankMarker); + }); + + expect(editor.getGutterMarker(0, TEST_GUTTER)).toBe(openMarker); + expect(editor.getGutterMarker(1, TEST_GUTTER)).toBe(sharedBlankMarker); + expect(editor.getGutterMarker(2, TEST_GUTTER)).toBe(sharedBlankMarker); + + await awaitsFor(function () { + return openMarker.isConnected && sharedBlankMarker.isConnected; + }, `${ENGINE_LABEL} original gutter markers should be rendered`); + + const wrappers = Array.from(root.querySelectorAll( + `.${TEST_GUTTER} .${CM6_GUTTER_MARKER_WRAPPER_CLASS}` + )); + expect(wrappers.length).toBe(3); + expect(wrappers.every(function (wrapper) { + return wrapper.parentElement.classList.contains("cm-gutterElement"); + })).toBe(true); + expect(wrappers.filter(function (wrapper) { + return wrapper.contains(sharedBlankMarker); + }).length).toBe(1); + + const foldedMarker = window.document.createElement("span"); + foldedMarker.className = "conformance-gutter-folded"; + editor.setGutterMarker(0, TEST_GUTTER, foldedMarker); + + expect(editor.getGutterMarker(0, TEST_GUTTER)).toBe(foldedMarker); + await awaitsFor(function () { + return foldedMarker.isConnected && !openMarker.isConnected; + }, `${ENGINE_LABEL} replacement gutter marker should be rendered`); + + expect(root.querySelectorAll( + `.${TEST_GUTTER} .${CM6_GUTTER_MARKER_WRAPPER_CLASS}` + ).length).toBe(3); + + editor.clearGutter(TEST_GUTTER); + expect(editor.getGutterMarker(0, TEST_GUTTER)).toBeUndefined(); + expect(editor.getGutterMarker(1, TEST_GUTTER)).toBeUndefined(); + expect(editor.getGutterMarker(2, TEST_GUTTER)).toBeUndefined(); + await awaitsFor(function () { + return !foldedMarker.isConnected && !sharedBlankMarker.isConnected; + }, `${ENGINE_LABEL} gutter markers should be removed`); + }); + + it("supports CM5 object-form gutter classes and inline styles", function () { + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + const firstGutter = { + className: "editor-surface-object-gutter", + style: "width: 17px; background-color: rgb(1, 2, 3);" + }; + const lineNumberGutter = { + className: LINE_NUMBER_GUTTER, + style: "min-width: 31px;" + }; + standaloneCodeMirror = new CodeMirror( + secondaryHolder.get(0), + { + value: "first\nsecond", + lineNumbers: true, + gutters: [ + firstGutter, + lineNumberGutter, + "editor-surface-string-gutter" + ] + } + ); + + const codeMirror = standaloneCodeMirror; + const gutterElements = Array.from( + codeMirror.getWrapperElement().querySelectorAll(".cm-gutter") + ); + expect(codeMirror.getOption("gutters")).toEqual([ + firstGutter, + lineNumberGutter, + "editor-surface-string-gutter" + ]); + expect(gutterElements.map(function (gutter) { + if (gutter.classList.contains("editor-surface-object-gutter")) { + return "editor-surface-object-gutter"; + } + if (gutter.classList.contains("CodeMirror-linenumbers")) { + return LINE_NUMBER_GUTTER; + } + return "editor-surface-string-gutter"; + })).toEqual([ + "editor-surface-object-gutter", + LINE_NUMBER_GUTTER, + "editor-surface-string-gutter" + ]); + expect(gutterElements[0].style.width).toBe("17px"); + expect(gutterElements[0].style.backgroundColor) + .toBe("rgb(1, 2, 3)"); + expect(gutterElements[1].style.minWidth).toBe("31px"); + + codeMirror.setOption("gutters", [ + "editor-surface-object-gutter", + LINE_NUMBER_GUTTER + ]); + const updatedGutters = Array.from( + codeMirror.getWrapperElement().querySelectorAll(".cm-gutter") + ); + expect(updatedGutters[0].style.width).toBe(""); + expect(updatedGutters[0].style.backgroundColor).toBe(""); + expect(updatedGutters[1].style.minWidth).toBe(""); + }); + + it("renders and refreshes gutter markers from legacy viewport handlers", async function () { + const content = Array.from({ length: 200 }, function (_value, index) { + return `line ${index}`; + }).join("\n"); + createEditor(content); + Editor.registerGutter(TEST_GUTTER, 10); + + const root = editor.getRootElement(); + root.parentElement.style.display = "block"; + root.parentElement.style.height = "180px"; + root.parentElement.style.left = "0"; + root.parentElement.style.top = "0"; + editor.setSize(600, 180); + editor.refresh(); + + await awaitsFor(function () { + const rect = root.getBoundingClientRect(); + const viewport = editor.getViewport(); + return rect.width > 0 && rect.height > 0 && + editor.getTextHeight() > 0 && viewport.to > viewport.from; + }, `${ENGINE_LABEL} editor should expose a visible viewport`); + + editor.setScrollPos(0, 0); + await awaitsFor(function () { + return editor.getScrollPos().y === 0 && editor.getViewport().from === 0; + }, `${ENGINE_LABEL} editor should start at the first viewport`); + + const initialViewport = editor.getViewport(); + editor._codeMirror.setOption(LEGACY_VIEWPORT_GUTTER_OPTION, true); + + await awaitsFor(function () { + return root.querySelectorAll(`.${LEGACY_VIEWPORT_MARKER_CLASS}`).length === + initialViewport.to - initialViewport.from; + }, `${ENGINE_LABEL} legacy option handler should mark the visible viewport`); + + expect(root.querySelector(`.${LEGACY_VISIBLE_GUTTER_CLASS}`)).toBeTruthy(); + const initialMarkers = Array.from( + root.querySelectorAll(`.${LEGACY_VIEWPORT_MARKER_CLASS}`) + ); + expect(initialMarkers.map(function (marker) { + return Number(marker.dataset.line); + })).toEqual(Array.from( + { length: initialViewport.to - initialViewport.from }, + function (_value, index) { + return initialViewport.from + index; + } + )); + + editor._codeMirror._lastViewport = null; + editor._codeMirror._emitViewportChange(); + + await awaitsFor(function () { + const refreshedMarkers = Array.from( + root.querySelectorAll(`.${LEGACY_VIEWPORT_MARKER_CLASS}`) + ); + return refreshedMarkers.length === initialMarkers.length && + refreshedMarkers.every(function (marker) { + return initialMarkers.indexOf(marker) === -1; + }); + }, `${ENGINE_LABEL} legacy option handler should refresh on viewport changes`); + + const refreshedMarkers = Array.from( + root.querySelectorAll(`.${LEGACY_VIEWPORT_MARKER_CLASS}`) + ); + expect(refreshedMarkers.map(function (marker) { + return Number(marker.dataset.line); + })).toEqual(Array.from( + { length: initialViewport.to - initialViewport.from }, + function (_value, index) { + return initialViewport.from + index; + } + )); + + editor._codeMirror.setOption(LEGACY_VIEWPORT_GUTTER_OPTION, false); + await awaitsFor(function () { + return !root.querySelector(`.${LEGACY_VIEWPORT_MARKER_CLASS}`) && + !root.querySelector(`.${LEGACY_VISIBLE_GUTTER_CLASS}`); + }, `${ENGINE_LABEL} legacy option gutter markers should be cleared`); + }); + + it("preserves repository-owned root classes through editor updates", function () { + createEditor("focus"); + + const root = editor.getRootElement(); + const persistentClasses = [ + "folding-enabled", + "over-gutter", + "find-highlighting" + ]; + const themeClasses = String( + editor._codeMirror.getOption("theme") || "default" + ).split(/\s+/).filter(Boolean).map(function (themeName) { + return `cm-s-${themeName}`; + }); + root.classList.add(...persistentClasses); + + editor.setCursorPos(0, 1); + persistentClasses.forEach(function (className) { + expect(root.classList.contains(className)).toBe(true); + }); + themeClasses.forEach(function (className) { + expect(root.classList.contains(className)).toBe(true); + }); + }); + + it("preserves legacy line-class targets and token removal semantics", async function () { + createEditor("first\nsecond\nthird"); + + const root = showEditor(); + const codeMirror = editor._codeMirror; + codeMirror.setOption("lineNumbers", true); + const lineHandle = codeMirror.addLineClass( + 1, + "text", + "editor-surface-text-class editor-surface-text-extra" + ); + codeMirror.addLineClass( + lineHandle, + "background", + "editor-surface-background-class" + ); + codeMirror.addLineClass( + lineHandle, + "wrap", + "editor-surface-wrap-class" + ); + codeMirror.addLineClass( + lineHandle, + "gutter", + "editor-surface-gutter-class" + ); + + await awaitsFor(function () { + const line = Array.from(root.querySelectorAll(".cm-line")) + .find(function (element) { + return element.textContent === "second"; + }); + return line && + line.classList.contains("editor-surface-text-class") && + line.classList.contains("editor-surface-background-class") && + line.classList.contains("editor-surface-wrap-class") && + root.querySelector( + ".cm-gutterElement.editor-surface-wrap-class" + ) && + root.querySelector( + ".cm-gutterElement.editor-surface-gutter-class" + ); + }, `${ENGINE_LABEL} legacy line classes should be rendered`); + + expect(root.querySelector( + ".cm-gutterElement.editor-surface-background-class" + )).toBeNull(); + codeMirror.removeLineClass( + lineHandle, + "text", + "editor-surface-text-class" + ); + expect(codeMirror.lineInfo(lineHandle).textClass) + .toBe("editor-surface-text-extra"); + + await awaitsFor(function () { + const line = Array.from(root.querySelectorAll(".cm-line")) + .find(function (element) { + return element.textContent === "second"; + }); + return line && + !line.classList.contains("editor-surface-text-class") && + line.classList.contains("editor-surface-text-extra"); + }, `${ENGINE_LABEL} one legacy line-class token should be removed`); + }); + + it("re-emits renderLine after an explicit editor refresh", async function () { + createEditor("first\nsecond"); + + showEditor(); + const codeMirror = editor._codeMirror; + let renderCount = 0; + codeMirror.on("renderLine", function () { + renderCount++; + }); + + codeMirror.refresh(); + await awaitsFor(function () { + return renderCount >= 2; + }, `${ENGINE_LABEL} refresh should render visible lines`); + const firstRenderCount = renderCount; + + codeMirror.refresh(); + await awaitsFor(function () { + return renderCount >= firstRenderCount + 2; + }, `${ENGINE_LABEL} repeated refresh should rerender visible lines`); + }); + + it("emits viewport and render-line updates after CM6 geometry changes", async function () { + const content = Array.from({ length: 120 }, function (_value, index) { + return `line ${index}`; + }).join("\n"); + createEditor(content); + + const root = showEditor(600, 180); + const codeMirror = editor._codeMirror; + await awaitsFor(function () { + return codeMirror.getViewport().to > codeMirror.getViewport().from; + }, `${ENGINE_LABEL} editor should expose an initial viewport`); + + codeMirror._lastViewport = codeMirror.getViewport(); + const viewportEvents = []; + let renderCount = 0; + codeMirror.on("viewportChange", function (_instance, from, to) { + viewportEvents.push({from: from, to: to}); + }); + codeMirror.on("renderLine", function () { + renderCount++; + }); + + root.style.height = "70px"; + codeMirror._view.requestMeasure(); + + await awaitsFor(function () { + return viewportEvents.length > 0 && renderCount > 0; + }, `${ENGINE_LABEL} geometry changes should refresh viewport consumers`); + expect(viewportEvents[viewportEvents.length - 1]) + .toEqual(codeMirror.getViewport()); + }); + + it("keeps a secondary full editor synchronized", function () { + createEditor("alpha\nbeta"); + secondaryHolder = SpecRunnerUtils.createMockElement() + .css({ width: "600px", height: "180px" }); + secondaryEditor = new Editor( + testDocument, + false, + secondaryHolder.get(0) + ); + + const eventOrder = []; + editor.on(`editorChange${EVENT_NAMESPACE}`, function () { + eventOrder.push("master.editorChange"); + }); + testDocument.on(`change${EVENT_NAMESPACE}`, function () { + eventOrder.push("document.change"); + }); + DocumentModule.on(`documentChange${EVENT_NAMESPACE}`, function (_event, changedDocument) { + if (changedDocument === testDocument) { + eventOrder.push("Document.documentChange"); + } + }); + secondaryEditor.on(`editorChange${EVENT_NAMESPACE}`, function () { + eventOrder.push("secondary.editorChange"); + }); + + secondaryEditor.replaceRange( + "BETA", + { line: 1, ch: 0 }, + { line: 1, ch: 4 }, + "+input" + ); + + expect(editor.document.getText()).toBe("alpha\nBETA"); + expect(editor.getTextBetween( + { line: 0, ch: 0 }, + { line: 1, ch: 4 } + )).toBe("alpha\nBETA"); + expect(eventOrder).toEqual([ + "master.editorChange", + "document.change", + "Document.documentChange", + "secondary.editorChange" + ]); + }); + + it("provides finite geometry and restores scroll position", async function () { + const content = Array.from({ length: 200 }, function (_value, index) { + return `line ${index}`; + }).join("\n"); + createEditor(content); + + const root = editor.getRootElement(); + root.parentElement.style.display = "block"; + root.parentElement.style.height = "180px"; + editor.setSize(600, 180); + editor.refresh(); + + await awaitsFor(function () { + const rect = root.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0 && editor.getTextHeight() > 0; + }, `${ENGINE_LABEL} editor should have measurable geometry`); + + const coordinates = editor.charCoords({ line: 1, ch: 1 }, "local"); + expect(Number.isFinite(coordinates.left)).toBe(true); + expect(Number.isFinite(coordinates.top)).toBe(true); + expect(coordinates.right).toBeGreaterThanOrEqual(coordinates.left); + expect(coordinates.bottom).toBeGreaterThan(coordinates.top); + + const roundTripPosition = editor.coordsChar({ + left: coordinates.left, + top: (coordinates.top + coordinates.bottom) / 2 + }, "local"); + expect(roundTripPosition.line).toBe(1); + expect(Math.abs(roundTripPosition.ch - 1)).toBeLessThanOrEqual(1); + + editor.setScrollPos(0, 120); + await awaitsFor(function () { + return editor.getScrollPos().y > 0; + }, `${ENGINE_LABEL} editor should scroll`); + + const viewport = editor.getViewport(); + expect(viewport.from).toBeLessThanOrEqual(viewport.to); + }); + + it("keeps local coordinates stable while scrolling and sizes end coordinates", async function () { + const content = Array.from({ length: 80 }, function (_value, index) { + return `line ${index}`; + }).join("\n"); + createEditor(content); + + const root = showEditor(); + await awaitsFor(function () { + const rect = root.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0 && editor.getTextHeight() > 0; + }, `${ENGINE_LABEL} editor should have measurable geometry`); + + const position = { line: 10, ch: 2 }; + const coordinatesBeforeScroll = editor.charCoords(position, "local"); + editor.setScrollPos(0, 100); + await awaitsFor(function () { + return editor.getScrollPos().y > 0; + }, `${ENGINE_LABEL} editor should scroll before checking local coordinates`); + const coordinatesAfterScroll = editor.charCoords(position, "local"); + + expect(Math.abs( + coordinatesAfterScroll.left - coordinatesBeforeScroll.left + )).toBeLessThanOrEqual(1); + expect(Math.abs( + coordinatesAfterScroll.top - coordinatesBeforeScroll.top + )).toBeLessThanOrEqual(1); + expect(Math.abs( + coordinatesAfterScroll.bottom - coordinatesBeforeScroll.bottom + )).toBeLessThanOrEqual(1); + + const lastLine = editor.lineCount() - 1; + const endCoordinates = editor.charCoords({ + line: lastLine, + ch: editor.getLine(lastLine).length + }, "local"); + const endHeight = endCoordinates.bottom - endCoordinates.top; + expect(Number.isFinite(endHeight)).toBe(true); + expect(Math.abs( + endHeight - editor._codeMirror.defaultTextHeight() + )).toBeLessThanOrEqual(1); + }); + }); + }); +}); diff --git a/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorAllAddons/main.js b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorAllAddons/main.js new file mode 100644 index 0000000000..4f3af31692 --- /dev/null +++ b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorAllAddons/main.js @@ -0,0 +1,155 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global define, brackets, window*/ + +define([ + "thirdparty/CodeMirror/addon/dialog/dialog", + "thirdparty/CodeMirror2/addon/display/autorefresh", + "thirdparty/CodeMirror/addon/display/fullscreen", + "thirdparty/CodeMirror2/addon/display/panel", + "thirdparty/CodeMirror/addon/edit/continuelist", + "thirdparty/CodeMirror2/addon/fold/foldcode", + "thirdparty/CodeMirror/addon/fold/foldgutter", + "thirdparty/CodeMirror2/addon/fold/indent-fold", + "thirdparty/CodeMirror/addon/hint/css-hint", + "thirdparty/CodeMirror2/addon/hint/html-hint", + "thirdparty/CodeMirror/addon/hint/javascript-hint", + "thirdparty/CodeMirror2/addon/hint/sql-hint", + "thirdparty/CodeMirror/addon/hint/xml-hint", + "thirdparty/CodeMirror2/addon/lint/coffeescript-lint", + "thirdparty/CodeMirror/addon/lint/css-lint", + "thirdparty/CodeMirror2/addon/lint/html-lint", + "thirdparty/CodeMirror/addon/lint/javascript-lint", + "thirdparty/CodeMirror2/addon/lint/json-lint", + "thirdparty/CodeMirror/addon/lint/lint", + "thirdparty/CodeMirror2/addon/lint/yaml-lint", + "thirdparty/CodeMirror/addon/merge/merge", + "thirdparty/CodeMirror2/addon/mode/loadmode", + "thirdparty/CodeMirror/addon/mode/multiplex_test", + "thirdparty/CodeMirror2/addon/runmode/colorize", + "thirdparty/CodeMirror/addon/runmode/runmode-standalone", + "thirdparty/CodeMirror2/addon/runmode/runmode.node", + "thirdparty/CodeMirror/addon/scroll/simplescrollbars", + "thirdparty/CodeMirror2/addon/selection/selection-pointer", + "thirdparty/CodeMirror/addon/tern/tern", + "thirdparty/CodeMirror2/addon/tern/worker", + "thirdparty/CodeMirror/addon/wrap/hardwrap", + "thirdparty/CodeMirror2/keymap/emacs", + "text!thirdparty/CodeMirror/addon/dialog/dialog.css", + "text!thirdparty/CodeMirror2/addon/display/fullscreen.css", + "text!thirdparty/CodeMirror/addon/fold/foldgutter.css", + "text!thirdparty/CodeMirror2/addon/hint/show-hint.css", + "text!thirdparty/CodeMirror/addon/lint/lint.css", + "text!thirdparty/CodeMirror2/addon/merge/merge.css", + "text!thirdparty/CodeMirror/addon/scroll/simplescrollbars.css", + "text!thirdparty/CodeMirror2/addon/search/match-highlighter.css", + "text!thirdparty/CodeMirror/addon/search/matchesonscrollbar.css", + "text!thirdparty/CodeMirror2/addon/tern/tern.css", + "text!thirdparty/CodeMirror/lib/codemirror.css", + "text!thirdparty/CodeMirror2/mode/tiddlywiki/tiddlywiki.css", + "text!thirdparty/CodeMirror/mode/tiki/tiki.css" +], function () { + const CodeMirror = brackets.getModule( + "thirdparty/CodeMirror/lib/codemirror" + ); + const dependencies = Array.prototype.slice.call(arguments); + const moduleCount = 32; + + window.extensionLoaderLegacyCodeMirrorAllAddons = { + allModulesUseFacade: dependencies.slice(0, moduleCount).every( + function (legacyModule) { + return legacyModule === CodeMirror; + } + ), + allStylesAreVirtual: dependencies.slice(moduleCount).every( + function (styleText) { + return styleText.indexOf( + "Phoenix CodeMirror 6 compatibility" + ) !== -1; + } + ), + hasDialogAPI: [ + "openConfirm", + "openDialog", + "openNotification" + ].every(function (methodName) { + return typeof CodeMirror.prototype[methodName] === "function"; + }), + hasDisplayAPI: Boolean( + CodeMirror.optionHandlers.autoRefresh && + CodeMirror.optionHandlers.fullScreen && + typeof CodeMirror.prototype.addPanel === "function" + ), + hasFoldAPI: Boolean( + typeof CodeMirror.prototype.foldCode === "function" && + typeof CodeMirror.prototype.foldOption === "function" && + typeof CodeMirror.prototype.isFolded === "function" && + typeof CodeMirror.fold.indent === "function" + ), + hasHintProviders: [ + "coffeescript", + "css", + "html", + "javascript", + "sql", + "xml" + ].every(function (helperName) { + return typeof CodeMirror.hint[helperName] === "function"; + }), + hasLintAPI: Boolean( + CodeMirror.optionHandlers.lint && + typeof CodeMirror.prototype.performLint === "function" && + CodeMirror.lint + ), + hasMergeAPI: Boolean( + typeof CodeMirror.MergeView === "function" && + typeof CodeMirror.commands.goNextDiff === "function" && + typeof CodeMirror.commands.goPrevDiff === "function" + ), + hasModeLoaderAPI: Boolean( + typeof CodeMirror.modeURL === "string" && + typeof CodeMirror.requireMode === "function" && + typeof CodeMirror.autoLoadMode === "function" + ), + hasRunModeAPI: Boolean( + typeof CodeMirror.runMode === "function" && + typeof CodeMirror.colorize === "function" + ), + hasScrollbarModels: Boolean( + typeof CodeMirror.scrollbarModel.simple === "function" && + typeof CodeMirror.scrollbarModel.overlay === "function" + ), + hasSelectionPointer: Boolean( + CodeMirror.optionHandlers.selectionPointer + ), + hasTernAPI: typeof CodeMirror.TernServer === "function", + hasHardWrapAPI: [ + "wrapParagraph", + "wrapParagraphsInRange", + "wrapRange" + ].every(function (methodName) { + return typeof CodeMirror.prototype[methodName] === "function"; + }), + hasEmacsKeyMap: Boolean( + CodeMirror.emacs && + CodeMirror.keyMap.emacs + ) + }; +}); diff --git a/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorFilesystem/main.js b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorFilesystem/main.js new file mode 100644 index 0000000000..78629c65e3 --- /dev/null +++ b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorFilesystem/main.js @@ -0,0 +1,306 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global $, brackets, define, module, window*/ + +define(function (require, exports, module) { + const FileSystem = brackets.getModule("filesystem/FileSystem"), + File = brackets.getModule("filesystem/File"), + Directory = brackets.getModule("filesystem/Directory"), + FileSystemError = brackets.getModule("filesystem/FileSystemError"), + FileUtils = brackets.getModule("file/FileUtils"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + function _callEntryMethod(entry, methodName, args) { + return new Promise(function (resolve) { + entry[methodName].apply(entry, args.concat(function (err) { + resolve(err || null); + })); + }); + } + + function _exists(entry) { + return new Promise(function (resolve, reject) { + entry.exists(function (err, exists) { + if (err) { + reject(err); + return; + } + resolve(exists); + }); + }); + } + + function _read(entry) { + return new Promise(function (resolve, reject) { + entry.read(function (err, content) { + if (err) { + reject(err); + return; + } + resolve(content); + }); + }); + } + + function _getContents(directory) { + return new Promise(function (resolve, reject) { + directory.getContents(function (err, entries) { + if (err) { + reject(err); + return; + } + resolve(entries); + }); + }); + } + + function _resolve(path) { + return new Promise(function (resolve) { + FileSystem.resolve(path, function (err, entry, stats) { + resolve({ + error: err || null, + entry: entry, + stats: stats + }); + }); + }); + } + + function _resolveAsync(path) { + return FileSystem.resolveAsync(path) + .then(function (result) { + return { + error: null, + entry: result.entry, + stat: result.stat + }; + }) + .catch(function (error) { + return { + error: error + }; + }); + } + + function _getRejectedError(promise) { + return promise.then(function () { + return null; + }).catch(function (error) { + return error; + }); + } + + function _metadataIsImmutable(entry) { + const metadata = { + path: entry._path, + name: entry._name, + parentPath: entry._parentPath, + id: entry._id + }; + const replacementPath = `${entry.fullPath}.changed`; + Reflect.set(entry, "_path", replacementPath); + Reflect.set(entry, "_name", "changed"); + Reflect.set(entry, "_parentPath", "/changed/"); + Reflect.set(entry, "_id", -1); + entry._setPath(replacementPath); + return entry._path === metadata.path && + entry._name === metadata.name && + entry._parentPath === metadata.parentPath && + entry._id === metadata.id; + } + + function _hasEntryReference(entry) { + return Object.getOwnPropertyNames(entry).some(function (propertyName) { + const value = entry[propertyName]; + return value && value !== entry && + (value instanceof File || value instanceof Directory); + }); + } + + exports.initExtension = function () { + const deferred = new $.Deferred(); + const applicationRoot = FileUtils.getNativeBracketsDirectoryPath(); + const vueModePath = + `${applicationRoot}/thirdparty/CodeMirror/mode/vue/vue.js`; + const themeDirectoryPath = + `${applicationRoot}/thirdparty/CodeMirror/theme`; + const unsupportedModePath = + `${applicationRoot}/thirdparty/CodeMirror/mode/not-real/not-real.js`; + const wrongModeBasenamePath = + `${applicationRoot}/thirdparty/CodeMirror/mode/vue/not-vue.js`; + const traversalPath = + `${applicationRoot}/thirdparty/CodeMirror/mode/vue/` + + "../../../../cm5-traversal-regression-does-not-exist.js"; + const unrelatedPath = ExtensionUtils.getModulePath( + module, + "thirdparty/CodeMirror/mode/vue/vue.js" + ); + const vueModeFile = FileSystem.getFileForPath(vueModePath); + const themeDirectory = + FileSystem.getDirectoryForPath(themeDirectoryPath); + const unsupportedModeFile = + FileSystem.getFileForPath(unsupportedModePath); + const wrongModeBasenameFile = + FileSystem.getFileForPath(wrongModeBasenamePath); + const traversalFile = FileSystem.getFileForPath(traversalPath); + const unrelatedFile = FileSystem.getFileForPath(unrelatedPath); + const hidesOriginalFunctions = [ + FileSystem.getFileForPath, + FileSystem.getDirectoryForPath, + FileSystem.existsAsync, + FileSystem.resolve, + FileSystem.resolveAsync + ].every(function (fileSystemFunction) { + return !Object.prototype.hasOwnProperty.call( + fileSystemFunction, + "original" + ); + }); + + Promise.all([ + _exists(vueModeFile), + _read(vueModeFile), + _getContents(themeDirectory), + _exists(unsupportedModeFile), + _exists(unrelatedFile), + _callEntryMethod(vueModeFile, "write", ["changed"]), + _callEntryMethod(vueModeFile, "rename", [`${vueModePath}.moved`]), + _callEntryMethod(vueModeFile, "unlink", []), + _callEntryMethod(vueModeFile, "moveToTrash", []), + _callEntryMethod(themeDirectory, "create", []), + _getRejectedError(vueModeFile.unlinkAsync()), + _getRejectedError(themeDirectory.createAsync()), + vueModeFile.existsAsync(), + vueModeFile.statAsync(), + themeDirectory.getContentsAsync(), + FileSystem.existsAsync(vueModePath), + FileSystem.existsAsync(unsupportedModePath), + FileSystem.existsAsync(unrelatedFile.fullPath), + _resolve(vueModePath), + FileSystem.resolveAsync(themeDirectoryPath), + _resolve(unsupportedModePath), + _resolveAsync(unrelatedFile.fullPath), + _exists(wrongModeBasenameFile), + FileSystem.existsAsync(wrongModeBasenamePath), + _resolveAsync(wrongModeBasenamePath), + _exists(traversalFile), + FileSystem.existsAsync(traversalPath), + _resolveAsync(traversalPath) + ]).then(function (results) { + const loadedLegacyAssets = + window.performance.getEntriesByType("resource") + .map(function (entry) { + return entry.name; + }) + .filter(function (resourceURL) { + return /\/thirdparty\/CodeMirror(?:2)?(?:\/|$)/.test( + resourceURL + ); + }); + window.extensionLoaderLegacyCodeMirrorFilesystem = { + vueModeExists: results[0], + vueModeIsCompatibilityModule: + results[1].indexOf("CodeMirror 6 compatibility module") !== -1, + themeEntries: results[2].length, + themeEntriesAreFiles: results[2].every(function (entry) { + return Object.getPrototypeOf(entry) === File.prototype && + entry.name.endsWith(".css"); + }), + hasMonokaiTheme: results[2].some(function (entry) { + return entry.name === "monokai.css"; + }), + unsupportedModeExists: results[3], + unrelatedPathExists: results[4], + writeError: results[5], + renameError: results[6], + unlinkError: results[7], + moveToTrashError: results[8], + createDirectoryError: results[9], + unlinkAsyncError: results[10], + createDirectoryAsyncError: results[11], + entryExistsAsync: results[12], + entryStatAsyncIsFile: results[13].isFile, + directoryContentsAsyncCount: + results[14].entries.length, + exportedExistsAsync: results[15], + unsupportedExistsAsync: results[16], + unrelatedExistsAsync: results[17], + resolveReturnsVirtualFile: + !results[18].error && + results[18].entry === vueModeFile && + results[18].stats.isFile, + resolveAsyncReturnsVirtualDirectory: + results[19].entry === themeDirectory && + results[19].stat.isDirectory, + virtualFileUsesFilePrototype: + Object.getPrototypeOf(vueModeFile) === File.prototype, + virtualDirectoryUsesDirectoryPrototype: + Object.getPrototypeOf(themeDirectory) === + Directory.prototype, + virtualEntryBackerIsInaccessible: + !Object.prototype.hasOwnProperty.call( + vueModeFile, + "_fileSystem" + ) && + vueModeFile._fileSystem === null && + !_hasEntryReference(vueModeFile) && + hidesOriginalFunctions, + virtualMetadataIsImmutable: + _metadataIsImmutable(vueModeFile) && + _metadataIsImmutable(themeDirectory), + unsupportedResolveError: results[20].error, + unrelatedResolveAsyncError: results[21].error, + wrongModeBasenameExists: results[22], + wrongModeBasenameExistsAsync: results[23], + wrongModeBasenameResolveError: results[24].error, + traversalEntryIsCanonical: + traversalFile.fullPath === + `${applicationRoot}/cm5-traversal-regression-does-not-exist.js`, + traversalEntryIsPhysical: + Object.getPrototypeOf(traversalFile) === File.prototype && + Object.prototype.hasOwnProperty.call( + traversalFile, + "_fileSystem" + ) && + !Object.prototype.hasOwnProperty.call( + traversalFile, + "read" + ), + traversalExists: results[25], + traversalExistsAsync: results[26], + traversalResolveError: results[27].error, + traversalDelegates: + results[25] === results[26] && + results[27].error === FileSystemError.NOT_FOUND, + virtualEntryIsCached: + FileSystem.getFileForPath(vueModePath) === vueModeFile && + FileSystem.getDirectoryForPath(themeDirectoryPath) === + themeDirectory, + loadedLegacyAssets: loadedLegacyAssets + }; + deferred.resolve(); + }).catch(function (error) { + deferred.reject(error); + }); + + return deferred.promise(); + }; +}); diff --git a/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorImports/main.js b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorImports/main.js new file mode 100644 index 0000000000..aec7681445 --- /dev/null +++ b/test/spec/ExtensionLoader-test-files/LegacyCodeMirrorImports/main.js @@ -0,0 +1,320 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global define, brackets, window*/ + +define([ + "thirdparty/CodeMirror", + "thirdparty/CodeMirror2", + "thirdparty/CodeMirror/lib/codemirror", + "thirdparty/CodeMirror2/lib/codemirror", + "thirdparty/CodeMirror/mode/meta", + "thirdparty/CodeMirror2/mode/meta.js?cache=1", + "thirdparty/CodeMirror/addon/display/rulers", + "thirdparty/CodeMirror/addon/search/searchcursor", + "thirdparty/CodeMirror/addon/search/match-highlighter", + "thirdparty/CodeMirror/addon/search/matchesonscrollbar", + "thirdparty/CodeMirror2/addon/scroll/annotatescrollbar", + "thirdparty/CodeMirror2/addon/edit/matchbrackets", + "thirdparty/CodeMirror/addon/edit/closebrackets", + "thirdparty/CodeMirror/addon/display/placeholder", + "thirdparty/CodeMirror/addon/hint/show-hint", + "thirdparty/CodeMirror2/addon/hint/anyword-hint", + "thirdparty/CodeMirror/addon/mode/overlay", + "thirdparty/CodeMirror2/addon/mode/multiplex", + "thirdparty/CodeMirror/addon/mode/simple", + "thirdparty/CodeMirror2/addon/scroll/scrollpastend", + "thirdparty/CodeMirror/addon/selection/active-line", + "thirdparty/CodeMirror2/addon/comment/comment", + "thirdparty/CodeMirror/addon/comment/continuecomment", + "thirdparty/CodeMirror2/addon/edit/closetag", + "thirdparty/CodeMirror/addon/edit/matchtags", + "thirdparty/CodeMirror2/addon/edit/trailingspace", + "thirdparty/CodeMirror/addon/fold/brace-fold", + "thirdparty/CodeMirror2/addon/fold/comment-fold", + "thirdparty/CodeMirror/addon/fold/markdown-fold", + "thirdparty/CodeMirror2/addon/fold/xml-fold", + "thirdparty/CodeMirror/addon/runmode/runmode", + "thirdparty/CodeMirror/addon/search/search", + "thirdparty/CodeMirror2/addon/search/jump-to-line", + "thirdparty/CodeMirror/addon/selection/mark-selection", + "thirdparty/CodeMirror2/keymap/sublime", + "thirdparty/CodeMirror/keymap/vim", + "thirdparty/CodeMirror2/keymap/vim", + "thirdparty/CodeMirror/theme/monokai", + "thirdparty/CodeMirror/mode/erlang/erlang", + "thirdparty/CodeMirror2/mode/pascal/pascal", + "thirdparty/CodeMirror/mode/cmake/cmake", + "thirdparty/CodeMirror2/mode/dockerfile/dockerfile", + "thirdparty/CodeMirror/mode/powershell/powershell", + "thirdparty/CodeMirror2/mode/protobuf/protobuf", + "thirdparty/CodeMirror/mode/r/r", + "thirdparty/CodeMirror2/mode/verilog/verilog", + "thirdparty/CodeMirror/mode/vhdl/vhdl", + "thirdparty/CodeMirror2/mode/twig/twig", + "thirdparty/CodeMirror/mode/vue/vue", + "text!thirdparty/CodeMirror/lib/codemirror.css", + "text!thirdparty/CodeMirror2/addon/fold/foldgutter.css", + "text!thirdparty/CodeMirror/addon/hint/show-hint.css", + "text!thirdparty/CodeMirror2/addon/search/match-highlighter.css", + "text!thirdparty/CodeMirror/addon/search/matchesonscrollbar.css", + "text!thirdparty/CodeMirror/theme/monokai.css" +], function ( + codeMirrorRoot, + codeMirror2Root, + codeMirrorLib, + codeMirror2Lib, + modeMeta, + modeMetaWithQuery, + rulersAddon, + searchCursorAddon, + matchHighlighterAddon, + matchesOnScrollbarAddon, + annotateScrollbarAddon, + matchBracketsAddon, + closeBracketsAddon, + placeholderAddon, + showHintAddon, + anywordHintAddon, + overlayAddon, + multiplexAddon, + simpleModeAddon, + scrollPastEndAddon, + activeLineAddon, + commentAddon, + continueCommentAddon, + closeTagAddon, + matchTagsAddon, + trailingSpaceAddon, + braceFoldAddon, + commentFoldAddon, + markdownFoldAddon, + xmlFoldAddon, + runModeAddon, + searchAddon, + jumpToLineAddon, + selectedTextAddon, + sublimeKeyMap, + vimKeyMap, + vim2KeyMap, + legacyTheme, + erlangMode, + pascalMode, + cmakeMode, + dockerfileMode, + powershellMode, + protobufMode, + rMode, + verilogMode, + vhdlMode, + twigMode, + vueMode, + legacyCoreStyles, + legacyFoldGutterStyles, + legacyHintStyles, + legacyMatchHighlighterStyles, + legacyScrollbarStyles, + legacyThemeStyles +) { + const modules = [ + codeMirrorRoot, + codeMirror2Root, + codeMirrorLib, + codeMirror2Lib, + modeMeta, + modeMetaWithQuery, + rulersAddon, + searchCursorAddon, + matchHighlighterAddon, + matchesOnScrollbarAddon, + annotateScrollbarAddon, + matchBracketsAddon, + closeBracketsAddon, + placeholderAddon, + showHintAddon, + anywordHintAddon, + overlayAddon, + multiplexAddon, + simpleModeAddon, + scrollPastEndAddon, + activeLineAddon, + commentAddon, + continueCommentAddon, + closeTagAddon, + matchTagsAddon, + trailingSpaceAddon, + braceFoldAddon, + commentFoldAddon, + markdownFoldAddon, + xmlFoldAddon, + runModeAddon, + searchAddon, + jumpToLineAddon, + selectedTextAddon, + sublimeKeyMap, + vimKeyMap, + vim2KeyMap, + legacyTheme, + erlangMode, + pascalMode, + cmakeMode, + dockerfileMode, + powershellMode, + protobufMode, + rMode, + verilogMode, + vhdlMode, + twigMode, + vueMode, + brackets.getModule("thirdparty/CodeMirror2/addon/comment/comment"), + brackets.getModule("thirdparty/CodeMirror/mode/scheme/scheme") + ]; + const legacyAssetPattern = + /\/thirdparty\/CodeMirror(?:2)?(?:\/|$)/; + const loadedLegacyAssets = window.performance.getEntriesByType("resource") + .map(function (entry) { + return entry.name; + }) + .filter(function (resourceURL) { + return legacyAssetPattern.test(resourceURL); + }); + + window.extensionLoaderLegacyCodeMirrorImports = { + addonAPIs: [ + "blockComment", + "closeTag", + "continueComment", + "findEnclosingTag", + "lineComment", + "runMode", + "scanForClosingTag", + "selectMatches", + "toMatchingTag", + "uncomment" + ].every(function (apiName) { + return typeof codeMirrorLib.prototype[apiName] === "function" || + typeof codeMirrorLib[apiName] === "function" || + typeof codeMirrorLib.commands[apiName] === "function"; + }), + foldHelpers: [ + "brace", + "brace-paren", + "comment", + "import", + "include", + "markdown", + "xml" + ].every(function (helperName) { + return typeof codeMirrorLib.fold[helperName] === "function"; + }), + allUseFacade: modules.every(function (legacyModule) { + return legacyModule === codeMirrorLib; + }), + hasAdditionalModes: [ + "cmake", + "dockerfile", + "powershell", + "protobuf", + "r", + "verilog", + "tlv", + "vhdl" + ].every(function (modeName) { + return Boolean(codeMirrorLib.modes[modeName]); + }), + hasErlangMode: Boolean(codeMirrorLib.modes.erlang), + hasHintCompatibility: Boolean( + codeMirrorLib.hint && + typeof codeMirrorLib.hint.anyword === "function" && + typeof codeMirrorLib.hint.fromList === "function" && + codeMirrorLib.hint.auto && + typeof codeMirrorLib.hint.auto.resolve === "function" && + typeof codeMirrorLib.showHint === "function" && + typeof codeMirrorLib.prototype.showHint === "function" && + typeof codeMirrorLib.commands.autocomplete === "function" + ), + hasModeMetadata: Boolean( + codeMirrorLib.modeInfo && + codeMirrorLib.modeInfo.length === 157 && + typeof codeMirrorLib.findModeByMIME === "function" && + typeof codeMirrorLib.findModeByExtension === "function" && + typeof codeMirrorLib.findModeByFileName === "function" && + typeof codeMirrorLib.findModeByName === "function" && + codeMirrorLib.findModeByMIME("application/problem+json").name === + "JSON" && + codeMirrorLib.findModeByFileName("README.md").name === + "GitHub Flavored Markdown" + ), + hasPascalMode: Boolean(codeMirrorLib.modes.pascal), + hasSchemeMode: Boolean(codeMirrorLib.modes.scheme), + hasSearchCommands: [ + "clearSearch", + "find", + "findNext", + "findPersistent", + "findPersistentNext", + "findPersistentPrev", + "findPrev", + "jumpToLine", + "replace", + "replaceAll" + ].every(function (commandName) { + return typeof codeMirrorLib.commands[commandName] === "function"; + }), + hasScrollbarAnnotations: Boolean( + typeof codeMirrorLib.prototype.annotateScrollbar === "function" && + typeof codeMirrorLib.prototype.showMatchesOnScrollbar === "function" + ), + hasTwigMode: Boolean( + codeMirrorLib.modes.twig && + codeMirrorLib.modes["twig:inner"] && + codeMirrorLib.resolveMode("text/x-twig").name === "twig" + ), + hasSublimeKeyMap: Boolean( + codeMirrorLib.keyMap.sublime && + codeMirrorLib.keyMap.pcSublime && + codeMirrorLib.keyMap.macSublime + ), + hasVimKeyMap: Boolean( + codeMirrorLib.Vim && + codeMirrorLib.keyMap.vim && + codeMirrorLib.keyMap["vim-insert"] && + codeMirrorLib.keyMap["vim-replace"] + ), + hasVueMode: Boolean( + codeMirrorLib.modes.vue && + codeMirrorLib.modes["vue-template"] && + codeMirrorLib.resolveMode("text/x-vue").name === "vue" + ), + inputStyle: codeMirrorLib.defaults.inputStyle, + legacyCoreStylesAreVirtual: + legacyCoreStyles.indexOf("CodeMirror 6 compatibility") !== -1 && + legacyFoldGutterStyles.indexOf("CodeMirror 6 compatibility") !== -1 && + legacyHintStyles.indexOf("CodeMirror 6 compatibility") !== -1 && + legacyMatchHighlighterStyles.indexOf("CodeMirror 6 compatibility") !== -1 && + legacyScrollbarStyles.indexOf("CodeMirror 6 compatibility") !== -1 && + legacyThemeStyles.indexOf("monokai theme is bundled") !== -1, + loadedLegacyAssets: loadedLegacyAssets, + trailingSpaceOption: Boolean( + codeMirrorLib.optionHandlers.showTrailingSpace + ), + version: codeMirrorLib.version + }; +}); diff --git a/test/spec/ExtensionLoader-test.js b/test/spec/ExtensionLoader-test.js index 291b3efedc..1bdddec901 100644 --- a/test/spec/ExtensionLoader-test.js +++ b/test/spec/ExtensionLoader-test.js @@ -28,8 +28,13 @@ define(function (require, exports, module) { // Load dependent modules var ExtensionLoader = require("utils/ExtensionLoader"), + FileSystemError = require("filesystem/FileSystemError"), ThemeManager = require("view/ThemeManager"), SpecRunnerUtils = require("spec/SpecRunnerUtils"); + const CodeMirror = require("editor/CodeMirrorCompat"), + CodeMirrorLegacyFileSystem = require("editor/CodeMirrorLegacyFileSystem"), + CodeMirrorLegacyModuleLoader = require("editor/CodeMirrorLegacyModuleLoader"), + CodeMirrorLegacyText = require("text"); const testPathSrc = SpecRunnerUtils.getTestPath("/spec/ExtensionLoader-test-files"); const testPath = Phoenix.isNativeApp ? Phoenix.VFS.getTauriAssetServeDir() + "tests": SpecRunnerUtils.getTempDirectory(); @@ -91,6 +96,9 @@ define(function (require, exports, module) { afterEach(function () { ExtensionLoader._setInitExtensionTimeout(origTimeout); + delete window.extensionLoaderLegacyCodeMirrorImports; + delete window.extensionLoaderLegacyCodeMirrorFilesystem; + delete window.extensionLoaderLegacyCodeMirrorAllAddons; }); it("should load a basic extension", async function () { @@ -109,6 +117,239 @@ define(function (require, exports, module) { await testLoadExtension("RequireJSConfig", "resolved"); }); + it("should install legacy filesystem compatibility after global initialization", function () { + expect(window.brackets).toBeDefined(); + expect(window.brackets.metadata).toBeDefined(); + expect(typeof window.brackets.getModule).toBe("function"); + expect(CodeMirrorLegacyFileSystem.isInstalled()).toBeTrue(); + }); + + it("should resolve legacy CodeMirror module IDs without loading CodeMirror 5", async function () { + await testLoadExtension("LegacyCodeMirrorImports", "resolved"); + + expect(window.extensionLoaderLegacyCodeMirrorImports).toEqual({ + addonAPIs: true, + allUseFacade: true, + foldHelpers: true, + hasAdditionalModes: true, + hasErlangMode: true, + hasHintCompatibility: true, + hasModeMetadata: true, + hasPascalMode: true, + hasSchemeMode: true, + hasSearchCommands: true, + hasScrollbarAnnotations: true, + hasTwigMode: true, + hasSublimeKeyMap: true, + hasVimKeyMap: true, + hasVueMode: true, + inputStyle: "contenteditable", + legacyCoreStylesAreVirtual: true, + loadedLegacyAssets: [], + trailingSpaceOption: true, + version: "5.65.16" + }); + }); + + it("should resolve every bundled legacy CodeMirror addon through the CM6 facade", async function () { + await testLoadExtension( + "LegacyCodeMirrorAllAddons", + "resolved" + ); + + expect(window.extensionLoaderLegacyCodeMirrorAllAddons).toEqual({ + allModulesUseFacade: true, + allStylesAreVirtual: true, + hasDialogAPI: true, + hasDisplayAPI: true, + hasFoldAPI: true, + hasHintProviders: true, + hasLintAPI: true, + hasMergeAPI: true, + hasModeLoaderAPI: true, + hasRunModeAPI: true, + hasScrollbarModels: true, + hasSelectionPointer: true, + hasTernAPI: true, + hasHardWrapAPI: true, + hasEmacsKeyMap: true + }); + }); + + it("should classify and strictly resolve legacy CodeMirror module IDs", function () { + expect(CodeMirrorLegacyModuleLoader.getModeName( + "thirdparty/CodeMirror2/mode/erlang/erlang" + )).toBe("erlang"); + expect(CodeMirrorLegacyModuleLoader.getModeName( + "thirdparty/CodeMirror/addon/mode/overlay" + )).toBeNull(); + expect(CodeMirrorLegacyModuleLoader.isLegacyModule( + "thirdparty/CodeMirror6/codemirror6" + )).toBeFalse(); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror2/addon/comment/comment" + )).toBe("addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/keymap/sublime" + )).toBe("sublime-keymap"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror2/keymap/vim" + )).toBe("vim-keymap"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/theme/monokai" + )).toBe("theme"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/addon/fold/brace-fold" + )).toBe("addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/addon/runmode/runmode" + )).toBe("addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/addon/hint/show-hint" + )).toBe("compat-addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror2/addon/dialog/dialog.js?cache=1" + )).toBe("extended-addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror2/addon/search/search.js?cache=1" + )).toBe("compat-addon"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror/mode/meta" + )).toBe("mode-meta"); + expect(CodeMirrorLegacyModuleLoader.getModuleType( + "thirdparty/CodeMirror2/mode/meta.js?cache=1" + )).toBe("mode-meta"); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/theme/monokai" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/mode/meta" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror2/mode/meta.js?cache=1" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/addon/hint/show-hint" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/mode/vue/vue" + )).toBe(CodeMirror); + expect(CodeMirror.resolveMode("script/x-vue").name).toBe("vue"); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/addon/search/matchesonscrollbar" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror2/addon/scroll/annotatescrollbar.js" + )).toBe(CodeMirror); + expect(CodeMirrorLegacyModuleLoader.resolveLegacyModule( + "thirdparty/CodeMirror/keymap/vim" + )).toBe(CodeMirror); + expect(CodeMirror.defaults.inputStyle).toBe("contenteditable"); + }); + + it("should virtualize supported legacy CSS and theme imports", function () { + const supportedStyles = [ + "addon/dialog/dialog.css", + "addon/display/fullscreen.css", + "addon/fold/foldgutter.css", + "addon/hint/show-hint.css", + "addon/lint/lint.css", + "addon/merge/merge.css", + "addon/scroll/simplescrollbars.css", + "addon/search/match-highlighter.css", + "addon/search/matchesonscrollbar.css", + "addon/tern/tern.css", + "lib/codemirror.css", + "mode/tiddlywiki/tiddlywiki.css", + "mode/tiki/tiki.css" + ]; + + supportedStyles.forEach(function (resourcePath, index) { + const root = index % 2 ? + "thirdparty/CodeMirror" : + "thirdparty/CodeMirror2"; + const query = index === supportedStyles.length - 1 ? + "?cache=1" : + ""; + expect(CodeMirrorLegacyText.getCompatibilityContent( + `${root}/${resourcePath}${query}` + )).toContain("CodeMirror 6 compatibility"); + }); + expect(CodeMirrorLegacyText.getCompatibilityContent( + "htmlContent/deprecated-extensions-dialog.html" + )).toBeNull(); + expect(CodeMirrorLegacyText.getCompatibilityContent( + "thirdparty/CodeMirror/theme/monokai.css" + )).toContain("monokai theme is bundled"); + expect(CodeMirrorLegacyText.legacyThemeNames.length).toBe(65); + CodeMirrorLegacyText.legacyThemeNames.forEach( + function (themeName, index) { + const root = index % 2 ? + "thirdparty/CodeMirror" : + "thirdparty/CodeMirror2"; + expect(CodeMirrorLegacyText.getCompatibilityContent( + `${root}/theme/${themeName}.css?cache=${index}` + )).toContain(`${themeName} theme is bundled`); + } + ); + expect(function () { + CodeMirrorLegacyText.getCompatibilityContent( + "thirdparty/CodeMirror/theme/not-a-stock-theme.css" + ); + }).toThrowError(/does not ship or load CM5 assets/); + expect(function () { + CodeMirrorLegacyText.getCompatibilityContent( + "thirdparty/CodeMirror/addon/hint/show-hint.js" + ); + }).toThrowError(/does not ship or load CM5 assets/); + }); + + it("should support legacy CodeMirror filesystem probes without CM5 files", async function () { + await testLoadExtension("LegacyCodeMirrorFilesystem", "resolved"); + + expect(window.extensionLoaderLegacyCodeMirrorFilesystem).toEqual({ + vueModeExists: true, + vueModeIsCompatibilityModule: true, + themeEntries: 65, + themeEntriesAreFiles: true, + hasMonokaiTheme: true, + unsupportedModeExists: false, + unrelatedPathExists: false, + writeError: FileSystemError.NOT_WRITABLE, + renameError: FileSystemError.NOT_WRITABLE, + unlinkError: FileSystemError.NOT_WRITABLE, + moveToTrashError: FileSystemError.NOT_WRITABLE, + createDirectoryError: FileSystemError.NOT_WRITABLE, + unlinkAsyncError: FileSystemError.NOT_WRITABLE, + createDirectoryAsyncError: FileSystemError.NOT_WRITABLE, + entryExistsAsync: true, + entryStatAsyncIsFile: true, + directoryContentsAsyncCount: 65, + exportedExistsAsync: true, + unsupportedExistsAsync: false, + unrelatedExistsAsync: false, + resolveReturnsVirtualFile: true, + resolveAsyncReturnsVirtualDirectory: true, + virtualFileUsesFilePrototype: true, + virtualDirectoryUsesDirectoryPrototype: true, + virtualEntryBackerIsInaccessible: true, + virtualMetadataIsImmutable: true, + unsupportedResolveError: FileSystemError.NOT_FOUND, + unrelatedResolveAsyncError: FileSystemError.NOT_FOUND, + wrongModeBasenameExists: false, + wrongModeBasenameExistsAsync: false, + wrongModeBasenameResolveError: FileSystemError.NOT_FOUND, + traversalEntryIsCanonical: true, + traversalEntryIsPhysical: true, + traversalExists: false, + traversalExistsAsync: false, + traversalResolveError: FileSystemError.NOT_FOUND, + traversalDelegates: true, + virtualEntryIsCached: true, + loadedLegacyAssets: [] + }); + }); + it("should log an error if an extension fails to init", async function () { await testLoadExtension("InitFail", "rejected", "[Extension] Error -- failed initExtension for InitFail"); }); diff --git a/test/spec/Extn-NavigationAndHistory-integ-test.js b/test/spec/Extn-NavigationAndHistory-integ-test.js index 3c0c96b8f6..4538a3238e 100644 --- a/test/spec/Extn-NavigationAndHistory-integ-test.js +++ b/test/spec/Extn-NavigationAndHistory-integ-test.js @@ -19,7 +19,7 @@ * */ -/*global describe, it, expect, beforeAll, afterAll, awaitsForDone, beforeEach, awaits, awaitsFor, path */ +/*global describe, it, expect, beforeAll, afterAll, awaitsForDone, beforeEach, awaits, awaitsFor, path, spyOn */ define(function (require, exports, module) { // Recommended to avoid reloading the integration test window Phoenix instance for each test. @@ -258,6 +258,7 @@ define(function (require, exports, module) { Commands, testWindow, MainViewManager, + EditorManager, brackets, $; @@ -269,6 +270,7 @@ define(function (require, exports, module) { CommandManager = brackets.test.CommandManager; Commands = brackets.test.Commands; MainViewManager = brackets.test.MainViewManager; + EditorManager = brackets.test.EditorManager; }, 30000); beforeEach(async ()=>{ @@ -284,6 +286,7 @@ define(function (require, exports, module) { brackets = null; $ = null; MainViewManager = null; + EditorManager = null; Commands = null; CommandManager = null; await SpecRunnerUtils.closeTestWindow(); @@ -309,7 +312,7 @@ define(function (require, exports, module) { async function _validateActiveFile(relativePath) { await awaitsFor(()=>{ - return MainViewManager.getCurrentlyViewedFile().fullPath === path.join(testProjectPath, relativePath); + return MainViewManager.getCurrentlyViewedPath() === path.join(testProjectPath, relativePath); }, "Active file to be " + relativePath); } @@ -370,5 +373,20 @@ define(function (require, exports, module) { await _validateActiveFile("test.js"); await _expectNavButton(false, true, "nav back only enabled"); }, 15000); + + it("Should ignore a destroyed previous editor on active editor change", async function () { + await navigateResetStack(); + await openFile("test.js"); + + const editor = EditorManager.getActiveEditor(); + const listSelectionsSpy = spyOn(editor._codeMirror, "listSelections").and.callThrough(); + editor._codeMirror.destroy(); + + EditorManager.trigger("activeEditorChange", null, editor); + + expect(editor._codeMirror._destroyed).toBe(true); + expect(editor._codeMirror._view).toBeNull(); + expect(listSelectionsSpy).not.toHaveBeenCalled(); + }); }); }); diff --git a/test/spec/FindReplace-integ-test.js b/test/spec/FindReplace-integ-test.js index 599021ce10..cc132b4906 100644 --- a/test/spec/FindReplace-integ-test.js +++ b/test/spec/FindReplace-integ-test.js @@ -755,8 +755,9 @@ define(function (require, exports, module) { requireExpectedMatches[0].end.ch++; // other results now include one more char requireExpectedMatches[1].end.ch++; requireExpectedMatches[2].end.ch++; - // in a new file, JS isn't color coded, so there's only one span each + 1 additional for current selection probably from ode mirror update - expectHighlightedMatches(requireExpectedMatches, 4); + // Each match is rendered once. Current-match styling is layered onto the + // selected match without requiring an additional highlight DOM node. + expectHighlightedMatches(requireExpectedMatches); expectSelection(requireExpectedMatches[0]); expectMatchIndex(0, 3); }); diff --git a/test/spec/InlineEditorProviders-integ-test.js b/test/spec/InlineEditorProviders-integ-test.js index 5c59e6f2b4..9e3819d550 100644 --- a/test/spec/InlineEditorProviders-integ-test.js +++ b/test/spec/InlineEditorProviders-integ-test.js @@ -117,7 +117,9 @@ define(function (require, exports, module) { // By the time we're called, the content of the widget should be in the DOM and have a nontrivial height. expect($.contains(testWindow.document.documentElement, inlineWidgets[0].htmlContent)).toBe(true); - expect(inlineWidgets[0].$htmlContent.height()).toBeGreaterThan(50); + await awaitsFor(function () { + return inlineWidgets[0].$htmlContent.height() > 50; + }, "inline editor to have nontrivial height", 5000); } editor = null; diff --git a/test/spec/LanguageManager-test.js b/test/spec/LanguageManager-test.js index d2e4657c01..dd738792ad 100644 --- a/test/spec/LanguageManager-test.js +++ b/test/spec/LanguageManager-test.js @@ -26,7 +26,7 @@ define(function (require, exports, module) { // Load dependent modules - var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), + var CodeMirror = require("editor/CodeMirrorCompat"), LanguageManager = require("language/LanguageManager"), PreferencesManager = require("preferences/PreferencesManager"); @@ -436,6 +436,11 @@ define(function (require, exports, module) { def = { id: id, name: "erlang", fileExtensions: ["erlang"], mode: "erlang" }, language; + // Other independent compatibility suites may have loaded the + // bundled mode already. Reset this public registry entry so + // this test still exercises LanguageManager's lazy-load path. + delete CodeMirror.modes[id]; + // erlang is not defined in the default set of languages in languages.json expect(CodeMirror.modes[id]).toBe(undefined); diff --git a/test/spec/LiveDevelopmentMultiBrowser-test.js b/test/spec/LiveDevelopmentMultiBrowser-test.js index 7d7688cc54..82824e13e1 100644 --- a/test/spec/LiveDevelopmentMultiBrowser-test.js +++ b/test/spec/LiveDevelopmentMultiBrowser-test.js @@ -67,7 +67,7 @@ define(function (require, exports, module) { if (!outerIFrame || !outerIFrame.src) { return false; } let srcURL = new URL(outerIFrame.src); return srcURL.pathname.endsWith(name) === true; - }, "waiting for name- " + name); + }, "waiting for name- " + name, 20000); // Ensure md viewer is in reader mode for tests if (name.endsWith(".md")) { _ensureMdReaderMode(); @@ -1343,6 +1343,34 @@ define(function (require, exports, module) { await endPreviewSession(); }, 30000); + it("should detach a closed editor before delayed highlight updates", async function () { + await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple1.html"]), + "SpecRunnerUtils.openProjectFiles simple1.html"); + await waitsForLiveDevelopmentToOpen(); + + const liveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(); + const editor = EditorManager.getActiveEditor(); + expect(liveDoc.editor).toBe(editor); + + await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE_ALL, { _forceClose: true }), + "closing all files"); + await awaitsFor(() => !liveDoc.editor, + "live document to detach from the closed editor"); + + expect(editor._codeMirror._destroyed).toBe(true); + expect(editor._codeMirror._view).toBeNull(); + + // Recreate a delayed callback retaining the destroyed editor. The + // live document must reject the stale CM6 adapter before reading it. + liveDoc.editor = editor; + expect(function () { + liveDoc.updateHighlight(); + }).not.toThrow(); + expect(liveDoc.editor).toBeNull(); + + await endPreviewSession(); + }, 30000); + it("should live highlight css classes highlight all elements", async function () { await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple2.html"]), "SpecRunnerUtils.openProjectFiles simple2.html"); @@ -1716,11 +1744,22 @@ define(function (require, exports, module) { }); sessionStorageSavedScrollPos = JSON.parse(sessionStorageSavedScrollPos); expect(sessionStorageSavedScrollPos.scrollY).toBe(savedWindowScrollY); + const longPageURL = _getLivePreviewIFrame().src; + await awaitsFor(() => { + const savedPosition = + testWindow._livePreviewIntegTest.getSavedScrollPosition(longPageURL); + return savedPosition && savedPosition.scrollY === savedWindowScrollY; + }, "Phoenix parent to cache the live preview scroll position"); // now switch to a different page, its scroll position should not the saved scroll pos of last page await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple1.html"]), "SpecRunnerUtils.openProjectFiles simple1.html"); - await waitsForLiveDevelopmentToOpen(); + await _waitForIframeSrc("simple1.html"); + await awaitsFor(() => { + const liveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(); + return LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_ACTIVE && + liveDoc && liveDoc.doc.file.fullPath.endsWith("/simple1.html"); + }, "live preview to finish switching to simple1.html", 20000); await forRemoteExec(`window.scrollY`, (result) => { return result !== savedWindowScrollY; }); @@ -1728,12 +1767,18 @@ define(function (require, exports, module) { // now switch back to old page and verify if the scroll position was restored await awaitsForDone(SpecRunnerUtils.openProjectFiles(["longPage.html"]), "SpecRunnerUtils.openProjectFiles longPage.html"); + await _waitForIframeSrc("longPage.html"); + await awaitsFor(() => { + const liveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(); + return LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_ACTIVE && + liveDoc && liveDoc.doc.file.fullPath.endsWith("/longPage.html"); + }, "live preview to finish switching to longPage.html", 20000); await forRemoteExec(`window.scrollY`, (result) => { return result === savedWindowScrollY; }); await endPreviewSession(); - }, 30000); + }, 150000); it("should pin live previews pin html file - 1", async function () { await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple1.html"]), diff --git a/test/spec/MarkdownSync-test.js b/test/spec/MarkdownSync-test.js new file mode 100644 index 0000000000..299ef2d2d9 --- /dev/null +++ b/test/spec/MarkdownSync-test.js @@ -0,0 +1,163 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2026 - present core.ai. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + */ + +/*global describe, it, expect, afterEach, jasmine, spyOn, window*/ + +define(function (require, exports, module) { + + const EditorManager = require("editor/EditorManager"), + MarkdownSync = require("extensionsIntegrated/Phoenix-live-preview/MarkdownSync"); + + function createCodeMirrorStub() { + return { + addLineClass: jasmine.createSpy("addLineClass"), + off: jasmine.createSpy("off"), + on: jasmine.createSpy("on"), + removeLineClass: jasmine.createSpy("removeLineClass") + }; + } + + function dispatchMarkdownMessage(iframeWindow, data) { + const event = new window.Event("message"); + Object.defineProperty(event, "data", { value: data }); + Object.defineProperty(event, "source", { value: iframeWindow }); + window.dispatchEvent(event); + } + + describe("unit:MarkdownSync", function () { + afterEach(function () { + MarkdownSync.deactivate(); + MarkdownSync.setCursorSyncEnabled(true); + }); + + it("removes listeners from the editor captured during activation", function () { + const activatedCodeMirror = createCodeMirrorStub(); + const currentCodeMirror = createCodeMirrorStub(); + const document = { + _masterEditor: { + _codeMirror: activatedCodeMirror + }, + file: { + fullPath: "/test/activated.md" + }, + off: jasmine.createSpy("document.off"), + on: jasmine.createSpy("document.on") + }; + const iframe = [{ + contentWindow: { + postMessage: jasmine.createSpy("postMessage") + } + }]; + let currentEditor = null; + + spyOn(EditorManager, "getCurrentFullEditor").and.callFake(function () { + return currentEditor; + }); + spyOn(EditorManager, "getActiveEditor").and.callFake(function () { + return currentEditor; + }); + + MarkdownSync.activate(document, iframe, "/test/"); + activatedCodeMirror.off.calls.reset(); + currentCodeMirror.off.calls.reset(); + + document._masterEditor = null; + currentEditor = { + _codeMirror: currentCodeMirror + }; + MarkdownSync.deactivate(); + + expect(activatedCodeMirror.off.calls.allArgs()).toEqual([ + ["cursorActivity", jasmine.any(Function)], + ["focus", jasmine.any(Function)], + ["change", jasmine.any(Function)], + ["scroll", jasmine.any(Function)] + ]); + expect(currentCodeMirror.off).not.toHaveBeenCalled(); + expect(document.off).toHaveBeenCalledWith( + "change", + jasmine.any(Function) + ); + }); + + it("replaces and clears the cursor-sync line highlight", function () { + const codeMirror = createCodeMirrorStub(); + const firstHandle = { line: 4 }; + const secondHandle = { line: 7 }; + const document = { + _masterEditor: { + _codeMirror: codeMirror + }, + file: { + fullPath: "/test/highlight.md" + }, + off: jasmine.createSpy("document.off"), + on: jasmine.createSpy("document.on") + }; + const iframeWindow = { + postMessage: jasmine.createSpy("postMessage") + }; + const iframe = [{ + contentWindow: iframeWindow + }]; + + codeMirror.addLineClass.and.returnValues(firstHandle, secondHandle); + MarkdownSync.setCursorSyncEnabled(true); + MarkdownSync.activate(document, iframe, "/test/"); + + dispatchMarkdownMessage(iframeWindow, { + type: "MDVIEWR_EVENT", + eventName: "mdviewrCursorLine", + sourceLine: 5 + }); + expect(codeMirror.addLineClass).toHaveBeenCalledWith( + 4, + "background", + "cm-cursor-sync-highlight" + ); + + dispatchMarkdownMessage(iframeWindow, { + type: "MDVIEWR_EVENT", + eventName: "mdviewrCursorLine", + sourceLine: 8 + }); + expect(codeMirror.removeLineClass).toHaveBeenCalledWith( + firstHandle, + "background", + "cm-cursor-sync-highlight" + ); + expect(codeMirror.addLineClass).toHaveBeenCalledWith( + 7, + "background", + "cm-cursor-sync-highlight" + ); + + dispatchMarkdownMessage(iframeWindow, { + type: "MDVIEWR_EVENT", + eventName: "mdviewrCursorSyncToggle", + enabled: false + }); + expect(codeMirror.removeLineClass).toHaveBeenCalledWith( + secondHandle, + "background", + "cm-cursor-sync-highlight" + ); + }); + }); +}); diff --git a/test/spec/QuickOpen-integ-test.js b/test/spec/QuickOpen-integ-test.js index 78a7918d4f..58c3817dea 100644 --- a/test/spec/QuickOpen-integ-test.js +++ b/test/spec/QuickOpen-integ-test.js @@ -30,6 +30,8 @@ define(function (require, exports, module) { describe("mainview:QuickOpen", function () { + const QUICK_OPEN_WAIT_TIMEOUT = 30000, + QUICK_OPEN_TEST_TIMEOUT = 300000; var testPath = SpecRunnerUtils.getTestPath("/spec/QuickOpen-test-files"); var brackets, testWindow, test$, executeCommand, EditorManager, DocumentManager, PreferencesManager; @@ -51,35 +53,45 @@ define(function (require, exports, module) { }, 30000); afterEach(async function () { + if (testWindow && test$ && getSearchField().length) { + brackets.test.MainViewManager.focusActivePane(); + await awaitsFor(function () { + return getSearchField().length === 0; + }, "Quick Open cleanup", QUICK_OPEN_WAIT_TIMEOUT); + } + + await SpecRunnerUtils.closeTestWindow(); testWindow = null; brackets = null; test$ = null; executeCommand = null; EditorManager = null; DocumentManager = null; - await SpecRunnerUtils.closeTestWindow(); }, 30000); function getSearchBar() { - return test$(".modal-bar"); + return getSearchField().closest(".modal-bar"); } function getSearchField() { - return test$(".modal-bar input[type='text']"); + return test$("#quickOpenSearch"); } function expectSearchBarOpen() { - expect(getSearchBar()[0]).toBeDefined(); + expect(getSearchBar().length).toBe(1); + expect(getSearchField().length).toBe(1); } - function enterSearchText(str, timeoutLength) { - timeoutLength = timeoutLength || 10; - + function enterSearchText(str) { expectSearchBarOpen(); + getSearchField().val(str).trigger("input"); + } - testWindow.setTimeout(function () { - getSearchField().val(str); - getSearchField().trigger("input"); - }, timeoutLength); + async function waitForSearchField() { + await awaitsFor(function () { + const $field = getSearchField(); + const $bar = $field.closest(".modal-bar"); + return $field.length === 1 && $bar.length === 1 && !$bar.hasClass("popout"); + }, "Quick Open field to be ready", QUICK_OPEN_WAIT_TIMEOUT); } function pressEnter() { @@ -90,10 +102,18 @@ define(function (require, exports, module) { SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_RETURN, "keydown", getSearchField()[0]); } - async function _forPopupVisible() { + async function _forExpectedFileResult(query, file) { await awaitsFor(function () { - return test$(".quick-search-container").is(":visible"); - }, "popup to be visible", 1000); + const $field = getSearchField(); + const $popup = test$("body > .quick-search-container:visible"); + const $highlightedResult = $popup.find("li.highlight"); + return $field.length === 1 && + $field.val() === query && + $popup.length === 1 && + $highlightedResult.length === 1 && + $highlightedResult.text().indexOf(file) !== -1 && + $highlightedResult.find(".quicksearch-namematch").length > 0; + }, "expected Quick Open result to be rendered", QUICK_OPEN_WAIT_TIMEOUT); } /** @@ -116,22 +136,20 @@ define(function (require, exports, module) { // Test quick open using a partial file name executeCommand(Commands.NAVIGATE_QUICK_OPEN); + await waitForSearchField(); - // need to set the timeout length here to ensure that it has a chance to load the file - // list. - enterSearchText(quickOpenQuery, 100); - - await awaitsFor(function () { - return getSearchField().val() === quickOpenQuery; - }, "filename entry timeout", 1000); - - await _forPopupVisible(); + enterSearchText(quickOpenQuery); + await _forExpectedFileResult(quickOpenQuery, file); pressEnter(); await awaitsFor(function () { editor = EditorManager.getCurrentFullEditor(); - return editor !== null && getSearchBar().length === 0; - }, "file opening timeout", 3000); + const currentDocument = DocumentManager.getCurrentDocument(); + return editor !== null && + currentDocument && + currentDocument.file.name === file && + getSearchField().length === 0; + }, "expected file to open", QUICK_OPEN_WAIT_TIMEOUT); $scroller = test$(editor.getScrollerElement()); @@ -142,20 +160,13 @@ define(function (require, exports, module) { if (gotoLineQuery) { // Test go to line executeCommand(Commands.NAVIGATE_GOTO_LINE); + await waitForSearchField(); enterSearchText(gotoLineQuery); - } - - if (gotoLineQuery) { - await awaitsFor(function () { - return getSearchField().val() === gotoLineQuery; - }, "goto line entry timeout", 1000); - pressEnter(); - - // wait for ModalBar to close await awaitsFor(function () { - return getSearchBar().length === 0; - }, "ModalBar close", 1000); + return getSearchField().length === 0 && + SpecRunnerUtils.editorHasCursorPosition(editor, line - 1, col - 1); + }, "expected Go to Line result to be committed", QUICK_OPEN_WAIT_TIMEOUT); } // The user enters a 1-based number, but the reported position @@ -163,9 +174,9 @@ define(function (require, exports, module) { expect(SpecRunnerUtils.editorHasCursorPosition(editor, line - 1, col - 1)).toBeTrue(); // We expect the result to be scrolled roughly to the middle of the window. - var offset = $scroller.offset().top; - var editorHeight = $scroller.height(); - var cursorPos = editor._codeMirror.cursorCoords(null, "page").bottom; + const offset = $scroller.offset().top; + const editorHeight = $scroller.height(); + const cursorPos = editor.charCoords(editor.getCursorPos(), "page").bottom; expect(cursorPos).toBeGreaterThan(editorHeight * 0.4 + offset); expect(cursorPos).toBeLessThan(editorHeight * 0.6 + offset); @@ -173,30 +184,30 @@ define(function (require, exports, module) { it("can open a file and jump to a line, centering that line on the screen", async function () { await quickOpenTest("lines", ":50", "lotsOfLines.html", 50, 1); - }, 300000); + }, QUICK_OPEN_TEST_TIMEOUT); it("can open a file and jump to a line and column, centering that line on the screen", async function () { await quickOpenTest("lines", ":50,20", "lotsOfLines.html", 50, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); it("can directly open a file in a given line and column, centering that line on the screen", async function () { await quickOpenTest("lines:150,20", null, "lotsOfLines.html", 150, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); it("can open a file and jump to a line and column with no space after comma", async function () { await quickOpenTest("lines", ":50,20", "lotsOfLines.html", 50, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); it("can open a file and jump to a line and column with space after comma", async function () { await quickOpenTest("lines", ":50, 20", "lotsOfLines.html", 50, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); it("can directly open a file with line:column format", async function () { await quickOpenTest("lines:150:20", null, "lotsOfLines.html", 150, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); it("can directly open a file with line:column format and spaces", async function () { await quickOpenTest("lines:150: 20", null, "lotsOfLines.html", 150, 20); - }); + }, QUICK_OPEN_TEST_TIMEOUT); }); }); diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index beef9a2aaa..53ec630c82 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -401,7 +401,7 @@ define(function (require, exports, module) { // future, we should fix things so that we either don't need mock documents or that this // is factored so it will just run in both. docToShim._handleEditorChange = function (event, editor, changeList) { - this.isDirty = !editor._codeMirror.isClean(); + this.isDirty = !editor.isClean(); this._notifyDocumentChange(changeList); }; docToShim.notifySaved = function () { @@ -509,7 +509,7 @@ define(function (require, exports, module) { * @param {{filename:string}} options * @return {!{doc:!Document, editor:!Editor}} */ - function createMockEditor(initialContent, languageId, visibleRange, options={}) { + function createMockEditor(initialContent, languageId, visibleRange, options = {}) { // create dummy Document, then Editor tied to it var doc = createMockDocument(initialContent, languageId, options.filename); return { doc: doc, editor: createMockEditorForDocument(doc, visibleRange) }; diff --git a/test/spec/md-editor-edit-integ-test.js b/test/spec/md-editor-edit-integ-test.js index 0da08f00f2..8e91400448 100644 --- a/test/spec/md-editor-edit-integ-test.js +++ b/test/spec/md-editor-edit-integ-test.js @@ -1068,6 +1068,17 @@ define(function (require, exports, module) { return null; } + async function _waitForListEditToSyncToCM() { + await awaitsFor(() => { + const editor = EditorManager.getActiveEditor(); + const win = _getMdIFrameWin(); + const viewerText = win && win.__getCurrentContent && + win.__getCurrentContent(); + return editor && viewerText !== ORIGINAL_LIST_MD && + editor.document.getText() === viewerText; + }, "list edit to sync back to CM"); + } + it("should clicking UL button when in OL switch list to unordered", async function () { const olLi = _findLiByText("First ordered"); expect(olLi).not.toBeNull(); @@ -1083,6 +1094,7 @@ define(function (require, exports, module) { await awaitsFor(() => { return olLi.closest("ul") !== null && olLi.closest("ol") === null; }, "ordered list to switch to unordered"); + await _waitForListEditToSyncToCM(); }, 10000); it("should clicking OL button when in UL switch list to ordered", async function () { @@ -1100,6 +1112,7 @@ define(function (require, exports, module) { await awaitsFor(() => { return ulLi.closest("ol") !== null && ulLi.closest("ul") === null; }, "unordered list to switch to ordered"); + await _waitForListEditToSyncToCM(); }, 10000); it("should UL/OL toggle preserve list content", async function () { @@ -1123,6 +1136,7 @@ define(function (require, exports, module) { const newTexts = Array.from(newList.querySelectorAll(":scope > li")) .map(li => li.textContent.trim()); expect(newTexts).toEqual(itemTexts); + await _waitForListEditToSyncToCM(); }, 10000); it("should toolbar UL button show active state when cursor in UL", async function () { diff --git a/test/spec/md-editor-integ-test.js b/test/spec/md-editor-integ-test.js index a2396a5423..22831848db 100644 --- a/test/spec/md-editor-integ-test.js +++ b/test/spec/md-editor-integ-test.js @@ -86,11 +86,23 @@ define(function (require, exports, module) { async function _focusMdContent() { const mdDoc = _getMdIFrameDoc(); const content = mdDoc.getElementById("viewer-content"); - content.focus(); + content.focus({ preventScroll: true }); await awaitsFor(() => mdDoc.activeElement === content || content.contains(mdDoc.activeElement), "md content to have focus"); } + function _flushLinkPopoverUpdate() { + const win = _getMdIFrameWin(); + expect(typeof win.__updateLinkPopoverForTest).toBe("function"); + win.__updateLinkPopoverForTest(); + } + + function _flushPendingContentChange() { + const win = _getMdIFrameWin(); + expect(typeof win.__flushPendingContentChangeForTest).toBe("function"); + win.__flushPendingContentChangeForTest(); + } + function _isMac() { return brackets.platform === "mac"; } @@ -641,6 +653,73 @@ define(function (require, exports, module) { "close all between cache tests"); }, 10000); + it("should not reapply a completed debounced edit after force-close and reopen", async function () { + await _openMdFileAndWaitForPreview("doc1.md"); + await _enterEditMode(); + + const editor = EditorManager.getActiveEditor(); + const originalText = editor.document.getText(); + const mdDoc = _getMdIFrameDoc(); + const content = mdDoc.getElementById("viewer-content"); + const heading = content.querySelector("h1"); + const transientHeading = "Transient Debounced Edit"; + + expect(editor.document.isDirty).toBeFalse(); + expect(heading.textContent).toBe("Document One"); + + heading.textContent = transientHeading; + content.dispatchEvent(new Event("input", { bubbles: true })); + + // Waiting for the CM document proves the debounce callback has + // already fired. Its timer must no longer be considered pending. + await awaitsFor(() => editor.document.getText().includes(transientHeading), + "debounced markdown edit to reach the Phoenix document"); + + await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }), + "force close edited doc1.md"); + await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple.html"]), + "switch away to simple.html"); + await _openMdFileAndWaitForPreview("doc1.md"); + + // Wait for an iframe-to-parent message queued after the file switch. + // This guarantees any stale content-change message emitted during + // handleSwitchFile() has already reached MarkdownSync. + const reopenedWin = _getMdIFrameWin(); + reopenedWin.__setEditModeForTest(true); + await awaitsFor(() => { + const reopenedContent = _getMdIFrameDoc().getElementById("viewer-content"); + return reopenedContent && reopenedContent.classList.contains("editing"); + }, "reopened markdown viewer to enter edit mode"); + + let modeChangeReceived = false; + const modeChangeHandler = function (event) { + if (event.source === reopenedWin && + event.data && event.data.type === "MDVIEWR_EVENT" && + event.data.eventName === "mdviewrEditModeChanged" && + event.data.editMode === false) { + modeChangeReceived = true; + } + }; + testWindow.addEventListener("message", modeChangeHandler); + try { + reopenedWin.__setEditModeForTest(false); + await awaitsFor(() => modeChangeReceived, + "post-reopen iframe message to reach Phoenix"); + } finally { + testWindow.removeEventListener("message", modeChangeHandler); + } + + const reopenedEditor = EditorManager.getActiveEditor(); + await _waitForMdPreviewReady(reopenedEditor); + + const reopenedContent = _getMdIFrameDoc().getElementById("viewer-content"); + expect(reopenedEditor.document.getText()).toBe(originalText); + expect(reopenedEditor.document.isDirty).toBeFalse(); + expect(reopenedWin.__getCurrentContent()).toBe(originalText); + expect(reopenedContent.querySelector("h1").textContent).toBe("Document One"); + expect(reopenedContent.textContent).not.toContain(transientHeading); + }, 15000); + it("should switch between MD files with viewer showing correct content", async function () { await _openMdFileAndWaitForPreview("doc1.md"); await awaitsFor(() => _getViewerH1Text().includes("Document One"), @@ -1254,9 +1333,26 @@ define(function (require, exports, module) { // Cursor should still be at 0 — the click while sync was off had no effect. // Re-enable cursor sync first (re-query btn in case toolbar re-rendered). const syncBtnAfter = _getMdIFrameDoc().getElementById("emb-cursor-sync"); - syncBtnAfter.click(); - await awaitsFor(() => syncBtnAfter.classList.contains("active"), - "cursor sync to be re-enabled"); + const mdIFrameWin = _getMdIFrameWin(); + let reenabledMessageReceived = false; + const reenabledHandler = function (event) { + if (event.source === mdIFrameWin && + event.data && event.data.type === "MDVIEWR_EVENT" && + event.data.eventName === "mdviewrCursorSyncToggle" && + event.data.enabled === true) { + reenabledMessageReceived = true; + } + }; + testWindow.addEventListener("message", reenabledHandler); + try { + syncBtnAfter.click(); + await awaitsFor(() => syncBtnAfter.classList.contains("active"), + "cursor sync to be re-enabled"); + await awaitsFor(() => reenabledMessageReceived, + "cursor sync re-enable message to reach Phoenix"); + } finally { + testWindow.removeEventListener("message", reenabledHandler); + } expect(_getCMCursorLine()).toBe(0); }, 10000); @@ -1652,6 +1748,7 @@ define(function (require, exports, module) { key: "ArrowRight", code: "ArrowRight", bubbles: true })); content.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); + _flushLinkPopoverUpdate(); await awaitsFor(() => { const popover = mdDoc.getElementById("link-popover"); @@ -1663,6 +1760,7 @@ define(function (require, exports, module) { popover.querySelector(".link-popover-edit-btn").click(); popover.querySelector(".link-popover-input").value = "https://edited-popover.example.com"; popover.querySelector(".link-popover-confirm-btn").click(); + _flushPendingContentChange(); await awaitsFor(() => content.querySelector("a[href='https://edited-popover.example.com']") !== null, @@ -1702,6 +1800,7 @@ define(function (require, exports, module) { key: "ArrowRight", code: "ArrowRight", bubbles: true })); content.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); + _flushLinkPopoverUpdate(); await awaitsFor(() => { const popover = mdDoc.getElementById("link-popover"); @@ -1709,6 +1808,7 @@ define(function (require, exports, module) { }, "link popover to appear"); mdDoc.getElementById("link-popover").querySelector(".link-popover-unlink-btn").click(); + _flushPendingContentChange(); await awaitsFor(() => content.querySelector("a[href*='remove-link-doc3']") === null, @@ -1776,6 +1876,7 @@ define(function (require, exports, module) { key: "ArrowRight", code: "ArrowRight", bubbles: true })); content.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); + _flushLinkPopoverUpdate(); await awaitsFor(() => { const popover = mdDoc.getElementById("link-popover"); @@ -1817,6 +1918,7 @@ define(function (require, exports, module) { key: "ArrowRight", code: "ArrowRight", bubbles: true })); content.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); + _flushLinkPopoverUpdate(); // Wait for link popover to appear await awaitsFor(() => { diff --git a/test/spec/md-editor-table-integ-test.js b/test/spec/md-editor-table-integ-test.js index 7ba97c7f00..9577b46518 100644 --- a/test/spec/md-editor-table-integ-test.js +++ b/test/spec/md-editor-table-integ-test.js @@ -555,8 +555,23 @@ define(function (require, exports, module) { } expect(hasDestructive).toBeTrue(); - // Close menu - mdDoc.dispatchEvent(new MouseEvent("click", { bubbles: true })); + // A delegated click may target the Document rather than an Element. + // Closing the menu must not assume EventTarget.closest() exists. + const mdWin = _getMdIFrameWin(); + const listenerErrors = []; + const errorHandler = (event) => { + listenerErrors.push(event.error || event.message); + event.preventDefault(); + }; + mdWin.addEventListener("error", errorHandler); + try { + mdDoc.dispatchEvent(new MouseEvent("click", { bubbles: true })); + } finally { + mdWin.removeEventListener("error", errorHandler); + } + + expect(listenerErrors).toEqual([]); + expect(menu.classList.contains("open")).toBeFalse(); }, 10000); it("should deleting table remove table-wrapper from DOM", async function () { @@ -721,4 +736,3 @@ define(function (require, exports, module) { }); }); }); -