@uiw/react-codemirror
Version:
CodeMirror component for React.
1,007 lines (938 loc) • 528 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("react"), require("react/jsx-runtime"), require("@codemirror/state"), require("@codemirror/theme-one-dark"), require("@codemirror/view"));
else if(typeof define === 'function' && define.amd)
define(["react", "react/jsx-runtime", , , ], factory);
else if(typeof exports === 'object')
exports["@uiw/codemirror"] = factory(require("react"), require("react/jsx-runtime"), require("@codemirror/state"), require("@codemirror/theme-one-dark"), require("@codemirror/view"));
else
root["@uiw/codemirror"] = factory(root["React"], root["ReactJSXRuntime"], root["CM"]["@codemirror/state"], root["CM"]["@codemirror/theme-one-dark"], root["CM"]["@codemirror/view"]);
})(self, (__WEBPACK_EXTERNAL_MODULE__442__, __WEBPACK_EXTERNAL_MODULE__742__, __WEBPACK_EXTERNAL_MODULE__60__, __WEBPACK_EXTERNAL_MODULE__708__, __WEBPACK_EXTERNAL_MODULE__730__) => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 89
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ defaultLightThemeOption: () => (/* reexport safe */ _theme_light__WEBPACK_IMPORTED_MODULE_5__.c),
/* harmony export */ getDefaultExtensions: () => (/* binding */ getDefaultExtensions)
/* harmony export */ });
/* harmony import */ var _codemirror_commands__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(720);
/* harmony import */ var _uiw_codemirror_extensions_basic_setup__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(687);
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(730);
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_codemirror_view__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(708);
/* harmony import */ var _codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _codemirror_state__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(60);
/* harmony import */ var _codemirror_state__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_codemirror_state__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _theme_light__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(806);
/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};
/* harmony reexport (unknown) */ for(const __WEBPACK_IMPORT_KEY__ in _codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3__) if(["default","getDefaultExtensions"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () => _codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3__[__WEBPACK_IMPORT_KEY__]
/* harmony reexport (unknown) */ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
var getDefaultExtensions=function getDefaultExtensions(){var optios=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};var _optios$indentWithTab=optios.indentWithTab,defaultIndentWithTab=_optios$indentWithTab===void 0?true:_optios$indentWithTab,_optios$editable=optios.editable,editable=_optios$editable===void 0?true:_optios$editable,_optios$readOnly=optios.readOnly,readOnly=_optios$readOnly===void 0?false:_optios$readOnly,_optios$theme=optios.theme,theme=_optios$theme===void 0?'light':_optios$theme,_optios$placeholder=optios.placeholder,placeholderStr=_optios$placeholder===void 0?'':_optios$placeholder,_optios$basicSetup=optios.basicSetup,defaultBasicSetup=_optios$basicSetup===void 0?true:_optios$basicSetup;var getExtensions=[];if(defaultIndentWithTab){getExtensions.unshift(_codemirror_view__WEBPACK_IMPORTED_MODULE_2__.keymap.of([_codemirror_commands__WEBPACK_IMPORTED_MODULE_0__/* .indentWithTab */ .Yc]));}if(defaultBasicSetup){if(typeof defaultBasicSetup==='boolean'){getExtensions.unshift((0,_uiw_codemirror_extensions_basic_setup__WEBPACK_IMPORTED_MODULE_1__/* .basicSetup */ .o)());}else{getExtensions.unshift((0,_uiw_codemirror_extensions_basic_setup__WEBPACK_IMPORTED_MODULE_1__/* .basicSetup */ .o)(defaultBasicSetup));}}if(placeholderStr){getExtensions.unshift((0,_codemirror_view__WEBPACK_IMPORTED_MODULE_2__.placeholder)(placeholderStr));}switch(theme){case'light':getExtensions.push(_theme_light__WEBPACK_IMPORTED_MODULE_5__/* .defaultLightThemeOption */ .c);break;case'dark':getExtensions.push(_codemirror_theme_one_dark__WEBPACK_IMPORTED_MODULE_3__.oneDark);break;case'none':break;default:getExtensions.push(theme);break;}if(editable===false){getExtensions.push(_codemirror_view__WEBPACK_IMPORTED_MODULE_2__.EditorView.editable.of(false));}if(readOnly){getExtensions.push(_codemirror_state__WEBPACK_IMPORTED_MODULE_4__.EditorState.readOnly.of(true));}return[].concat(getExtensions);};
/***/ },
/***/ 806
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ c: () => (/* binding */ defaultLightThemeOption)
/* harmony export */ });
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(730);
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_codemirror_view__WEBPACK_IMPORTED_MODULE_0__);
var defaultLightThemeOption=_codemirror_view__WEBPACK_IMPORTED_MODULE_0__.EditorView.theme({'&':{backgroundColor:'#fff'}},{dark:false});
/***/ },
/***/ 341
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
Q: () => (/* binding */ ExternalChange),
q: () => (/* binding */ useCodeMirror)
});
;// ../node_modules/@babel/runtime/helpers/esm/arrayLikeToArray.js
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
;// ../node_modules/@babel/runtime/helpers/esm/arrayWithoutHoles.js
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
;// ../node_modules/@babel/runtime/helpers/esm/iterableToArray.js
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
;// ../node_modules/@babel/runtime/helpers/esm/unsupportedIterableToArray.js
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
;// ../node_modules/@babel/runtime/helpers/esm/nonIterableSpread.js
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
;// ../node_modules/@babel/runtime/helpers/esm/toConsumableArray.js
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
;// ../node_modules/@babel/runtime/helpers/esm/arrayWithHoles.js
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
;// ../node_modules/@babel/runtime/helpers/esm/iterableToArrayLimit.js
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
;// ../node_modules/@babel/runtime/helpers/esm/nonIterableRest.js
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
;// ../node_modules/@babel/runtime/helpers/esm/slicedToArray.js
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
// EXTERNAL MODULE: external {"root":"React","commonjs2":"react","commonjs":"react","amd":"react"}
var external_root_React_commonjs2_react_commonjs_react_amd_react_ = __webpack_require__(442);
// EXTERNAL MODULE: external {"root":["CM","@codemirror/state"],"commonjs":"@codemirror/state","commonjs2":"@codemirror/state"}
var state_ = __webpack_require__(60);
// EXTERNAL MODULE: external {"root":["CM","@codemirror/view"],"commonjs":"@codemirror/view","commonjs2":"@codemirror/view"}
var view_ = __webpack_require__(730);
// EXTERNAL MODULE: ./src/getDefaultExtensions.ts
var getDefaultExtensions = __webpack_require__(89);
// EXTERNAL MODULE: ./src/utils.ts
var utils = __webpack_require__(369);
;// ../node_modules/@babel/runtime/helpers/esm/classCallCheck.js
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
// EXTERNAL MODULE: ../node_modules/@babel/runtime/helpers/esm/toPropertyKey.js + 2 modules
var toPropertyKey = __webpack_require__(236);
;// ../node_modules/@babel/runtime/helpers/esm/createClass.js
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, (0,toPropertyKey/* default */.A)(o.key), o);
}
}
function _createClass(e, r, t) {
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
;// ./src/timeoutLatch.ts
// Setting / Unsetting timeouts for every keystroke was a significant overhead
// Inspired from https://github.com/iostreamer-X/timeout-latch
var TimeoutLatch=/*#__PURE__*/function(){function TimeoutLatch(callback,timeoutMS){_classCallCheck(this,TimeoutLatch);this.timeLeftMS=void 0;this.timeoutMS=void 0;this.isCancelled=false;this.isTimeExhausted=false;this.callbacks=[];this.timeLeftMS=timeoutMS;this.timeoutMS=timeoutMS;this.callbacks.push(callback);}return _createClass(TimeoutLatch,[{key:"tick",value:function tick(){if(!this.isCancelled&&!this.isTimeExhausted){this.timeLeftMS--;if(this.timeLeftMS<=0){this.isTimeExhausted=true;var callbacks=this.callbacks.slice();this.callbacks.length=0;callbacks.forEach(function(callback){try{callback();}catch(error){console.error('TimeoutLatch callback error:',error);}});}}}},{key:"cancel",value:function cancel(){this.isCancelled=true;this.callbacks.length=0;}},{key:"reset",value:function reset(){this.timeLeftMS=this.timeoutMS;this.isCancelled=false;this.isTimeExhausted=false;}},{key:"isDone",get:function get(){return this.isCancelled||this.isTimeExhausted;}}]);}();var Scheduler=/*#__PURE__*/function(){function Scheduler(){_classCallCheck(this,Scheduler);this.interval=null;this.latches=new Set();}return _createClass(Scheduler,[{key:"add",value:function add(latch){this.latches.add(latch);this.start();}},{key:"remove",value:function remove(latch){this.latches["delete"](latch);if(this.latches.size===0){this.stop();}}},{key:"start",value:function start(){var _this=this;if(this.interval===null){this.interval=setInterval(function(){_this.latches.forEach(function(latch){latch.tick();if(latch.isDone){_this.remove(latch);}});},1);}}},{key:"stop",value:function stop(){if(this.interval!==null){clearInterval(this.interval);this.interval=null;}}}]);}();var globalScheduler=null;var getScheduler=function getScheduler(){if(typeof window==='undefined'){return new Scheduler();}if(!globalScheduler){globalScheduler=new Scheduler();}return globalScheduler;};
;// ./src/useCodeMirror.ts
var ExternalChange=state_.Annotation.define();var TYPING_TIMOUT=200;// ms
var emptyExtensions=[];function useCodeMirror(props){var value=props.value,selection=props.selection,onChange=props.onChange,onStatistics=props.onStatistics,onCreateEditor=props.onCreateEditor,onUpdate=props.onUpdate,_props$extensions=props.extensions,extensions=_props$extensions===void 0?emptyExtensions:_props$extensions,autoFocus=props.autoFocus,_props$theme=props.theme,theme=_props$theme===void 0?'light':_props$theme,_props$height=props.height,height=_props$height===void 0?null:_props$height,_props$minHeight=props.minHeight,minHeight=_props$minHeight===void 0?null:_props$minHeight,_props$maxHeight=props.maxHeight,maxHeight=_props$maxHeight===void 0?null:_props$maxHeight,_props$width=props.width,width=_props$width===void 0?null:_props$width,_props$minWidth=props.minWidth,minWidth=_props$minWidth===void 0?null:_props$minWidth,_props$maxWidth=props.maxWidth,maxWidth=_props$maxWidth===void 0?null:_props$maxWidth,_props$placeholder=props.placeholder,placeholderStr=_props$placeholder===void 0?'':_props$placeholder,_props$editable=props.editable,editable=_props$editable===void 0?true:_props$editable,_props$readOnly=props.readOnly,readOnly=_props$readOnly===void 0?false:_props$readOnly,_props$indentWithTab=props.indentWithTab,defaultIndentWithTab=_props$indentWithTab===void 0?true:_props$indentWithTab,_props$basicSetup=props.basicSetup,defaultBasicSetup=_props$basicSetup===void 0?true:_props$basicSetup,root=props.root,initialState=props.initialState;var _useState=(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useState)(),_useState2=_slicedToArray(_useState,2),container=_useState2[0],setContainer=_useState2[1];var _useState3=(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useState)(),_useState4=_slicedToArray(_useState3,2),view=_useState4[0],setView=_useState4[1];var _useState5=(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useState)(),_useState6=_slicedToArray(_useState5,2),state=_useState6[0],setState=_useState6[1];var typingLatch=(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useState)(function(){return{current:null};})[0];var pendingUpdate=(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useState)(function(){return{current:null};})[0];var defaultThemeOption=view_.EditorView.theme({'&':{height:height,minHeight:minHeight,maxHeight:maxHeight,width:width,minWidth:minWidth,maxWidth:maxWidth},'& .cm-scroller':{height:'100% !important'}});var updateListener=view_.EditorView.updateListener.of(function(vu){if(vu.docChanged&&typeof onChange==='function'&&// Fix echoing of the remote changes:
// If transaction is market as remote we don't have to call `onChange` handler again
!vu.transactions.some(function(tr){return tr.annotation(ExternalChange);})){if(typingLatch.current){typingLatch.current.reset();}else{typingLatch.current=new TimeoutLatch(function(){if(pendingUpdate.current){var forceUpdate=pendingUpdate.current;pendingUpdate.current=null;forceUpdate();}typingLatch.current=null;},TYPING_TIMOUT);getScheduler().add(typingLatch.current);}var doc=vu.state.doc;var _value=doc.toString();onChange(_value,vu);}onStatistics&&onStatistics((0,utils/* getStatistics */.m)(vu));});var defaultExtensions=(0,getDefaultExtensions.getDefaultExtensions)({theme:theme,editable:editable,readOnly:readOnly,placeholder:placeholderStr,indentWithTab:defaultIndentWithTab,basicSetup:defaultBasicSetup});var getExtensions=[updateListener,defaultThemeOption].concat(_toConsumableArray(defaultExtensions));if(onUpdate&&typeof onUpdate==='function'){getExtensions.push(view_.EditorView.updateListener.of(onUpdate));}getExtensions=getExtensions.concat(extensions);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useLayoutEffect)(function(){if(container&&!state){var config={doc:value,selection:selection,extensions:getExtensions};var stateCurrent=initialState?state_.EditorState.fromJSON(initialState.json,config,initialState.fields):state_.EditorState.create(config);setState(stateCurrent);if(!view){var viewCurrent=new view_.EditorView({state:stateCurrent,parent:container,root:root});setView(viewCurrent);onCreateEditor&&onCreateEditor(viewCurrent,stateCurrent);}}return function(){if(view){setState(undefined);setView(undefined);}};},[container,state]);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useEffect)(function(){if(props.container){setContainer(props.container);}},[props.container]);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useEffect)(function(){return function(){if(view){view.destroy();setView(undefined);}if(typingLatch.current){typingLatch.current.cancel();typingLatch.current=null;}};},[view]);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useEffect)(function(){if(autoFocus&&view){view.focus();}},[autoFocus,view]);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useEffect)(function(){if(view){view.dispatch({effects:state_.StateEffect.reconfigure.of(getExtensions)});}// eslint-disable-next-line react-hooks/exhaustive-deps
},[theme,extensions,height,minHeight,maxHeight,width,minWidth,maxWidth,placeholderStr,editable,readOnly,defaultIndentWithTab,defaultBasicSetup,onChange,onUpdate]);(0,external_root_React_commonjs2_react_commonjs_react_amd_react_.useEffect)(function(){if(value===undefined){return;}var currentValue=view?view.state.doc.toString():'';if(view&&value!==currentValue){var isTyping=typingLatch.current&&!typingLatch.current.isDone;var forceUpdate=function forceUpdate(){if(view&&value!==view.state.doc.toString()){view.dispatch({changes:{from:0,to:view.state.doc.toString().length,insert:value||''},annotations:[ExternalChange.of(true)]});}};if(!isTyping){forceUpdate();}else{pendingUpdate.current=forceUpdate;}}},[value,view]);return{state:state,setState:setState,view:view,setView:setView,container:container,setContainer:setContainer};}
/***/ },
/***/ 369
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ m: () => (/* binding */ getStatistics)
/* harmony export */ });
var getStatistics=function getStatistics(view){return{line:view.state.doc.lineAt(view.state.selection.main.from),lineCount:view.state.doc.lines,lineBreak:view.state.lineBreak,length:view.state.doc.length,readOnly:view.state.readOnly,tabSize:view.state.tabSize,selection:view.state.selection,selectionAsSingle:view.state.selection.asSingle().main,ranges:view.state.selection.ranges,selectionCode:view.state.sliceDoc(view.state.selection.main.from,view.state.selection.main.to),selections:view.state.selection.ranges.map(function(r){return view.state.sliceDoc(r.from,r.to);}),selectedText:view.state.selection.ranges.some(function(r){return!r.empty;})};};
/***/ },
/***/ 442
(module) {
module.exports = __WEBPACK_EXTERNAL_MODULE__442__;
/***/ },
/***/ 742
(module) {
module.exports = __WEBPACK_EXTERNAL_MODULE__742__;
/***/ },
/***/ 60
(module) {
module.exports = __WEBPACK_EXTERNAL_MODULE__60__;
/***/ },
/***/ 708
(module) {
module.exports = __WEBPACK_EXTERNAL_MODULE__708__;
/***/ },
/***/ 730
(module) {
module.exports = __WEBPACK_EXTERNAL_MODULE__730__;
/***/ },
/***/ 687
(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
o: () => (/* binding */ basicSetup),
V: () => (/* binding */ minimalSetup)
});
// EXTERNAL MODULE: external {"root":["CM","@codemirror/view"],"commonjs":"@codemirror/view","commonjs2":"@codemirror/view"}
var view_ = __webpack_require__(730);
// EXTERNAL MODULE: external {"root":["CM","@codemirror/state"],"commonjs":"@codemirror/state","commonjs2":"@codemirror/state"}
var state_ = __webpack_require__(60);
// EXTERNAL MODULE: ../node_modules/@codemirror/commands/dist/index.js
var dist = __webpack_require__(720);
;// ../node_modules/crelt/index.js
function crelt() {
var elt = arguments[0]
if (typeof elt == "string") elt = document.createElement(elt)
var i = 1, next = arguments[1]
if (next && typeof next == "object" && next.nodeType == null && !Array.isArray(next)) {
for (var name in next) if (Object.prototype.hasOwnProperty.call(next, name)) {
var value = next[name]
if (typeof value == "string") elt.setAttribute(name, value)
else if (value != null) elt[name] = value
}
i++
}
for (; i < arguments.length; i++) add(elt, arguments[i])
return elt
}
function add(elt, child) {
if (typeof child == "string") {
elt.appendChild(document.createTextNode(child))
} else if (child == null) {
} else if (child.nodeType != null) {
elt.appendChild(child)
} else if (Array.isArray(child)) {
for (var i = 0; i < child.length; i++) add(elt, child[i])
} else {
throw new RangeError("Unsupported child node: " + child)
}
}
;// ../node_modules/@codemirror/search/dist/index.js
const basicNormalize = typeof String.prototype.normalize == "function"
? x => x.normalize("NFKD") : x => x;
/**
A search cursor provides an iterator over text matches in a
document.
*/
class SearchCursor {
/**
Create a text cursor. The query is the search string, `from` to
`to` provides the region to search.
When `normalize` is given, it will be called, on both the query
string and the content it is matched against, before comparing.
You can, for example, create a case-insensitive search by
passing `s => s.toLowerCase()`.
Text is always normalized with
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text, query, from = 0, to = text.length, normalize, test) {
this.test = test;
/**
The current match (only holds a meaningful value after
[`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when
`done` is false).
*/
this.value = { from: 0, to: 0 };
/**
Whether the end of the iterated region has been reached.
*/
this.done = false;
this.matches = [];
this.buffer = "";
this.bufferPos = 0;
this.iter = text.iterRange(from, to);
this.bufferStart = from;
this.normalize = normalize ? x => normalize(basicNormalize(x)) : basicNormalize;
this.query = this.normalize(query);
}
peek() {
if (this.bufferPos == this.buffer.length) {
this.bufferStart += this.buffer.length;
this.iter.next();
if (this.iter.done)
return -1;
this.bufferPos = 0;
this.buffer = this.iter.value;
}
return (0,state_.codePointAt)(this.buffer, this.bufferPos);
}
/**
Look for the next match. Updates the iterator's
[`value`](https://codemirror.net/6/docs/ref/#search.SearchCursor.value) and
[`done`](https://codemirror.net/6/docs/ref/#search.SearchCursor.done) properties. Should be called
at least once before using the cursor.
*/
next() {
while (this.matches.length)
this.matches.pop();
return this.nextOverlapping();
}
/**
The `next` method will ignore matches that partially overlap a
previous match. This method behaves like `next`, but includes
such matches.
*/
nextOverlapping() {
for (;;) {
let next = this.peek();
if (next < 0) {
this.done = true;
return this;
}
let str = (0,state_.fromCodePoint)(next), start = this.bufferStart + this.bufferPos;
this.bufferPos += (0,state_.codePointSize)(next);
let norm = this.normalize(str);
if (norm.length)
for (let i = 0, pos = start;; i++) {
let code = norm.charCodeAt(i);
let match = this.match(code, pos, this.bufferPos + this.bufferStart);
if (i == norm.length - 1) {
if (match) {
this.value = match;
return this;
}
break;
}
if (pos == start && i < str.length && str.charCodeAt(i) == code)
pos++;
}
}
}
match(code, pos, end) {
let match = null;
for (let i = 0; i < this.matches.length; i += 2) {
let index = this.matches[i], keep = false;
if (this.query.charCodeAt(index) == code) {
if (index == this.query.length - 1) {
match = { from: this.matches[i + 1], to: end };
}
else {
this.matches[i]++;
keep = true;
}
}
if (!keep) {
this.matches.splice(i, 2);
i -= 2;
}
}
if (this.query.charCodeAt(0) == code) {
if (this.query.length == 1)
match = { from: pos, to: end };
else
this.matches.push(1, pos);
}
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferStart))
match = null;
return match;
}
}
if (typeof Symbol != "undefined")
SearchCursor.prototype[Symbol.iterator] = function () { return this; };
const empty = { from: -1, to: -1, match: /*@__PURE__*//.*/.exec("") };
const baseFlags = "gm" + (/x/.unicode == null ? "" : "u");
/**
This class is similar to [`SearchCursor`](https://codemirror.net/6/docs/ref/#search.SearchCursor)
but searches for a regular expression pattern instead of a plain
string.
*/
class RegExpCursor {
/**
Create a cursor that will search the given range in the given
document. `query` should be the raw pattern (as you'd pass it to
`new RegExp`).
*/
constructor(text, query, options, from = 0, to = text.length) {
this.text = text;
this.to = to;
this.curLine = "";
/**
Set to `true` when the cursor has reached the end of the search
range.
*/
this.done = false;
/**
Will contain an object with the extent of the match and the
match object when [`next`](https://codemirror.net/6/docs/ref/#search.RegExpCursor.next)
sucessfully finds a match.
*/
this.value = empty;
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
return new MultilineRegExpCursor(text, query, options, from, to);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = toCharEnd(text, from);
this.getLine(this.curLineStart);
}
getLine(skip) {
this.iter.next(skip);
if (this.iter.lineBreak) {
this.curLine = "";
}
else {
this.curLine = this.iter.value;
if (this.curLineStart + this.curLine.length > this.to)
this.curLine = this.curLine.slice(0, this.to - this.curLineStart);
this.iter.next();
}
}
nextLine() {
this.curLineStart = this.curLineStart + this.curLine.length + 1;
if (this.curLineStart > this.to)
this.curLine = "";
else
this.getLine(0);
}
/**
Move to the next match, if there is one.
*/
next() {
for (let off = this.matchPos - this.curLineStart;;) {
this.re.lastIndex = off;
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
if (match) {
let from = this.curLineStart + match.index, to = from + match[0].length;
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
if (from == this.curLineStart + this.curLine.length)
this.nextLine();
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
return this;
}
off = this.matchPos - this.curLineStart;
}
else if (this.curLineStart + this.curLine.length < this.to) {
this.nextLine();
off = 0;
}
else {
this.done = true;
return this;
}
}
}
}
const flattened = /*@__PURE__*/new WeakMap();
// Reusable (partially) flattened document strings
class FlattenedDoc {
constructor(from, text) {
this.from = from;
this.text = text;
}
get to() { return this.from + this.text.length; }
static get(doc, from, to) {
let cached = flattened.get(doc);
if (!cached || cached.from >= to || cached.to <= from) {
let flat = new FlattenedDoc(from, doc.sliceString(from, to));
flattened.set(doc, flat);
return flat;
}
if (cached.from == from && cached.to == to)
return cached;
let { text, from: cachedFrom } = cached;
if (cachedFrom > from) {
text = doc.sliceString(from, cachedFrom) + text;
cachedFrom = from;
}
if (cached.to < to)
text += doc.sliceString(cached.to, to);
flattened.set(doc, new FlattenedDoc(cachedFrom, text));
return new FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
}
}
class MultilineRegExpCursor {
constructor(text, query, options, from, to) {
this.text = text;
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = toCharEnd(text, from);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
}
next() {
for (;;) {
let off = this.re.lastIndex = this.matchPos - this.flat.from;
let match = this.re.exec(this.flat.text);
// Skip empty matches directly after the last match
if (match && !match[0] && match.index == off) {
this.re.lastIndex = off + 1;
match = this.re.exec(this.flat.text);
}
if (match) {
let from = this.flat.from + match.index, to = from + match[0].length;
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&
(!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
return this;
}
}
if (this.flat.to == this.to) {
this.done = true;
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
}
}
if (typeof Symbol != "undefined") {
RegExpCursor.prototype[Symbol.iterator] = MultilineRegExpCursor.prototype[Symbol.iterator] =
function () { return this; };
}
function validRegExp(source) {
try {
new RegExp(source, baseFlags);
return true;
}
catch (_a) {
return false;
}
}
function toCharEnd(text, pos) {
if (pos >= text.length)
return pos;
let line = text.lineAt(pos), next;
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)
pos++;
return pos;
}
/**
Command that shows a dialog asking the user for a line number, and
when a valid position is provided, moves the cursor to that line.
Supports line numbers, relative line offsets prefixed with `+` or
`-`, document percentages suffixed with `%`, and an optional
column position by adding `:` and a second number after the line
number.
*/
const gotoLine = view => {
let { state } = view;
let line = String(state.doc.lineAt(view.state.selection.main.head).number);
let { close, result } = (0,view_.showDialog)(view, {
label: state.phrase("Go to line"),
input: { type: "text", name: "line", value: line },
focus: true,
submitLabel: state.phrase("go"),
});
result.then(form => {
let match = form && /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(form.elements["line"].value);
if (!match) {
view.dispatch({ effects: close });
return;
}
let startLine = state.doc.lineAt(state.selection.main.head);
let [, sign, ln, cl, percent] = match;
let col = cl ? +cl.slice(1) : 0;
let line = ln ? +ln : startLine.number;
if (ln && percent) {
let pc = line / 100;
if (sign)
pc = pc * (sign == "-" ? -1 : 1) + (startLine.number / state.doc.lines);
line = Math.round(state.doc.lines * pc);
}
else if (ln && sign) {
line = line * (sign == "-" ? -1 : 1) + startLine.number;
}
let docLine = state.doc.line(Math.max(1, Math.min(state.doc.lines, line)));
let selection = state_.EditorSelection.cursor(docLine.from + Math.max(0, Math.min(col, docLine.length)));
view.dispatch({
effects: [close, view_.EditorView.scrollIntoView(selection.from, { y: 'center' })],
selection,
});
});
return true;
};
const defaultHighlightOptions = {
highlightWordAroundCursor: false,
minSelectionLength: 1,
maxMatches: 100,
wholeWords: false
};
const highlightConfig = /*@__PURE__*/state_.Facet.define({
combine(options) {
return (0,state_.combineConfig)(options, defaultHighlightOptions, {
highlightWordAroundCursor: (a, b) => a || b,
minSelectionLength: Math.min,
maxMatches: Math.min
});
}
});
/**
This extension highlights text that matches the selection. It uses
the `"cm-selectionMatch"` class for the highlighting. When
`highlightWordAroundCursor` is enabled, the word at the cursor
itself will be highlighted with `"cm-selectionMatch-main"`.
*/
function highlightSelectionMatches(options) {
let ext = [defaultTheme, matchHighlighter];
if (options)
ext.push(highlightConfig.of(options));
return ext;
}
const matchDeco = /*@__PURE__*/view_.Decoration.mark({ class: "cm-selectionMatch" });
const mainMatchDeco = /*@__PURE__*/view_.Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" });
// Whether the characters directly outside the given positions are non-word characters
function insideWordBoundaries(check, state, from, to) {
return (from == 0 || check(state.sliceDoc(from - 1, from)) != state_.CharCategory.Word) &&
(to == state.doc.length || check(state.sliceDoc(to, to + 1)) != state_.CharCategory.Word);
}
// Whether the characters directly at the given positions are word characters
function insideWord(check, state, from, to) {
return check(state.sliceDoc(from, from + 1)) == state_.CharCategory.Word
&& check(state.sliceDoc(to - 1, to)) == state_.CharCategory.Word;
}
const matchHighlighter = /*@__PURE__*/view_.ViewPlugin.fromClass(class {
constructor(view) {
this.decorations = this.getDeco(view);
}
update(update) {
if (update.selectionSet || update.docChanged || update.viewportChanged)
this.decorations = this.getDeco(update.view);
}
getDeco(view) {
let conf = view.state.facet(highlightConfig);
let { state } = view, sel = state.selection;
if (sel.ranges.length > 1)
return view_.Decoration.none;
let range = sel.main, query, check = null;
if (range.empty) {
if (!conf.highlightWordAroundCursor)
return view_.Decoration.none;
let word = state.wordAt(range.head);
if (!word)
return view_.Decoration.none;
check = state.charCategorizer(range.head);
query = state.sliceDoc(word.from, word.to);
}
else {
let len = range.to - range.from;
if (len < conf.minSelectionLength || len > 200)
return view_.Decoration.none;
if (conf.wholeWords) {
query = state.sliceDoc(range.from, range.to); // TODO: allow and include leading/trailing space?
check = state.charCategorizer(range.head);
if (!(insideWordBoundaries(check, state, range.from, range.to) &&
insideWord(check, state, range.from, range.to)))
return view_.Decoration.none;
}
else {
query = state.sliceDoc(range.from, range.to);
if (!query)
return view_.Decoration.none;
}
}
let deco = [];
for (let part of view.visibleRanges) {
let cursor = new SearchCursor(state.doc, query, part.from, part.to);
while (!cursor.next().done) {
let { from, to } = cursor.value;
if (!check || insideWordBoundaries(check, state, from, to)) {
if (range.empty && from <= range.from && to >= range.to)
deco.push(mainMatchDeco.range(from, to));
else if (from >= range.to || to <= range.from)
deco.push(matchDeco.range(from, to));
if (deco.length > conf.maxMatches)
return view_.Decoration.none;
}
}
}
return view_.Decoration.set(deco);
}
}, {
decorations: v => v.decorations
});
const defaultTheme = /*@__PURE__*/view_.EditorView.baseTheme({
".cm-selectionMatch": { backgroundColor: "#99ff7780" },
".cm-searchMatch .cm-selectionMatch": { backgroundColor: "transparent" }
});
// Select the words around the cursors.
const selectWord = ({ state, dispatch }) => {
let { selection } = state;
let newSel = state_.EditorSelection.create(selection.ranges.map(range => state.wordAt(range.head) || state_.EditorSelection.cursor(range.head)), selection.mainIndex);
if (newSel.eq(selection))
return false;
dispatch(state.update({ selection: newSel }));
return true;
};
// Find next occurrence of query relative to last cursor. Wrap around
// the document if there are no more matches.
function findNextOccurrence(state, query) {
let { main, ranges } = state.selection;
let word = state.wordAt(main.head), fullWord = word && word.from == main.from && word.to == main.to;
for (let cycled = false, cursor = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to);;) {
cursor.next();
if (cursor.done) {
if (cycled)
return null;
cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1));
cycled = true;
}
else {
if (cycled && ranges.some(r => r.from == cursor.value.from))
continue;
if (fullWord) {
let word = state.wordAt(cursor.value.from);
if (!word || word.from != cursor.value.from || word.to != cursor.value.to)
continue;
}
return cursor.value;
}
}
}
/**
Select next occurrence of the current selection. Expand selection
to the surrounding word when the selection is empty.
*/
const selectNextOccurrence = ({ state, dispatch }) => {
let { ranges } = state.selection;
if (ranges.some(sel => sel.from === sel.to))
return selectWord({ state, dispatch });
let searchedText = state.sliceDoc(ranges[0].from, ranges[0].to);
if (state.selection.ranges.some(r => state.sliceDoc(r.from, r.to) != searchedText))
return false;
let range = findNextOccurrence(state, searchedText);
if (!range)
return false;
dispatch(state.update({
selection: state.selection.addRange(state_.EditorSelection.range(range.from, range.to), false),
effects: view_.EditorView.scrollIntoView(range.to)
}));
return true;
};
const searchConfigFacet = /*@__PURE__*/state_.Facet.define({
combine(configs) {
return (0,state_.combineConfig)(configs, {
top: false,
caseSensitive: false,
literal: false,
regexp: false,
wholeWord: false,
createPanel: view => new SearchPanel(view),
scrollToMatch: range => view_.EditorView.scrollIntoView(range)
});
}
});
/**
Add search state to the editor configuration, and optionally
configure the search extension.
([`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel) will automatically
enable this if it isn't already on).
*/
function search(config) {
return config ? [searchConfigFacet.of(config), searchExtensions] : searchExtensions;
}
/**
A search query. Part of the editor's search state.
*/
class SearchQuery {
/**
Create a query object.
*/
constructor(config) {
this.search = config.search;
this.caseSensitive = !!config.caseSensitive;
this.literal = !!config.literal;
this.regexp = !!config.regexp;
this.replace = config.replace || "";
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
this.test = config.test;
}
/**
@internal
*/
unquote(text) {
return this.literal ? text :
text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
/**
Compare this query to another query.
*/
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
this.wholeWord == other.wholeWord && this.test == other.test;
}
/**
@internal
*/
create() {
return this.regexp ? new RegExpQuery(this) : new StringQuery(this);
}
/**
Get a search cursor for this query, searching through the given
range in the given state.
*/
getCursor(state, from = 0, to) {
let st = state.doc ? state : state_.EditorState.create({ doc: state });
if (to == null)
to = st.doc.length;
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
}
}
class QueryType {
constructor(spec) {
this.spec = spec;
}
}
function wrapStringTest(test, state, inner) {
return (from, to, buffer, bufferPos) => {
if (inner && !inner(from, to, buffer, bufferPos))
return false;
let match = from >= bufferPos && to <= bufferPos + buffer.length
? buffer.slice(from - bufferPos, to - bufferPos)
: state.doc.sliceString(from, to);
return test(match, state, from, to);
};
}
function stringCursor(spec, state, from, to) {
let test;
if (spec.wholeWord)
test = stringWordTest(state.doc, state.charCategorizer(state.selection.main.head));
if (spec.test)
test = wrapStringTest(spec.test, state, test);
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), test);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(charBefore(buf, from - bufPos)) != state_.CharCategory.Word ||
categorizer(charAfter(buf, from - bufPos)) != state_.CharCategory.Word) &&
(categorizer(charAfter(buf, to - bufPos)) != state_.CharCategory.Word ||
categorizer(charBefore(buf, to - bufPos)) != state_.CharCategory.Word);
};
}
class StringQuery extends QueryType {
constructor(spec) {
super(spec);
}
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done) {
let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);
cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();
}
return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;
}
// Searching in reverse is, rather than implementing an inverted search
// cursor, done by scanning chunk after chunk forward.
prevMatchInRange(state, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, state, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* FindPrev.ChunkSize */;
}
}
prevMatch(state, curFrom, curTo) {
let found = this.prevMatchInRange(state, 0, curFrom);
if (!found)
found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);
return found && (found.from != curFrom || found.to != curTo) ? found : null;
}
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
ranges.push(cursor.value);
}
return ranges;
}
highlight(state, from, to, add) {
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
function wrapRegexpTest(test, state, inner) {
return (from, to, match) => {
return (!inner || inner(from, to, match)) && test(match[0], state, from, to);
};
}
function regexpCursor(spec, state, from, to) {
let test;
if (spec.wholeWord)
test = regexpWordTest(state.charCategorizer(state.selection.main.head));
if (spec.test)
test = wrapRegexpTest(spec.test, state, test);
return new RegExpCursor(state.doc, spec.search, { ignoreCase: !spec.caseSensitive, test }, from, to);
}
function charBefore(str, index) {
return str.slice((0,state_.findClusterBreak)(str, index, false), index);
}
function charAfter(str, index) {
return str.slice(index, (0,state_.findClusterBreak)(str, index));
}
function regexpWordTest(categorizer) {
return (_from, _to, match) => !match[0].length ||
(categorizer(charBefore(match.input, match.index)) != state_.CharCategory.Word ||
categorizer(charAfter(match.input, match.index)) != state_.CharCategory.Word) &&
(categorizer(charAfter(match.input, match.index + match[0].length)) != state_.CharCategory.Word ||
categorizer(charBefore(match.input, match.index + match[0].length)) != state_.CharCategory.Word);
}
class RegExpQuery extends QueryType {
nextMatch(state, curFrom, curTo) {
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
if (cursor.done)
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(state, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);
let cursor = regexpCursor(this.spec, state, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
return range;
if (start == from)
return null;
}
}
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
if