| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362 |
- import { NodeProp, Tree, IterMode, TreeFragment, Parser, NodeType, NodeSet } from '@lezer/common';
- import { StateEffect, StateField, Facet, EditorState, countColumn, combineConfig, RangeSet, RangeSetBuilder, Prec } from '@codemirror/state';
- import { ViewPlugin, logException, Decoration, EditorView, WidgetType, gutter, GutterMarker } from '@codemirror/view';
- import { tags, tagHighlighter, highlightTree, styleTags } from '@lezer/highlight';
- import { StyleModule } from 'style-mod';
- var _a;
- /**
- Node prop stored in a parser's top syntax node to provide the
- facet that stores language-specific data for that language.
- */
- const languageDataProp = /*@__PURE__*/new NodeProp();
- /**
- Helper function to define a facet (to be added to the top syntax
- node(s) for a language via
- [`languageDataProp`](https://codemirror.net/6/docs/ref/#language.languageDataProp)), that will be
- used to associate language data with the language. You
- probably only need this when subclassing
- [`Language`](https://codemirror.net/6/docs/ref/#language.Language).
- */
- function defineLanguageFacet(baseData) {
- return Facet.define({
- combine: baseData ? values => values.concat(baseData) : undefined
- });
- }
- /**
- A language object manages parsing and per-language
- [metadata](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt). Parse data is
- managed as a [Lezer](https://lezer.codemirror.net) tree. The class
- can be used directly, via the [`LRLanguage`](https://codemirror.net/6/docs/ref/#language.LRLanguage)
- subclass for [Lezer](https://lezer.codemirror.net/) LR parsers, or
- via the [`StreamLanguage`](https://codemirror.net/6/docs/ref/#language.StreamLanguage) subclass
- for stream parsers.
- */
- class Language {
- /**
- Construct a language object. If you need to invoke this
- directly, first define a data facet with
- [`defineLanguageFacet`](https://codemirror.net/6/docs/ref/#language.defineLanguageFacet), and then
- configure your parser to [attach](https://codemirror.net/6/docs/ref/#language.languageDataProp) it
- to the language's outer syntax node.
- */
- constructor(
- /**
- The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) facet
- used for this language.
- */
- data, parser, extraExtensions = []) {
- this.data = data;
- // Kludge to define EditorState.tree as a debugging helper,
- // without the EditorState package actually knowing about
- // languages and lezer trees.
- if (!EditorState.prototype.hasOwnProperty("tree"))
- Object.defineProperty(EditorState.prototype, "tree", { get() { return syntaxTree(this); } });
- this.parser = parser;
- this.extension = [
- language.of(this),
- EditorState.languageData.of((state, pos, side) => state.facet(languageDataFacetAt(state, pos, side)))
- ].concat(extraExtensions);
- }
- /**
- Query whether this language is active at the given position.
- */
- isActiveAt(state, pos, side = -1) {
- return languageDataFacetAt(state, pos, side) == this.data;
- }
- /**
- Find the document regions that were parsed using this language.
- The returned regions will _include_ any nested languages rooted
- in this language, when those exist.
- */
- findRegions(state) {
- let lang = state.facet(language);
- if ((lang === null || lang === void 0 ? void 0 : lang.data) == this.data)
- return [{ from: 0, to: state.doc.length }];
- if (!lang || !lang.allowsNesting)
- return [];
- let result = [];
- let explore = (tree, from) => {
- if (tree.prop(languageDataProp) == this.data) {
- result.push({ from, to: from + tree.length });
- return;
- }
- let mount = tree.prop(NodeProp.mounted);
- if (mount) {
- if (mount.tree.prop(languageDataProp) == this.data) {
- if (mount.overlay)
- for (let r of mount.overlay)
- result.push({ from: r.from + from, to: r.to + from });
- else
- result.push({ from: from, to: from + tree.length });
- return;
- }
- else if (mount.overlay) {
- let size = result.length;
- explore(mount.tree, mount.overlay[0].from + from);
- if (result.length > size)
- return;
- }
- }
- for (let i = 0; i < tree.children.length; i++) {
- let ch = tree.children[i];
- if (ch instanceof Tree)
- explore(ch, tree.positions[i] + from);
- }
- };
- explore(syntaxTree(state), 0);
- return result;
- }
- /**
- Indicates whether this language allows nested languages. The
- default implementation returns true.
- */
- get allowsNesting() { return true; }
- }
- /**
- @internal
- */
- Language.setState = /*@__PURE__*/StateEffect.define();
- function languageDataFacetAt(state, pos, side) {
- let topLang = state.facet(language);
- if (!topLang)
- return null;
- let facet = topLang.data;
- if (topLang.allowsNesting) {
- for (let node = syntaxTree(state).topNode; node; node = node.enter(pos, side, IterMode.ExcludeBuffers))
- facet = node.type.prop(languageDataProp) || facet;
- }
- return facet;
- }
- /**
- A subclass of [`Language`](https://codemirror.net/6/docs/ref/#language.Language) for use with Lezer
- [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser)
- parsers.
- */
- class LRLanguage extends Language {
- constructor(data, parser) {
- super(data, parser);
- this.parser = parser;
- }
- /**
- Define a language from a parser.
- */
- static define(spec) {
- let data = defineLanguageFacet(spec.languageData);
- return new LRLanguage(data, spec.parser.configure({
- props: [languageDataProp.add(type => type.isTop ? data : undefined)]
- }));
- }
- /**
- Create a new instance of this language with a reconfigured
- version of its parser.
- */
- configure(options) {
- return new LRLanguage(this.data, this.parser.configure(options));
- }
- get allowsNesting() { return this.parser.hasWrappers(); }
- }
- /**
- Get the syntax tree for a state, which is the current (possibly
- incomplete) parse tree of the active
- [language](https://codemirror.net/6/docs/ref/#language.Language), or the empty tree if there is no
- language available.
- */
- function syntaxTree(state) {
- let field = state.field(Language.state, false);
- return field ? field.tree : Tree.empty;
- }
- /**
- Try to get a parse tree that spans at least up to `upto`. The
- method will do at most `timeout` milliseconds of work to parse
- up to that point if the tree isn't already available.
- */
- function ensureSyntaxTree(state, upto, timeout = 50) {
- var _a;
- let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
- return !parse ? null : parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
- }
- /**
- Queries whether there is a full syntax tree available up to the
- given document position. If there isn't, the background parse
- process _might_ still be working and update the tree further, but
- there is no guarantee of that—the parser will [stop
- working](https://codemirror.net/6/docs/ref/#language.syntaxParserRunning) when it has spent a
- certain amount of time or has moved beyond the visible viewport.
- Always returns false if no language has been enabled.
- */
- function syntaxTreeAvailable(state, upto = state.doc.length) {
- var _a;
- return ((_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context.isDone(upto)) || false;
- }
- /**
- Move parsing forward, and update the editor state afterwards to
- reflect the new tree. Will work for at most `timeout`
- milliseconds. Returns true if the parser managed get to the given
- position in that time.
- */
- function forceParsing(view, upto = view.viewport.to, timeout = 100) {
- let success = ensureSyntaxTree(view.state, upto, timeout);
- if (success != syntaxTree(view.state))
- view.dispatch({});
- return !!success;
- }
- /**
- Tells you whether the language parser is planning to do more
- parsing work (in a `requestIdleCallback` pseudo-thread) or has
- stopped running, either because it parsed the entire document,
- because it spent too much time and was cut off, or because there
- is no language parser enabled.
- */
- function syntaxParserRunning(view) {
- var _a;
- return ((_a = view.plugin(parseWorker)) === null || _a === void 0 ? void 0 : _a.isWorking()) || false;
- }
- // Lezer-style Input object for a Text document.
- class DocInput {
- constructor(doc, length = doc.length) {
- this.doc = doc;
- this.length = length;
- this.cursorPos = 0;
- this.string = "";
- this.cursor = doc.iter();
- }
- syncTo(pos) {
- this.string = this.cursor.next(pos - this.cursorPos).value;
- this.cursorPos = pos + this.string.length;
- return this.cursorPos - this.string.length;
- }
- chunk(pos) {
- this.syncTo(pos);
- return this.string;
- }
- get lineChunks() { return true; }
- read(from, to) {
- let stringStart = this.cursorPos - this.string.length;
- if (from < stringStart || to >= this.cursorPos)
- return this.doc.sliceString(from, to);
- else
- return this.string.slice(from - stringStart, to - stringStart);
- }
- }
- let currentContext = null;
- /**
- A parse context provided to parsers working on the editor content.
- */
- class ParseContext {
- constructor(parser,
- /**
- The current editor state.
- */
- state,
- /**
- Tree fragments that can be reused by incremental re-parses.
- */
- fragments = [],
- /**
- @internal
- */
- tree,
- /**
- @internal
- */
- treeLen,
- /**
- The current editor viewport (or some overapproximation
- thereof). Intended to be used for opportunistically avoiding
- work (in which case
- [`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.ParseContext.skipUntilInView)
- should be called to make sure the parser is restarted when the
- skipped region becomes visible).
- */
- viewport,
- /**
- @internal
- */
- skipped,
- /**
- This is where skipping parsers can register a promise that,
- when resolved, will schedule a new parse. It is cleared when
- the parse worker picks up the promise. @internal
- */
- scheduleOn) {
- this.parser = parser;
- this.state = state;
- this.fragments = fragments;
- this.tree = tree;
- this.treeLen = treeLen;
- this.viewport = viewport;
- this.skipped = skipped;
- this.scheduleOn = scheduleOn;
- this.parse = null;
- /**
- @internal
- */
- this.tempSkipped = [];
- }
- /**
- @internal
- */
- static create(parser, state, viewport) {
- return new ParseContext(parser, state, [], Tree.empty, 0, viewport, [], null);
- }
- startParse() {
- return this.parser.startParse(new DocInput(this.state.doc), this.fragments);
- }
- /**
- @internal
- */
- work(until, upto) {
- if (upto != null && upto >= this.state.doc.length)
- upto = undefined;
- if (this.tree != Tree.empty && this.isDone(upto !== null && upto !== void 0 ? upto : this.state.doc.length)) {
- this.takeTree();
- return true;
- }
- return this.withContext(() => {
- var _a;
- if (typeof until == "number") {
- let endTime = Date.now() + until;
- until = () => Date.now() > endTime;
- }
- if (!this.parse)
- this.parse = this.startParse();
- if (upto != null && (this.parse.stoppedAt == null || this.parse.stoppedAt > upto) &&
- upto < this.state.doc.length)
- this.parse.stopAt(upto);
- for (;;) {
- let done = this.parse.advance();
- if (done) {
- this.fragments = this.withoutTempSkipped(TreeFragment.addTree(done, this.fragments, this.parse.stoppedAt != null));
- this.treeLen = (_a = this.parse.stoppedAt) !== null && _a !== void 0 ? _a : this.state.doc.length;
- this.tree = done;
- this.parse = null;
- if (this.treeLen < (upto !== null && upto !== void 0 ? upto : this.state.doc.length))
- this.parse = this.startParse();
- else
- return true;
- }
- if (until())
- return false;
- }
- });
- }
- /**
- @internal
- */
- takeTree() {
- let pos, tree;
- if (this.parse && (pos = this.parse.parsedPos) >= this.treeLen) {
- if (this.parse.stoppedAt == null || this.parse.stoppedAt > pos)
- this.parse.stopAt(pos);
- this.withContext(() => { while (!(tree = this.parse.advance())) { } });
- this.treeLen = pos;
- this.tree = tree;
- this.fragments = this.withoutTempSkipped(TreeFragment.addTree(this.tree, this.fragments, true));
- this.parse = null;
- }
- }
- withContext(f) {
- let prev = currentContext;
- currentContext = this;
- try {
- return f();
- }
- finally {
- currentContext = prev;
- }
- }
- withoutTempSkipped(fragments) {
- for (let r; r = this.tempSkipped.pop();)
- fragments = cutFragments(fragments, r.from, r.to);
- return fragments;
- }
- /**
- @internal
- */
- changes(changes, newState) {
- let { fragments, tree, treeLen, viewport, skipped } = this;
- this.takeTree();
- if (!changes.empty) {
- let ranges = [];
- changes.iterChangedRanges((fromA, toA, fromB, toB) => ranges.push({ fromA, toA, fromB, toB }));
- fragments = TreeFragment.applyChanges(fragments, ranges);
- tree = Tree.empty;
- treeLen = 0;
- viewport = { from: changes.mapPos(viewport.from, -1), to: changes.mapPos(viewport.to, 1) };
- if (this.skipped.length) {
- skipped = [];
- for (let r of this.skipped) {
- let from = changes.mapPos(r.from, 1), to = changes.mapPos(r.to, -1);
- if (from < to)
- skipped.push({ from, to });
- }
- }
- }
- return new ParseContext(this.parser, newState, fragments, tree, treeLen, viewport, skipped, this.scheduleOn);
- }
- /**
- @internal
- */
- updateViewport(viewport) {
- if (this.viewport.from == viewport.from && this.viewport.to == viewport.to)
- return false;
- this.viewport = viewport;
- let startLen = this.skipped.length;
- for (let i = 0; i < this.skipped.length; i++) {
- let { from, to } = this.skipped[i];
- if (from < viewport.to && to > viewport.from) {
- this.fragments = cutFragments(this.fragments, from, to);
- this.skipped.splice(i--, 1);
- }
- }
- if (this.skipped.length >= startLen)
- return false;
- this.reset();
- return true;
- }
- /**
- @internal
- */
- reset() {
- if (this.parse) {
- this.takeTree();
- this.parse = null;
- }
- }
- /**
- Notify the parse scheduler that the given region was skipped
- because it wasn't in view, and the parse should be restarted
- when it comes into view.
- */
- skipUntilInView(from, to) {
- this.skipped.push({ from, to });
- }
- /**
- Returns a parser intended to be used as placeholder when
- asynchronously loading a nested parser. It'll skip its input and
- mark it as not-really-parsed, so that the next update will parse
- it again.
-
- When `until` is given, a reparse will be scheduled when that
- promise resolves.
- */
- static getSkippingParser(until) {
- return new class extends Parser {
- createParse(input, fragments, ranges) {
- let from = ranges[0].from, to = ranges[ranges.length - 1].to;
- let parser = {
- parsedPos: from,
- advance() {
- let cx = currentContext;
- if (cx) {
- for (let r of ranges)
- cx.tempSkipped.push(r);
- if (until)
- cx.scheduleOn = cx.scheduleOn ? Promise.all([cx.scheduleOn, until]) : until;
- }
- this.parsedPos = to;
- return new Tree(NodeType.none, [], [], to - from);
- },
- stoppedAt: null,
- stopAt() { }
- };
- return parser;
- }
- };
- }
- /**
- @internal
- */
- isDone(upto) {
- upto = Math.min(upto, this.state.doc.length);
- let frags = this.fragments;
- return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
- }
- /**
- Get the context for the current parse, or `null` if no editor
- parse is in progress.
- */
- static get() { return currentContext; }
- }
- function cutFragments(fragments, from, to) {
- return TreeFragment.applyChanges(fragments, [{ fromA: from, toA: to, fromB: from, toB: to }]);
- }
- class LanguageState {
- constructor(
- // A mutable parse state that is used to preserve work done during
- // the lifetime of a state when moving to the next state.
- context) {
- this.context = context;
- this.tree = context.tree;
- }
- apply(tr) {
- if (!tr.docChanged && this.tree == this.context.tree)
- return this;
- let newCx = this.context.changes(tr.changes, tr.state);
- // If the previous parse wasn't done, go forward only up to its
- // end position or the end of the viewport, to avoid slowing down
- // state updates with parse work beyond the viewport.
- let upto = this.context.treeLen == tr.startState.doc.length ? undefined
- : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
- if (!newCx.work(20 /* Apply */, upto))
- newCx.takeTree();
- return new LanguageState(newCx);
- }
- static init(state) {
- let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
- let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
- if (!parseState.work(20 /* Apply */, vpTo))
- parseState.takeTree();
- return new LanguageState(parseState);
- }
- }
- Language.state = /*@__PURE__*/StateField.define({
- create: LanguageState.init,
- update(value, tr) {
- for (let e of tr.effects)
- if (e.is(Language.setState))
- return e.value;
- if (tr.startState.facet(language) != tr.state.facet(language))
- return LanguageState.init(tr.state);
- return value.apply(tr);
- }
- });
- let requestIdle = (callback) => {
- let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
- return () => clearTimeout(timeout);
- };
- if (typeof requestIdleCallback != "undefined")
- requestIdle = (callback) => {
- let idle = -1, timeout = setTimeout(() => {
- idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
- }, 100 /* MinPause */);
- return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
- };
- const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending)
- ? () => navigator.scheduling.isInputPending() : null;
- const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
- constructor(view) {
- this.view = view;
- this.working = null;
- this.workScheduled = 0;
- // End of the current time chunk
- this.chunkEnd = -1;
- // Milliseconds of budget left for this chunk
- this.chunkBudget = -1;
- this.work = this.work.bind(this);
- this.scheduleWork();
- }
- update(update) {
- let cx = this.view.state.field(Language.state).context;
- if (cx.updateViewport(update.view.viewport) || this.view.viewport.to > cx.treeLen)
- this.scheduleWork();
- if (update.docChanged) {
- if (this.view.hasFocus)
- this.chunkBudget += 50 /* ChangeBonus */;
- this.scheduleWork();
- }
- this.checkAsyncSchedule(cx);
- }
- scheduleWork() {
- if (this.working)
- return;
- let { state } = this.view, field = state.field(Language.state);
- if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
- this.working = requestIdle(this.work);
- }
- work(deadline) {
- this.working = null;
- let now = Date.now();
- if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
- this.chunkEnd = now + 30000 /* ChunkTime */;
- this.chunkBudget = 3000 /* ChunkBudget */;
- }
- if (this.chunkBudget <= 0)
- return; // No more budget
- let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
- if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
- return;
- let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Slice */, deadline && !isInputPending ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
- let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
- let done = field.context.work(() => {
- return isInputPending && isInputPending() || Date.now() > endTime;
- }, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
- this.chunkBudget -= Date.now() - now;
- if (done || this.chunkBudget <= 0) {
- field.context.takeTree();
- this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
- }
- if (this.chunkBudget > 0 && !(done && !viewportFirst))
- this.scheduleWork();
- this.checkAsyncSchedule(field.context);
- }
- checkAsyncSchedule(cx) {
- if (cx.scheduleOn) {
- this.workScheduled++;
- cx.scheduleOn
- .then(() => this.scheduleWork())
- .catch(err => logException(this.view.state, err))
- .then(() => this.workScheduled--);
- cx.scheduleOn = null;
- }
- }
- destroy() {
- if (this.working)
- this.working();
- }
- isWorking() {
- return !!(this.working || this.workScheduled > 0);
- }
- }, {
- eventHandlers: { focus() { this.scheduleWork(); } }
- });
- /**
- The facet used to associate a language with an editor state. Used
- by `Language` object's `extension` property (so you don't need to
- manually wrap your languages in this). Can be used to access the
- current language on a state.
- */
- const language = /*@__PURE__*/Facet.define({
- combine(languages) { return languages.length ? languages[0] : null; },
- enables: [Language.state, parseWorker]
- });
- /**
- This class bundles a [language](https://codemirror.net/6/docs/ref/#language.Language) with an
- optional set of supporting extensions. Language packages are
- encouraged to export a function that optionally takes a
- configuration object and returns a `LanguageSupport` instance, as
- the main way for client code to use the package.
- */
- class LanguageSupport {
- /**
- Create a language support object.
- */
- constructor(
- /**
- The language object.
- */
- language,
- /**
- An optional set of supporting extensions. When nesting a
- language in another language, the outer language is encouraged
- to include the supporting extensions for its inner languages
- in its own set of support extensions.
- */
- support = []) {
- this.language = language;
- this.support = support;
- this.extension = [language, support];
- }
- }
- /**
- Language descriptions are used to store metadata about languages
- and to dynamically load them. Their main role is finding the
- appropriate language for a filename or dynamically loading nested
- parsers.
- */
- class LanguageDescription {
- constructor(
- /**
- The name of this language.
- */
- name,
- /**
- Alternative names for the mode (lowercased, includes `this.name`).
- */
- alias,
- /**
- File extensions associated with this language.
- */
- extensions,
- /**
- Optional filename pattern that should be associated with this
- language.
- */
- filename, loadFunc,
- /**
- If the language has been loaded, this will hold its value.
- */
- support = undefined) {
- this.name = name;
- this.alias = alias;
- this.extensions = extensions;
- this.filename = filename;
- this.loadFunc = loadFunc;
- this.support = support;
- this.loading = null;
- }
- /**
- Start loading the the language. Will return a promise that
- resolves to a [`LanguageSupport`](https://codemirror.net/6/docs/ref/#language.LanguageSupport)
- object when the language successfully loads.
- */
- load() {
- return this.loading || (this.loading = this.loadFunc().then(support => this.support = support, err => { this.loading = null; throw err; }));
- }
- /**
- Create a language description.
- */
- static of(spec) {
- let { load, support } = spec;
- if (!load) {
- if (!support)
- throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");
- load = () => Promise.resolve(support);
- }
- return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, load, support);
- }
- /**
- Look for a language in the given array of descriptions that
- matches the filename. Will first match
- [`filename`](https://codemirror.net/6/docs/ref/#language.LanguageDescription.filename) patterns,
- and then [extensions](https://codemirror.net/6/docs/ref/#language.LanguageDescription.extensions),
- and return the first language that matches.
- */
- static matchFilename(descs, filename) {
- for (let d of descs)
- if (d.filename && d.filename.test(filename))
- return d;
- let ext = /\.([^.]+)$/.exec(filename);
- if (ext)
- for (let d of descs)
- if (d.extensions.indexOf(ext[1]) > -1)
- return d;
- return null;
- }
- /**
- Look for a language whose name or alias matches the the given
- name (case-insensitively). If `fuzzy` is true, and no direct
- matchs is found, this'll also search for a language whose name
- or alias occurs in the string (for names shorter than three
- characters, only when surrounded by non-word characters).
- */
- static matchLanguageName(descs, name, fuzzy = true) {
- name = name.toLowerCase();
- for (let d of descs)
- if (d.alias.some(a => a == name))
- return d;
- if (fuzzy)
- for (let d of descs)
- for (let a of d.alias) {
- let found = name.indexOf(a);
- if (found > -1 && (a.length > 2 || !/\w/.test(name[found - 1]) && !/\w/.test(name[found + a.length])))
- return d;
- }
- return null;
- }
- }
- /**
- Facet that defines a way to provide a function that computes the
- appropriate indentation depth at the start of a given line, or
- `null` to indicate no appropriate indentation could be determined.
- */
- const indentService = /*@__PURE__*/Facet.define();
- /**
- Facet for overriding the unit by which indentation happens.
- Should be a string consisting either entirely of spaces or
- entirely of tabs. When not set, this defaults to 2 spaces.
- */
- const indentUnit = /*@__PURE__*/Facet.define({
- combine: values => {
- if (!values.length)
- return " ";
- if (!/^(?: +|\t+)$/.test(values[0]))
- throw new Error("Invalid indent unit: " + JSON.stringify(values[0]));
- return values[0];
- }
- });
- /**
- Return the _column width_ of an indent unit in the state.
- Determined by the [`indentUnit`](https://codemirror.net/6/docs/ref/#language.indentUnit)
- facet, and [`tabSize`](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize) when that
- contains tabs.
- */
- function getIndentUnit(state) {
- let unit = state.facet(indentUnit);
- return unit.charCodeAt(0) == 9 ? state.tabSize * unit.length : unit.length;
- }
- /**
- Create an indentation string that covers columns 0 to `cols`.
- Will use tabs for as much of the columns as possible when the
- [`indentUnit`](https://codemirror.net/6/docs/ref/#language.indentUnit) facet contains
- tabs.
- */
- function indentString(state, cols) {
- let result = "", ts = state.tabSize;
- if (state.facet(indentUnit).charCodeAt(0) == 9)
- while (cols >= ts) {
- result += "\t";
- cols -= ts;
- }
- for (let i = 0; i < cols; i++)
- result += " ";
- return result;
- }
- /**
- Get the indentation at the given position. Will first consult any
- [indent services](https://codemirror.net/6/docs/ref/#language.indentService) that are registered,
- and if none of those return an indentation, this will check the
- syntax tree for the [indent node prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp)
- and use that if found. Returns a number when an indentation could
- be determined, and null otherwise.
- */
- function getIndentation(context, pos) {
- if (context instanceof EditorState)
- context = new IndentContext(context);
- for (let service of context.state.facet(indentService)) {
- let result = service(context, pos);
- if (result != null)
- return result;
- }
- let tree = syntaxTree(context.state);
- return tree ? syntaxIndentation(context, tree, pos) : null;
- }
- /**
- Create a change set that auto-indents all lines touched by the
- given document range.
- */
- function indentRange(state, from, to) {
- let updated = Object.create(null);
- let context = new IndentContext(state, { overrideIndentation: start => { var _a; return (_a = updated[start]) !== null && _a !== void 0 ? _a : -1; } });
- let changes = [];
- for (let pos = from; pos <= to;) {
- let line = state.doc.lineAt(pos);
- let indent = getIndentation(context, line.from);
- if (indent == null)
- continue;
- if (!/\S/.test(line.text))
- indent = 0;
- let cur = /^\s*/.exec(line.text)[0];
- let norm = indentString(state, indent);
- if (cur != norm) {
- updated[line.from] = indent;
- changes.push({ from: line.from, to: line.from + cur.length, insert: norm });
- }
- pos = line.to + 1;
- }
- return state.changes(changes);
- }
- /**
- Indentation contexts are used when calling [indentation
- services](https://codemirror.net/6/docs/ref/#language.indentService). They provide helper utilities
- useful in indentation logic, and can selectively override the
- indentation reported for some lines.
- */
- class IndentContext {
- /**
- Create an indent context.
- */
- constructor(
- /**
- The editor state.
- */
- state,
- /**
- @internal
- */
- options = {}) {
- this.state = state;
- this.options = options;
- this.unit = getIndentUnit(state);
- }
- /**
- Get a description of the line at the given position, taking
- [simulated line
- breaks](https://codemirror.net/6/docs/ref/#language.IndentContext.constructor^options.simulateBreak)
- into account. If there is such a break at `pos`, the `bias`
- argument determines whether the part of the line line before or
- after the break is used.
- */
- lineAt(pos, bias = 1) {
- let line = this.state.doc.lineAt(pos);
- let { simulateBreak, simulateDoubleBreak } = this.options;
- if (simulateBreak != null && simulateBreak >= line.from && simulateBreak <= line.to) {
- if (simulateDoubleBreak && simulateBreak == pos)
- return { text: "", from: pos };
- else if (bias < 0 ? simulateBreak < pos : simulateBreak <= pos)
- return { text: line.text.slice(simulateBreak - line.from), from: simulateBreak };
- else
- return { text: line.text.slice(0, simulateBreak - line.from), from: line.from };
- }
- return line;
- }
- /**
- Get the text directly after `pos`, either the entire line
- or the next 100 characters, whichever is shorter.
- */
- textAfterPos(pos, bias = 1) {
- if (this.options.simulateDoubleBreak && pos == this.options.simulateBreak)
- return "";
- let { text, from } = this.lineAt(pos, bias);
- return text.slice(pos - from, Math.min(text.length, pos + 100 - from));
- }
- /**
- Find the column for the given position.
- */
- column(pos, bias = 1) {
- let { text, from } = this.lineAt(pos, bias);
- let result = this.countColumn(text, pos - from);
- let override = this.options.overrideIndentation ? this.options.overrideIndentation(from) : -1;
- if (override > -1)
- result += override - this.countColumn(text, text.search(/\S|$/));
- return result;
- }
- /**
- Find the column position (taking tabs into account) of the given
- position in the given string.
- */
- countColumn(line, pos = line.length) {
- return countColumn(line, this.state.tabSize, pos);
- }
- /**
- Find the indentation column of the line at the given point.
- */
- lineIndent(pos, bias = 1) {
- let { text, from } = this.lineAt(pos, bias);
- let override = this.options.overrideIndentation;
- if (override) {
- let overriden = override(from);
- if (overriden > -1)
- return overriden;
- }
- return this.countColumn(text, text.search(/\S|$/));
- }
- /**
- Returns the [simulated line
- break](https://codemirror.net/6/docs/ref/#language.IndentContext.constructor^options.simulateBreak)
- for this context, if any.
- */
- get simulatedBreak() {
- return this.options.simulateBreak || null;
- }
- }
- /**
- A syntax tree node prop used to associate indentation strategies
- with node types. Such a strategy is a function from an indentation
- context to a column number or null, where null indicates that no
- definitive indentation can be determined.
- */
- const indentNodeProp = /*@__PURE__*/new NodeProp();
- // Compute the indentation for a given position from the syntax tree.
- function syntaxIndentation(cx, ast, pos) {
- return indentFrom(ast.resolveInner(pos).enterUnfinishedNodesBefore(pos), pos, cx);
- }
- function ignoreClosed(cx) {
- return cx.pos == cx.options.simulateBreak && cx.options.simulateDoubleBreak;
- }
- function indentStrategy(tree) {
- let strategy = tree.type.prop(indentNodeProp);
- if (strategy)
- return strategy;
- let first = tree.firstChild, close;
- if (first && (close = first.type.prop(NodeProp.closedBy))) {
- let last = tree.lastChild, closed = last && close.indexOf(last.name) > -1;
- return cx => delimitedStrategy(cx, true, 1, undefined, closed && !ignoreClosed(cx) ? last.from : undefined);
- }
- return tree.parent == null ? topIndent : null;
- }
- function indentFrom(node, pos, base) {
- for (; node; node = node.parent) {
- let strategy = indentStrategy(node);
- if (strategy)
- return strategy(TreeIndentContext.create(base, pos, node));
- }
- return null;
- }
- function topIndent() { return 0; }
- /**
- Objects of this type provide context information and helper
- methods to indentation functions registered on syntax nodes.
- */
- class TreeIndentContext extends IndentContext {
- constructor(base,
- /**
- The position at which indentation is being computed.
- */
- pos,
- /**
- The syntax tree node to which the indentation strategy
- applies.
- */
- node) {
- super(base.state, base.options);
- this.base = base;
- this.pos = pos;
- this.node = node;
- }
- /**
- @internal
- */
- static create(base, pos, node) {
- return new TreeIndentContext(base, pos, node);
- }
- /**
- Get the text directly after `this.pos`, either the entire line
- or the next 100 characters, whichever is shorter.
- */
- get textAfter() {
- return this.textAfterPos(this.pos);
- }
- /**
- Get the indentation at the reference line for `this.node`, which
- is the line on which it starts, unless there is a node that is
- _not_ a parent of this node covering the start of that line. If
- so, the line at the start of that node is tried, again skipping
- on if it is covered by another such node.
- */
- get baseIndent() {
- let line = this.state.doc.lineAt(this.node.from);
- // Skip line starts that are covered by a sibling (or cousin, etc)
- for (;;) {
- let atBreak = this.node.resolve(line.from);
- while (atBreak.parent && atBreak.parent.from == atBreak.from)
- atBreak = atBreak.parent;
- if (isParent(atBreak, this.node))
- break;
- line = this.state.doc.lineAt(atBreak.from);
- }
- return this.lineIndent(line.from);
- }
- /**
- Continue looking for indentations in the node's parent nodes,
- and return the result of that.
- */
- continue() {
- let parent = this.node.parent;
- return parent ? indentFrom(parent, this.pos, this.base) : 0;
- }
- }
- function isParent(parent, of) {
- for (let cur = of; cur; cur = cur.parent)
- if (parent == cur)
- return true;
- return false;
- }
- // Check whether a delimited node is aligned (meaning there are
- // non-skipped nodes on the same line as the opening delimiter). And
- // if so, return the opening token.
- function bracketedAligned(context) {
- let tree = context.node;
- let openToken = tree.childAfter(tree.from), last = tree.lastChild;
- if (!openToken)
- return null;
- let sim = context.options.simulateBreak;
- let openLine = context.state.doc.lineAt(openToken.from);
- let lineEnd = sim == null || sim <= openLine.from ? openLine.to : Math.min(openLine.to, sim);
- for (let pos = openToken.to;;) {
- let next = tree.childAfter(pos);
- if (!next || next == last)
- return null;
- if (!next.type.isSkipped)
- return next.from < lineEnd ? openToken : null;
- pos = next.to;
- }
- }
- /**
- An indentation strategy for delimited (usually bracketed) nodes.
- Will, by default, indent one unit more than the parent's base
- indent unless the line starts with a closing token. When `align`
- is true and there are non-skipped nodes on the node's opening
- line, the content of the node will be aligned with the end of the
- opening node, like this:
- foo(bar,
- baz)
- */
- function delimitedIndent({ closing, align = true, units = 1 }) {
- return (context) => delimitedStrategy(context, align, units, closing);
- }
- function delimitedStrategy(context, align, units, closing, closedAt) {
- let after = context.textAfter, space = after.match(/^\s*/)[0].length;
- let closed = closing && after.slice(space, space + closing.length) == closing || closedAt == context.pos + space;
- let aligned = align ? bracketedAligned(context) : null;
- if (aligned)
- return closed ? context.column(aligned.from) : context.column(aligned.to);
- return context.baseIndent + (closed ? 0 : context.unit * units);
- }
- /**
- An indentation strategy that aligns a node's content to its base
- indentation.
- */
- const flatIndent = (context) => context.baseIndent;
- /**
- Creates an indentation strategy that, by default, indents
- continued lines one unit more than the node's base indentation.
- You can provide `except` to prevent indentation of lines that
- match a pattern (for example `/^else\b/` in `if`/`else`
- constructs), and you can change the amount of units used with the
- `units` option.
- */
- function continuedIndent({ except, units = 1 } = {}) {
- return (context) => {
- let matchExcept = except && except.test(context.textAfter);
- return context.baseIndent + (matchExcept ? 0 : units * context.unit);
- };
- }
- const DontIndentBeyond = 200;
- /**
- Enables reindentation on input. When a language defines an
- `indentOnInput` field in its [language
- data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt), which must hold a regular
- expression, the line at the cursor will be reindented whenever new
- text is typed and the input from the start of the line up to the
- cursor matches that regexp.
- To avoid unneccesary reindents, it is recommended to start the
- regexp with `^` (usually followed by `\s*`), and end it with `$`.
- For example, `/^\s*\}$/` will reindent when a closing brace is
- added at the start of a line.
- */
- function indentOnInput() {
- return EditorState.transactionFilter.of(tr => {
- if (!tr.docChanged || !tr.isUserEvent("input.type") && !tr.isUserEvent("input.complete"))
- return tr;
- let rules = tr.startState.languageDataAt("indentOnInput", tr.startState.selection.main.head);
- if (!rules.length)
- return tr;
- let doc = tr.newDoc, { head } = tr.newSelection.main, line = doc.lineAt(head);
- if (head > line.from + DontIndentBeyond)
- return tr;
- let lineStart = doc.sliceString(line.from, head);
- if (!rules.some(r => r.test(lineStart)))
- return tr;
- let { state } = tr, last = -1, changes = [];
- for (let { head } of state.selection.ranges) {
- let line = state.doc.lineAt(head);
- if (line.from == last)
- continue;
- last = line.from;
- let indent = getIndentation(state, line.from);
- if (indent == null)
- continue;
- let cur = /^\s*/.exec(line.text)[0];
- let norm = indentString(state, indent);
- if (cur != norm)
- changes.push({ from: line.from, to: line.from + cur.length, insert: norm });
- }
- return changes.length ? [tr, { changes, sequential: true }] : tr;
- });
- }
- /**
- A facet that registers a code folding service. When called with
- the extent of a line, such a function should return a foldable
- range that starts on that line (but continues beyond it), if one
- can be found.
- */
- const foldService = /*@__PURE__*/Facet.define();
- /**
- This node prop is used to associate folding information with
- syntax node types. Given a syntax node, it should check whether
- that tree is foldable and return the range that can be collapsed
- when it is.
- */
- const foldNodeProp = /*@__PURE__*/new NodeProp();
- /**
- [Fold](https://codemirror.net/6/docs/ref/#language.foldNodeProp) function that folds everything but
- the first and the last child of a syntax node. Useful for nodes
- that start and end with delimiters.
- */
- function foldInside(node) {
- let first = node.firstChild, last = node.lastChild;
- return first && first.to < last.from ? { from: first.to, to: last.type.isError ? node.to : last.from } : null;
- }
- function syntaxFolding(state, start, end) {
- let tree = syntaxTree(state);
- if (tree.length < end)
- return null;
- let inner = tree.resolveInner(end);
- let found = null;
- for (let cur = inner; cur; cur = cur.parent) {
- if (cur.to <= end || cur.from > end)
- continue;
- if (found && cur.from < start)
- break;
- let prop = cur.type.prop(foldNodeProp);
- if (prop && (cur.to < tree.length - 50 || tree.length == state.doc.length || !isUnfinished(cur))) {
- let value = prop(cur, state);
- if (value && value.from <= end && value.from >= start && value.to > end)
- found = value;
- }
- }
- return found;
- }
- function isUnfinished(node) {
- let ch = node.lastChild;
- return ch && ch.to == node.to && ch.type.isError;
- }
- /**
- Check whether the given line is foldable. First asks any fold
- services registered through
- [`foldService`](https://codemirror.net/6/docs/ref/#language.foldService), and if none of them return
- a result, tries to query the [fold node
- prop](https://codemirror.net/6/docs/ref/#language.foldNodeProp) of syntax nodes that cover the end
- of the line.
- */
- function foldable(state, lineStart, lineEnd) {
- for (let service of state.facet(foldService)) {
- let result = service(state, lineStart, lineEnd);
- if (result)
- return result;
- }
- return syntaxFolding(state, lineStart, lineEnd);
- }
- function mapRange(range, mapping) {
- let from = mapping.mapPos(range.from, 1), to = mapping.mapPos(range.to, -1);
- return from >= to ? undefined : { from, to };
- }
- /**
- State effect that can be attached to a transaction to fold the
- given range. (You probably only need this in exceptional
- circumstances—usually you'll just want to let
- [`foldCode`](https://codemirror.net/6/docs/ref/#language.foldCode) and the [fold
- gutter](https://codemirror.net/6/docs/ref/#language.foldGutter) create the transactions.)
- */
- const foldEffect = /*@__PURE__*/StateEffect.define({ map: mapRange });
- /**
- State effect that unfolds the given range (if it was folded).
- */
- const unfoldEffect = /*@__PURE__*/StateEffect.define({ map: mapRange });
- function selectedLines(view) {
- let lines = [];
- for (let { head } of view.state.selection.ranges) {
- if (lines.some(l => l.from <= head && l.to >= head))
- continue;
- lines.push(view.lineBlockAt(head));
- }
- return lines;
- }
- /**
- The state field that stores the folded ranges (as a [decoration
- set](https://codemirror.net/6/docs/ref/#view.DecorationSet)). Can be passed to
- [`EditorState.toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) and
- [`fromJSON`](https://codemirror.net/6/docs/ref/#state.EditorState^fromJSON) to serialize the fold
- state.
- */
- const foldState = /*@__PURE__*/StateField.define({
- create() {
- return Decoration.none;
- },
- update(folded, tr) {
- folded = folded.map(tr.changes);
- for (let e of tr.effects) {
- if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to))
- folded = folded.update({ add: [foldWidget.range(e.value.from, e.value.to)] });
- else if (e.is(unfoldEffect))
- folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
- filterFrom: e.value.from, filterTo: e.value.to });
- }
- // Clear folded ranges that cover the selection head
- if (tr.selection) {
- let onSelection = false, { head } = tr.selection.main;
- folded.between(head, head, (a, b) => { if (a < head && b > head)
- onSelection = true; });
- if (onSelection)
- folded = folded.update({
- filterFrom: head,
- filterTo: head,
- filter: (a, b) => b <= head || a >= head
- });
- }
- return folded;
- },
- provide: f => EditorView.decorations.from(f),
- toJSON(folded, state) {
- let ranges = [];
- folded.between(0, state.doc.length, (from, to) => { ranges.push(from, to); });
- return ranges;
- },
- fromJSON(value) {
- if (!Array.isArray(value) || value.length % 2)
- throw new RangeError("Invalid JSON for fold state");
- let ranges = [];
- for (let i = 0; i < value.length;) {
- let from = value[i++], to = value[i++];
- if (typeof from != "number" || typeof to != "number")
- throw new RangeError("Invalid JSON for fold state");
- ranges.push(foldWidget.range(from, to));
- }
- return Decoration.set(ranges, true);
- }
- });
- /**
- Get a [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) containing the folded ranges
- in the given state.
- */
- function foldedRanges(state) {
- return state.field(foldState, false) || RangeSet.empty;
- }
- function findFold(state, from, to) {
- var _a;
- let found = null;
- (_a = state.field(foldState, false)) === null || _a === void 0 ? void 0 : _a.between(from, to, (from, to) => {
- if (!found || found.from > from)
- found = { from, to };
- });
- return found;
- }
- function foldExists(folded, from, to) {
- let found = false;
- folded.between(from, from, (a, b) => { if (a == from && b == to)
- found = true; });
- return found;
- }
- function maybeEnable(state, other) {
- return state.field(foldState, false) ? other : other.concat(StateEffect.appendConfig.of(codeFolding()));
- }
- /**
- Fold the lines that are selected, if possible.
- */
- const foldCode = view => {
- for (let line of selectedLines(view)) {
- let range = foldable(view.state, line.from, line.to);
- if (range) {
- view.dispatch({ effects: maybeEnable(view.state, [foldEffect.of(range), announceFold(view, range)]) });
- return true;
- }
- }
- return false;
- };
- /**
- Unfold folded ranges on selected lines.
- */
- const unfoldCode = view => {
- if (!view.state.field(foldState, false))
- return false;
- let effects = [];
- for (let line of selectedLines(view)) {
- let folded = findFold(view.state, line.from, line.to);
- if (folded)
- effects.push(unfoldEffect.of(folded), announceFold(view, folded, false));
- }
- if (effects.length)
- view.dispatch({ effects });
- return effects.length > 0;
- };
- function announceFold(view, range, fold = true) {
- let lineFrom = view.state.doc.lineAt(range.from).number, lineTo = view.state.doc.lineAt(range.to).number;
- return EditorView.announce.of(`${view.state.phrase(fold ? "Folded lines" : "Unfolded lines")} ${lineFrom} ${view.state.phrase("to")} ${lineTo}.`);
- }
- /**
- Fold all top-level foldable ranges. Note that, in most cases,
- folding information will depend on the [syntax
- tree](https://codemirror.net/6/docs/ref/#language.syntaxTree), and folding everything may not work
- reliably when the document hasn't been fully parsed (either
- because the editor state was only just initialized, or because the
- document is so big that the parser decided not to parse it
- entirely).
- */
- const foldAll = view => {
- let { state } = view, effects = [];
- for (let pos = 0; pos < state.doc.length;) {
- let line = view.lineBlockAt(pos), range = foldable(state, line.from, line.to);
- if (range)
- effects.push(foldEffect.of(range));
- pos = (range ? view.lineBlockAt(range.to) : line).to + 1;
- }
- if (effects.length)
- view.dispatch({ effects: maybeEnable(view.state, effects) });
- return !!effects.length;
- };
- /**
- Unfold all folded code.
- */
- const unfoldAll = view => {
- let field = view.state.field(foldState, false);
- if (!field || !field.size)
- return false;
- let effects = [];
- field.between(0, view.state.doc.length, (from, to) => { effects.push(unfoldEffect.of({ from, to })); });
- view.dispatch({ effects });
- return true;
- };
- /**
- Default fold-related key bindings.
- - Ctrl-Shift-[ (Cmd-Alt-[ on macOS): [`foldCode`](https://codemirror.net/6/docs/ref/#language.foldCode).
- - Ctrl-Shift-] (Cmd-Alt-] on macOS): [`unfoldCode`](https://codemirror.net/6/docs/ref/#language.unfoldCode).
- - Ctrl-Alt-[: [`foldAll`](https://codemirror.net/6/docs/ref/#language.foldAll).
- - Ctrl-Alt-]: [`unfoldAll`](https://codemirror.net/6/docs/ref/#language.unfoldAll).
- */
- const foldKeymap = [
- { key: "Ctrl-Shift-[", mac: "Cmd-Alt-[", run: foldCode },
- { key: "Ctrl-Shift-]", mac: "Cmd-Alt-]", run: unfoldCode },
- { key: "Ctrl-Alt-[", run: foldAll },
- { key: "Ctrl-Alt-]", run: unfoldAll }
- ];
- const defaultConfig = {
- placeholderDOM: null,
- placeholderText: "…"
- };
- const foldConfig = /*@__PURE__*/Facet.define({
- combine(values) { return combineConfig(values, defaultConfig); }
- });
- /**
- Create an extension that configures code folding.
- */
- function codeFolding(config) {
- let result = [foldState, baseTheme$1];
- if (config)
- result.push(foldConfig.of(config));
- return result;
- }
- const foldWidget = /*@__PURE__*/Decoration.replace({ widget: /*@__PURE__*/new class extends WidgetType {
- toDOM(view) {
- let { state } = view, conf = state.facet(foldConfig);
- let onclick = (event) => {
- let line = view.lineBlockAt(view.posAtDOM(event.target));
- let folded = findFold(view.state, line.from, line.to);
- if (folded)
- view.dispatch({ effects: unfoldEffect.of(folded) });
- event.preventDefault();
- };
- if (conf.placeholderDOM)
- return conf.placeholderDOM(view, onclick);
- let element = document.createElement("span");
- element.textContent = conf.placeholderText;
- element.setAttribute("aria-label", state.phrase("folded code"));
- element.title = state.phrase("unfold");
- element.className = "cm-foldPlaceholder";
- element.onclick = onclick;
- return element;
- }
- } });
- const foldGutterDefaults = {
- openText: "⌄",
- closedText: "›",
- markerDOM: null,
- domEventHandlers: {},
- foldingChanged: () => false
- };
- class FoldMarker extends GutterMarker {
- constructor(config, open) {
- super();
- this.config = config;
- this.open = open;
- }
- eq(other) { return this.config == other.config && this.open == other.open; }
- toDOM(view) {
- if (this.config.markerDOM)
- return this.config.markerDOM(this.open);
- let span = document.createElement("span");
- span.textContent = this.open ? this.config.openText : this.config.closedText;
- span.title = view.state.phrase(this.open ? "Fold line" : "Unfold line");
- return span;
- }
- }
- /**
- Create an extension that registers a fold gutter, which shows a
- fold status indicator before foldable lines (which can be clicked
- to fold or unfold the line).
- */
- function foldGutter(config = {}) {
- let fullConfig = Object.assign(Object.assign({}, foldGutterDefaults), config);
- let canFold = new FoldMarker(fullConfig, true), canUnfold = new FoldMarker(fullConfig, false);
- let markers = ViewPlugin.fromClass(class {
- constructor(view) {
- this.from = view.viewport.from;
- this.markers = this.buildMarkers(view);
- }
- update(update) {
- if (update.docChanged || update.viewportChanged ||
- update.startState.facet(language) != update.state.facet(language) ||
- update.startState.field(foldState, false) != update.state.field(foldState, false) ||
- syntaxTree(update.startState) != syntaxTree(update.state) ||
- fullConfig.foldingChanged(update))
- this.markers = this.buildMarkers(update.view);
- }
- buildMarkers(view) {
- let builder = new RangeSetBuilder();
- for (let line of view.viewportLineBlocks) {
- let mark = findFold(view.state, line.from, line.to) ? canUnfold
- : foldable(view.state, line.from, line.to) ? canFold : null;
- if (mark)
- builder.add(line.from, line.from, mark);
- }
- return builder.finish();
- }
- });
- let { domEventHandlers } = fullConfig;
- return [
- markers,
- gutter({
- class: "cm-foldGutter",
- markers(view) { var _a; return ((_a = view.plugin(markers)) === null || _a === void 0 ? void 0 : _a.markers) || RangeSet.empty; },
- initialSpacer() {
- return new FoldMarker(fullConfig, false);
- },
- domEventHandlers: Object.assign(Object.assign({}, domEventHandlers), { click: (view, line, event) => {
- if (domEventHandlers.click && domEventHandlers.click(view, line, event))
- return true;
- let folded = findFold(view.state, line.from, line.to);
- if (folded) {
- view.dispatch({ effects: unfoldEffect.of(folded) });
- return true;
- }
- let range = foldable(view.state, line.from, line.to);
- if (range) {
- view.dispatch({ effects: foldEffect.of(range) });
- return true;
- }
- return false;
- } })
- }),
- codeFolding()
- ];
- }
- const baseTheme$1 = /*@__PURE__*/EditorView.baseTheme({
- ".cm-foldPlaceholder": {
- backgroundColor: "#eee",
- border: "1px solid #ddd",
- color: "#888",
- borderRadius: ".2em",
- margin: "0 1px",
- padding: "0 1px",
- cursor: "pointer"
- },
- ".cm-foldGutter span": {
- padding: "0 1px",
- cursor: "pointer"
- }
- });
- /**
- A highlight style associates CSS styles with higlighting
- [tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
- */
- class HighlightStyle {
- constructor(spec, options) {
- let modSpec;
- function def(spec) {
- let cls = StyleModule.newName();
- (modSpec || (modSpec = Object.create(null)))["." + cls] = spec;
- return cls;
- }
- const all = typeof options.all == "string" ? options.all : options.all ? def(options.all) : undefined;
- const scopeOpt = options.scope;
- this.scope = scopeOpt instanceof Language ? (type) => type.prop(languageDataProp) == scopeOpt.data
- : scopeOpt ? (type) => type == scopeOpt : undefined;
- this.style = tagHighlighter(spec.map(style => ({
- tag: style.tag,
- class: style.class || def(Object.assign({}, style, { tag: null }))
- })), {
- all,
- }).style;
- this.module = modSpec ? new StyleModule(modSpec) : null;
- this.themeType = options.themeType;
- }
- /**
- Create a highlighter style that associates the given styles to
- the given tags. The specs must be objects that hold a style tag
- or array of tags in their `tag` property, and either a single
- `class` property providing a static CSS class (for highlighter
- that rely on external styling), or a
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)-style
- set of CSS properties (which define the styling for those tags).
-
- The CSS rules created for a highlighter will be emitted in the
- order of the spec's properties. That means that for elements that
- have multiple tags associated with them, styles defined further
- down in the list will have a higher CSS precedence than styles
- defined earlier.
- */
- static define(specs, options) {
- return new HighlightStyle(specs, options || {});
- }
- }
- const highlighterFacet = /*@__PURE__*/Facet.define();
- const fallbackHighlighter = /*@__PURE__*/Facet.define({
- combine(values) { return values.length ? [values[0]] : null; }
- });
- function getHighlighters(state) {
- let main = state.facet(highlighterFacet);
- return main.length ? main : state.facet(fallbackHighlighter);
- }
- /**
- Wrap a highlighter in an editor extension that uses it to apply
- syntax highlighting to the editor content.
- When multiple (non-fallback) styles are provided, the styling
- applied is the union of the classes they emit.
- */
- function syntaxHighlighting(highlighter, options) {
- let ext = [treeHighlighter], themeType;
- if (highlighter instanceof HighlightStyle) {
- if (highlighter.module)
- ext.push(EditorView.styleModule.of(highlighter.module));
- themeType = highlighter.themeType;
- }
- if (options === null || options === void 0 ? void 0 : options.fallback)
- ext.push(fallbackHighlighter.of(highlighter));
- else if (themeType)
- ext.push(highlighterFacet.computeN([EditorView.darkTheme], state => {
- return state.facet(EditorView.darkTheme) == (themeType == "dark") ? [highlighter] : [];
- }));
- else
- ext.push(highlighterFacet.of(highlighter));
- return ext;
- }
- /**
- Returns the CSS classes (if any) that the highlighters active in
- the state would assign to the given style
- [tags](https://lezer.codemirror.net/docs/ref#highlight.Tag) and
- (optional) language
- [scope](https://codemirror.net/6/docs/ref/#language.HighlightStyle^define^options.scope).
- */
- function highlightingFor(state, tags, scope) {
- let highlighters = getHighlighters(state);
- let result = null;
- if (highlighters)
- for (let highlighter of highlighters) {
- if (!highlighter.scope || scope && highlighter.scope(scope)) {
- let cls = highlighter.style(tags);
- if (cls)
- result = result ? result + " " + cls : cls;
- }
- }
- return result;
- }
- class TreeHighlighter {
- constructor(view) {
- this.markCache = Object.create(null);
- this.tree = syntaxTree(view.state);
- this.decorations = this.buildDeco(view, getHighlighters(view.state));
- }
- update(update) {
- let tree = syntaxTree(update.state), highlighters = getHighlighters(update.state);
- let styleChange = highlighters != getHighlighters(update.startState);
- if (tree.length < update.view.viewport.to && !styleChange && tree.type == this.tree.type) {
- this.decorations = this.decorations.map(update.changes);
- }
- else if (tree != this.tree || update.viewportChanged || styleChange) {
- this.tree = tree;
- this.decorations = this.buildDeco(update.view, highlighters);
- }
- }
- buildDeco(view, highlighters) {
- if (!highlighters || !this.tree.length)
- return Decoration.none;
- let builder = new RangeSetBuilder();
- for (let { from, to } of view.visibleRanges) {
- highlightTree(this.tree, highlighters, (from, to, style) => {
- builder.add(from, to, this.markCache[style] || (this.markCache[style] = Decoration.mark({ class: style })));
- }, from, to);
- }
- return builder.finish();
- }
- }
- const treeHighlighter = /*@__PURE__*/Prec.high(/*@__PURE__*/ViewPlugin.fromClass(TreeHighlighter, {
- decorations: v => v.decorations
- }));
- /**
- A default highlight style (works well with light themes).
- */
- const defaultHighlightStyle = /*@__PURE__*/HighlightStyle.define([
- { tag: tags.meta,
- color: "#7a757a" },
- { tag: tags.link,
- textDecoration: "underline" },
- { tag: tags.heading,
- textDecoration: "underline",
- fontWeight: "bold" },
- { tag: tags.emphasis,
- fontStyle: "italic" },
- { tag: tags.strong,
- fontWeight: "bold" },
- { tag: tags.strikethrough,
- textDecoration: "line-through" },
- { tag: tags.keyword,
- color: "#708" },
- { tag: [tags.atom, tags.bool, tags.url, tags.contentSeparator, tags.labelName],
- color: "#219" },
- { tag: [tags.literal, tags.inserted],
- color: "#164" },
- { tag: [tags.string, tags.deleted],
- color: "#a11" },
- { tag: [tags.regexp, tags.escape, /*@__PURE__*/tags.special(tags.string)],
- color: "#e40" },
- { tag: /*@__PURE__*/tags.definition(tags.variableName),
- color: "#00f" },
- { tag: /*@__PURE__*/tags.local(tags.variableName),
- color: "#30a" },
- { tag: [tags.typeName, tags.namespace],
- color: "#085" },
- { tag: tags.className,
- color: "#167" },
- { tag: [/*@__PURE__*/tags.special(tags.variableName), tags.macroName],
- color: "#256" },
- { tag: /*@__PURE__*/tags.definition(tags.propertyName),
- color: "#00c" },
- { tag: tags.comment,
- color: "#940" },
- { tag: tags.invalid,
- color: "#f00" }
- ]);
- const baseTheme = /*@__PURE__*/EditorView.baseTheme({
- "&.cm-focused .cm-matchingBracket": { backgroundColor: "#328c8252" },
- "&.cm-focused .cm-nonmatchingBracket": { backgroundColor: "#bb555544" }
- });
- const DefaultScanDist = 10000, DefaultBrackets = "()[]{}";
- const bracketMatchingConfig = /*@__PURE__*/Facet.define({
- combine(configs) {
- return combineConfig(configs, {
- afterCursor: true,
- brackets: DefaultBrackets,
- maxScanDistance: DefaultScanDist,
- renderMatch: defaultRenderMatch
- });
- }
- });
- const matchingMark = /*@__PURE__*/Decoration.mark({ class: "cm-matchingBracket" }), nonmatchingMark = /*@__PURE__*/Decoration.mark({ class: "cm-nonmatchingBracket" });
- function defaultRenderMatch(match) {
- let decorations = [];
- let mark = match.matched ? matchingMark : nonmatchingMark;
- decorations.push(mark.range(match.start.from, match.start.to));
- if (match.end)
- decorations.push(mark.range(match.end.from, match.end.to));
- return decorations;
- }
- const bracketMatchingState = /*@__PURE__*/StateField.define({
- create() { return Decoration.none; },
- update(deco, tr) {
- if (!tr.docChanged && !tr.selection)
- return deco;
- let decorations = [];
- let config = tr.state.facet(bracketMatchingConfig);
- for (let range of tr.state.selection.ranges) {
- if (!range.empty)
- continue;
- let match = matchBrackets(tr.state, range.head, -1, config)
- || (range.head > 0 && matchBrackets(tr.state, range.head - 1, 1, config))
- || (config.afterCursor &&
- (matchBrackets(tr.state, range.head, 1, config) ||
- (range.head < tr.state.doc.length && matchBrackets(tr.state, range.head + 1, -1, config))));
- if (match)
- decorations = decorations.concat(config.renderMatch(match, tr.state));
- }
- return Decoration.set(decorations, true);
- },
- provide: f => EditorView.decorations.from(f)
- });
- const bracketMatchingUnique = [
- bracketMatchingState,
- baseTheme
- ];
- /**
- Create an extension that enables bracket matching. Whenever the
- cursor is next to a bracket, that bracket and the one it matches
- are highlighted. Or, when no matching bracket is found, another
- highlighting style is used to indicate this.
- */
- function bracketMatching(config = {}) {
- return [bracketMatchingConfig.of(config), bracketMatchingUnique];
- }
- function matchingNodes(node, dir, brackets) {
- let byProp = node.prop(dir < 0 ? NodeProp.openedBy : NodeProp.closedBy);
- if (byProp)
- return byProp;
- if (node.name.length == 1) {
- let index = brackets.indexOf(node.name);
- if (index > -1 && index % 2 == (dir < 0 ? 1 : 0))
- return [brackets[index + dir]];
- }
- return null;
- }
- /**
- Find the matching bracket for the token at `pos`, scanning
- direction `dir`. Only the `brackets` and `maxScanDistance`
- properties are used from `config`, if given. Returns null if no
- bracket was found at `pos`, or a match result otherwise.
- */
- function matchBrackets(state, pos, dir, config = {}) {
- let maxScanDistance = config.maxScanDistance || DefaultScanDist, brackets = config.brackets || DefaultBrackets;
- let tree = syntaxTree(state), node = tree.resolveInner(pos, dir);
- for (let cur = node; cur; cur = cur.parent) {
- let matches = matchingNodes(cur.type, dir, brackets);
- if (matches && cur.from < cur.to)
- return matchMarkedBrackets(state, pos, dir, cur, matches, brackets);
- }
- return matchPlainBrackets(state, pos, dir, tree, node.type, maxScanDistance, brackets);
- }
- function matchMarkedBrackets(_state, _pos, dir, token, matching, brackets) {
- let parent = token.parent, firstToken = { from: token.from, to: token.to };
- let depth = 0, cursor = parent === null || parent === void 0 ? void 0 : parent.cursor();
- if (cursor && (dir < 0 ? cursor.childBefore(token.from) : cursor.childAfter(token.to)))
- do {
- if (dir < 0 ? cursor.to <= token.from : cursor.from >= token.to) {
- if (depth == 0 && matching.indexOf(cursor.type.name) > -1 && cursor.from < cursor.to) {
- return { start: firstToken, end: { from: cursor.from, to: cursor.to }, matched: true };
- }
- else if (matchingNodes(cursor.type, dir, brackets)) {
- depth++;
- }
- else if (matchingNodes(cursor.type, -dir, brackets)) {
- depth--;
- if (depth == 0)
- return {
- start: firstToken,
- end: cursor.from == cursor.to ? undefined : { from: cursor.from, to: cursor.to },
- matched: false
- };
- }
- }
- } while (dir < 0 ? cursor.prevSibling() : cursor.nextSibling());
- return { start: firstToken, matched: false };
- }
- function matchPlainBrackets(state, pos, dir, tree, tokenType, maxScanDistance, brackets) {
- let startCh = dir < 0 ? state.sliceDoc(pos - 1, pos) : state.sliceDoc(pos, pos + 1);
- let bracket = brackets.indexOf(startCh);
- if (bracket < 0 || (bracket % 2 == 0) != (dir > 0))
- return null;
- let startToken = { from: dir < 0 ? pos - 1 : pos, to: dir > 0 ? pos + 1 : pos };
- let iter = state.doc.iterRange(pos, dir > 0 ? state.doc.length : 0), depth = 0;
- for (let distance = 0; !(iter.next()).done && distance <= maxScanDistance;) {
- let text = iter.value;
- if (dir < 0)
- distance += text.length;
- let basePos = pos + distance * dir;
- for (let pos = dir > 0 ? 0 : text.length - 1, end = dir > 0 ? text.length : -1; pos != end; pos += dir) {
- let found = brackets.indexOf(text[pos]);
- if (found < 0 || tree.resolveInner(basePos + pos, 1).type != tokenType)
- continue;
- if ((found % 2 == 0) == (dir > 0)) {
- depth++;
- }
- else if (depth == 1) { // Closing
- return { start: startToken, end: { from: basePos + pos, to: basePos + pos + 1 }, matched: (found >> 1) == (bracket >> 1) };
- }
- else {
- depth--;
- }
- }
- if (dir > 0)
- distance += text.length;
- }
- return iter.done ? { start: startToken, matched: false } : null;
- }
- // Counts the column offset in a string, taking tabs into account.
- // Used mostly to find indentation.
- function countCol(string, end, tabSize, startIndex = 0, startValue = 0) {
- if (end == null) {
- end = string.search(/[^\s\u00a0]/);
- if (end == -1)
- end = string.length;
- }
- let n = startValue;
- for (let i = startIndex; i < end; i++) {
- if (string.charCodeAt(i) == 9)
- n += tabSize - (n % tabSize);
- else
- n++;
- }
- return n;
- }
- /**
- Encapsulates a single line of input. Given to stream syntax code,
- which uses it to tokenize the content.
- */
- class StringStream {
- /**
- Create a stream.
- */
- constructor(
- /**
- The line.
- */
- string, tabSize,
- /**
- The current indent unit size.
- */
- indentUnit) {
- this.string = string;
- this.tabSize = tabSize;
- this.indentUnit = indentUnit;
- /**
- The current position on the line.
- */
- this.pos = 0;
- /**
- The start position of the current token.
- */
- this.start = 0;
- this.lastColumnPos = 0;
- this.lastColumnValue = 0;
- }
- /**
- True if we are at the end of the line.
- */
- eol() { return this.pos >= this.string.length; }
- /**
- True if we are at the start of the line.
- */
- sol() { return this.pos == 0; }
- /**
- Get the next code unit after the current position, or undefined
- if we're at the end of the line.
- */
- peek() { return this.string.charAt(this.pos) || undefined; }
- /**
- Read the next code unit and advance `this.pos`.
- */
- next() {
- if (this.pos < this.string.length)
- return this.string.charAt(this.pos++);
- }
- /**
- Match the next character against the given string, regular
- expression, or predicate. Consume and return it if it matches.
- */
- eat(match) {
- let ch = this.string.charAt(this.pos);
- let ok;
- if (typeof match == "string")
- ok = ch == match;
- else
- ok = ch && (match instanceof RegExp ? match.test(ch) : match(ch));
- if (ok) {
- ++this.pos;
- return ch;
- }
- }
- /**
- Continue matching characters that match the given string,
- regular expression, or predicate function. Return true if any
- characters were consumed.
- */
- eatWhile(match) {
- let start = this.pos;
- while (this.eat(match)) { }
- return this.pos > start;
- }
- /**
- Consume whitespace ahead of `this.pos`. Return true if any was
- found.
- */
- eatSpace() {
- let start = this.pos;
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos)))
- ++this.pos;
- return this.pos > start;
- }
- /**
- Move to the end of the line.
- */
- skipToEnd() { this.pos = this.string.length; }
- /**
- Move to directly before the given character, if found on the
- current line.
- */
- skipTo(ch) {
- let found = this.string.indexOf(ch, this.pos);
- if (found > -1) {
- this.pos = found;
- return true;
- }
- }
- /**
- Move back `n` characters.
- */
- backUp(n) { this.pos -= n; }
- /**
- Get the column position at `this.pos`.
- */
- column() {
- if (this.lastColumnPos < this.start) {
- this.lastColumnValue = countCol(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
- this.lastColumnPos = this.start;
- }
- return this.lastColumnValue;
- }
- /**
- Get the indentation column of the current line.
- */
- indentation() {
- return countCol(this.string, null, this.tabSize);
- }
- /**
- Match the input against the given string or regular expression
- (which should start with a `^`). Return true or the regexp match
- if it matches.
-
- Unless `consume` is set to `false`, this will move `this.pos`
- past the matched text.
-
- When matching a string `caseInsensitive` can be set to true to
- make the match case-insensitive.
- */
- match(pattern, consume, caseInsensitive) {
- if (typeof pattern == "string") {
- let cased = (str) => caseInsensitive ? str.toLowerCase() : str;
- let substr = this.string.substr(this.pos, pattern.length);
- if (cased(substr) == cased(pattern)) {
- if (consume !== false)
- this.pos += pattern.length;
- return true;
- }
- else
- return null;
- }
- else {
- let 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;
- }
- }
- /**
- Get the current token.
- */
- current() { return this.string.slice(this.start, this.pos); }
- }
- function fullParser(spec) {
- return {
- token: spec.token,
- blankLine: spec.blankLine || (() => { }),
- startState: spec.startState || (() => true),
- copyState: spec.copyState || defaultCopyState,
- indent: spec.indent || (() => null),
- languageData: spec.languageData || {},
- tokenTable: spec.tokenTable || noTokens
- };
- }
- function defaultCopyState(state) {
- if (typeof state != "object")
- return state;
- let newState = {};
- for (let prop in state) {
- let val = state[prop];
- newState[prop] = (val instanceof Array ? val.slice() : val);
- }
- return newState;
- }
- /**
- A [language](https://codemirror.net/6/docs/ref/#language.Language) class based on a CodeMirror
- 5-style [streaming parser](https://codemirror.net/6/docs/ref/#language.StreamParser).
- */
- class StreamLanguage extends Language {
- constructor(parser) {
- let data = defineLanguageFacet(parser.languageData);
- let p = fullParser(parser), self;
- let impl = new class extends Parser {
- createParse(input, fragments, ranges) {
- return new Parse(self, input, fragments, ranges);
- }
- };
- super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))]);
- this.topNode = docID(data);
- self = this;
- this.streamParser = p;
- this.stateAfter = new NodeProp({ perNode: true });
- this.tokenTable = parser.tokenTable ? new TokenTable(p.tokenTable) : defaultTokenTable;
- }
- /**
- Define a stream language.
- */
- static define(spec) { return new StreamLanguage(spec); }
- getIndent(cx, pos) {
- let tree = syntaxTree(cx.state), at = tree.resolve(pos);
- while (at && at.type != this.topNode)
- at = at.parent;
- if (!at)
- return null;
- let start = findState(this, tree, 0, at.from, pos), statePos, state;
- if (start) {
- state = start.state;
- statePos = start.pos + 1;
- }
- else {
- state = this.streamParser.startState(cx.unit);
- statePos = 0;
- }
- if (pos - statePos > 10000 /* MaxIndentScanDist */)
- return null;
- while (statePos < pos) {
- let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
- if (line.length) {
- let stream = new StringStream(line.text, cx.state.tabSize, cx.unit);
- while (stream.pos < end - line.from)
- readToken(this.streamParser.token, stream, state);
- }
- else {
- this.streamParser.blankLine(state, cx.unit);
- }
- if (end == pos)
- break;
- statePos = line.to + 1;
- }
- let { text } = cx.lineAt(pos);
- return this.streamParser.indent(state, /^\s*(.*)/.exec(text)[1], cx);
- }
- get allowsNesting() { return false; }
- }
- function findState(lang, tree, off, startPos, before) {
- let state = off >= startPos && off + tree.length <= before && tree.prop(lang.stateAfter);
- if (state)
- return { state: lang.streamParser.copyState(state), pos: off + tree.length };
- for (let i = tree.children.length - 1; i >= 0; i--) {
- let child = tree.children[i], pos = off + tree.positions[i];
- let found = child instanceof Tree && pos < before && findState(lang, child, pos, startPos, before);
- if (found)
- return found;
- }
- return null;
- }
- function cutTree(lang, tree, from, to, inside) {
- if (inside && from <= 0 && to >= tree.length)
- return tree;
- if (!inside && tree.type == lang.topNode)
- inside = true;
- for (let i = tree.children.length - 1; i >= 0; i--) {
- let pos = tree.positions[i], child = tree.children[i], inner;
- if (pos < to && child instanceof Tree) {
- if (!(inner = cutTree(lang, child, from - pos, to - pos, inside)))
- break;
- return !inside ? inner
- : new Tree(tree.type, tree.children.slice(0, i).concat(inner), tree.positions.slice(0, i + 1), pos + inner.length);
- }
- }
- return null;
- }
- function findStartInFragments(lang, fragments, startPos, editorState) {
- for (let f of fragments) {
- let from = f.from + (f.openStart ? 25 : 0), to = f.to - (f.openEnd ? 25 : 0);
- let found = from <= startPos && to > startPos && findState(lang, f.tree, 0 - f.offset, startPos, to), tree;
- if (found && (tree = cutTree(lang, f.tree, startPos + f.offset, found.pos + f.offset, false)))
- return { state: found.state, tree };
- }
- return { state: lang.streamParser.startState(editorState ? getIndentUnit(editorState) : 4), tree: Tree.empty };
- }
- class Parse {
- constructor(lang, input, fragments, ranges) {
- this.lang = lang;
- this.input = input;
- this.fragments = fragments;
- this.ranges = ranges;
- this.stoppedAt = null;
- this.chunks = [];
- this.chunkPos = [];
- this.chunk = [];
- this.chunkReused = undefined;
- this.rangeIndex = 0;
- this.to = ranges[ranges.length - 1].to;
- let context = ParseContext.get(), from = ranges[0].from;
- let { state, tree } = findStartInFragments(lang, fragments, from, context === null || context === void 0 ? void 0 : context.state);
- this.state = state;
- this.parsedPos = this.chunkStart = from + tree.length;
- for (let i = 0; i < tree.children.length; i++) {
- this.chunks.push(tree.children[i]);
- this.chunkPos.push(tree.positions[i]);
- }
- if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
- this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
- context.skipUntilInView(this.parsedPos, context.viewport.from);
- this.parsedPos = context.viewport.from;
- }
- this.moveRangeIndex();
- }
- advance() {
- let context = ParseContext.get();
- let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
- let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
- if (context)
- end = Math.min(end, context.viewport.to);
- while (this.parsedPos < end)
- this.parseLine(context);
- if (this.chunkStart < this.parsedPos)
- this.finishChunk();
- if (this.parsedPos >= parseEnd)
- return this.finish();
- if (context && this.parsedPos >= context.viewport.to) {
- context.skipUntilInView(this.parsedPos, parseEnd);
- return this.finish();
- }
- return null;
- }
- stopAt(pos) {
- this.stoppedAt = pos;
- }
- lineAfter(pos) {
- let chunk = this.input.chunk(pos);
- if (!this.input.lineChunks) {
- let eol = chunk.indexOf("\n");
- if (eol > -1)
- chunk = chunk.slice(0, eol);
- }
- else if (chunk == "\n") {
- chunk = "";
- }
- return pos + chunk.length <= this.to ? chunk : chunk.slice(0, this.to - pos);
- }
- nextLine() {
- let from = this.parsedPos, line = this.lineAfter(from), end = from + line.length;
- for (let index = this.rangeIndex;;) {
- let rangeEnd = this.ranges[index].to;
- if (rangeEnd >= end)
- break;
- line = line.slice(0, rangeEnd - (end - line.length));
- index++;
- if (index == this.ranges.length)
- break;
- let rangeStart = this.ranges[index].from;
- let after = this.lineAfter(rangeStart);
- line += after;
- end = rangeStart + after.length;
- }
- return { line, end };
- }
- skipGapsTo(pos, offset, side) {
- for (;;) {
- let end = this.ranges[this.rangeIndex].to, offPos = pos + offset;
- if (side > 0 ? end > offPos : end >= offPos)
- break;
- let start = this.ranges[++this.rangeIndex].from;
- offset += start - end;
- }
- return offset;
- }
- moveRangeIndex() {
- while (this.ranges[this.rangeIndex].to < this.parsedPos)
- this.rangeIndex++;
- }
- emitToken(id, from, to, size, offset) {
- if (this.ranges.length > 1) {
- offset = this.skipGapsTo(from, offset, 1);
- from += offset;
- let len0 = this.chunk.length;
- offset = this.skipGapsTo(to, offset, -1);
- to += offset;
- size += this.chunk.length - len0;
- }
- this.chunk.push(id, from, to, size);
- return offset;
- }
- parseLine(context) {
- let { line, end } = this.nextLine(), offset = 0, { streamParser } = this.lang;
- let stream = new StringStream(line, context ? context.state.tabSize : 4, context ? getIndentUnit(context.state) : 2);
- if (stream.eol()) {
- streamParser.blankLine(this.state, stream.indentUnit);
- }
- else {
- while (!stream.eol()) {
- let token = readToken(streamParser.token, stream, this.state);
- if (token)
- offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
- if (stream.start > 10000 /* MaxLineLength */)
- break;
- }
- }
- this.parsedPos = end;
- this.moveRangeIndex();
- if (this.parsedPos < this.to)
- this.parsedPos++;
- }
- finishChunk() {
- let tree = Tree.build({
- buffer: this.chunk,
- start: this.chunkStart,
- length: this.parsedPos - this.chunkStart,
- nodeSet,
- topID: 0,
- maxBufferLength: 2048 /* ChunkSize */,
- reused: this.chunkReused
- });
- tree = new Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
- this.chunks.push(tree);
- this.chunkPos.push(this.chunkStart - this.ranges[0].from);
- this.chunk = [];
- this.chunkReused = undefined;
- this.chunkStart = this.parsedPos;
- }
- finish() {
- return new Tree(this.lang.topNode, this.chunks, this.chunkPos, this.parsedPos - this.ranges[0].from).balance();
- }
- }
- function readToken(token, stream, state) {
- stream.start = stream.pos;
- for (let i = 0; i < 10; i++) {
- let result = token(stream, state);
- if (stream.pos > stream.start)
- return result;
- }
- throw new Error("Stream parser failed to advance stream.");
- }
- const noTokens = /*@__PURE__*/Object.create(null);
- const typeArray = [NodeType.none];
- const nodeSet = /*@__PURE__*/new NodeSet(typeArray);
- const warned = [];
- const defaultTable = /*@__PURE__*/Object.create(null);
- for (let [legacyName, name] of [
- ["variable", "variableName"],
- ["variable-2", "variableName.special"],
- ["string-2", "string.special"],
- ["def", "variableName.definition"],
- ["tag", "tagName"],
- ["attribute", "attributeName"],
- ["type", "typeName"],
- ["builtin", "variableName.standard"],
- ["qualifier", "modifier"],
- ["error", "invalid"],
- ["header", "heading"],
- ["property", "propertyName"]
- ])
- defaultTable[legacyName] = /*@__PURE__*/createTokenType(noTokens, name);
- class TokenTable {
- constructor(extra) {
- this.extra = extra;
- this.table = Object.assign(Object.create(null), defaultTable);
- }
- resolve(tag) {
- return !tag ? 0 : this.table[tag] || (this.table[tag] = createTokenType(this.extra, tag));
- }
- }
- const defaultTokenTable = /*@__PURE__*/new TokenTable(noTokens);
- function warnForPart(part, msg) {
- if (warned.indexOf(part) > -1)
- return;
- warned.push(part);
- console.warn(msg);
- }
- function createTokenType(extra, tagStr) {
- let tag = null;
- for (let part of tagStr.split(".")) {
- let value = (extra[part] || tags[part]);
- if (!value) {
- warnForPart(part, `Unknown highlighting tag ${part}`);
- }
- else if (typeof value == "function") {
- if (!tag)
- warnForPart(part, `Modifier ${part} used at start of tag`);
- else
- tag = value(tag);
- }
- else {
- if (tag)
- warnForPart(part, `Tag ${part} used as modifier`);
- else
- tag = value;
- }
- }
- if (!tag)
- return 0;
- let name = tagStr.replace(/ /g, "_"), type = NodeType.define({
- id: typeArray.length,
- name,
- props: [styleTags({ [name]: tag })]
- });
- typeArray.push(type);
- return type.id;
- }
- function docID(data) {
- let type = NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)] });
- typeArray.push(type);
- return type;
- }
- export { HighlightStyle, IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, StreamLanguage, StringStream, TreeIndentContext, bracketMatching, codeFolding, continuedIndent, defaultHighlightStyle, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldAll, foldCode, foldEffect, foldGutter, foldInside, foldKeymap, foldNodeProp, foldService, foldState, foldable, foldedRanges, forceParsing, getIndentUnit, getIndentation, highlightingFor, indentNodeProp, indentOnInput, indentRange, indentService, indentString, indentUnit, language, languageDataProp, matchBrackets, syntaxHighlighting, syntaxParserRunning, syntaxTree, syntaxTreeAvailable, unfoldAll, unfoldCode, unfoldEffect };
|