diff --git a/.gitignore b/.gitignore index 4081fc1..ad6984c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ *.swo __pycache__ -deploy/*/index.html +deploy/index.html diff --git a/deploy/de/regexr.css b/deploy/de/regexr.css deleted file mode 100644 index cec44eb..0000000 --- a/deploy/de/regexr.css +++ /dev/null @@ -1,2086 +0,0 @@ -@charset "UTF-8"; -.CodeMirror { -font-family:monospace; -height:300px; -color:#000 -} -.CodeMirror-lines { -padding:4px 0 -} -.CodeMirror pre { -padding:0 4px -} -.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler { -background-color:#fff -} -.CodeMirror-gutters { -border-right:1px solid #ddd; -background-color:#f7f7f7; -white-space:nowrap -} -.CodeMirror-linenumber { -padding:0 3px 0 5px; -min-width:20px; -text-align:right; -color:#999; -white-space:nowrap -} -.CodeMirror-guttermarker { -color:#000 -} -.CodeMirror-guttermarker-subtle { -color:#999 -} -.CodeMirror-cursor { -border-left:1px solid #000; -border-right:none; -width:0 -} -.CodeMirror div.CodeMirror-secondarycursor { -border-left:1px solid silver -} -.cm-fat-cursor .CodeMirror-cursor { -width:auto; -border:0!important; -background:#7e7 -} -.cm-fat-cursor div.CodeMirror-cursors { -z-index:1 -} -.cm-animate-fat-cursor { -width:auto; -border:0; --webkit-animation:blink 1.06s steps(1) infinite; --moz-animation:blink 1.06s steps(1) infinite; -animation:blink 1.06s steps(1) infinite; -background-color:#7e7 -} -@-moz-keyframes blink { -50% { -background-color:transparent -} - -} -@-webkit-keyframes blink { -50% { -background-color:transparent -} - -} -@keyframes blink { -50% { -background-color:transparent -} - -} -.cm-tab { -display:inline-block; -text-decoration:inherit -} -.CodeMirror-rulers { -position:absolute; -left:0; -right:0; -top:-50px; -bottom:-20px; -overflow:hidden -} -.CodeMirror-ruler { -border-left:1px solid #ccc; -top:0; -bottom:0; -position:absolute -} -.cm-s-default .cm-header { -color:#00f -} -.cm-s-default .cm-quote { -color:#090 -} -.cm-negative { -color:#d44 -} -.cm-positive { -color:#292 -} -.cm-header,.cm-strong { -font-weight:700 -} -.cm-em { -font-style:italic -} -.cm-link { -text-decoration:underline -} -.cm-strikethrough { -text-decoration:line-through -} -.cm-s-default .cm-keyword { -color:#708 -} -.cm-s-default .cm-atom { -color:#219 -} -.cm-s-default .cm-number { -color:#164 -} -.cm-s-default .cm-def { -color:#00f -} -.cm-s-default .cm-variable-2 { -color:#05a -} -.cm-s-default .cm-type,.cm-s-default .cm-variable-3 { -color:#085 -} -.cm-s-default .cm-comment { -color:#a50 -} -.cm-s-default .cm-string { -color:#a11 -} -.cm-s-default .cm-string-2 { -color:#f50 -} -.cm-s-default .cm-meta { -color:#555 -} -.cm-s-default .cm-qualifier { -color:#555 -} -.cm-s-default .cm-builtin { -color:#30a -} -.cm-s-default .cm-bracket { -color:#997 -} -.cm-s-default .cm-tag { -color:#170 -} -.cm-s-default .cm-attribute { -color:#00c -} -.cm-s-default .cm-hr { -color:#999 -} -.cm-s-default .cm-link { -color:#00c -} -.cm-s-default .cm-error { -color:red -} -.cm-invalidchar { -color:red -} -.CodeMirror-composing { -border-bottom:2px solid -} -div.CodeMirror span.CodeMirror-matchingbracket { -color:#0f0 -} -div.CodeMirror span.CodeMirror-nonmatchingbracket { -color:#f22 -} -.CodeMirror-matchingtag { -background:rgba(255,150,0,.3) -} -.CodeMirror-activeline-background { -background:#e8f2ff -} -.CodeMirror { -position:relative; -overflow:hidden; -background:#fff -} -.CodeMirror-scroll { -overflow:scroll!important; -margin-bottom:-30px; -margin-right:-30px; -padding-bottom:30px; -height:100%; -outline:0; -position:relative -} -.CodeMirror-sizer { -position:relative; -border-right:30px solid transparent -} -.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar { -position:absolute; -z-index:6; -display:none -} -.CodeMirror-vscrollbar { -right:0; -top:0; -overflow-x:hidden; -overflow-y:scroll -} -.CodeMirror-hscrollbar { -bottom:0; -left:0; -overflow-y:hidden; -overflow-x:scroll -} -.CodeMirror-scrollbar-filler { -right:0; -bottom:0 -} -.CodeMirror-gutter-filler { -left:0; -bottom:0 -} -.CodeMirror-gutters { -position:absolute; -left:0; -top:0; -min-height:100%; -z-index:3 -} -.CodeMirror-gutter { -white-space:normal; -height:100%; -display:inline-block; -vertical-align:top; -margin-bottom:-30px -} -.CodeMirror-gutter-wrapper { -position:absolute; -z-index:4; -background:0 0!important; -border:none!important -} -.CodeMirror-gutter-background { -position:absolute; -top:0; -bottom:0; -z-index:4 -} -.CodeMirror-gutter-elt { -position:absolute; -cursor:default; -z-index:4 -} -.CodeMirror-gutter-wrapper ::selection { -background-color:transparent -} -.CodeMirror-gutter-wrapper ::-moz-selection { -background-color:transparent -} -.CodeMirror-lines { -cursor:text; -min-height:1px -} -.CodeMirror pre { --moz-border-radius:0; --webkit-border-radius:0; -border-radius:0; -border-width:0; -background:0 0; -font-family:inherit; -font-size:inherit; -margin:0; -white-space:pre; -word-wrap:normal; -line-height:inherit; -color:inherit; -z-index:2; -position:relative; -overflow:visible; --webkit-tap-highlight-color:transparent; --webkit-font-variant-ligatures:contextual; -font-variant-ligatures:contextual -} -.CodeMirror-wrap pre { -word-wrap:break-word; -white-space:pre-wrap; -word-break:normal -} -.CodeMirror-linebackground { -position:absolute; -left:0; -right:0; -top:0; -bottom:0; -z-index:0 -} -.CodeMirror-linewidget { -position:relative; -z-index:2; -overflow:auto -} -.CodeMirror-rtl pre { -direction:rtl -} -.CodeMirror-code { -outline:0 -} -.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer { --moz-box-sizing:content-box; -box-sizing:content-box -} -.CodeMirror-measure { -position:absolute; -width:100%; -height:0; -overflow:hidden; -visibility:hidden -} -.CodeMirror-cursor { -position:absolute; -pointer-events:none -} -.CodeMirror-measure pre { -position:static -} -div.CodeMirror-cursors { -visibility:hidden; -position:relative; -z-index:3 -} -div.CodeMirror-dragcursors { -visibility:visible -} -.CodeMirror-focused div.CodeMirror-cursors { -visibility:visible -} -.CodeMirror-selected { -background:#d9d9d9 -} -.CodeMirror-focused .CodeMirror-selected { -background:#d7d4f0 -} -.CodeMirror-crosshair { -cursor:crosshair -} -.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection { -background:#d7d4f0 -} -.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection { -background:#d7d4f0 -} -.cm-searching { -background:#ffa; -background:rgba(255,255,0,.4) -} -.cm-force-border { -padding-right:.1px -} -@media print { -.CodeMirror div.CodeMirror-cursors { -visibility:hidden -} - -} -.cm-tab-wrap-hack:after { -content:'' -} -span.CodeMirror-selectedtext { -background:0 0 -} -.doc>section.text .tests .list li header,.doc>section>header,.sidebar>.min>.list>li:first-child { -display:flex; -align-items:center; -padding:0 1rem 0 1rem; -flex:0 2.5rem -} -.doc>section.text .tests .list li header h1,.doc>section>header h1,.sidebar>.min>.list>li:first-child h1 { -flex:1 -} -#cheatsheet table th,.community>header .icon,.container .header .settings,.doc>section.text .tests .list li header .icon.delete,.doc>section.tools>article .icon.help,.example svg.icon.load,.sidebar>.full .list .row,.sidebar>.full .list li,.sidebar>.full>header .icon,.sidebar>.min .list .row,.sidebar>.min .list li,.tooltip .list li,.tooltip header .help.icon,a { -transition:color .2s; -color:#d7dadc -} -#cheatsheet table th:not(.inactive),.community>header .icon:not(.inactive),.container .header .settings:not(.inactive),.doc>section.text .tests .list li header .icon.delete:not(.inactive),.doc>section.tools>article .icon.help:not(.inactive),.example svg.icon.load:not(.inactive),.sidebar>.full .list .row:not(.inactive),.sidebar>.full .list li:not(.inactive),.sidebar>.full>header .icon:not(.inactive),.sidebar>.min .list .row:not(.inactive),.sidebar>.min .list li:not(.inactive),.tooltip .list li:not(.inactive),.tooltip header .help.icon:not(.inactive),a:not(.inactive) { -cursor:pointer -} -#cheatsheet table th:not(.inactive):hover,.community>header .icon:not(.inactive):hover,.container .header .settings:not(.inactive):hover,.doc>section.text .tests .list li header .icon.delete:not(.inactive):hover,.doc>section.tools>article .icon.help:not(.inactive):hover,.example svg.icon.load:not(.inactive):hover,.sidebar>.full .list .row:not(.inactive):hover,.sidebar>.full .list li:not(.inactive):hover,.sidebar>.full>header .icon:not(.inactive):hover,.sidebar>.min .list .row:not(.inactive):hover,.sidebar>.min .list li:not(.inactive):hover,.tooltip .list li:not(.inactive):hover,.tooltip header .help.icon:not(.inactive):hover,a:not(.inactive):hover { -color:#e7e9ea -} -#cheatsheet table th:not(.inactive):hover svg.icon,.community>header .icon:not(.inactive):hover svg.icon,.container .header .settings:not(.inactive):hover svg.icon,.doc>section.text .tests .list li header .icon.delete:not(.inactive):hover svg.icon,.doc>section.tools>article .icon.help:not(.inactive):hover svg.icon,.example svg.icon.load:not(.inactive):hover svg.icon,.sidebar>.full .list .row:not(.inactive):hover svg.icon,.sidebar>.full .list li:not(.inactive):hover svg.icon,.sidebar>.full>header .icon:not(.inactive):hover svg.icon,.sidebar>.min .list .row:not(.inactive):hover svg.icon,.sidebar>.min .list li:not(.inactive):hover svg.icon,.tooltip .list li:not(.inactive):hover svg.icon,.tooltip header .help.icon:not(.inactive):hover svg.icon,a:not(.inactive):hover svg.icon { -color:currentColor -} -#cheatsheet table th svg.icon,.community>header .icon svg.icon,.container .header .settings svg.icon,.doc>section.text .tests .list li header .icon.delete svg.icon,.doc>section.tools>article .icon.help svg.icon,.example svg.icon.load svg.icon,.sidebar>.full .list .row svg.icon,.sidebar>.full .list li svg.icon,.sidebar>.full>header .icon svg.icon,.sidebar>.min .list .row svg.icon,.sidebar>.min .list li svg.icon,.tooltip .list li svg.icon,.tooltip header .help.icon svg.icon,a svg.icon { -transition:fill .2s -} -#tooltip .texthover i,.example code i { -background:#595f64; -color:#f0f1f2; -font-weight:400; -padding:0 .25em; -margin-left:.25em -} -.doc>section.tools>article .details span.hover,.doc>section.tools>article .details table tr.group:hover,.doc>section.tools>article .explain div.selected { -outline:solid 2px rgba(16,17,18,.3); -z-index:10 -} -.doc>section.tools>article .explain div.related { -outline:solid 1px rgba(16,17,18,.3); -z-index:9 -} -body,html { -margin:0; -width:100%; -height:100%; -min-height:540px; -min-width:500px; -font-family:"Roboto Condensed",sans-serif; -font-size:16px; -color:#f0f1f2; --webkit-user-select:none; -user-select:none; -cursor:default -} -body>.container,html>.container { -height:100%; -display:flex; -flex-flow:column -} -body>.container>.app,html>.container>.app { -flex:1; -min-height:0; -display:flex; -align-items:stretch -} -::-webkit-scrollbar { -width:.5em; -height:.5em -} -::-webkit-scrollbar-track { -background:rgba(126,135,142,.25) -} -::-webkit-scrollbar-thumb { -background-color:#7e878e; -border-radius:.5em -} -.app .sidebar,.container .header,.tooltip { --moz-osx-font-smoothing:grayscale; --webkit-font-smoothing:antialiased -} -code,pre { -font-family:"Source Code Pro",monospace -} -pre { -margin:0 -} -a { -color:#70b0e0; -text-decoration:none -} -h1,h2 { -font-size:1rem; -font-weight:700; -margin:0 -} -.list { -display:flex; -flex-direction:column; -margin:0; -padding:0 -} -.list li { -display:block -} -hr { -border:0; -border-top:1px solid rgba(126,135,142,.25); -margin:.75em 0 1em 0 -} -span.match { -background:rgba(112,176,224,.5); -color:#101112 -} -span.error { -color:#d22; -font-weight:700 -} -span.error.warning { -color:#d22 -} -.anim-spin { -animation:1s linear infinite spin -} -@keyframes spin { -from { -transform:rotate(0) -} -to { -transform:rotate(360deg) -} - -} -#tooltip-signin ul.list li,.button,.buttonbar>*,.container .header ul.file li,.control,.segcontrol>*,input,textarea { -padding:.375rem .75rem; -font-size:.875rem; -border-radius:.125rem -} -input,textarea { -border-radius:0; -background:rgba(240,241,242,.05); -border:none; -border-bottom:solid 1px rgba(240,241,242,.05); -font-family:"Roboto Condensed",sans-serif; -font-size:1rem; -display:block; -width:100%; -box-sizing:border-box; -color:#f0f1f2; -resize:vertical -} -input::placeholder,textarea::placeholder { -color:currentColor; -opacity:.4 -} -input:focus,textarea:focus { -outline:0 -} -input:focus::placeholder,textarea:focus::placeholder { -opacity:.2 -} -.status { -display:inline-block; -color:rgba(240,241,242,.5) -} -.status .icon.success { -color:#70b0e0 -} -.status .icon.alert { -color:#d22 -} -svg.icon { -width:1.125em; -height:1.125em; -display:inline-block; -fill:currentColor; -vertical-align:middle -} -svg.icon.small { -width:.75em; -height:.75em -} -svg.icon.inline { -margin-top:-.25em -} -.control { -background:rgba(240,241,242,.2) -} -#tooltip-signin ul.list li,.button,.buttonbar>*,.container .header ul.file li,.segcontrol>* { -background:rgba(240,241,242,.35); -display:inline-block; -cursor:pointer; -transition:background-color .2s ease-out; -transition:opacity .2s ease-out; -box-shadow:0 2px 1px rgba(0,0,0,.1); -line-height:normal -} -#tooltip-signin ul.list li.default,.button.default,.buttonbar>.default,.container .header ul.file li.default,.segcontrol>.default { -font-weight:700; -background:#70b0e0; -color:#101112 -} -#tooltip-signin ul.list li.default:hover,.button.default:hover,.buttonbar>.default:hover,.container .header ul.file li.default:hover,.segcontrol>.default:hover { -background:#f0f1f2 -} -#tooltip-signin ul.list li.selected,#tooltip-signin ul.list li:hover,.button.selected,.button:hover,.buttonbar>.selected,.buttonbar>:hover,.container .header ul.file li.selected,.container .header ul.file li:hover,.segcontrol>.selected,.segcontrol>:hover { -background:rgba(240,241,242,.75) -} -#tooltip-signin ul.list li.selected>svg.icon.dropdown,#tooltip-signin ul.list li:hover>svg.icon.dropdown,.button.selected>svg.icon.dropdown,.button:hover>svg.icon.dropdown,.buttonbar>.selected>svg.icon.dropdown,.buttonbar>:hover>svg.icon.dropdown,.container .header ul.file li.selected>svg.icon.dropdown,.container .header ul.file li:hover>svg.icon.dropdown,.segcontrol>.selected>svg.icon.dropdown,.segcontrol>:hover>svg.icon.dropdown { -margin-top:.5em -} -#tooltip-signin ul.list li.disabled,.button.disabled,.buttonbar>.disabled,.container .header ul.file li.disabled,.segcontrol>.disabled { -opacity:.35; -cursor:default; -pointer-events:none -} -#tooltip-signin ul.list li>svg.icon,.button>svg.icon,.buttonbar>*>svg.icon,.container .header ul.file li>svg.icon,.segcontrol>*>svg.icon { -margin-top:-.125em -} -#tooltip-signin ul.list li>svg.icon+*,.button>svg.icon+*,.buttonbar>*>svg.icon+*,.container .header ul.file li>svg.icon+*,.segcontrol>*>svg.icon+* { -margin-left:.25em -} -#tooltip-signin ul.list li>svg.icon.dropdown,.button>svg.icon.dropdown,.buttonbar>*>svg.icon.dropdown,.container .header ul.file li>svg.icon.dropdown,.segcontrol>*>svg.icon.dropdown { -transition:margin-top .2s; -margin-top:.125em; -width:.375em; -height:.375em -} -.buttonbar,.controlbar,.segcontrol { -display:flex; -padding:0; -margin:0 -} -.buttonbar>*,.controlbar>*,.segcontrol>* { -margin-left:.25rem -} -.buttonbar>*,.segcontrol>* { -margin-left:1px; -border-radius:0; -display:inline-block -} -.buttonbar>:first-child,.segcontrol>:first-child { -border-radius:.125rem 0 0 .125rem -} -.buttonbar>:last-child,.segcontrol>:last-child { -border-radius:0 .125rem .125rem 0 -} -.segcontrol>:first-child { -border-radius:.125rem 0 0 .125rem -} -.segcontrol>:last-child { -border-radius:0 .125rem .125rem 0 -} -.tooltip { -display:flex; -flex-flow:column; -align-items:center; -z-index:10000; -position:absolute; -transform:translate(-50%,0); -transition-property:opacity,margin-top; -transition-duration:.2s -} -.tooltip>.content { -background:rgba(16,17,18,.85); -color:#f0f1f2; -padding:1rem; -border-radius:.125rem; -max-width:15rem; -box-shadow:0 3px 4px rgba(0,0,0,.25); -overflow-wrap:break-word; --webkit-backdrop-filter:blur(3px); -backdrop-filter:blur(3px) -} -.tooltip>.content code { -font-weight:700 -} -.tooltip>.content>.warningtext { -display:block; -color:#b7bcc0; -margin-top:.5rem -} -.tooltip header { -display:flex -} -.tooltip header h1 { -flex:1 -} -.tooltip header .help.icon { -margin-left:1em; -color:#7e878e -} -.tooltip>.tip { -fill:rgba(16,17,18,.85); -width:8px; -height:8px; -transform:scale(1,-1) -} -.tooltip.flipped { -transform:translate(-50%,-100%) -} -.tooltip.flipped .tip { -transform:scale(1,1); -order:1 -} -.tooltip .list li { -color:#b7bcc0 -} -.tooltip .list li+li { -margin-top:.5em -} -.tooltip .list li .check.icon { -color:#585e63; -margin-right:.25em -} -.tooltip .list li.selected .check.icon { -color:#70b0e0 -} -.tooltip .list li:not(.inactive):hover { -color:#e7e9ea -} -.tooltip .list li em { -font-style:normal; -font-weight:700; -color:#f0f1f2; -padding:0 .15em; -background:rgba(126,135,142,.3); -border-radius:.125rem -} -.example svg.icon.load { -color:#595f64; -float:right; -display:inline-block; -padding-left:.5rem -} -.example code { -display:inline-block; -word-break:break-all; -width:100% -} -.container .header { -display:flex; -align-items:center; -justify-content:space-between; -background:#101112; -padding:0 1rem; -height:54px; -z-index:1000 -} -.container .header .logo { -flex:none; -font-size:1.125rem; -margin-top:.0625rem; -color:#70b0e0; -width:1.5em; -height:1.5em -} -.container .header .settings { -color:#70b0e0; -display:flex; -min-width:0; -max-width:20em -} -.container .header .settings .name { -margin:0 .25rem 0 .5rem; -text-overflow:ellipsis; -white-space:nowrap; -overflow:hidden -} -.container .header .settings .icon.share { -flex:none; -color:#595f64; -width:1em; -margin-right:.5rem -} -.container .header .signin.selected a { -color:#f0f1f2!important -} -.container .header ul { -padding:0; -white-space:nowrap -} -.container .header ul li { -display:inline-block -} -.container .header ul li:first-child { -margin-left:0 -} -.container .header ul.file { -text-align:right -} -.container .header ul.file li { -color:#b7bcc0; -background:#282b2d -} -.container .header ul.file li:hover { -background:#3a3e41 -} -.container .header ul.file li.save { -color:#70b0e0; -font-weight:700 -} -.container .header ul.file li.save .savekey { -font-size:.875em; -font-weight:400; -color:#7e878e -} -#tooltip-signin ul.list .container .header li.theme,.container .header #tooltip-signin ul.list li.theme,.container .header .button.theme,.container .header ul.file li.theme { -padding:.3755rem; -background:#282b2d -} -#tooltip-signin ul.list .container .header li.theme.selected .icon,.container .header #tooltip-signin ul.list li.theme.selected .icon,.container .header .button.theme.selected .icon,.container .header ul.file li.theme.selected .icon { -color:#70b0e0 -} -#tooltip-signin ul.list .container .header li.theme:hover,.container .header #tooltip-signin ul.list li.theme:hover,.container .header .button.theme:hover,.container .header ul.file li.theme:hover { -background:#3a3e41 -} -.container .header ul.etc { -flex:1; -text-align:right; -font-size:.875rem; -color:#595f64 -} -.container .header ul.etc li { -margin-left:1rem -} -#tooltip-signin ul.list li { -margin:.25rem 0; -padding:.625rem; -color:#e7e9ea; -opacity:.85 -} -#tooltip-signin ul.list li:hover { -opacity:1 -} -#tooltip-signin ul.list li[data-id=GitHub] { -background:#777!important -} -#tooltip-signin ul.list li[data-id=Google] { -background:#c94130!important -} -#tooltip-signin ul.list li>svg.icon { -margin-right:.625rem -} -#tooltip-signin .signout { -display:none -} -#tooltip-signin .signout .signoutbtn { -cursor:pointer; -color:#70b0e0; -font-weight:700 -} -#tooltip-signin .signout .signoutbtn:hover { -color:#f0f1f2 -} -#tooltip-signin.authenticated .signin { -display:none -} -#tooltip-signin.authenticated .signout { -display:block -} -#tooltip-signin .distract { -display:none; -color:#7e878e -} -#tooltip-signin .distract .distractor { -margin-right:.5rem -} -#tooltip-signin.wait .distract { -display:block -} -#tooltip-signin.wait .signin .list,#tooltip-signin.wait .signout .signoutbtn { -display:none -} -.doc { -flex:8 800px; -display:flex; -flex-flow:column -} -.doc>section { -background:#f0f1f2; -color:#585e63; -flex:1 1 0%; -min-height:0; -transition:flex .2s ease-out; -display:flex; -flex-direction:column -} -.doc>section>header { -color:#26292b; -background:#b7bcc0 -} -.doc>section>header>.max { -transition:transform .2s; -color:#7e878e; -margin-left:1rem; -cursor:pointer -} -.doc>section>header>.max:hover { -color:#f0f1f2 -} -.doc>section>article { -flex:1; -min-height:0 -} -.doc>section.closed { -flex:0 2.5rem -} -.doc>section.closed>header { -cursor:pointer -} -.doc>section.closed>header>:not(h1) { -opacity:.8 -} -.doc>section.closed>header>.max { -transform:rotate(45deg) -} -.doc.tests-mode section.tools { -display:none -} -.doc input,.doc textarea { -background:rgba(240,241,242,.05); -border-color:rgba(240,241,242,.05) -} -.doc .control { -background:rgba(240,241,242,.2) -} -#tooltip-signin ul.list .doc li,.container .header ul.file .doc li,.doc #tooltip-signin ul.list li,.doc .button,.doc .buttonbar>*,.doc .container .header ul.file li,.doc .segcontrol>* { -background:rgba(240,241,242,.35) -} -.doc .segcontrol>* { -color:#000; -font-weight:bold -} -#tooltip-signin ul.list .doc li.selected,#tooltip-signin ul.list .doc li:hover,.container .header ul.file .doc li.selected,.container .header ul.file .doc li:hover,.doc #tooltip-signin ul.list li.selected,.doc #tooltip-signin ul.list li:hover,.doc .button.selected,.doc .button:hover,.doc .buttonbar>.selected,.doc .buttonbar>:hover,.doc .container .header ul.file li.selected,.doc .container .header ul.file li:hover,.doc .segcontrol>.selected,.doc .segcontrol>:hover { -background:rgba(240,241,242,.75) -} -#tooltip-signin ul.list .doc li.default:hover,.container .header ul.file .doc li.default:hover,.doc #tooltip-signin ul.list li.default:hover,.doc .button.default:hover,.doc .buttonbar>.default:hover,.doc .container .header ul.file li.default:hover,.doc .segcontrol>.default:hover { -background:#f0f1f2 -} -.doc>section.expression { -flex:0 6.25rem -} -.doc>section.expression>header { -background:#70b0e0 -} -.doc>section.expression>.editor { -position:relative -} -.doc>section.expression>.editor .icon.alert { -display:none; -position:relative; -float:right; -margin:1.25rem; -color:#d22; -z-index:10; --webkit-user-select:none; -user-select:none -} -.doc>section.expression>.editor>.CodeMirror { -padding:1rem 0 0 1rem; -font-weight:700 -} -.doc>section.expression>.editor.error>.icon.alert { -display:block -} -#tooltip-flags>.list li,#tooltip-flavor>.list li { -cursor:pointer; -color:#b7bcc0 -} -#tooltip-flags>.list li:hover,#tooltip-flavor>.list li:hover { -color:#f0f1f2 -} -#tooltip-flags>.list li+li,#tooltip-flavor>.list li+li { -margin-top:.5em -} -#tooltip-flags>.list li .check.icon,#tooltip-flavor>.list li .check.icon { -color:#585e63; -margin-right:.25em -} -#tooltip-flags>.list li.selected .check.icon,#tooltip-flavor>.list li.selected .check.icon { -color:#70b0e0 -} -#tooltip-flags>.list li em,#tooltip-flavor>.list li em { -font-style:normal; -font-weight:700; -color:#f0f1f2; -padding:0 .15em; -background:rgba(126,135,142,.3); -border-radius:.125rem -} -.exp-related { -border-bottom:solid 1px rgba(16,17,18,.3); -border-top:solid 1px rgba(16,17,18,.3); -margin-bottom:-1px; -margin-top:-1px -} -.exp-related-left { -border-left:solid 1px rgba(16,17,18,.3); -margin-left:-1px -} -.exp-related-right { -border-right:solid 1px rgba(16,17,18,.3); -margin-right:-1px -} -.exp-selected { -border-top:solid 2px rgba(16,17,18,.3); -border-bottom:solid 2px rgba(16,17,18,.3) -} -.exp-selected-left { -border-left:solid 2px rgba(16,17,18,.3); -margin-left:-2px -} -.exp-selected-right { -border-right:solid 2px rgba(16,17,18,.3); -margin-right:-2px -} -.exp-error { -border-bottom:solid 2px #d22 -} -.exp-warning { -border-bottom:dotted 2px #d22 -} -.exp-char { -color:#101112 -} -.exp-decorator { -color:#b7bcc0 -} -.exp-esc { -color:#c0c -} -.exp-lazy,.exp-possessive,.exp-quant { -color:#58f -} -.exp-alt { -color:#0a0 -} -.exp-anchor { -color:#840 -} -.exp-group,.exp-lookaround,.exp-ref { -color:#0a0 -} -.exp-charclass,.exp-set,.exp-subst { -color:#d70 -} -.exp-group-0 { -background:rgba(0,238,0,.11) -} -.exp-group-1 { -background:rgba(0,238,0,.22) -} -.exp-group-2 { -background:rgba(0,238,0,.33) -} -.exp-group-3 { -background:rgba(0,238,0,.44) -} -.exp-group-set { -background:rgba(255,238,0,.3) -} -.exp-comment { -color:#b7bcc0; -background:rgba(16,17,18,.05); -font-style:italic; -border-bottom:solid 3px #d7dadc -} -.exp-special { -color:#c0c -} -.doc>section.text article { -display:flex -} -.doc>section.text>header .modelist li { -font-size:1rem; -padding-top:.275rem; -padding-bottom:.275rem -} -.doc>section.text>header .modelist li:last-child::after { -transition:opacity 1s; -font-size:.625rem; -padding:.25em .5em; -display:inline-block; -position:absolute; -color:#f0f1f2; -background:#70b0e0; -letter-spacing:.075em; -transform:translate(.25rem,-.125rem); -content:"NEW" -} -#tooltip-signin ul.list .doc>section.text>header li.add,.container .header ul.file .doc>section.text>header li.add,.doc>section.text>header #tooltip-signin ul.list li.add,.doc>section.text>header .button.add,.doc>section.text>header .container .header ul.file li.add { -display:none!important -} -.doc>section.text>header .result { -font-weight:700 -} -.doc>section.text>header .result.error { -background:#d22; -color:#e7e9ea -} -.doc>section.text>header .result.error.warning { -background:#d22 -} -.doc>section.text>header .result.pass { -background:#0a0; -color:#e7e9ea -} -.doc>section.text>header .result.fail { -background:#d22; -color:#e7e9ea -} -.doc>section.text>header .result em { -font-style:normal; -font-weight:400; -opacity:.75 -} -.doc>section.text.tests-viewed>header .modelist li:last-child::after { -opacity:0 -} -.doc>section.text .editor { -position:relative; -box-sizing:border-box; -display:flex; -height:100%; -width:100%; -padding:1rem 0 1rem 1rem -} -.doc>section.text .editor>.pad { -box-sizing:border-box; -position:relative; -overflow:hidden; -width:100%; -flex:1 -} -.doc>section.text .editor>.pad>.CodeMirror { -color:#101112 -} -.doc>section.text .editor>.pad>iframe.resizedetector { -opacity:0; -z-index:-1000; -border:none; -width:100%; -height:100%; -position:absolute -} -.doc>section.text .editor>.pad>canvas.highlights { -transition:opacity .2s; -position:absolute -} -.doc>section.text .tests { -display:none; -width:100%; -background:#d7dadc; -padding:1rem; -box-sizing:border-box; -overflow:auto -} -.doc>section.text .tests .default { -transition:opacity 1s; -padding:1rem .5rem; -color:#585e63 -} -.doc>section.text .tests .list:empty~.default { -opacity:1 -} -.doc>section.text .tests.tests-added .default { -opacity:0 -} -#tooltip-signin ul.list .doc>section.text .tests li.add,.container .header ul.file .doc>section.text .tests li.add,.doc>section.text .tests #tooltip-signin ul.list li.add,.doc>section.text .tests .add.button,.doc>section.text .tests .container .header ul.file li.add { -box-sizing:border-box; -width:100%; -box-shadow:none; -opacity:.5; -background:rgba(183,188,192,.75); -color:#101112; -padding:.75rem .625rem; -margin-top:.125rem; -font-weight:700; -border-radius:.125rem -} -#tooltip-signin ul.list .doc>section.text .tests li.add:hover,.container .header ul.file .doc>section.text .tests li.add:hover,.doc>section.text .tests #tooltip-signin ul.list li.add:hover,.doc>section.text .tests .add.button:hover,.doc>section.text .tests .container .header ul.file li.add:hover { -opacity:1 -} -.doc>section.text .tests .list li { -background:#e7e9ea; -box-shadow:0 1px 2px rgba(0,0,0,.1); -transition:margin .1s linear; -margin:.125rem 0; -display:inline-block; -width:100% -} -.doc>section.text .tests .list li:first-child { -margin-top:0 -} -.doc>section.text .tests .list li:hover { -outline:solid 2px rgba(16,17,18,.3) -} -.doc>section.text .tests .list li header { -padding:.5rem .75rem -} -.doc>section.text .tests .list li header input.name { -color:#383b3f; -font-weight:700; -padding:0; -margin:0 .5rem; -background:0 0; -border:none -} -.doc>section.text .tests .list li header .icon.delete { -color:#b7bcc0 -} -.doc>section.text .tests .list li header .icon.delete:hover { -color:#d22!important -} -.doc>section.text .tests .list li header .icon.fail { -display:none; -color:#d22 -} -.doc>section.text .tests .list li header .icon.pass { -color:#0a0 -} -.doc>section.text .tests .list li header .type { -background:0 0; -text-transform:uppercase; -font-size:.75em; -color:#b7bcc0; -font-weight:700; -box-shadow:none -} -.doc>section.text .tests .list li article { -border-top:solid 1px #d7dadc; -padding:.5rem 0 .5rem .75rem; -display:none; -height:6em; -align-items:stretch -} -.doc>section.text .tests .list li article .bar { -background:#0a0; -width:0; -outline:solid 1px #0a0; -margin:.25rem .875rem .25rem .375rem -} -.doc>section.text .tests .list li article .editor { -padding:0 -} -.doc>section.text .tests .list li:not(.selected) { -cursor:pointer -} -.doc>section.text .tests .list li:not(.selected) .type .icon.dropdown { -opacity:0 -} -.doc>section.text .tests .list li:not(.selected) * { -pointer-events:none -} -.doc>section.text .tests .list li.selected { -outline:solid 2px rgba(16,17,18,.3); -background:#f0f1f2; -box-shadow:0 2px 3px rgba(0,0,0,.25); -margin:.75rem 0 -} -.doc>section.text .tests .list li.selected:first-child { -margin-top:0 -} -.doc>section.text .tests .list li.selected article { -display:flex -} -.doc>section.text .tests .list li.selected .type { -color:#585e63 -} -.doc>section.text .tests .list li.selected .type:hover { -color:#26292b -} -.doc>section.text .tests .list li.fail .icon.fail { -display:block -} -.doc>section.text .tests .list li.fail .icon.pass { -display:none -} -.doc>section.text .tests .list li.fail .bar { -outline:solid 1px #d22 -} -#tooltip-signin ul.list .doc.tests-mode>section.text>header li.add,.container .header ul.file .doc.tests-mode>section.text>header li.add,.doc.tests-mode>section.text>header #tooltip-signin ul.list li.add,.doc.tests-mode>section.text>header .button.add,.doc.tests-mode>section.text>header .container .header ul.file li.add { -display:inline-block -} -.doc.tests-mode>section.text>article>.editor { -display:none -} -.doc.tests-mode>section.text .tests { -display:block -} -#tooltip .texthover .more { -display:inline-block; -color:#b7bcc0; -font-style:italic; -margin-top:.75rem -} -.doc>section.tools>article { -display:flex; -flex-direction:column; -background:#e7e9ea -} -.doc>section.tools>article .icon.help { -position:absolute; -right:.25rem; -transform:translate(0,-50%) translate(0,1.875rem); -padding:1rem; -color:rgba(16,17,18,.25); -z-index:10 -} -.doc>section.tools>article .icon.help:hover { -color:#26292b!important -} -.doc>section.tools>article .inputtool { -display:none; -flex-direction:column; -flex:1 -} -.doc>section.tools>article .inputtool .editor { -position:relative; -flex:0 3.75rem; -background:#f0f1f2; -border-bottom:solid 1px #d7dadc -} -.doc>section.tools>article .inputtool .editor>.CodeMirror { -padding:1rem 0 0 1rem; -height:100%; -font-weight:700 -} -.doc>section.tools>article .inputtool .result { -flex:1 -} -.doc>section.tools>article .inputtool .result textarea { -box-sizing:border-box; -padding:1rem 0 1rem 1rem; -resize:none; -display:block; -border:0; -background:0 0; -width:100%; -height:100%; -margin:0; -font-family:"Source Code Pro",monospace; -font-size:16px; -color:#101112 -} -.doc>section.tools>article .inputtool .result textarea:focus { -outline:0 -} -.doc>section.tools>article .content { -flex:1 1 0%; -min-height:0; -overflow-y:auto; -display:block; -padding:1rem -} -.doc>section.tools>article.showinput .inputtool { -display:flex -} -.doc>section.tools>article.showinput .content { -display:none -} -.doc>section.tools>article .explain>.desc { -color:#7e878e; -display:block; -padding:.5rem -} -.doc>section.tools>article .explain div { -padding:.5em 1em .5em 2.5em; -margin-top:.75em; -border:solid 1px rgba(183,188,192,.5); -border-left:solid .25em rgba(183,188,192,.5); -background:#f0f1f2; -cursor:pointer -} -.doc>section.tools>article .explain div.applied { -margin-left:1em; -margin-top:0; -border:none; -background:rgba(183,188,192,.3) -} -.doc>section.tools>article .explain div.applied+div.applied { -margin-top:1px; -margin-left:2em -} -.doc>section.tools>article .explain div.error { -background:#edd2d3; -border-color:#d22 -} -.doc>section.tools>article .explain div.error .error-title { -font-weight:700 -} -.doc>section.tools>article .explain div.error .warningtext { -color:#7e878e; -margin-left:.5rem -} -.doc>section.tools>article .explain code { -font-weight:700 -} -.doc>section.tools>article .explain code.token { -display:inline-block; -min-width:1.25em; -margin:-1px .5em 0 -1.75em; -float:left -} -.doc>section.tools>article .explain div.close { -margin:0; -padding:0; -border:none; -background:0 0 -} -.doc>section.tools>article .explain .exp-group-0,.doc>section.tools>article .explain .exp-group-1,.doc>section.tools>article .explain .exp-group-2,.doc>section.tools>article .explain .exp-group-3 { -border-color:rgba(0,170,0,.25) -} -.doc>section.tools>article .explain .exp-group-0 { -background:#ddf1df -} -.doc>section.tools>article .explain .exp-group-1 { -background:#d1f1d3 -} -.doc>section.tools>article .explain .exp-group-2 { -background:#c5f0c6 -} -.doc>section.tools>article .explain .exp-group-3 { -background:#b9f0ba -} -.doc>section.tools>article .explain .exp-group-4 { -background:#383b3f; -color:#f0f1f2; -padding:1rem -} -.doc>section.tools>article .explain .exp-group-set { -background:#f4f0b6; -border-color:rgba(221,119,0,.25) -} -.doc>section.tools>article .details>div.desc { -padding:.5rem; -color:#7e878e; -margin-bottom:.5em -} -.doc>section.tools>article .details>div.desc code { -color:#585e63; -font-weight:700 -} -.doc>section.tools>article .details table { -font-family:"Source Code Pro",monospace; -width:100%; -border-spacing:0; -border:0 -} -.doc>section.tools>article .details table tr:nth-child(odd) td { -background:#e1e3e4 -} -.doc>section.tools>article .details table tr.match td { -background:#d7dadc; -border-bottom:1px dotted #b7bcc0 -} -.doc>section.tools>article .details table td { -padding:.5rem 1rem; -vertical-align:top -} -.doc>section.tools>article .details table td:first-child { -font-weight:700; -white-space:nowrap; -padding-right:0 -} -.doc>section.tools>article .details table td:nth-child(2) { -color:#7e878e; -white-space:nowrap; -border-right:1px dotted #b7bcc0 -} -.doc>section.tools>article .details table td:nth-child(3) { -white-space:pre-wrap; -width:100%; -color:#101112; --webkit-user-select:text; -user-select:text; --webkit-user-select:contain; -user-select:contain -} -.doc>section.tools>article .details table td:nth-child(3) em { -color:#7e878e -} -.doc>section.tools>article .details .group-1 { -background:hsla(60,100%,50%,.5) -} -.doc>section.tools>article .details .group-2 { -background:hsla(120,100%,50%,.5) -} -.doc>section.tools>article .details .group-3 { -background:hsla(230,100%,70%,.5) -} -.doc>section.tools>article .details .group-4 { -background:hsla(280,100%,60%,.5) -} -.doc>section.tools>article .details .group-5 { -background:hsla(350,100%,60%,.5) -} -.doc>section.tools>article .details .group-0 { -background:hsla(30,100%,50%,.5) -} -.doc>section.tools>article .details .group-0,.doc>section.tools>article .details .group-1,.doc>section.tools>article .details .group-2,.doc>section.tools>article .details .group-3,.doc>section.tools>article .details .group-4,.doc>section.tools>article .details .group-5 { -outline:.5px solid rgba(16,17,18,.25) -} -.sidebar>.full .list .row.active:hover,.sidebar>.full .list li.active:hover,.sidebar>.min .list .row.active:hover,.sidebar>.min .list li.active:hover { -color:#f0f1f2 -} -.sidebar>.full .list .row.active>.icon.check,.sidebar>.full .list .row.active>.icon:first-child,.sidebar>.full .list li.active>.icon.check,.sidebar>.full .list li.active>.icon:first-child,.sidebar>.min .list .row.active>.icon.check,.sidebar>.min .list .row.active>.icon:first-child,.sidebar>.min .list li.active>.icon.check,.sidebar>.min .list li.active>.icon:first-child { -color:#70b0e0 -} -.sidebar { -flex:1 0 350px; -width:350px; -transition:flex .2s ease-out; -background:#282b2d; -display:flex; -flex-direction:column; -color:#b7bcc0; -border-right:1px solid #3a3e41; -box-shadow:1px 2px 8px rgba(0,0,0,.45); -z-index:100 -} -.sidebar>.blocker { -display:none -} -.sidebar .list>li,.sidebar .row { -display:flex; -flex:0 0 auto; -padding:.5rem; -align-items:center; -justify-content:center; -border-bottom:1px solid rgba(240,241,242,.06); -transition:background .2s; -color:#b7bcc0 -} -.sidebar .list>li>*,.sidebar .row>* { -margin:0 .5rem -} -.sidebar .list>li .label,.sidebar .list>li input,.sidebar .row .label,.sidebar .row input { -flex:1 -} -.sidebar .list>li .icon,.sidebar .row .icon { -color:#595f64 -} -.sidebar header.row { -flex:0 0 2.5rem; -padding:0 -} -.sidebar header.row .backrow { -display:flex; -flex:1 0 auto; -align-items:center; -cursor:pointer; -margin:0; -padding:.5rem 0 -} -.sidebar header.row .backrow .icon { -flex:0 0 auto; -margin:0 1rem -} -.sidebar header.row .backrow:hover { -color:#f0f1f2 -} -.sidebar header.row .backrow:hover .icon { -color:currentColor -} -.sidebar header.row .close.icon { -padding:.75rem 1rem; -margin:0 -} -.sidebar code { -color:#f0f1f2 -} -.sidebar.closed { -flex:0 50px -} -.sidebar>.full .list,.sidebar>.min .list { -flex:1 1 0%; -display:flex; -align-items:stretch; -overflow-y:auto; -background:#3a3e41; -border-bottom:2px solid #3a3e41 -} -.sidebar>.full .list .row,.sidebar>.full .list li,.sidebar>.min .list .row,.sidebar>.min .list li { -color:#b7bcc0 -} -.sidebar>.full .list .row:hover:not(.inactive),.sidebar>.full .list li:hover:not(.inactive),.sidebar>.min .list .row:hover:not(.inactive),.sidebar>.min .list li:hover:not(.inactive) { -background:#4a4f53 -} -.sidebar>.full .list .row:hover:not(.inactive) .icon,.sidebar>.full .list .row:hover:not(.inactive) .rating,.sidebar>.full .list .row:hover:not(.inactive) .token,.sidebar>.full .list li:hover:not(.inactive) .icon,.sidebar>.full .list li:hover:not(.inactive) .rating,.sidebar>.full .list li:hover:not(.inactive) .token,.sidebar>.min .list .row:hover:not(.inactive) .icon,.sidebar>.min .list .row:hover:not(.inactive) .rating,.sidebar>.min .list .row:hover:not(.inactive) .token,.sidebar>.min .list li:hover:not(.inactive) .icon,.sidebar>.min .list li:hover:not(.inactive) .rating,.sidebar>.min .list li:hover:not(.inactive) .token { -color:currentColor -} -.sidebar>.full .list .row.selected,.sidebar>.full .list li.selected,.sidebar>.min .list .row.selected,.sidebar>.min .list li.selected { -background:#595f64; -color:#d7dadc -} -.sidebar>.full .list .row.selected .icon,.sidebar>.full .list .row.selected .rating,.sidebar>.full .list .row.selected .token,.sidebar>.full .list li.selected .icon,.sidebar>.full .list li.selected .rating,.sidebar>.full .list li.selected .token,.sidebar>.min .list .row.selected .icon,.sidebar>.min .list .row.selected .rating,.sidebar>.min .list .row.selected .token,.sidebar>.min .list li.selected .icon,.sidebar>.min .list li.selected .rating,.sidebar>.min .list li.selected .token { -color:currentColor -} -.sidebar>.full .list .row.selected:hover,.sidebar>.full .list li.selected:hover,.sidebar>.min .list .row.selected:hover,.sidebar>.min .list li.selected:hover { -background:#595f64; -color:#f0f1f2 -} -.sidebar>.full .list .row.disabled,.sidebar>.full .list li.disabled,.sidebar>.min .list .row.disabled,.sidebar>.min .list li.disabled { -pointer-events:none; -opacity:.35 -} -.sidebar>.full .list .row.link:not(.disabled),.sidebar>.full .list li.link:not(.disabled),.sidebar>.min .list .row.link:not(.disabled),.sidebar>.min .list li.link:not(.disabled) { -overflow:hidden -} -.sidebar>.full .list .row.link:not(.disabled) .url,.sidebar>.full .list li.link:not(.disabled) .url,.sidebar>.min .list .row.link:not(.disabled) .url,.sidebar>.min .list li.link:not(.disabled) .url { -color:#70b0e0; -margin:0 -} -.sidebar>.full .list .row.link:not(.disabled):hover,.sidebar>.full .list li.link:not(.disabled):hover,.sidebar>.min .list .row.link:not(.disabled):hover,.sidebar>.min .list li.link:not(.disabled):hover { -color:#f0f1f2 -} -.sidebar>.full .list .row.link:not(.disabled):hover .url,.sidebar>.full .list li.link:not(.disabled):hover .url,.sidebar>.min .list .row.link:not(.disabled):hover .url,.sidebar>.min .list li.link:not(.disabled):hover .url { -color:#f0f1f2 -} -.sidebar>.full .list .row.link:not(.disabled) .icon.copy,.sidebar>.full .list li.link:not(.disabled) .icon.copy,.sidebar>.min .list .row.link:not(.disabled) .icon.copy,.sidebar>.min .list li.link:not(.disabled) .icon.copy { -padding:1rem; -margin:-1rem -.5rem; -color:#595f64 -} -.sidebar>.full .list .row.link:not(.disabled) .icon.copy:hover,.sidebar>.full .list li.link:not(.disabled) .icon.copy:hover,.sidebar>.min .list .row.link:not(.disabled) .icon.copy:hover,.sidebar>.min .list li.link:not(.disabled) .icon.copy:hover { -color:#f0f1f2 -} -.sidebar>.full .list .row.loading,.sidebar>.full .list li.loading,.sidebar>.min .list .row.loading,.sidebar>.min .list li.loading { -padding:1rem; -display:block; -border:none; -color:#7e878e; -pointer-events:none -} -.sidebar>.full .list .row .token,.sidebar>.full .list li .token,.sidebar>.min .list .row .token,.sidebar>.min .list li .token { -color:#7e878e; -font-family:"Source Code Pro",monospace; -font-weight:700 -} -.sidebar>.full .list .row .rating,.sidebar>.full .list li .rating,.sidebar>.min .list .row .rating,.sidebar>.min .list li .rating { -display:flex; -align-items:center; -color:#7e878e; -font-size:.875em -} -.sidebar>.full .list .row .rating .favorites.icon,.sidebar>.full .list li .rating .favorites.icon,.sidebar>.min .list .row .rating .favorites.icon,.sidebar>.min .list li .rating .favorites.icon { -color:#70b0e0; -padding:0 .25rem -} -.sidebar>.min { -display:none; -background:#3a3e41 -} -.sidebar>.min>.list>li:first-child { -background:#282b2d; -padding:0 -} -.sidebar>.full { -display:flex; -flex-direction:column; -flex:1; -min-height:0 -} -.sidebar>.full.no-search>.search { -display:none -} -.sidebar>.full.no-list>.list { -display:none -} -.sidebar>.full>header h1 { -color:#f0f1f2 -} -.sidebar>.full>header .icon { -color:#595f64 -} -.sidebar>.full>.search { -padding:0 .5rem -} -.sidebar>.full>.search input { -background:0 0; -padding:0 -} -.sidebar>.full>.search input:focus { -border-bottom:1px dotted #3a3e41; -margin-bottom:-1px -} -.sidebar>.full>.search svg.icon { -width:1em; -height:1em; -margin-right:.375em -} -.sidebar>.full>.search svg.icon.search:hover { -color:#f0f1f2; -cursor:pointer -} -.sidebar>.full>.search svg.icon.distractor { -display:none -} -.sidebar>.full>.search.wait svg.icon.search { -display:none -} -.sidebar>.full>.search.wait svg.icon.distractor { -display:block -} -.sidebar>.full>.content { -flex:1 1 0%; -overflow-y:auto; -overflow-x:hidden; -padding:1rem; --webkit-user-select:text; -user-select:text; -overflow-wrap:break-word; -border-top:2px solid #282b2d; -line-height:1.4em -} -.sidebar>.full>.content h1 { -margin-top:1.5em; -color:#f0f1f2 -} -.sidebar>.full>.content code em { -font-style:normal; -background:rgba(112,176,224,.5); -color:#f0f1f2; -margin-right:1px -} -.sidebar>.full>.content b { -color:#d7dadc -} -.sidebar>.full>.content ul { -padding-left:1.5em; -margin-bottom:0 -} -.sidebar>.full>.hello { -flex:0 0 0; -padding:0; -border:.25rem solid #101112; -background:#101112 -} -.sidebar>.full>.hello .noad { -padding:.75rem; -color:#7e878e -} -.sidebar>.full>.hello .noad b { -color:#b7bcc0 -} -#cheatsheet table { -width:100% -} -#cheatsheet table th { -text-align:left; -background:#3a3e41; -padding:.25rem .5rem; -color:#70b0e0 -} -#cheatsheet table tr+tr>th { -border-top:.5rem solid #282b2d -} -#cheatsheet table td { -padding:0 -} -#cheatsheet table td:first-child { -font-family:"Source Code Pro",monospace -} -#cheatsheet table a { -color:#f0f1f2 -} -#cheatsheet table a+a { -margin-left:.375em -} -.community>header { -background:#101112; -padding:1rem; -margin:-1rem; -margin-bottom:.5rem; -display:flex; -justify-content:space-between -} -.community>header .icon { -color:#595f64 -} -.community>header .icon.selected:not(:hover) { -color:#70b0e0 -} -.community>header>.name { -color:#f0f1f2; -font-weight:700; -overflow:hidden; -display:block; -white-space:nowrap; -text-overflow:ellipsis -} -.community>header>div { -display:flex -} -.community>header>div .favorites { -margin:0 1rem -} -.community .row.rate .icon.thumbdown,.community .row.rate .icon.thumbup { -cursor:pointer -} -.community .row.rate .icon.thumbdown.selected:hover,.community .row.rate .icon.thumbdown:hover,.community .row.rate .icon.thumbup.selected:hover,.community .row.rate .icon.thumbup:hover { -color:#f0f1f2 -} -.community .row.rate .icon.thumbdown.selected,.community .row.rate .icon.thumbup.selected { -color:#70b0e0 -} -.community .row.rate .icon.thumbdown { -transform:translate(0,.125em) scale(1,-1) -} -.community>.author { -margin-top:.125em; -color:#7e878e -} -.community .actions.list { -background:0; -margin:-.5rem -1rem 0 -1rem -} -.community>.desc { -margin-top:1rem -} -#share_community,#share_main { -margin:-1rem -} -#share_community .list,#share_main .list { -background:0 0; -border:none -} -#share_community .inputs input,#share_community .inputs textarea,#share_main .inputs input,#share_main .inputs textarea { -color:#f0f1f2; -margin:2px 0 -} -#share_community #tooltip-signin ul.list li,#share_community .button,#share_community .container .header ul.file li,#share_main #tooltip-signin ul.list li,#share_main .button,#share_main .container .header ul.file li,#tooltip-signin ul.list #share_community li,#tooltip-signin ul.list #share_main li,.container .header ul.file #share_community li,.container .header ul.file #share_main li { -padding-left:2rem; -padding-right:2rem -} -#share_community .buttons.wait #tooltip-signin ul.list li,#share_community .buttons.wait .button,#share_community .buttons.wait .container .header ul.file li,#share_main .buttons.wait #tooltip-signin ul.list li,#share_main .buttons.wait .button,#share_main .buttons.wait .container .header ul.file li,#tooltip-signin ul.list #share_community .buttons.wait li,#tooltip-signin ul.list #share_main .buttons.wait li,.container .header ul.file #share_community .buttons.wait li,.container .header ul.file #share_main .buttons.wait li { -opacity:.35; -pointer-events:none -} -#share_community .status,#share_main .status { -margin-left:.5rem -} -#share_main .signin.row .signout { -display:none -} -#share_main .signin.row.authenticated { -display:none -} -#share_main .row:not(.active) .icon.check { -display:none -} -#share_main .delete.row:hover { -background:rgba(221,34,34,.6) -} -#share_main>.info { -padding:1rem; -margin-bottom:.5rem; -border-bottom:solid 1px #3a3e41 -} -#share_main>.info>.row { -padding:0 0 .5rem 0; -border:0; -color:#b7bcc0; -align-items:flex-start -} -#share_main>.info>.row svg.icon { -color:currentColor; -margin:0 -} -#share_main>.save { -flex-direction:column; -align-items:flex-start; -padding:1.25rem .5rem; -margin-bottom:.5rem; -background:#3a3e41; -border:0; -box-shadow:-3px 3px 6px rgba(0,0,0,.1) -} -#share_main>.save .message { -color:#f0f1f2 -} -#share_main>.save .buttons { -padding:.5rem 0 0 0; -border:0; -color:#f0f1f2 -} -#share_main>.save .buttons #tooltip-signin ul.list li,#share_main>.save .buttons .button,#share_main>.save .buttons .container .header ul.file li,#tooltip-signin ul.list #share_main>.save .buttons li,.container .header ul.file #share_main>.save .buttons li { -margin:0 .5rem 0 0 -} -#share_community { -margin:0 .5rem -} -#share_community>.buttons { -justify-content:flex-end; -color:#f0f1f2; -border:none -} -#share_community .buttons,#share_community .inputs { -margin:1rem 0 -} -.native-js { -transition:all .25s ease-in-out; -opacity:0; -position:absolute; -top:0 -} -.native-js[data-state=visible] { -position:static; -opacity:1 -} -.native-js[data-state=visible]+.noad { -display:none -} -.native-img { -margin-right:.75rem; -max-height:40px; -max-width:27%; -border-radius:3px -} -.native-flex { -display:flex; -padding:.75rem; -text-decoration:none; -flex-flow:row nowrap; -justify-content:space-between; -align-items:center -} -.native-main { -display:flex; -flex-flow:row nowrap; -align-items:center -} -.native-details { -display:flex; -font-size:12px; -position:relative; -max-height:5em; -flex-flow:column -} -.native-company { -margin-bottom:4px; -text-transform:uppercase; -letter-spacing:1px; -font-weight:700; -font-size:11px -} -.native-desc { -letter-spacing:1px; -font-weight:300; -line-height:1.3em; -text-overflow:ellipsis; -max-height:5em; -overflow:hidden; -position:relative -} -#carbonads { -display:block; -overflow:hidden; -background-color:#101112; -font-size:13px; -line-height:1.5 -} -#carbonads a { -color:inherit; -text-decoration:none -} -#carbonads a:hover { -color:inherit -} -#carbonads span { -position:relative; -display:block; -overflow:hidden -} -.carbon-img { -display:block; -float:left; -margin:0; -line-height:1 -} -.carbon-img img { -display:block -} -.carbon-text { -display:block; -float:left; -padding:8px 1em; -max-width:calc(100% - 130px - 2em); -text-align:left; -letter-spacing:.5px -} -.carbon-poweredby { -position:absolute; -right:0; -bottom:0; -left:130px; -display:block; -padding:8px 13px; -border-top:solid 1px #282b2d; -text-transform:uppercase; -letter-spacing:1px; -font-weight:400; -font-size:9px; -line-height:1 -} -@media (max-width:900px) { -body>.container>.header>.etc>.github { -display:none -} -body>.container>.header>.file .savekey { -display:none -} -body>.container>.app { -display:block; -position:relative; -background:#e7e9ea -} -body>.container>.app>.doc { -position:absolute; -top:0; -bottom:0; -left:50px; -right:0 -} -body>.container>.app>.doc>.blocker { -display:none; -width:100%; -height:100%; -position:fixed; -background:rgba(215,218,220,.5); -z-index:10 -} -body>.container>.app>.doc.fadeback { -filter:blur(2px) -} -body>.container>.app>.doc.fadeback>.blocker { -display:block -} -body>.container>.app>.sidebar { -position:absolute; -top:0; -bottom:0; -left:0; -z-index:10000; -transition:width .2s ease-out; -width:calc(10% + 350px); -box-shadow:10px 0 12px rgba(0,0,0,.45); -background:#282b2d; -opacity:.94 -} -body>.container>.app>.sidebar.closed { -width:50px; -min-width:50px; -box-shadow:1px 2px 8px rgba(0,0,0,.45); -opacity:1 -} -body>.container>.app>.sidebar.closed:before { -content:none -} - -} -#export.theme { -color:#70b0e0 -} -#export.match { -color:rgba(112,176,224,.5) -} -#export.selected-stroke { -color:rgba(16,17,18,.3) -} -.CodeMirror { -font-family:"Source Code Pro",monospace; -background:0 0; -position:absolute; -box-sizing:border-box -} -.CodeMirror div.CodeMirror-cursor { -pointer-events:none; -border-left:1px solid #101112 -} -.CodeMirror-selected { -background:rgba(170,170,170,.55) -} -.CodeMirror-focused .CodeMirror-selected { -background:rgba(140,150,255,.8) -} -.editor.multiline .CodeMirror-line:not(:last-child)>span:after { -pointer-events:none; -color:rgba(127,127,127,.33); -content:"¬" -} -.CodeMirror-line .cm-space::before,.CodeMirror-line .cm-special::before { -color:rgba(127,127,127,.33); -content:"•"; -position:absolute -} -.CodeMirror-line .cm-special::before { -color:#d22 -} -.CodeMirror-line .cm-tab { -background:url(data:image/png; -base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REZENEEyN0Q3NTc0MTFFNzlFMTZGQ0Q1MEREODEyREEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REZENEEyN0U3NTc0MTFFNzlFMTZGQ0Q1MEREODEyREEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpERkQ0QTI3Qjc1NzQxMUU3OUUxNkZDRDUwREQ4MTJEQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpERkQ0QTI3Qzc1NzQxMUU3OUUxNkZDRDUwREQ4MTJEQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgWEz28AAABVSURBVHja7JVBCgAgCAQt+rf6cstjYN4iFxoQxJMjos3MCJlO4HyB26iqZLXyAswskQTUCmUSI7OruE4usxX9jCKELKK8w04e6Qqdmnfa/8SPmQIMANcZrZCVJGBIAAAAAElFTkSuQmCC); -background-position:100%; -background-repeat:no-repeat -} - diff --git a/deploy/de/regexr.js b/deploy/de/regexr.js deleted file mode 100644 index 6111932..0000000 --- a/deploy/de/regexr.js +++ /dev/null @@ -1,21008 +0,0 @@ -var regexr = (function () { - 'use strict'; - - // CodeMirror, copyright (c) by Marijn Haverbeke and others - // Distributed under an MIT license: http://codemirror.net/LICENSE - - // This is CodeMirror (http://codemirror.net), a code editor - // implemented in JavaScript on top of the browser's DOM. - // - // You can find some technical background for some of the code below - // at http://marijnhaverbeke.nl/blog/#cm-internals . - - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.CodeMirror = factory(); - })(window, function () { - - // Kludges for bugs and behavior differences that can't be feature - // detected are enabled based on userAgent etc sniffing. - var userAgent = navigator.userAgent; - var platform = navigator.platform; - var gecko = /gecko\/\d/i.test(userAgent); - var ie_upto10 = /MSIE \d/.test(userAgent); - var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); - var edge = /Edge\/(\d+)/.exec(userAgent); - var ie = ie_upto10 || ie_11up || edge; - var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); - var webkit = !edge && /WebKit\//.test(userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); - var chrome = !edge && /Chrome\//.test(userAgent); - var presto = /Opera\//.test(userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); - var phantom = /PhantomJS/.test(userAgent); - var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); - var android = /Android/.test(userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); - var mac = ios || /Mac/.test(platform); - var chromeOS = /\bCrOS\b/.test(userAgent); - var windows = /win/i.test(platform); - var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); - if (presto_version) { - presto_version = Number(presto_version[1]); - } - if (presto_version && presto_version >= 15) { - presto = false; - webkit = true; - } - // Some browsers use the wrong event properties to signal cmd/ctrl on OS X - var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); - var captureRightClick = gecko || ie && ie_version >= 9; - function classTest(cls) { - return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); - } - var rmClass = function (node, cls) { - var current = node.className; - var match = classTest(cls).exec(current); - if (match) { - var after = current.slice(match.index + match[0].length); - node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); - } - }; - function removeChildren(e) { - for (var count = e.childNodes.length; count > 0; --count) { - e.removeChild(e.firstChild); - } - return e; - } - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) { - e.className = className; - } - if (style) { - e.style.cssText = style; - } - if (typeof content == "string") { - e.appendChild(document.createTextNode(content)); - } else if (content) { - for (var i = 0; i < content.length; ++i) { - e.appendChild(content[i]); - } - } - return e; - } - // wrapper for elt, which removes the elt from the accessibility tree - function eltP(tag, content, className, style) { - var e = elt(tag, content, className, style); - e.setAttribute("role", "presentation"); - return e; - } - var range; - if (document.createRange) { - range = function (node, start, end, endNode) { - var r = document.createRange(); - r.setEnd(endNode || node, end); - r.setStart(node, start); - return r; - }; - } else { - range = function (node, start, end) { - var r = document.body.createTextRange(); - try { - r.moveToElementText(node.parentNode); - } catch (e) { - return r; - } - r.collapse(true); - r.moveEnd("character", end); - r.moveStart("character", start); - return r; - }; - } - function contains(parent, child) { - if (child.nodeType == 3) - // Android browser always returns false when child is a textnode - { - child = child.parentNode; - } - if (parent.contains) { - return parent.contains(child); - } - do { - if (child.nodeType == 11) { - child = child.host; - } - if (child == parent) { - return true; - } - } while (child = child.parentNode); - } - function activeElt() { - // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. - // IE < 10 will throw when accessed while the page is loading or in an iframe. - // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. - var activeElement; - try { - activeElement = document.activeElement; - } catch (e) { - activeElement = document.body || null; - } - while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { - activeElement = activeElement.shadowRoot.activeElement; - } - return activeElement; - } - function addClass(node, cls) { - var current = node.className; - if (!classTest(cls).test(current)) { - node.className += (current ? " " : "") + cls; - } - } - function joinClasses(a, b) { - var as = a.split(" "); - for (var i = 0; i < as.length; i++) { - if (as[i] && !classTest(as[i]).test(b)) { - b += " " + as[i]; - } - } - return b; - } - var selectInput = function (node) { - node.select(); - }; - if (ios) - // Mobile Safari apparently has a bug where select() is broken. - { - selectInput = function (node) { - node.selectionStart = 0; - node.selectionEnd = node.value.length; - }; - } else if (ie) - // Suppress mysterious IE10 errors - { - selectInput = function (node) { - try { - node.select(); - } catch (_e) {} - }; - } - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function () { - return f.apply(null, args); - }; - } - function copyObj(obj, target, overwrite) { - if (!target) { - target = {}; - } - for (var prop in obj) { - if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) { - target[prop] = obj[prop]; - } - } - return target; - } - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - function countColumn(string, end, tabSize, startIndex, startValue) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) { - end = string.length; - } - } - for (var i = startIndex || 0, n = startValue || 0;;) { - var nextTab = string.indexOf("\t", i); - if (nextTab < 0 || nextTab >= end) { - return n + (end - i); - } - n += nextTab - i; - n += tabSize - n % tabSize; - i = nextTab + 1; - } - } - var Delayed = function () { - this.id = null; - }; - Delayed.prototype.set = function (ms, f) { - clearTimeout(this.id); - this.id = setTimeout(f, ms); - }; - function indexOf(array, elt) { - for (var i = 0; i < array.length; ++i) { - if (array[i] == elt) { - return i; - } - } - return -1; - } - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerGap = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = { - toString: function () { - return "CodeMirror.Pass"; - } - }; - - // Reused option objects for setSelection & friends - var sel_dontScroll = { - scroll: false - }; - var sel_mouse = { - origin: "*mouse" - }; - var sel_move = { - origin: "+move" - }; - // The inverse of countColumn -- find the offset that corresponds to - // a particular column. - function findColumn(string, goal, tabSize) { - for (var pos = 0, col = 0;;) { - var nextTab = string.indexOf("\t", pos); - if (nextTab == -1) { - nextTab = string.length; - } - var skipped = nextTab - pos; - if (nextTab == string.length || col + skipped >= goal) { - return pos + Math.min(skipped, goal - col); - } - col += nextTab - pos; - col += tabSize - col % tabSize; - pos = nextTab + 1; - if (col >= goal) { - return pos; - } - } - } - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) { - spaceStrs.push(lst(spaceStrs) + " "); - } - return spaceStrs[n]; - } - function lst(arr) { - return arr[arr.length - 1]; - } - function map(array, f) { - var out = []; - for (var i = 0; i < array.length; i++) { - out[i] = f(array[i], i); - } - return out; - } - function insertSorted(array, value, score) { - var pos = 0, - priority = score(value); - while (pos < array.length && score(array[pos]) <= priority) { - pos++; - } - array.splice(pos, 0, value); - } - function nothing() {} - function createObj(base, props) { - var inst; - if (Object.create) { - inst = Object.create(base); - } else { - nothing.prototype = base; - inst = new nothing(); - } - if (props) { - copyObj(props, inst); - } - return inst; - } - var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - function isWordCharBasic(ch) { - return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - } - function isWordChar(ch, helper) { - if (!helper) { - return isWordCharBasic(ch); - } - if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { - return true; - } - return helper.test(ch); - } - function isEmpty(obj) { - for (var n in obj) { - if (obj.hasOwnProperty(n) && obj[n]) { - return false; - } - } - return true; - } - - // Extending unicode characters. A series of a non-extending char + - // any number of extending chars is treated as a single unit as far - // as editing and measuring is concerned. This is not fully correct, - // since some scripts/fonts/browsers also treat other configurations - // of code points as a group. - var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; - function isExtendingChar(ch) { - return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); - } - - // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. - function skipExtendingChars(str, pos, dir) { - while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { - pos += dir; - } - return pos; - } - - // Returns the value from the range [`from`; `to`] that satisfies - // `pred` and is closest to `from`. Assumes that at least `to` satisfies `pred`. - function findFirst(pred, from, to) { - for (;;) { - if (Math.abs(from - to) <= 1) { - return pred(from) ? from : to; - } - var mid = Math.floor((from + to) / 2); - if (pred(mid)) { - to = mid; - } else { - from = mid; - } - } - } - - // The display handles the DOM integration, both for input reading - // and content drawing. It holds references to DOM nodes and - // display-related state. - - function Display(place, doc, input) { - var d = this; - this.input = input; - - // Covers bottom-right square when both scrollbars are present. - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - d.scrollbarFiller.setAttribute("cm-not-content", "true"); - // Covers bottom of gutter when coverGutterNextToScrollbar is on - // and h scrollbar is present. - d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); - d.gutterFiller.setAttribute("cm-not-content", "true"); - // Will contain the actual code, positioned to cover the viewport. - d.lineDiv = eltP("div", null, "CodeMirror-code"); - // Elements are added to these to represent selection and cursors. - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - d.cursorDiv = elt("div", null, "CodeMirror-cursors"); - // A visibility: hidden element used to find the size of things. - d.measure = elt("div", null, "CodeMirror-measure"); - // When lines outside of the viewport are measured, they are drawn in this. - d.lineMeasure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], null, "position: relative; outline: none"); - var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); - // Moved around its parent to cover visible view. - d.mover = elt("div", [lines], null, "position: relative"); - // Set to the height of the document, allowing scrolling. - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - d.sizerWidth = null; - // Behavior of elts with overflow: auto and padding is - // inconsistent across browsers. This is used to ensure the - // scrollable area is big enough. - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); - // Will contain the gutters, if any. - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Actual scrollable element. - d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); - d.scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); - - // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) - if (ie && ie_version < 8) { - d.gutters.style.zIndex = -1; - d.scroller.style.paddingRight = 0; - } - if (!webkit && !(gecko && mobile)) { - d.scroller.draggable = true; - } - if (place) { - if (place.appendChild) { - place.appendChild(d.wrapper); - } else { - place(d.wrapper); - } - } - - // Current rendered range (may be bigger than the view window). - d.viewFrom = d.viewTo = doc.first; - d.reportedViewFrom = d.reportedViewTo = doc.first; - // Information about the rendered lines. - d.view = []; - d.renderedView = null; - // Holds info about a single rendered line when it was rendered - // for measurement, while not in view. - d.externalMeasured = null; - // Empty space (in pixels) above the view - d.viewOffset = 0; - d.lastWrapHeight = d.lastWrapWidth = 0; - d.updateLineNumbers = null; - d.nativeBarWidth = d.barHeight = d.barWidth = 0; - d.scrollbarsClipped = false; - - // Used to only resize the line number gutter when necessary (when - // the amount of lines crosses a boundary that makes its width change) - d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; - // Set to true when a non-horizontal-scrolling line widget is - // added. As an optimization, line widget aligning is skipped when - // this is false. - d.alignWidgets = false; - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - d.maxLine = null; - d.maxLineLength = 0; - d.maxLineChanged = false; - - // Used for measuring wheel scrolling granularity - d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - - // True when shift is held down. - d.shift = false; - - // Used to track whether anything happened since the context menu - // was opened. - d.selForContextMenu = null; - d.activeTouch = null; - input.init(d); - } - - // Find the line object corresponding to the given line number. - function getLine(doc, n) { - n -= doc.first; - if (n < 0 || n >= doc.size) { - throw new Error("There is no line " + (n + doc.first) + " in the document."); - } - var chunk = doc; - while (!chunk.lines) { - for (var i = 0;; ++i) { - var child = chunk.children[i], - sz = child.chunkSize(); - if (n < sz) { - chunk = child; - break; - } - n -= sz; - } - } - return chunk.lines[n]; - } - - // Get the part of a document between two positions, as an array of - // strings. - function getBetween(doc, start, end) { - var out = [], - n = start.line; - doc.iter(start.line, end.line + 1, function (line) { - var text = line.text; - if (n == end.line) { - text = text.slice(0, end.ch); - } - if (n == start.line) { - text = text.slice(start.ch); - } - out.push(text); - ++n; - }); - return out; - } - // Get the lines between from and to, as array of strings. - function getLines(doc, from, to) { - var out = []; - doc.iter(from, to, function (line) { - out.push(line.text); - }); // iter aborts when callback returns truthy value - return out; - } - - // Update the height of a line, propagating the height change - // upwards to parent nodes. - function updateLineHeight(line, height) { - var diff = height - line.height; - if (diff) { - for (var n = line; n; n = n.parent) { - n.height += diff; - } - } - } - - // Given a line object, find its line number by walking up through - // its parent links. - function lineNo(line) { - if (line.parent == null) { - return null; - } - var cur = line.parent, - no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) { - break; - } - no += chunk.children[i].chunkSize(); - } - } - return no + cur.first; - } - - // Find the line at the given vertical position, using the height - // information in the document tree. - function lineAtHeight(chunk, h) { - var n = chunk.first; - outer: do { - for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { - var child = chunk.children[i$1], - ch = child.height; - if (h < ch) { - chunk = child; - continue outer; - } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - var i = 0; - for (; i < chunk.lines.length; ++i) { - var line = chunk.lines[i], - lh = line.height; - if (h < lh) { - break; - } - h -= lh; - } - return n + i; - } - function isLine(doc, l) { - return l >= doc.first && l < doc.first + doc.size; - } - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - - // A Pos instance represents a position within the text. - function Pos(line, ch, sticky) { - if (sticky === void 0) sticky = null; - if (!(this instanceof Pos)) { - return new Pos(line, ch, sticky); - } - this.line = line; - this.ch = ch; - this.sticky = sticky; - } - - // Compare two positions, return 0 if they are the same, a negative - // number when a is less, and a positive number otherwise. - function cmp(a, b) { - return a.line - b.line || a.ch - b.ch; - } - function equalCursorPos(a, b) { - return a.sticky == b.sticky && cmp(a, b) == 0; - } - function copyPos(x) { - return Pos(x.line, x.ch); - } - function maxPos(a, b) { - return cmp(a, b) < 0 ? b : a; - } - function minPos(a, b) { - return cmp(a, b) < 0 ? a : b; - } - - // Most of the external API clips given positions to make sure they - // actually exist within the document. - function clipLine(doc, n) { - return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1)); - } - function clipPos(doc, pos) { - if (pos.line < doc.first) { - return Pos(doc.first, 0); - } - var last = doc.first + doc.size - 1; - if (pos.line > last) { - return Pos(last, getLine(doc, last).text.length); - } - return clipToLen(pos, getLine(doc, pos.line).text.length); - } - function clipToLen(pos, linelen) { - var ch = pos.ch; - if (ch == null || ch > linelen) { - return Pos(pos.line, linelen); - } else if (ch < 0) { - return Pos(pos.line, 0); - } else { - return pos; - } - } - function clipPosArray(doc, array) { - var out = []; - for (var i = 0; i < array.length; i++) { - out[i] = clipPos(doc, array[i]); - } - return out; - } - - // Optimize some code when these features are not used. - var sawReadOnlySpans = false; - var sawCollapsedSpans = false; - function seeReadOnlySpans() { - sawReadOnlySpans = true; - } - function seeCollapsedSpans() { - sawCollapsedSpans = true; - } - - // TEXTMARKER SPANS - - function MarkedSpan(marker, from, to) { - this.marker = marker; - this.from = from; - this.to = to; - } - - // Search an array of spans for a span matching the given marker. - function getMarkedSpanFor(spans, marker) { - if (spans) { - for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) { - return span; - } - } - } - } - // Remove a span from an array, returning undefined if no spans are - // left (we don't store arrays for lines without spans). - function removeMarkedSpan(spans, span) { - var r; - for (var i = 0; i < spans.length; ++i) { - if (spans[i] != span) { - (r || (r = [])).push(spans[i]); - } - } - return r; - } - // Add a span to a line. - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.attachLine(line); - } - - // Used for the algorithm that adjusts markers for a change in the - // document. These functions cut an array of spans at a given - // character position, returning an array of remaining chunks (or - // undefined if nothing remains). - function markedSpansBefore(old, startCh, isInsert) { - var nw; - if (old) { - for (var i = 0; i < old.length; ++i) { - var span = old[i], - marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); - } - } - } - return nw; - } - function markedSpansAfter(old, endCh, isInsert) { - var nw; - if (old) { - for (var i = 0; i < old.length; ++i) { - var span = old[i], - marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, span.to == null ? null : span.to - endCh)); - } - } - } - return nw; - } - - // Given a change object, compute the new set of marker spans that - // cover the line in which the change took place. Removes spans - // entirely within the change, reconnects spans belonging to the - // same marker that appear on both sides of the change, and cuts off - // spans partially within the change. Returns an array of span - // arrays with one element for each line in (after) the change. - function stretchSpansOverChange(doc, change) { - if (change.full) { - return null; - } - var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; - var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; - if (!oldFirst && !oldLast) { - return null; - } - var startCh = change.from.ch, - endCh = change.to.ch, - isInsert = cmp(change.from, change.to) == 0; - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh, isInsert); - var last = markedSpansAfter(oldLast, endCh, isInsert); - - // Next, merge those two ends - var sameLine = change.text.length == 1, - offset = lst(change.text).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) { - span.to = startCh; - } else if (sameLine) { - span.to = found.to == null ? null : found.to + offset; - } - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i$1 = 0; i$1 < last.length; ++i$1) { - var span$1 = last[i$1]; - if (span$1.to != null) { - span$1.to += offset; - } - if (span$1.from == null) { - var found$1 = getMarkedSpanFor(first, span$1.marker); - if (!found$1) { - span$1.from = offset; - if (sameLine) { - (first || (first = [])).push(span$1); - } - } - } else { - span$1.from += offset; - if (sameLine) { - (first || (first = [])).push(span$1); - } - } - } - } - // Make sure we didn't create any zero-length spans - if (first) { - first = clearEmptySpans(first); - } - if (last && last != first) { - last = clearEmptySpans(last); - } - var newMarkers = [first]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = change.text.length - 2, - gapMarkers; - if (gap > 0 && first) { - for (var i$2 = 0; i$2 < first.length; ++i$2) { - if (first[i$2].to == null) { - (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); - } - } - } - for (var i$3 = 0; i$3 < gap; ++i$3) { - newMarkers.push(gapMarkers); - } - newMarkers.push(last); - } - return newMarkers; - } - - // Remove spans that are empty and don't have a clearWhenEmpty - // option of false. - function clearEmptySpans(spans) { - for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) { - spans.splice(i--, 1); - } - } - if (!spans.length) { - return null; - } - return spans; - } - - // Used to 'clip' out readOnly ranges when making a change. - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function (line) { - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) { - (markers || (markers = [])).push(mark); - } - } - } - }); - if (!markers) { - return null; - } - var parts = [{ - from: from, - to: to - }]; - for (var i = 0; i < markers.length; ++i) { - var mk = markers[i], - m = mk.find(0); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { - continue; - } - var newParts = [j, 1], - dfrom = cmp(p.from, m.from), - dto = cmp(p.to, m.to); - if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) { - newParts.push({ - from: p.from, - to: m.from - }); - } - if (dto > 0 || !mk.inclusiveRight && !dto) { - newParts.push({ - from: m.to, - to: p.to - }); - } - parts.splice.apply(parts, newParts); - j += newParts.length - 3; - } - } - return parts; - } - - // Connect or disconnect spans from a line. - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) { - return; - } - for (var i = 0; i < spans.length; ++i) { - spans[i].marker.detachLine(line); - } - line.markedSpans = null; - } - function attachMarkedSpans(line, spans) { - if (!spans) { - return; - } - for (var i = 0; i < spans.length; ++i) { - spans[i].marker.attachLine(line); - } - line.markedSpans = spans; - } - - // Helpers used when computing which overlapping collapsed span - // counts as the larger one. - function extraLeft(marker) { - return marker.inclusiveLeft ? -1 : 0; - } - function extraRight(marker) { - return marker.inclusiveRight ? 1 : 0; - } - - // Returns a number indicating which of two overlapping collapsed - // spans is larger (and thus includes the other). Falls back to - // comparing ids when the spans cover exactly the same range. - function compareCollapsedMarkers(a, b) { - var lenDiff = a.lines.length - b.lines.length; - if (lenDiff != 0) { - return lenDiff; - } - var aPos = a.find(), - bPos = b.find(); - var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); - if (fromCmp) { - return -fromCmp; - } - var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); - if (toCmp) { - return toCmp; - } - return b.id - a.id; - } - - // Find out whether a line ends or starts in a collapsed span. If - // so, return the marker for that span. - function collapsedSpanAtSide(line, start) { - var sps = sawCollapsedSpans && line.markedSpans, - found; - if (sps) { - for (var sp = void 0, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { - found = sp.marker; - } - } - } - return found; - } - function collapsedSpanAtStart(line) { - return collapsedSpanAtSide(line, true); - } - function collapsedSpanAtEnd(line) { - return collapsedSpanAtSide(line, false); - } - - // Test whether there exists a collapsed span that partially - // overlaps (covers the start or end, but not both) of a new span. - // Such overlap is not allowed. - function conflictingCollapsedRange(doc, lineNo, from, to, marker) { - var line = getLine(doc, lineNo); - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) { - for (var i = 0; i < sps.length; ++i) { - var sp = sps[i]; - if (!sp.marker.collapsed) { - continue; - } - var found = sp.marker.find(0); - var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); - var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); - if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { - continue; - } - if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) { - return true; - } - } - } - } - - // A visual line is a line as drawn on the screen. Folding, for - // example, can cause multiple logical lines to appear on the same - // visual line. This finds the start of the visual line that the - // given line is part of (usually that is the line itself). - function visualLine(line) { - var merged; - while (merged = collapsedSpanAtStart(line)) { - line = merged.find(-1, true).line; - } - return line; - } - function visualLineEnd(line) { - var merged; - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - } - return line; - } - - // Returns an array of logical lines that continue the visual line - // started by the argument, or undefined if there are no such lines. - function visualLineContinued(line) { - var merged, lines; - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - (lines || (lines = [])).push(line); - } - return lines; - } - - // Get the line number of the start of the visual line that the - // given line number is part of. - function visualLineNo(doc, lineN) { - var line = getLine(doc, lineN), - vis = visualLine(line); - if (line == vis) { - return lineN; - } - return lineNo(vis); - } - - // Get the line number of the start of the next visual line after - // the given line. - function visualLineEndNo(doc, lineN) { - if (lineN > doc.lastLine()) { - return lineN; - } - var line = getLine(doc, lineN), - merged; - if (!lineIsHidden(doc, line)) { - return lineN; - } - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - } - return lineNo(line) + 1; - } - - // Compute whether a line is hidden. Lines count as hidden when they - // are part of a visual line that starts with another line, or when - // they are entirely covered by collapsed, non-widget span. - function lineIsHidden(doc, line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) { - for (var sp = void 0, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) { - continue; - } - if (sp.from == null) { - return true; - } - if (sp.marker.widgetNode) { - continue; - } - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) { - return true; - } - } - } - } - function lineIsHiddenInner(doc, line, span) { - if (span.to == null) { - var end = span.marker.find(1, true); - return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); - } - if (span.marker.inclusiveRight && span.to == line.text.length) { - return true; - } - for (var sp = void 0, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && (sp.to == null || sp.to != span.from) && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) { - return true; - } - } - } - - // Find the height above the given line. - function heightAtLine(lineObj) { - lineObj = visualLine(lineObj); - var h = 0, - chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) { - break; - } else { - h += line.height; - } - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i$1 = 0; i$1 < p.children.length; ++i$1) { - var cur = p.children[i$1]; - if (cur == chunk) { - break; - } else { - h += cur.height; - } - } - } - return h; - } - - // Compute the character length of a line, taking into account - // collapsed ranges (see markText) that might hide parts, and join - // other lines onto it. - function lineLength(line) { - if (line.height == 0) { - return 0; - } - var len = line.text.length, - merged, - cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(0, true); - cur = found.from.line; - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found$1 = merged.find(0, true); - len -= cur.text.length - found$1.from.ch; - cur = found$1.to.line; - len += cur.text.length - found$1.to.ch; - } - return len; - } - - // Find the longest line in the document. - function findMaxLine(cm) { - var d = cm.display, - doc = cm.doc; - d.maxLine = getLine(doc, doc.first); - d.maxLineLength = lineLength(d.maxLine); - d.maxLineChanged = true; - doc.iter(function (line) { - var len = lineLength(line); - if (len > d.maxLineLength) { - d.maxLineLength = len; - d.maxLine = line; - } - }); - } - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) { - return f(from, to, "ltr"); - } - var found = false; - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) { - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - found = true; - } - } - if (!found) { - f(from, to, "ltr"); - } - } - var bidiOther = null; - function getBidiPartAt(order, ch, sticky) { - var found; - bidiOther = null; - for (var i = 0; i < order.length; ++i) { - var cur = order[i]; - if (cur.from < ch && cur.to > ch) { - return i; - } - if (cur.to == ch) { - if (cur.from != cur.to && sticky == "before") { - found = i; - } else { - bidiOther = i; - } - } - if (cur.from == ch) { - if (cur.from != cur.to && sticky != "before") { - found = i; - } else { - bidiOther = i; - } - } - } - return found != null ? found : bidiOther; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = function () { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; - // Character types for codepoints 0x600 to 0x6f9 - var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; - function charType(code) { - if (code <= 0xf7) { - return lowTypes.charAt(code); - } else if (0x590 <= code && code <= 0x5f4) { - return "R"; - } else if (0x600 <= code && code <= 0x6f9) { - return arabicTypes.charAt(code - 0x600); - } else if (0x6ee <= code && code <= 0x8ac) { - return "r"; - } else if (0x2000 <= code && code <= 0x200b) { - return "w"; - } else if (code == 0x200c) { - return "b"; - } else { - return "L"; - } - } - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, - isStrong = /[LRr]/, - countsAsLeft = /[Lb1n]/, - countsAsNum = /[1n]/; - function BidiSpan(level, from, to) { - this.level = level; - this.from = from; - this.to = to; - } - return function (str, direction) { - var outerType = direction == "ltr" ? "L" : "R"; - if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { - return false; - } - var len = str.length, - types = []; - for (var i = 0; i < len; ++i) { - types.push(charType(str.charCodeAt(i))); - } - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { - var type = types[i$1]; - if (type == "m") { - types[i$1] = prev; - } else { - prev = type; - } - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { - var type$1 = types[i$2]; - if (type$1 == "1" && cur == "r") { - types[i$2] = "n"; - } else if (isStrong.test(type$1)) { - cur = type$1; - if (type$1 == "r") { - types[i$2] = "R"; - } - } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { - var type$2 = types[i$3]; - if (type$2 == "+" && prev$1 == "1" && types[i$3 + 1] == "1") { - types[i$3] = "1"; - } else if (type$2 == "," && prev$1 == types[i$3 + 1] && (prev$1 == "1" || prev$1 == "n")) { - types[i$3] = prev$1; - } - prev$1 = type$2; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i$4 = 0; i$4 < len; ++i$4) { - var type$3 = types[i$4]; - if (type$3 == ",") { - types[i$4] = "N"; - } else if (type$3 == "%") { - var end = void 0; - for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} - var replace = i$4 && types[i$4 - 1] == "!" || end < len && types[end] == "1" ? "1" : "N"; - for (var j = i$4; j < end; ++j) { - types[j] = replace; - } - i$4 = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { - var type$4 = types[i$5]; - if (cur$1 == "L" && type$4 == "1") { - types[i$5] = "L"; - } else if (isStrong.test(type$4)) { - cur$1 = type$4; - } - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i$6 = 0; i$6 < len; ++i$6) { - if (isNeutral.test(types[i$6])) { - var end$1 = void 0; - for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} - var before = (i$6 ? types[i$6 - 1] : outerType) == "L"; - var after = (end$1 < len ? types[end$1] : outerType) == "L"; - var replace$1 = before == after ? before ? "L" : "R" : outerType; - for (var j$1 = i$6; j$1 < end$1; ++j$1) { - types[j$1] = replace$1; - } - i$6 = end$1 - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], - m; - for (var i$7 = 0; i$7 < len;) { - if (countsAsLeft.test(types[i$7])) { - var start = i$7; - for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} - order.push(new BidiSpan(0, start, i$7)); - } else { - var pos = i$7, - at = order.length; - for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} - for (var j$2 = pos; j$2 < i$7;) { - if (countsAsNum.test(types[j$2])) { - if (pos < j$2) { - order.splice(at, 0, new BidiSpan(1, pos, j$2)); - } - var nstart = j$2; - for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} - order.splice(at, 0, new BidiSpan(2, nstart, j$2)); - pos = j$2; - } else { - ++j$2; - } - } - if (pos < i$7) { - order.splice(at, 0, new BidiSpan(1, pos, i$7)); - } - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift(new BidiSpan(0, 0, m[0].length)); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push(new BidiSpan(0, len - m[0].length, len)); - } - return direction == "rtl" ? order.reverse() : order; - }; - }(); - - // Get the bidi ordering for the given line (and cache it). Returns - // false for lines that are fully left-to-right, and an array of - // BidiSpan objects otherwise. - function getOrder(line, direction) { - var order = line.order; - if (order == null) { - order = line.order = bidiOrdering(line.text, direction); - } - return order; - } - function moveCharLogically(line, ch, dir) { - var target = skipExtendingChars(line.text, ch + dir, dir); - return target < 0 || target > line.text.length ? null : target; - } - function moveLogically(line, start, dir) { - var ch = moveCharLogically(line, start.ch, dir); - return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before"); - } - function endOfLine(visually, cm, lineObj, lineNo, dir) { - if (visually) { - var order = getOrder(lineObj, cm.doc.direction); - if (order) { - var part = dir < 0 ? lst(order) : order[0]; - var moveInStorageOrder = dir < 0 == (part.level == 1); - var sticky = moveInStorageOrder ? "after" : "before"; - var ch; - // With a wrapped rtl chunk (possibly spanning multiple bidi parts), - // it could be that the last bidi part is not on the last visual line, - // since visual lines contain content order-consecutive chunks. - // Thus, in rtl, we are looking for the first (content-order) character - // in the rtl chunk that is on the last line (that is, the same line - // as the last (content-order) character). - if (part.level > 0) { - var prep = prepareMeasureForLine(cm, lineObj); - ch = dir < 0 ? lineObj.text.length - 1 : 0; - var targetTop = measureCharPrepared(cm, prep, ch).top; - ch = findFirst(function (ch) { - return measureCharPrepared(cm, prep, ch).top == targetTop; - }, dir < 0 == (part.level == 1) ? part.from : part.to - 1, ch); - if (sticky == "before") { - ch = moveCharLogically(lineObj, ch, 1); - } - } else { - ch = dir < 0 ? part.to : part.from; - } - return new Pos(lineNo, ch, sticky); - } - } - return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after"); - } - function moveVisually(cm, line, start, dir) { - var bidi = getOrder(line, cm.doc.direction); - if (!bidi) { - return moveLogically(line, start, dir); - } - if (start.ch >= line.text.length) { - start.ch = line.text.length; - start.sticky = "before"; - } else if (start.ch <= 0) { - start.ch = 0; - start.sticky = "after"; - } - var partPos = getBidiPartAt(bidi, start.ch, start.sticky), - part = bidi[partPos]; - if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { - // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, - // nothing interesting happens. - return moveLogically(line, start, dir); - } - var mv = function (pos, dir) { - return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); - }; - var prep; - var getWrappedLineExtent = function (ch) { - if (!cm.options.lineWrapping) { - return { - begin: 0, - end: line.text.length - }; - } - prep = prep || prepareMeasureForLine(cm, line); - return wrappedLineExtentChar(cm, line, prep, ch); - }; - var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); - if (cm.doc.direction == "rtl" || part.level == 1) { - var moveInStorageOrder = part.level == 1 == dir < 0; - var ch = mv(start, moveInStorageOrder ? 1 : -1); - if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { - // Case 2: We move within an rtl part or in an rtl editor on the same visual line - var sticky = moveInStorageOrder ? "before" : "after"; - return new Pos(start.line, ch, sticky); - } - } - - // Case 3: Could not move within this bidi part in this visual line, so leave - // the current bidi part - - var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { - var getRes = function (ch, moveInStorageOrder) { - return moveInStorageOrder ? new Pos(start.line, mv(ch, 1), "before") : new Pos(start.line, ch, "after"); - }; - for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { - var part = bidi[partPos]; - var moveInStorageOrder = dir > 0 == (part.level != 1); - var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); - if (part.from <= ch && ch < part.to) { - return getRes(ch, moveInStorageOrder); - } - ch = moveInStorageOrder ? part.from : mv(part.to, -1); - if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { - return getRes(ch, moveInStorageOrder); - } - } - }; - - // Case 3a: Look for other bidi parts on the same visual line - var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); - if (res) { - return res; - } - - // Case 3b: Look for other bidi parts on the next visual line - var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); - if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { - res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); - if (res) { - return res; - } - } - - // Case 4: Nowhere to move - return null; - } - - // EVENT HANDLING - - // Lightweight event framework. on/off also work on DOM nodes, - // registering native DOM handlers. - - var noHandlers = []; - var on = function (emitter, type, f) { - if (emitter.addEventListener) { - emitter.addEventListener(type, f, false); - } else if (emitter.attachEvent) { - emitter.attachEvent("on" + type, f); - } else { - var map = emitter._handlers || (emitter._handlers = {}); - map[type] = (map[type] || noHandlers).concat(f); - } - }; - function getHandlers(emitter, type) { - return emitter._handlers && emitter._handlers[type] || noHandlers; - } - function off(emitter, type, f) { - if (emitter.removeEventListener) { - emitter.removeEventListener(type, f, false); - } else if (emitter.detachEvent) { - emitter.detachEvent("on" + type, f); - } else { - var map = emitter._handlers, - arr = map && map[type]; - if (arr) { - var index = indexOf(arr, f); - if (index > -1) { - map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); - } - } - } - } - function signal(emitter, type /*, values...*/) { - var handlers = getHandlers(emitter, type); - if (!handlers.length) { - return; - } - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < handlers.length; ++i) { - handlers[i].apply(null, args); - } - } - - // The DOM events that CodeMirror handles can be overridden by - // registering a (non-DOM) handler on the editor for the event name, - // and preventDefault-ing the event in that handler. - function signalDOMEvent(cm, e, override) { - if (typeof e == "string") { - e = { - type: e, - preventDefault: function () { - this.defaultPrevented = true; - } - }; - } - signal(cm, override || e.type, cm, e); - return e_defaultPrevented(e) || e.codemirrorIgnore; - } - function signalCursorActivity(cm) { - var arr = cm._handlers && cm._handlers.cursorActivity; - if (!arr) { - return; - } - var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); - for (var i = 0; i < arr.length; ++i) { - if (indexOf(set, arr[i]) == -1) { - set.push(arr[i]); - } - } - } - function hasHandler(emitter, type) { - return getHandlers(emitter, type).length > 0; - } - - // Add on and off methods to a constructor's prototype, to make - // registering events on such objects more convenient. - function eventMixin(ctor) { - ctor.prototype.on = function (type, f) { - on(this, type, f); - }; - ctor.prototype.off = function (type, f) { - off(this, type, f); - }; - } - - // Due to the fact that we still support jurassic IE versions, some - // compatibility wrappers are needed. - - function e_preventDefault(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - } - function e_stopPropagation(e) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } - } - function e_defaultPrevented(e) { - return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; - } - function e_stop(e) { - e_preventDefault(e); - e_stopPropagation(e); - } - function e_target(e) { - return e.target || e.srcElement; - } - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) { - b = 1; - } else if (e.button & 2) { - b = 3; - } else if (e.button & 4) { - b = 2; - } - } - if (mac && e.ctrlKey && b == 1) { - b = 3; - } - return b; - } - - // Detect drag-and-drop - var dragAndDrop = function () { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie && ie_version < 9) { - return false; - } - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - var zwspSupported; - function zeroWidthElement(measure) { - if (zwspSupported == null) { - var test = elt("span", "\u200b"); - removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); - if (measure.firstChild.offsetHeight != 0) { - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); - } - } - var node = zwspSupported ? elt("span", "\u200b") : elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - node.setAttribute("cm-text", ""); - return node; - } - - // Feature-detect IE's crummy client rect reporting for bidi text - var badBidiRects; - function hasBadBidiRects(measure) { - if (badBidiRects != null) { - return badBidiRects; - } - var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); - var r0 = range(txt, 0, 1).getBoundingClientRect(); - var r1 = range(txt, 1, 2).getBoundingClientRect(); - removeChildren(measure); - if (!r0 || r0.left == r0.right) { - return false; - } // Safari returns null in some cases (#2780) - return badBidiRects = r1.right - r0.right < 3; - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { - var pos = 0, - result = [], - l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) { - nl = string.length; - } - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function (string) { - return string.split(/\r\n?|\n/); - }; - var hasSelection = window.getSelection ? function (te) { - try { - return te.selectionStart != te.selectionEnd; - } catch (e) { - return false; - } - } : function (te) { - var range; - try { - range = te.ownerDocument.selection.createRange(); - } catch (e) {} - if (!range || range.parentElement() != te) { - return false; - } - return range.compareEndPoints("StartToEnd", range) != 0; - }; - var hasCopyEvent = function () { - var e = elt("div"); - if ("oncopy" in e) { - return true; - } - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == "function"; - }(); - var badZoomedRects = null; - function hasBadZoomedRects(measure) { - if (badZoomedRects != null) { - return badZoomedRects; - } - var node = removeChildrenAndAdd(measure, elt("span", "x")); - var normal = node.getBoundingClientRect(); - var fromRange = range(node, 0, 1).getBoundingClientRect(); - return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; - } - var modes = {}; - var mimeModes = {}; - // Extra arguments are stored as the mode's dependencies, which is - // used by (legacy) mechanisms like loadmode.js to automatically - // load a mode. (Preferred mechanism is the require/define calls.) - function defineMode(name, mode) { - if (arguments.length > 2) { - mode.dependencies = Array.prototype.slice.call(arguments, 2); - } - modes[name] = mode; - } - function defineMIME(mime, spec) { - mimeModes[mime] = spec; - } - - // Given a MIME type, a {name, ...options} config object, or a name - // string, return a mode config object. - function resolveMode(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - var found = mimeModes[spec.name]; - if (typeof found == "string") { - found = { - name: found - }; - } - spec = createObj(found, spec); - spec.name = found.name; - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { - return resolveMode("application/xml"); - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { - return resolveMode("application/json"); - } - if (typeof spec == "string") { - return { - name: spec - }; - } else { - return spec || { - name: "null" - }; - } - } - - // Given a mode spec (anything that resolveMode accepts), find and - // initialize an actual mode object. - function getMode(options, spec) { - spec = resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) { - return getMode(options, "text/plain"); - } - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) { - continue; - } - if (modeObj.hasOwnProperty(prop)) { - modeObj["_" + prop] = modeObj[prop]; - } - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - if (spec.helperType) { - modeObj.helperType = spec.helperType; - } - if (spec.modeProps) { - for (var prop$1 in spec.modeProps) { - modeObj[prop$1] = spec.modeProps[prop$1]; - } - } - return modeObj; - } - - // This can be used to attach properties to mode objects from - // outside the actual mode definition. - var modeExtensions = {}; - function extendMode(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : modeExtensions[mode] = {}; - copyObj(properties, exts); - } - function copyState(mode, state) { - if (state === true) { - return state; - } - if (mode.copyState) { - return mode.copyState(state); - } - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) { - val = val.concat([]); - } - nstate[n] = val; - } - return nstate; - } - - // Given a mode and a state (for that mode), find the inner mode and - // state at the position that the state refers to. - function innerMode(mode, state) { - var info; - while (mode.innerMode) { - info = mode.innerMode(state); - if (!info || info.mode == mode) { - break; - } - state = info.state; - mode = info.mode; - } - return info || { - mode: mode, - state: state - }; - } - function startState(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - } - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - var StringStream = function (string, tabSize, lineOracle) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - this.lastColumnPos = this.lastColumnValue = 0; - this.lineStart = 0; - this.lineOracle = lineOracle; - }; - StringStream.prototype.eol = function () { - return this.pos >= this.string.length; - }; - StringStream.prototype.sol = function () { - return this.pos == this.lineStart; - }; - StringStream.prototype.peek = function () { - return this.string.charAt(this.pos) || undefined; - }; - StringStream.prototype.next = function () { - if (this.pos < this.string.length) { - return this.string.charAt(this.pos++); - } - }; - StringStream.prototype.eat = function (match) { - var ch = this.string.charAt(this.pos); - var ok; - if (typeof match == "string") { - ok = ch == match; - } else { - ok = ch && (match.test ? match.test(ch) : match(ch)); - } - if (ok) { - ++this.pos; - return ch; - } - }; - StringStream.prototype.eatWhile = function (match) { - var start = this.pos; - while (this.eat(match)) {} - return this.pos > start; - }; - StringStream.prototype.eatSpace = function () { - var this$1 = this; - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { - ++this$1.pos; - } - return this.pos > start; - }; - StringStream.prototype.skipToEnd = function () { - this.pos = this.string.length; - }; - StringStream.prototype.skipTo = function (ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) { - this.pos = found; - return true; - } - }; - StringStream.prototype.backUp = function (n) { - this.pos -= n; - }; - StringStream.prototype.column = function () { - if (this.lastColumnPos < this.start) { - this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); - this.lastColumnPos = this.start; - } - return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }; - StringStream.prototype.indentation = function () { - return countColumn(this.string, null, this.tabSize) - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }; - StringStream.prototype.match = function (pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function (str) { - return caseInsensitive ? str.toLowerCase() : str; - }; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) { - this.pos += pattern.length; - } - return true; - } - } else { - var 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; - } - }; - StringStream.prototype.current = function () { - return this.string.slice(this.start, this.pos); - }; - StringStream.prototype.hideFirstChars = function (n, inner) { - this.lineStart += n; - try { - return inner(); - } finally { - this.lineStart -= n; - } - }; - StringStream.prototype.lookAhead = function (n) { - var oracle = this.lineOracle; - return oracle && oracle.lookAhead(n); - }; - var SavedContext = function (state, lookAhead) { - this.state = state; - this.lookAhead = lookAhead; - }; - var Context = function (doc, state, line, lookAhead) { - this.state = state; - this.doc = doc; - this.line = line; - this.maxLookAhead = lookAhead || 0; - }; - Context.prototype.lookAhead = function (n) { - var line = this.doc.getLine(this.line + n); - if (line != null && n > this.maxLookAhead) { - this.maxLookAhead = n; - } - return line; - }; - Context.prototype.nextLine = function () { - this.line++; - if (this.maxLookAhead > 0) { - this.maxLookAhead--; - } - }; - Context.fromSaved = function (doc, saved, line) { - if (saved instanceof SavedContext) { - return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead); - } else { - return new Context(doc, copyState(doc.mode, saved), line); - } - }; - Context.prototype.save = function (copy) { - var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; - return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state; - }; - - // Compute a style array (an array starting with a mode generation - // -- for invalidation -- followed by pairs of end positions and - // style strings), which is used to highlight the tokens on the - // line. - function highlightLine(cm, line, context, forceToEnd) { - // A styles array always starts with a number identifying the - // mode/overlays that it is based on (for easy invalidation). - var st = [cm.state.modeGen], - lineClasses = {}; - // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, context, function (end, style) { - return st.push(end, style); - }, lineClasses, forceToEnd); - var state = context.state; - - // Run overlays, adjust style array. - var loop = function (o) { - var overlay = cm.state.overlays[o], - i = 1, - at = 0; - context.state = true; - runMode(cm, line.text, overlay.mode, context, function (end, style) { - var start = i; - // Ensure there's a token end at the current position, and that i points at it - while (at < end) { - var i_end = st[i]; - if (i_end > end) { - st.splice(i, 1, end, st[i + 1], i_end); - } - i += 2; - at = Math.min(end, i_end); - } - if (!style) { - return; - } - if (overlay.opaque) { - st.splice(start, i - start, end, "overlay " + style); - i = start + 2; - } else { - for (; start < i; start += 2) { - var cur = st[start + 1]; - st[start + 1] = (cur ? cur + " " : "") + "overlay " + style; - } - } - }, lineClasses); - }; - for (var o = 0; o < cm.state.overlays.length; ++o) loop(o); - context.state = state; - return { - styles: st, - classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null - }; - } - function getLineStyles(cm, line, updateFrontier) { - if (!line.styles || line.styles[0] != cm.state.modeGen) { - var context = getContextBefore(cm, lineNo(line)); - var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); - var result = highlightLine(cm, line, context); - if (resetState) { - context.state = resetState; - } - line.stateAfter = context.save(!resetState); - line.styles = result.styles; - if (result.classes) { - line.styleClasses = result.classes; - } else if (line.styleClasses) { - line.styleClasses = null; - } - if (updateFrontier === cm.doc.highlightFrontier) { - cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); - } - } - return line.styles; - } - function getContextBefore(cm, n, precise) { - var doc = cm.doc, - display = cm.display; - if (!doc.mode.startState) { - return new Context(doc, true, n); - } - var start = findStartLine(cm, n, precise); - var saved = start > doc.first && getLine(doc, start - 1).stateAfter; - var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); - doc.iter(start, n, function (line) { - processLine(cm, line.text, context); - var pos = context.line; - line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; - context.nextLine(); - }); - if (precise) { - doc.modeFrontier = context.line; - } - return context; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. Used for lines that - // aren't currently visible. - function processLine(cm, text, context, startAt) { - var mode = cm.doc.mode; - var stream = new StringStream(text, cm.options.tabSize, context); - stream.start = stream.pos = startAt || 0; - if (text == "") { - callBlankLine(mode, context.state); - } - while (!stream.eol()) { - readToken(mode, stream, context.state); - stream.start = stream.pos; - } - } - function callBlankLine(mode, state) { - if (mode.blankLine) { - return mode.blankLine(state); - } - if (!mode.innerMode) { - return; - } - var inner = innerMode(mode, state); - if (inner.mode.blankLine) { - return inner.mode.blankLine(inner.state); - } - } - function readToken(mode, stream, state, inner) { - for (var i = 0; i < 10; i++) { - if (inner) { - inner[0] = innerMode(mode, state).mode; - } - var style = mode.token(stream, state); - if (stream.pos > stream.start) { - return style; - } - } - throw new Error("Mode " + mode.name + " failed to advance stream."); - } - var Token = function (stream, type, state) { - this.start = stream.start; - this.end = stream.pos; - this.string = stream.current(); - this.type = type || null; - this.state = state; - }; - - // Utility for getTokenAt and getLineTokens - function takeToken(cm, pos, precise, asArray) { - var doc = cm.doc, - mode = doc.mode, - style; - pos = clipPos(doc, pos); - var line = getLine(doc, pos.line), - context = getContextBefore(cm, pos.line, precise); - var stream = new StringStream(line.text, cm.options.tabSize, context), - tokens; - if (asArray) { - tokens = []; - } - while ((asArray || stream.pos < pos.ch) && !stream.eol()) { - stream.start = stream.pos; - style = readToken(mode, stream, context.state); - if (asArray) { - tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); - } - } - return asArray ? tokens : new Token(stream, style, context.state); - } - function extractLineClasses(type, output) { - if (type) { - for (;;) { - var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); - if (!lineClass) { - break; - } - type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); - var prop = lineClass[1] ? "bgClass" : "textClass"; - if (output[prop] == null) { - output[prop] = lineClass[2]; - } else if (!new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)").test(output[prop])) { - output[prop] += " " + lineClass[2]; - } - } - } - return type; - } - - // Run the given mode's parser over a line, calling f for each token. - function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { - var flattenSpans = mode.flattenSpans; - if (flattenSpans == null) { - flattenSpans = cm.options.flattenSpans; - } - var curStart = 0, - curStyle = null; - var stream = new StringStream(text, cm.options.tabSize, context), - style; - var inner = cm.options.addModeClass && [null]; - if (text == "") { - extractLineClasses(callBlankLine(mode, context.state), lineClasses); - } - while (!stream.eol()) { - if (stream.pos > cm.options.maxHighlightLength) { - flattenSpans = false; - if (forceToEnd) { - processLine(cm, text, context, stream.pos); - } - stream.pos = text.length; - style = null; - } else { - style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); - } - if (inner) { - var mName = inner[0].name; - if (mName) { - style = "m-" + (style ? mName + " " + style : mName); - } - } - if (!flattenSpans || curStyle != style) { - while (curStart < stream.start) { - curStart = Math.min(stream.start, curStart + 5000); - f(curStart, curStyle); - } - curStyle = style; - } - stream.start = stream.pos; - } - while (curStart < stream.pos) { - // Webkit seems to refuse to render text nodes longer than 57444 - // characters, and returns inaccurate measurements in nodes - // starting around 5000 chars. - var pos = Math.min(stream.pos, curStart + 5000); - f(pos, curStyle); - curStart = pos; - } - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n, precise) { - var minindent, - minline, - doc = cm.doc; - var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); - for (var search = n; search > lim; --search) { - if (search <= doc.first) { - return doc.first; - } - var line = getLine(doc, search - 1), - after = line.stateAfter; - if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) { - return search; - } - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - function retreatFrontier(doc, n) { - doc.modeFrontier = Math.min(doc.modeFrontier, n); - if (doc.highlightFrontier < n - 10) { - return; - } - var start = doc.first; - for (var line = n - 1; line > start; line--) { - var saved = getLine(doc, line).stateAfter; - // change is on 3 - // state on line 1 looked ahead 2 -- so saw 3 - // test 1 + 2 < 3 should cover this - if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { - start = line + 1; - break; - } - } - doc.highlightFrontier = Math.min(doc.highlightFrontier, start); - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - var Line = function (text, markedSpans, estimateHeight) { - this.text = text; - attachMarkedSpans(this, markedSpans); - this.height = estimateHeight ? estimateHeight(this) : 1; - }; - Line.prototype.lineNo = function () { - return lineNo(this); - }; - eventMixin(Line); - - // Change the content (text, markers) of a line. Automatically - // invalidates cached information and tries to re-estimate the - // line's height. - function updateLine(line, text, markedSpans, estimateHeight) { - line.text = text; - if (line.stateAfter) { - line.stateAfter = null; - } - if (line.styles) { - line.styles = null; - } - if (line.order != null) { - line.order = null; - } - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - var estHeight = estimateHeight ? estimateHeight(line) : 1; - if (estHeight != line.height) { - updateLineHeight(line, estHeight); - } - } - - // Detach a line from the document tree and its markers. - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - // Convert a style as returned by a mode (either null, or a string - // containing one or more styles) to a CSS style. This is cached, - // and also looks for line-wide styles. - var styleToClassCache = {}; - var styleToClassCacheWithMode = {}; - function interpretTokenStyle(style, options) { - if (!style || /^\s*$/.test(style)) { - return null; - } - var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; - return cache[style] || (cache[style] = style.replace(/\S+/g, "cm-$&")); - } - - // Render the DOM representation of the text of a line. Also builds - // up a 'line map', which points at the DOM nodes that represent - // specific stretches of text, and is used by the measuring code. - // The returned object contains the DOM node, this map, and - // information about line-wide styles that were set by the mode. - function buildLineContent(cm, lineView) { - // The padding-right forces the element to have a 'border', which - // is needed on Webkit to be able to get line-level bounding - // rectangles for it (in measureChar). - var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); - var builder = { - pre: eltP("pre", [content], "CodeMirror-line"), - content: content, - col: 0, - pos: 0, - cm: cm, - trailingSpace: false, - splitSpaces: (ie || webkit) && cm.getOption("lineWrapping") - }; - lineView.measure = {}; - - // Iterate over the logical lines that make up this visual line. - for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { - var line = i ? lineView.rest[i - 1] : lineView.line, - order = void 0; - builder.pos = 0; - builder.addToken = buildToken; - // Optionally wire in some hacks into the token-rendering - // algorithm, to deal with browser quirks. - if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) { - builder.addToken = buildTokenBadBidi(builder.addToken, order); - } - builder.map = []; - var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); - insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); - if (line.styleClasses) { - if (line.styleClasses.bgClass) { - builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); - } - if (line.styleClasses.textClass) { - builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); - } - } - - // Ensure at least a single node is present, for measuring. - if (builder.map.length == 0) { - builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); - } - - // Store the map and a cache object for the current logical line - if (i == 0) { - lineView.measure.map = builder.map; - lineView.measure.cache = {}; - } else { - (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); - (lineView.measure.caches || (lineView.measure.caches = [])).push({}); - } - } - - // See issue #2901 - if (webkit) { - var last = builder.content.lastChild; - if (/\bcm-tab\b/.test(last.className) || last.querySelector && last.querySelector(".cm-tab")) { - builder.content.className = "cm-tab-wrap-hack"; - } - } - signal(cm, "renderLine", cm, lineView.line, builder.pre); - if (builder.pre.className) { - builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); - } - return builder; - } - function defaultSpecialCharPlaceholder(ch) { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + ch.charCodeAt(0).toString(16); - token.setAttribute("aria-label", token.title); - return token; - } - - // Build up the DOM representation for a single token, and add it to - // the line map. Takes care to render special characters separately. - function buildToken(builder, text, style, startStyle, endStyle, title, css) { - if (!text) { - return; - } - var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; - var special = builder.cm.state.specialChars, - mustWrap = false; - var content; - if (!special.test(text)) { - builder.col += text.length; - content = document.createTextNode(displayText); - builder.map.push(builder.pos, builder.pos + text.length, content); - if (ie && ie_version < 9) { - mustWrap = true; - } - builder.pos += text.length; - } else { - content = document.createDocumentFragment(); - var pos = 0; - while (true) { - special.lastIndex = pos; - var m = special.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); - if (ie && ie_version < 9) { - content.appendChild(elt("span", [txt])); - } else { - content.appendChild(txt); - } - builder.map.push(builder.pos, builder.pos + skipped, txt); - builder.col += skipped; - builder.pos += skipped; - } - if (!m) { - break; - } - pos += skipped + 1; - var txt$1 = void 0; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, - tabWidth = tabSize - builder.col % tabSize; - txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - txt$1.setAttribute("role", "presentation"); - txt$1.setAttribute("cm-text", "\t"); - builder.col += tabWidth; - } else if (m[0] == "\r" || m[0] == "\n") { - txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); - txt$1.setAttribute("cm-text", m[0]); - builder.col += 1; - } else { - txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); - txt$1.setAttribute("cm-text", m[0]); - if (ie && ie_version < 9) { - content.appendChild(elt("span", [txt$1])); - } else { - content.appendChild(txt$1); - } - builder.col += 1; - } - builder.map.push(builder.pos, builder.pos + 1, txt$1); - builder.pos++; - } - } - builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; - if (style || startStyle || endStyle || mustWrap || css) { - var fullStyle = style || ""; - if (startStyle) { - fullStyle += startStyle; - } - if (endStyle) { - fullStyle += endStyle; - } - var token = elt("span", [content], fullStyle, css); - if (title) { - token.title = title; - } - return builder.content.appendChild(token); - } - builder.content.appendChild(content); - } - function splitSpaces(text, trailingBefore) { - if (text.length > 1 && !/ /.test(text)) { - return text; - } - var spaceBefore = trailingBefore, - result = ""; - for (var i = 0; i < text.length; i++) { - var ch = text.charAt(i); - if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) { - ch = "\u00a0"; - } - result += ch; - spaceBefore = ch == " "; - } - return result; - } - - // Work around nonsense dimensions being reported for stretches of - // right-to-left text. - function buildTokenBadBidi(inner, order) { - return function (builder, text, style, startStyle, endStyle, title, css) { - style = style ? style + " cm-force-border" : "cm-force-border"; - var start = builder.pos, - end = start + text.length; - for (;;) { - // Find the part that overlaps with the start of this text - var part = void 0; - for (var i = 0; i < order.length; i++) { - part = order[i]; - if (part.to > start && part.from <= start) { - break; - } - } - if (part.to >= end) { - return inner(builder, text, style, startStyle, endStyle, title, css); - } - inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); - startStyle = null; - text = text.slice(part.to - start); - start = part.to; - } - }; - } - function buildCollapsedSpan(builder, size, marker, ignoreWidget) { - var widget = !ignoreWidget && marker.widgetNode; - if (widget) { - builder.map.push(builder.pos, builder.pos + size, widget); - } - if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { - if (!widget) { - widget = builder.content.appendChild(document.createElement("span")); - } - widget.setAttribute("cm-marker", marker.id); - } - if (widget) { - builder.cm.display.input.setUneditable(widget); - builder.content.appendChild(widget); - } - builder.pos += size; - builder.trailingSpace = false; - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder, styles) { - var spans = line.markedSpans, - allText = line.text, - at = 0; - if (!spans) { - for (var i$1 = 1; i$1 < styles.length; i$1 += 2) { - builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1 + 1], builder.cm.options)); - } - return; - } - var len = allText.length, - pos = 0, - i = 1, - text = "", - style, - css; - var nextChange = 0, - spanStyle, - spanEndStyle, - spanStartStyle, - title, - collapsed; - for (;;) { - if (nextChange == pos) { - // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = title = css = ""; - collapsed = null; - nextChange = Infinity; - var foundBookmarks = [], - endStyles = void 0; - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], - m = sp.marker; - if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { - foundBookmarks.push(m); - } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { - if (sp.to != null && sp.to != pos && nextChange > sp.to) { - nextChange = sp.to; - spanEndStyle = ""; - } - if (m.className) { - spanStyle += " " + m.className; - } - if (m.css) { - css = (css ? css + ";" : "") + m.css; - } - if (m.startStyle && sp.from == pos) { - spanStartStyle += " " + m.startStyle; - } - if (m.endStyle && sp.to == nextChange) { - (endStyles || (endStyles = [])).push(m.endStyle, sp.to); - } - if (m.title && !title) { - title = m.title; - } - if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) { - collapsed = sp; - } - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - } - if (endStyles) { - for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) { - if (endStyles[j$1 + 1] == nextChange) { - spanEndStyle += " " + endStyles[j$1]; - } - } - } - if (!collapsed || collapsed.from == pos) { - for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) { - buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); - } - } - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, collapsed.marker, collapsed.from == null); - if (collapsed.to == null) { - return; - } - if (collapsed.to == pos) { - collapsed = false; - } - } - } - if (pos >= len) { - break; - } - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - if (!collapsed) { - var tokenText = end > upto ? text.slice(0, upto - pos) : text; - builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); - } - if (end >= upto) { - text = text.slice(upto - pos); - pos = upto; - break; - } - pos = end; - spanStartStyle = ""; - } - text = allText.slice(at, at = styles[i++]); - style = interpretTokenStyle(styles[i++], builder.cm.options); - } - } - } - - // These objects are used to represent the visible (currently drawn) - // part of the document. A LineView may correspond to multiple - // logical lines, if those are connected by collapsed ranges. - function LineView(doc, line, lineN) { - // The starting line - this.line = line; - // Continuing lines, if any - this.rest = visualLineContinued(line); - // Number of logical lines in this visual line - this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; - this.node = this.text = null; - this.hidden = lineIsHidden(doc, line); - } - - // Create a range of LineView objects for the given lines. - function buildViewArray(cm, from, to) { - var array = [], - nextPos; - for (var pos = from; pos < to; pos = nextPos) { - var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); - nextPos = pos + view.size; - array.push(view); - } - return array; - } - var operationGroup = null; - function pushOperation(op) { - if (operationGroup) { - operationGroup.ops.push(op); - } else { - op.ownsGroup = operationGroup = { - ops: [op], - delayedCallbacks: [] - }; - } - } - function fireCallbacksForOps(group) { - // Calls delayed callbacks and cursorActivity handlers until no - // new ones appear - var callbacks = group.delayedCallbacks, - i = 0; - do { - for (; i < callbacks.length; i++) { - callbacks[i].call(null); - } - for (var j = 0; j < group.ops.length; j++) { - var op = group.ops[j]; - if (op.cursorActivityHandlers) { - while (op.cursorActivityCalled < op.cursorActivityHandlers.length) { - op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); - } - } - } - } while (i < callbacks.length); - } - function finishOperation(op, endCb) { - var group = op.ownsGroup; - if (!group) { - return; - } - try { - fireCallbacksForOps(group); - } finally { - operationGroup = null; - endCb(group); - } - } - var orphanDelayedCallbacks = null; - - // Often, we want to signal events at a point where we are in the - // middle of some work, but don't want the handler to start calling - // other methods on the editor, which might be in an inconsistent - // state or simply not expect any other events to happen. - // signalLater looks whether there are any handlers, and schedules - // them to be executed when the last operation ends, or, if no - // operation is active, when a timeout fires. - function signalLater(emitter, type /*, values...*/) { - var arr = getHandlers(emitter, type); - if (!arr.length) { - return; - } - var args = Array.prototype.slice.call(arguments, 2), - list; - if (operationGroup) { - list = operationGroup.delayedCallbacks; - } else if (orphanDelayedCallbacks) { - list = orphanDelayedCallbacks; - } else { - list = orphanDelayedCallbacks = []; - setTimeout(fireOrphanDelayed, 0); - } - var loop = function (i) { - list.push(function () { - return arr[i].apply(null, args); - }); - }; - for (var i = 0; i < arr.length; ++i) loop(i); - } - function fireOrphanDelayed() { - var delayed = orphanDelayedCallbacks; - orphanDelayedCallbacks = null; - for (var i = 0; i < delayed.length; ++i) { - delayed[i](); - } - } - - // When an aspect of a line changes, a string is added to - // lineView.changes. This updates the relevant part of the line's - // DOM structure. - function updateLineForChanges(cm, lineView, lineN, dims) { - for (var j = 0; j < lineView.changes.length; j++) { - var type = lineView.changes[j]; - if (type == "text") { - updateLineText(cm, lineView); - } else if (type == "gutter") { - updateLineGutter(cm, lineView, lineN, dims); - } else if (type == "class") { - updateLineClasses(cm, lineView); - } else if (type == "widget") { - updateLineWidgets(cm, lineView, dims); - } - } - lineView.changes = null; - } - - // Lines with gutter elements, widgets or a background class need to - // be wrapped, and have the extra elements added to the wrapper div - function ensureLineWrapped(lineView) { - if (lineView.node == lineView.text) { - lineView.node = elt("div", null, null, "position: relative"); - if (lineView.text.parentNode) { - lineView.text.parentNode.replaceChild(lineView.node, lineView.text); - } - lineView.node.appendChild(lineView.text); - if (ie && ie_version < 8) { - lineView.node.style.zIndex = 2; - } - } - return lineView.node; - } - function updateLineBackground(cm, lineView) { - var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; - if (cls) { - cls += " CodeMirror-linebackground"; - } - if (lineView.background) { - if (cls) { - lineView.background.className = cls; - } else { - lineView.background.parentNode.removeChild(lineView.background); - lineView.background = null; - } - } else if (cls) { - var wrap = ensureLineWrapped(lineView); - lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); - cm.display.input.setUneditable(lineView.background); - } - } - - // Wrapper around buildLineContent which will reuse the structure - // in display.externalMeasured when possible. - function getLineContent(cm, lineView) { - var ext = cm.display.externalMeasured; - if (ext && ext.line == lineView.line) { - cm.display.externalMeasured = null; - lineView.measure = ext.measure; - return ext.built; - } - return buildLineContent(cm, lineView); - } - - // Redraw the line's text. Interacts with the background and text - // classes because the mode may output tokens that influence these - // classes. - function updateLineText(cm, lineView) { - var cls = lineView.text.className; - var built = getLineContent(cm, lineView); - if (lineView.text == lineView.node) { - lineView.node = built.pre; - } - lineView.text.parentNode.replaceChild(built.pre, lineView.text); - lineView.text = built.pre; - if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { - lineView.bgClass = built.bgClass; - lineView.textClass = built.textClass; - updateLineClasses(cm, lineView); - } else if (cls) { - lineView.text.className = cls; - } - } - function updateLineClasses(cm, lineView) { - updateLineBackground(cm, lineView); - if (lineView.line.wrapClass) { - ensureLineWrapped(lineView).className = lineView.line.wrapClass; - } else if (lineView.node != lineView.text) { - lineView.node.className = ""; - } - var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; - lineView.text.className = textClass || ""; - } - function updateLineGutter(cm, lineView, lineN, dims) { - if (lineView.gutter) { - lineView.node.removeChild(lineView.gutter); - lineView.gutter = null; - } - if (lineView.gutterBackground) { - lineView.node.removeChild(lineView.gutterBackground); - lineView.gutterBackground = null; - } - if (lineView.line.gutterClass) { - var wrap = ensureLineWrapped(lineView); - lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + dims.gutterTotalWidth + "px"); - cm.display.input.setUneditable(lineView.gutterBackground); - wrap.insertBefore(lineView.gutterBackground, lineView.text); - } - var markers = lineView.line.gutterMarkers; - if (cm.options.lineNumbers || markers) { - var wrap$1 = ensureLineWrapped(lineView); - var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"); - cm.display.input.setUneditable(gutterWrap); - wrap$1.insertBefore(gutterWrap, lineView.text); - if (lineView.line.gutterClass) { - gutterWrap.className += " " + lineView.line.gutterClass; - } - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) { - lineView.lineNumber = gutterWrap.appendChild(elt("div", lineNumberFor(cm.options, lineN), "CodeMirror-linenumber CodeMirror-gutter-elt", "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + cm.display.lineNumInnerWidth + "px")); - } - if (markers) { - for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], - found = markers.hasOwnProperty(id) && markers[id]; - if (found) { - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - } - } - } - function updateLineWidgets(cm, lineView, dims) { - if (lineView.alignable) { - lineView.alignable = null; - } - for (var node = lineView.node.firstChild, next = void 0; node; node = next) { - next = node.nextSibling; - if (node.className == "CodeMirror-linewidget") { - lineView.node.removeChild(node); - } - } - insertLineWidgets(cm, lineView, dims); - } - - // Build a line's DOM representation from scratch - function buildLineElement(cm, lineView, lineN, dims) { - var built = getLineContent(cm, lineView); - lineView.text = lineView.node = built.pre; - if (built.bgClass) { - lineView.bgClass = built.bgClass; - } - if (built.textClass) { - lineView.textClass = built.textClass; - } - updateLineClasses(cm, lineView); - updateLineGutter(cm, lineView, lineN, dims); - insertLineWidgets(cm, lineView, dims); - return lineView.node; - } - - // A lineView may contain multiple logical lines (when merged by - // collapsed spans). The widgets for all of them need to be drawn. - function insertLineWidgets(cm, lineView, dims) { - insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); - if (lineView.rest) { - for (var i = 0; i < lineView.rest.length; i++) { - insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); - } - } - } - function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { - if (!line.widgets) { - return; - } - var wrap = ensureLineWrapped(lineView); - for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], - node = elt("div", [widget.node], "CodeMirror-linewidget"); - if (!widget.handleMouseEvents) { - node.setAttribute("cm-ignore-events", "true"); - } - positionLineWidget(widget, node, lineView, dims); - cm.display.input.setUneditable(node); - if (allowAbove && widget.above) { - wrap.insertBefore(node, lineView.gutter || lineView.text); - } else { - wrap.appendChild(node); - } - signalLater(widget, "redraw"); - } - } - function positionLineWidget(widget, node, lineView, dims) { - if (widget.noHScroll) { - (lineView.alignable || (lineView.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) { - node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - } - } - function widgetHeight(widget) { - if (widget.height != null) { - return widget.height; - } - var cm = widget.doc.cm; - if (!cm) { - return 0; - } - if (!contains(document.body, widget.node)) { - var parentStyle = "position: relative;"; - if (widget.coverGutter) { - parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; - } - if (widget.noHScroll) { - parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; - } - removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); - } - return widget.height = widget.node.parentNode.offsetHeight; - } - - // Return true when the given mouse event happened in a widget - function eventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n || n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true" || n.parentNode == display.sizer && n != display.mover) { - return true; - } - } - } - - // POSITION MEASUREMENT - - function paddingTop(display) { - return display.lineSpace.offsetTop; - } - function paddingVert(display) { - return display.mover.offsetHeight - display.lineSpace.offsetHeight; - } - function paddingH(display) { - if (display.cachedPaddingH) { - return display.cachedPaddingH; - } - var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); - var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; - var data = { - left: parseInt(style.paddingLeft), - right: parseInt(style.paddingRight) - }; - if (!isNaN(data.left) && !isNaN(data.right)) { - display.cachedPaddingH = data; - } - return data; - } - function scrollGap(cm) { - return scrollerGap - cm.display.nativeBarWidth; - } - function displayWidth(cm) { - return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; - } - function displayHeight(cm) { - return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; - } - - // Ensure the lineView.wrapping.heights array is populated. This is - // an array of bottom offsets for the lines that make up a drawn - // line. When lineWrapping is on, there might be more than one - // height. - function ensureLineHeights(cm, lineView, rect) { - var wrapping = cm.options.lineWrapping; - var curWidth = wrapping && displayWidth(cm); - if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { - var heights = lineView.measure.heights = []; - if (wrapping) { - lineView.measure.width = curWidth; - var rects = lineView.text.firstChild.getClientRects(); - for (var i = 0; i < rects.length - 1; i++) { - var cur = rects[i], - next = rects[i + 1]; - if (Math.abs(cur.bottom - next.bottom) > 2) { - heights.push((cur.bottom + next.top) / 2 - rect.top); - } - } - } - heights.push(rect.bottom - rect.top); - } - } - - // Find a line map (mapping character offsets to text nodes) and a - // measurement cache for the given line number. (A line view might - // contain multiple lines when collapsed ranges are present.) - function mapFromLineView(lineView, line, lineN) { - if (lineView.line == line) { - return { - map: lineView.measure.map, - cache: lineView.measure.cache - }; - } - for (var i = 0; i < lineView.rest.length; i++) { - if (lineView.rest[i] == line) { - return { - map: lineView.measure.maps[i], - cache: lineView.measure.caches[i] - }; - } - } - for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) { - if (lineNo(lineView.rest[i$1]) > lineN) { - return { - map: lineView.measure.maps[i$1], - cache: lineView.measure.caches[i$1], - before: true - }; - } - } - } - - // Render a line into the hidden node display.externalMeasured. Used - // when measurement is needed for a line that's not in the viewport. - function updateExternalMeasurement(cm, line) { - line = visualLine(line); - var lineN = lineNo(line); - var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); - view.lineN = lineN; - var built = view.built = buildLineContent(cm, view); - view.text = built.pre; - removeChildrenAndAdd(cm.display.lineMeasure, built.pre); - return view; - } - - // Get a {top, bottom, left, right} box (in line-local coordinates) - // for a given character. - function measureChar(cm, line, ch, bias) { - return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); - } - - // Find a line view that corresponds to the given line number. - function findViewForLine(cm, lineN) { - if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) { - return cm.display.view[findViewIndex(cm, lineN)]; - } - var ext = cm.display.externalMeasured; - if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) { - return ext; - } - } - - // Measurement can be split in two steps, the set-up work that - // applies to the whole line, and the measurement of the actual - // character. Functions like coordsChar, that need to do a lot of - // measurements in a row, can thus ensure that the set-up work is - // only done once. - function prepareMeasureForLine(cm, line) { - var lineN = lineNo(line); - var view = findViewForLine(cm, lineN); - if (view && !view.text) { - view = null; - } else if (view && view.changes) { - updateLineForChanges(cm, view, lineN, getDimensions(cm)); - cm.curOp.forceUpdate = true; - } - if (!view) { - view = updateExternalMeasurement(cm, line); - } - var info = mapFromLineView(view, line, lineN); - return { - line: line, - view: view, - rect: null, - map: info.map, - cache: info.cache, - before: info.before, - hasHeights: false - }; - } - - // Given a prepared measurement object, measures the position of an - // actual character (or fetches it from the cache). - function measureCharPrepared(cm, prepared, ch, bias, varHeight) { - if (prepared.before) { - ch = -1; - } - var key = ch + (bias || ""), - found; - if (prepared.cache.hasOwnProperty(key)) { - found = prepared.cache[key]; - } else { - if (!prepared.rect) { - prepared.rect = prepared.view.text.getBoundingClientRect(); - } - if (!prepared.hasHeights) { - ensureLineHeights(cm, prepared.view, prepared.rect); - prepared.hasHeights = true; - } - found = measureCharInner(cm, prepared, ch, bias); - if (!found.bogus) { - prepared.cache[key] = found; - } - } - return { - left: found.left, - right: found.right, - top: varHeight ? found.rtop : found.top, - bottom: varHeight ? found.rbottom : found.bottom - }; - } - var nullRect = { - left: 0, - right: 0, - top: 0, - bottom: 0 - }; - function nodeAndOffsetInLineMap(map, ch, bias) { - var node, start, end, collapse, mStart, mEnd; - // First, search the line map for the text node corresponding to, - // or closest to, the target character. - for (var i = 0; i < map.length; i += 3) { - mStart = map[i]; - mEnd = map[i + 1]; - if (ch < mStart) { - start = 0; - end = 1; - collapse = "left"; - } else if (ch < mEnd) { - start = ch - mStart; - end = start + 1; - } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { - end = mEnd - mStart; - start = end - 1; - if (ch >= mEnd) { - collapse = "right"; - } - } - if (start != null) { - node = map[i + 2]; - if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) { - collapse = bias; - } - if (bias == "left" && start == 0) { - while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { - node = map[(i -= 3) + 2]; - collapse = "left"; - } - } - if (bias == "right" && start == mEnd - mStart) { - while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { - node = map[(i += 3) + 2]; - collapse = "right"; - } - } - break; - } - } - return { - node: node, - start: start, - end: end, - collapse: collapse, - coverStart: mStart, - coverEnd: mEnd - }; - } - function getUsefulRect(rects, bias) { - var rect = nullRect; - if (bias == "left") { - for (var i = 0; i < rects.length; i++) { - if ((rect = rects[i]).left != rect.right) { - break; - } - } - } else { - for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { - if ((rect = rects[i$1]).left != rect.right) { - break; - } - } - } - return rect; - } - function measureCharInner(cm, prepared, ch, bias) { - var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); - var node = place.node, - start = place.start, - end = place.end, - collapse = place.collapse; - var rect; - if (node.nodeType == 3) { - // If it is a text node, use a range to retrieve the coordinates. - for (var i$1 = 0; i$1 < 4; i$1++) { - // Retry a maximum of 4 times when nonsense rectangles are returned - while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { - --start; - } - while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { - ++end; - } - if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { - rect = node.parentNode.getBoundingClientRect(); - } else { - rect = getUsefulRect(range(node, start, end).getClientRects(), bias); - } - if (rect.left || rect.right || start == 0) { - break; - } - end = start; - start = start - 1; - collapse = "right"; - } - if (ie && ie_version < 11) { - rect = maybeUpdateRectForZooming(cm.display.measure, rect); - } - } else { - // If it is a widget, simply get the box for the whole widget. - if (start > 0) { - collapse = bias = "right"; - } - var rects; - if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) { - rect = rects[bias == "right" ? rects.length - 1 : 0]; - } else { - rect = node.getBoundingClientRect(); - } - } - if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { - var rSpan = node.parentNode.getClientRects()[0]; - if (rSpan) { - rect = { - left: rSpan.left, - right: rSpan.left + charWidth(cm.display), - top: rSpan.top, - bottom: rSpan.bottom - }; - } else { - rect = nullRect; - } - } - var rtop = rect.top - prepared.rect.top, - rbot = rect.bottom - prepared.rect.top; - var mid = (rtop + rbot) / 2; - var heights = prepared.view.measure.heights; - var i = 0; - for (; i < heights.length - 1; i++) { - if (mid < heights[i]) { - break; - } - } - var top = i ? heights[i - 1] : 0, - bot = heights[i]; - var result = { - left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, - right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, - top: top, - bottom: bot - }; - if (!rect.left && !rect.right) { - result.bogus = true; - } - if (!cm.options.singleCursorHeightPerLine) { - result.rtop = rtop; - result.rbottom = rbot; - } - return result; - } - - // Work around problem with bounding client rects on ranges being - // returned incorrectly when zoomed on IE10 and below. - function maybeUpdateRectForZooming(measure, rect) { - if (!window.screen || screen.logicalXDPI == null || screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) { - return rect; - } - var scaleX = screen.logicalXDPI / screen.deviceXDPI; - var scaleY = screen.logicalYDPI / screen.deviceYDPI; - return { - left: rect.left * scaleX, - right: rect.right * scaleX, - top: rect.top * scaleY, - bottom: rect.bottom * scaleY - }; - } - function clearLineMeasurementCacheFor(lineView) { - if (lineView.measure) { - lineView.measure.cache = {}; - lineView.measure.heights = null; - if (lineView.rest) { - for (var i = 0; i < lineView.rest.length; i++) { - lineView.measure.caches[i] = {}; - } - } - } - } - function clearLineMeasurementCache(cm) { - cm.display.externalMeasure = null; - removeChildren(cm.display.lineMeasure); - for (var i = 0; i < cm.display.view.length; i++) { - clearLineMeasurementCacheFor(cm.display.view[i]); - } - } - function clearCaches(cm) { - clearLineMeasurementCache(cm); - cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; - if (!cm.options.lineWrapping) { - cm.display.maxLineChanged = true; - } - cm.display.lineNumChars = null; - } - function pageScrollX() { - // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 - // which causes page_Offset and bounding client rects to use - // different reference viewports and invalidate our calculations. - if (chrome && android) { - return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)); - } - return window.pageXOffset || (document.documentElement || document.body).scrollLeft; - } - function pageScrollY() { - if (chrome && android) { - return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)); - } - return window.pageYOffset || (document.documentElement || document.body).scrollTop; - } - - // Converts a {top, bottom, left, right} box from line-local - // coordinates into another coordinate system. Context may be one of - // "line", "div" (display.lineDiv), "local"./null (editor), "window", - // or "page". - function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { - if (!includeWidgets && lineObj.widgets) { - for (var i = 0; i < lineObj.widgets.length; ++i) { - if (lineObj.widgets[i].above) { - var size = widgetHeight(lineObj.widgets[i]); - rect.top += size; - rect.bottom += size; - } - } - } - if (context == "line") { - return rect; - } - if (!context) { - context = "local"; - } - var yOff = heightAtLine(lineObj); - if (context == "local") { - yOff += paddingTop(cm.display); - } else { - yOff -= cm.display.viewOffset; - } - if (context == "page" || context == "window") { - var lOff = cm.display.lineSpace.getBoundingClientRect(); - yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); - rect.left += xOff; - rect.right += xOff; - } - rect.top += yOff; - rect.bottom += yOff; - return rect; - } - - // Coverts a box from "div" coords to another coordinate system. - // Context may be "window", "page", "div", or "local"./null. - function fromCoordSystem(cm, coords, context) { - if (context == "div") { - return coords; - } - var left = coords.left, - top = coords.top; - // First move into "page" coordinate system - if (context == "page") { - left -= pageScrollX(); - top -= pageScrollY(); - } else if (context == "local" || !context) { - var localBox = cm.display.sizer.getBoundingClientRect(); - left += localBox.left; - top += localBox.top; - } - var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); - return { - left: left - lineSpaceBox.left, - top: top - lineSpaceBox.top - }; - } - function charCoords(cm, pos, context, lineObj, bias) { - if (!lineObj) { - lineObj = getLine(cm.doc, pos.line); - } - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); - } - - // Returns a box for a given cursor position, which may have an - // 'other' property containing the position of the secondary cursor - // on a bidi boundary. - // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` - // and after `char - 1` in writing order of `char - 1` - // A cursor Pos(line, char, "after") is on the same visual line as `char` - // and before `char` in writing order of `char` - // Examples (upper-case letters are RTL, lower-case are LTR): - // Pos(0, 1, ...) - // before after - // ab a|b a|b - // aB a|B aB| - // Ab |Ab A|b - // AB B|A B|A - // Every position after the last character on a line is considered to stick - // to the last character on the line. - function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { - lineObj = lineObj || getLine(cm.doc, pos.line); - if (!preparedMeasure) { - preparedMeasure = prepareMeasureForLine(cm, lineObj); - } - function get(ch, right) { - var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); - if (right) { - m.left = m.right; - } else { - m.right = m.left; - } - return intoCoordSystem(cm, lineObj, m, context); - } - var order = getOrder(lineObj, cm.doc.direction), - ch = pos.ch, - sticky = pos.sticky; - if (ch >= lineObj.text.length) { - ch = lineObj.text.length; - sticky = "before"; - } else if (ch <= 0) { - ch = 0; - sticky = "after"; - } - if (!order) { - return get(sticky == "before" ? ch - 1 : ch, sticky == "before"); - } - function getBidi(ch, partPos, invert) { - var part = order[partPos], - right = part.level % 2 != 0; - return get(invert ? ch - 1 : ch, right != invert); - } - var partPos = getBidiPartAt(order, ch, sticky); - var other = bidiOther; - var val = getBidi(ch, partPos, sticky == "before"); - if (other != null) { - val.other = getBidi(ch, other, sticky != "before"); - } - return val; - } - - // Used to cheaply estimate the coordinates for a position. Used for - // intermediate scroll updates. - function estimateCoords(cm, pos) { - var left = 0; - pos = clipPos(cm.doc, pos); - if (!cm.options.lineWrapping) { - left = charWidth(cm.display) * pos.ch; - } - var lineObj = getLine(cm.doc, pos.line); - var top = heightAtLine(lineObj) + paddingTop(cm.display); - return { - left: left, - right: left, - top: top, - bottom: top + lineObj.height - }; - } - - // Positions returned by coordsChar contain some extra information. - // xRel is the relative x position of the input coordinates compared - // to the found position (so xRel > 0 means the coordinates are to - // the right of the character position, for example). When outside - // is true, that means the coordinates lie outside the line's - // vertical range. - function PosWithInfo(line, ch, sticky, outside, xRel) { - var pos = Pos(line, ch, sticky); - pos.xRel = xRel; - if (outside) { - pos.outside = true; - } - return pos; - } - - // Compute the character position closest to the given coordinates. - // Input must be lineSpace-local ("div" coordinate system). - function coordsChar(cm, x, y) { - var doc = cm.doc; - y += cm.display.viewOffset; - if (y < 0) { - return PosWithInfo(doc.first, 0, null, true, -1); - } - var lineN = lineAtHeight(doc, y), - last = doc.first + doc.size - 1; - if (lineN > last) { - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1); - } - if (x < 0) { - x = 0; - } - var lineObj = getLine(doc, lineN); - for (;;) { - var found = coordsCharInner(cm, lineObj, lineN, x, y); - var merged = collapsedSpanAtEnd(lineObj); - var mergedPos = merged && merged.find(0, true); - if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) { - lineN = lineNo(lineObj = mergedPos.to.line); - } else { - return found; - } - } - } - function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { - var measure = function (ch) { - return intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line"); - }; - var end = lineObj.text.length; - var begin = findFirst(function (ch) { - return measure(ch - 1).bottom <= y; - }, end, 0); - end = findFirst(function (ch) { - return measure(ch).top > y; - }, begin, end); - return { - begin: begin, - end: end - }; - } - function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { - var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; - return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop); - } - function coordsCharInner(cm, lineObj, lineNo, x, y) { - y -= heightAtLine(lineObj); - var begin = 0, - end = lineObj.text.length; - var preparedMeasure = prepareMeasureForLine(cm, lineObj); - var pos; - var order = getOrder(lineObj, cm.doc.direction); - if (order) { - if (cm.options.lineWrapping) { - var assign; - assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = assign.begin, end = assign.end, assign; - } - pos = new Pos(lineNo, Math.floor(begin + (end - begin) / 2)); - var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left; - var dir = beginLeft < x ? 1 : -1; - var prevDiff, - diff = beginLeft - x, - prevPos; - var steps = Math.ceil((end - begin) / 4); - outer: do { - prevDiff = diff; - prevPos = pos; - var i = 0; - for (; i < steps; ++i) { - var prevPos$1 = pos; - pos = moveVisually(cm, lineObj, pos, dir); - if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos.ch - 1 : pos.ch)) { - pos = prevPos$1; - break outer; - } - } - diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x; - if (steps > 1) { - var diff_change_per_step = Math.abs(diff - prevDiff) / steps; - steps = Math.min(steps, Math.ceil(Math.abs(diff) / diff_change_per_step)); - dir = diff < 0 ? 1 : -1; - } - } while (diff != 0 && (steps > 1 || dir < 0 != diff < 0 && Math.abs(diff) <= Math.abs(prevDiff))); - if (Math.abs(diff) > Math.abs(prevDiff)) { - if (diff < 0 == prevDiff < 0) { - throw new Error("Broke out of infinite loop in coordsCharInner"); - } - pos = prevPos; - } - } else { - var ch = findFirst(function (ch) { - var box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line"); - if (box.top > y) { - // For the cursor stickiness - end = Math.min(ch, end); - return true; - } else if (box.bottom <= y) { - return false; - } else if (box.left > x) { - return true; - } else if (box.right < x) { - return false; - } else { - return x - box.left < box.right - x; - } - }, begin, end); - ch = skipExtendingChars(lineObj.text, ch, 1); - pos = new Pos(lineNo, ch, ch == end ? "before" : "after"); - } - var coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure); - if (y < coords.top || coords.bottom < y) { - pos.outside = true; - } - pos.xRel = x < coords.left ? -1 : x > coords.right ? 1 : 0; - return pos; - } - var measureText; - // Compute the default text height. - function textHeight(display) { - if (display.cachedTextHeight != null) { - return display.cachedTextHeight; - } - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) { - display.cachedTextHeight = height; - } - removeChildren(display.measure); - return height || 1; - } - - // Compute the default character width. - function charWidth(display) { - if (display.cachedCharWidth != null) { - return display.cachedCharWidth; - } - var anchor = elt("span", "xxxxxxxxxx"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var rect = anchor.getBoundingClientRect(), - width = (rect.right - rect.left) / 10; - if (width > 2) { - display.cachedCharWidth = width; - } - return width || 10; - } - - // Do a bulk-read of the DOM positions and sizes needed to draw the - // view, so that we don't interleave reading and writing to the DOM. - function getDimensions(cm) { - var d = cm.display, - left = {}, - width = {}; - var gutterLeft = d.gutters.clientLeft; - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; - width[cm.options.gutters[i]] = n.clientWidth; - } - return { - fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth - }; - } - - // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, - // but using getBoundingClientRect to get a sub-pixel-accurate - // result. - function compensateForHScroll(display) { - return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; - } - - // Returns a function that estimates the height of a line, to use as - // first approximation until the line becomes visible (and is thus - // properly measurable). - function estimateHeight(cm) { - var th = textHeight(cm.display), - wrapping = cm.options.lineWrapping; - var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); - return function (line) { - if (lineIsHidden(cm.doc, line)) { - return 0; - } - var widgetsHeight = 0; - if (line.widgets) { - for (var i = 0; i < line.widgets.length; i++) { - if (line.widgets[i].height) { - widgetsHeight += line.widgets[i].height; - } - } - } - if (wrapping) { - return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; - } else { - return widgetsHeight + th; - } - }; - } - function estimateLineHeights(cm) { - var doc = cm.doc, - est = estimateHeight(cm); - doc.iter(function (line) { - var estHeight = est(line); - if (estHeight != line.height) { - updateLineHeight(line, estHeight); - } - }); - } - - // Given a mouse event, find the corresponding position. If liberal - // is false, it checks whether a gutter or scrollbar was clicked, - // and returns null if it was. forRect is used by rectangular - // selections, and tries to estimate a character position even for - // coordinates beyond the right of the text. - function posFromMouse(cm, e, liberal, forRect) { - var display = cm.display; - if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { - return null; - } - var x, - y, - space = display.lineSpace.getBoundingClientRect(); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { - x = e.clientX - space.left; - y = e.clientY - space.top; - } catch (e) { - return null; - } - var coords = coordsChar(cm, x, y), - line; - if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { - var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; - coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); - } - return coords; - } - - // Find the view element corresponding to a given line. Return null - // when the line isn't visible. - function findViewIndex(cm, n) { - if (n >= cm.display.viewTo) { - return null; - } - n -= cm.display.viewFrom; - if (n < 0) { - return null; - } - var view = cm.display.view; - for (var i = 0; i < view.length; i++) { - n -= view[i].size; - if (n < 0) { - return i; - } - } - } - function updateSelection(cm) { - cm.display.input.showSelection(cm.display.input.prepareSelection()); - } - function prepareSelection(cm, primary) { - var doc = cm.doc, - result = {}; - var curFragment = result.cursors = document.createDocumentFragment(); - var selFragment = result.selection = document.createDocumentFragment(); - for (var i = 0; i < doc.sel.ranges.length; i++) { - if (primary === false && i == doc.sel.primIndex) { - continue; - } - var range = doc.sel.ranges[i]; - if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { - continue; - } - var collapsed = range.empty(); - if (collapsed || cm.options.showCursorWhenSelecting) { - drawSelectionCursor(cm, range.head, curFragment); - } - if (!collapsed) { - drawSelectionRange(cm, range, selFragment); - } - } - return result; - } - - // Draws a cursor for the given range - function drawSelectionCursor(cm, head, output) { - var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); - var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); - cursor.style.left = pos.left + "px"; - cursor.style.top = pos.top + "px"; - cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - if (pos.other) { - // Secondary cursor, shown when on a 'jump' in bi-directional text - var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); - otherCursor.style.display = ""; - otherCursor.style.left = pos.other.left + "px"; - otherCursor.style.top = pos.other.top + "px"; - otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } - } - - // Draws the given range as a highlighted selection - function drawSelectionRange(cm, range, output) { - var display = cm.display, - doc = cm.doc; - var fragment = document.createDocumentFragment(); - var padding = paddingH(cm.display), - leftSide = padding.left; - var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; - function add(left, top, width, bottom) { - if (top < 0) { - top = 0; - } - top = Math.round(top); - bottom = Math.round(bottom); - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px")); - } - function drawForLine(line, fromArg, toArg) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length; - var start, end; - function coords(ch, bias) { - return charCoords(cm, Pos(line, ch), "div", lineObj, bias); - } - iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) { - var leftPos = coords(from, "left"), - rightPos, - left, - right; - if (from == to) { - rightPos = leftPos; - left = right = leftPos.left; - } else { - rightPos = coords(to - 1, "right"); - if (dir == "rtl") { - var tmp = leftPos; - leftPos = rightPos; - rightPos = tmp; - } - left = leftPos.left; - right = rightPos.right; - } - if (fromArg == null && from == 0) { - left = leftSide; - } - if (rightPos.top - leftPos.top > 3) { - // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = leftSide; - if (leftPos.bottom < rightPos.top) { - add(left, leftPos.bottom, null, rightPos.top); - } - } - if (toArg == null && to == lineLen) { - right = rightSide; - } - if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) { - start = leftPos; - } - if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) { - end = rightPos; - } - if (left < leftSide + 1) { - left = leftSide; - } - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return { - start: start, - end: end - }; - } - var sFrom = range.from(), - sTo = range.to(); - if (sFrom.line == sTo.line) { - drawForLine(sFrom.line, sFrom.ch, sTo.ch); - } else { - var fromLine = getLine(doc, sFrom.line), - toLine = getLine(doc, sTo.line); - var singleVLine = visualLine(fromLine) == visualLine(toLine); - var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; - var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; - if (singleVLine) { - if (leftEnd.top < rightStart.top - 2) { - add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); - add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); - } else { - add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); - } - } - if (leftEnd.bottom < rightStart.top) { - add(leftSide, leftEnd.bottom, null, rightStart.top); - } - } - output.appendChild(fragment); - } - - // Cursor-blinking - function restartBlink(cm) { - if (!cm.state.focused) { - return; - } - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursorDiv.style.visibility = ""; - if (cm.options.cursorBlinkRate > 0) { - display.blinker = setInterval(function () { - return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - } else if (cm.options.cursorBlinkRate < 0) { - display.cursorDiv.style.visibility = "hidden"; - } - } - function ensureFocus(cm) { - if (!cm.state.focused) { - cm.display.input.focus(); - onFocus(cm); - } - } - function delayBlurEvent(cm) { - cm.state.delayingBlurEvent = true; - setTimeout(function () { - if (cm.state.delayingBlurEvent) { - cm.state.delayingBlurEvent = false; - onBlur(cm); - } - }, 100); - } - function onFocus(cm, e) { - if (cm.state.delayingBlurEvent) { - cm.state.delayingBlurEvent = false; - } - if (cm.options.readOnly == "nocursor") { - return; - } - if (!cm.state.focused) { - signal(cm, "focus", cm, e); - cm.state.focused = true; - addClass(cm.display.wrapper, "CodeMirror-focused"); - // This test prevents this from firing when a context - // menu is closed (since the input reset would kill the - // select-all detection hack) - if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { - cm.display.input.reset(); - if (webkit) { - setTimeout(function () { - return cm.display.input.reset(true); - }, 20); - } // Issue #1730 - } - cm.display.input.receivedFocus(); - } - restartBlink(cm); - } - function onBlur(cm, e) { - if (cm.state.delayingBlurEvent) { - return; - } - if (cm.state.focused) { - signal(cm, "blur", cm, e); - cm.state.focused = false; - rmClass(cm.display.wrapper, "CodeMirror-focused"); - } - clearInterval(cm.display.blinker); - setTimeout(function () { - if (!cm.state.focused) { - cm.display.shift = false; - } - }, 150); - } - - // Read the actual heights of the rendered lines, and update their - // stored heights to match. - function updateHeightsInViewport(cm) { - var display = cm.display; - var prevBottom = display.lineDiv.offsetTop; - for (var i = 0; i < display.view.length; i++) { - var cur = display.view[i], - height = void 0; - if (cur.hidden) { - continue; - } - if (ie && ie_version < 8) { - var bot = cur.node.offsetTop + cur.node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = cur.node.getBoundingClientRect(); - height = box.bottom - box.top; - } - var diff = cur.line.height - height; - if (height < 2) { - height = textHeight(display); - } - if (diff > .005 || diff < -.005) { - updateLineHeight(cur.line, height); - updateWidgetHeight(cur.line); - if (cur.rest) { - for (var j = 0; j < cur.rest.length; j++) { - updateWidgetHeight(cur.rest[j]); - } - } - } - } - } - - // Read and store the height of line widgets associated with the - // given line. - function updateWidgetHeight(line) { - if (line.widgets) { - for (var i = 0; i < line.widgets.length; ++i) { - line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; - } - } - } - - // Compute the lines that are visible in a given viewport (defaults - // the the current scroll position). viewport may contain top, - // height, and ensure (see op.scrollToPos) properties. - function visibleLines(display, doc, viewport) { - var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; - top = Math.floor(top - paddingTop(display)); - var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; - var from = lineAtHeight(doc, top), - to = lineAtHeight(doc, bottom); - // Ensure is a {from: {line, ch}, to: {line, ch}} object, and - // forces those lines into the viewport (if possible). - if (viewport && viewport.ensure) { - var ensureFrom = viewport.ensure.from.line, - ensureTo = viewport.ensure.to.line; - if (ensureFrom < from) { - from = ensureFrom; - to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); - } else if (Math.min(ensureTo, doc.lastLine()) >= to) { - from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); - to = ensureTo; - } - } - return { - from: from, - to: Math.max(to, from + 1) - }; - } - - // Re-align line numbers and gutter marks to compensate for - // horizontal scrolling. - function alignHorizontally(cm) { - var display = cm.display, - view = display.view; - if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { - return; - } - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; - var gutterW = display.gutters.offsetWidth, - left = comp + "px"; - for (var i = 0; i < view.length; i++) { - if (!view[i].hidden) { - if (cm.options.fixedGutter) { - if (view[i].gutter) { - view[i].gutter.style.left = left; - } - if (view[i].gutterBackground) { - view[i].gutterBackground.style.left = left; - } - } - var align = view[i].alignable; - if (align) { - for (var j = 0; j < align.length; j++) { - align[j].style.left = left; - } - } - } - } - if (cm.options.fixedGutter) { - display.gutters.style.left = comp + gutterW + "px"; - } - } - - // Used to ensure that the line number gutter is still the right - // size for the current document size. Returns true when an update - // is needed. - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) { - return false; - } - var doc = cm.doc, - last = lineNumberFor(cm.options, doc.first + doc.size - 1), - display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, - padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - updateGutterSpace(cm); - return true; - } - return false; - } - - // SCROLLING THINGS INTO VIEW - - // If an editor sits on the top or bottom of the window, partially - // scrolled out of view, this ensures that the cursor is visible. - function maybeScrollWindow(cm, rect) { - if (signalDOMEvent(cm, "scrollCursorIntoView")) { - return; - } - var display = cm.display, - box = display.sizer.getBoundingClientRect(), - doScroll = null; - if (rect.top + box.top < 0) { - doScroll = true; - } else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { - doScroll = false; - } - if (doScroll != null && !phantom) { - var scrollNode = elt("div", "\u200b", null, "position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + rect.left + "px; width: " + Math.max(2, rect.right - rect.left) + "px;"); - cm.display.lineSpace.appendChild(scrollNode); - scrollNode.scrollIntoView(doScroll); - cm.display.lineSpace.removeChild(scrollNode); - } - } - - // Scroll a given position into view (immediately), verifying that - // it actually became visible (as line heights are accurately - // measured, the position of something may 'drift' during drawing). - function scrollPosIntoView(cm, pos, end, margin) { - if (margin == null) { - margin = 0; - } - var rect; - if (!cm.options.lineWrapping && pos == end) { - // Set pos and end to the cursor positions around the character pos sticks to - // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch - // If pos == Pos(_, 0, "before"), pos and end are unchanged - pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; - end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; - } - for (var limit = 0; limit < 5; limit++) { - var changed = false; - var coords = cursorCoords(cm, pos); - var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); - rect = { - left: Math.min(coords.left, endCoords.left), - top: Math.min(coords.top, endCoords.top) - margin, - right: Math.max(coords.left, endCoords.left), - bottom: Math.max(coords.bottom, endCoords.bottom) + margin - }; - var scrollPos = calculateScrollPos(cm, rect); - var startTop = cm.doc.scrollTop, - startLeft = cm.doc.scrollLeft; - if (scrollPos.scrollTop != null) { - updateScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.doc.scrollTop - startTop) > 1) { - changed = true; - } - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { - changed = true; - } - } - if (!changed) { - break; - } - } - return rect; - } - - // Scroll a given set of coordinates into view (immediately). - function scrollIntoView(cm, rect) { - var scrollPos = calculateScrollPos(cm, rect); - if (scrollPos.scrollTop != null) { - updateScrollTop(cm, scrollPos.scrollTop); - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - } - } - - // Calculate a new scroll position needed to scroll the given - // rectangle into view. Returns an object with scrollTop and - // scrollLeft properties. When these are undefined, the - // vertical/horizontal position does not need to be adjusted. - function calculateScrollPos(cm, rect) { - var display = cm.display, - snapMargin = textHeight(cm.display); - if (rect.top < 0) { - rect.top = 0; - } - var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; - var screen = displayHeight(cm), - result = {}; - if (rect.bottom - rect.top > screen) { - rect.bottom = rect.top + screen; - } - var docBottom = cm.doc.height + paddingVert(display); - var atTop = rect.top < snapMargin, - atBottom = rect.bottom > docBottom - snapMargin; - if (rect.top < screentop) { - result.scrollTop = atTop ? 0 : rect.top; - } else if (rect.bottom > screentop + screen) { - var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); - if (newTop != screentop) { - result.scrollTop = newTop; - } - } - var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); - var tooWide = rect.right - rect.left > screenw; - if (tooWide) { - rect.right = rect.left + screenw; - } - if (rect.left < 10) { - result.scrollLeft = 0; - } else if (rect.left < screenleft) { - result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); - } else if (rect.right > screenw + screenleft - 3) { - result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; - } - return result; - } - - // Store a relative adjustment to the scroll position in the current - // operation (to be applied when the operation finishes). - function addToScrollTop(cm, top) { - if (top == null) { - return; - } - resolveScrollToPos(cm); - cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; - } - - // Make sure that at the end of the operation the current cursor is - // shown. - function ensureCursorVisible(cm) { - resolveScrollToPos(cm); - var cur = cm.getCursor(); - cm.curOp.scrollToPos = { - from: cur, - to: cur, - margin: cm.options.cursorScrollMargin - }; - } - function scrollToCoords(cm, x, y) { - if (x != null || y != null) { - resolveScrollToPos(cm); - } - if (x != null) { - cm.curOp.scrollLeft = x; - } - if (y != null) { - cm.curOp.scrollTop = y; - } - } - function scrollToRange(cm, range) { - resolveScrollToPos(cm); - cm.curOp.scrollToPos = range; - } - - // When an operation has its scrollToPos property set, and another - // scroll action is applied before the end of the operation, this - // 'simulates' scrolling that position into view in a cheap way, so - // that the effect of intermediate scroll commands is not ignored. - function resolveScrollToPos(cm) { - var range = cm.curOp.scrollToPos; - if (range) { - cm.curOp.scrollToPos = null; - var from = estimateCoords(cm, range.from), - to = estimateCoords(cm, range.to); - scrollToCoordsRange(cm, from, to, range.margin); - } - } - function scrollToCoordsRange(cm, from, to, margin) { - var sPos = calculateScrollPos(cm, { - left: Math.min(from.left, to.left), - top: Math.min(from.top, to.top) - margin, - right: Math.max(from.right, to.right), - bottom: Math.max(from.bottom, to.bottom) + margin - }); - scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); - } - - // Sync the scrollable area and scrollbars, ensure the viewport - // covers the visible area. - function updateScrollTop(cm, val) { - if (Math.abs(cm.doc.scrollTop - val) < 2) { - return; - } - if (!gecko) { - updateDisplaySimple(cm, { - top: val - }); - } - setScrollTop(cm, val, true); - if (gecko) { - updateDisplaySimple(cm); - } - startWorker(cm, 100); - } - function setScrollTop(cm, val, forceScroll) { - val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val); - if (cm.display.scroller.scrollTop == val && !forceScroll) { - return; - } - cm.doc.scrollTop = val; - cm.display.scrollbars.setScrollTop(val); - if (cm.display.scroller.scrollTop != val) { - cm.display.scroller.scrollTop = val; - } - } - - // Sync scroller and scrollbar, ensure the gutter elements are - // aligned. - function setScrollLeft(cm, val, isScroller, forceScroll) { - val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); - if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { - return; - } - cm.doc.scrollLeft = val; - alignHorizontally(cm); - if (cm.display.scroller.scrollLeft != val) { - cm.display.scroller.scrollLeft = val; - } - cm.display.scrollbars.setScrollLeft(val); - } - - // SCROLLBARS - - // Prepare DOM reads needed to update the scrollbars. Done in one - // shot to minimize update/measure roundtrips. - function measureForScrollbars(cm) { - var d = cm.display, - gutterW = d.gutters.offsetWidth; - var docH = Math.round(cm.doc.height + paddingVert(cm.display)); - return { - clientHeight: d.scroller.clientHeight, - viewHeight: d.wrapper.clientHeight, - scrollWidth: d.scroller.scrollWidth, - clientWidth: d.scroller.clientWidth, - viewWidth: d.wrapper.clientWidth, - barLeft: cm.options.fixedGutter ? gutterW : 0, - docHeight: docH, - scrollHeight: docH + scrollGap(cm) + d.barHeight, - nativeBarWidth: d.nativeBarWidth, - gutterWidth: gutterW - }; - } - var NativeScrollbars = function (place, scroll, cm) { - this.cm = cm; - var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); - var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); - place(vert); - place(horiz); - on(vert, "scroll", function () { - if (vert.clientHeight) { - scroll(vert.scrollTop, "vertical"); - } - }); - on(horiz, "scroll", function () { - if (horiz.clientWidth) { - scroll(horiz.scrollLeft, "horizontal"); - } - }); - this.checkedZeroWidth = false; - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - if (ie && ie_version < 8) { - this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; - } - }; - NativeScrollbars.prototype.update = function (measure) { - var needsH = measure.scrollWidth > measure.clientWidth + 1; - var needsV = measure.scrollHeight > measure.clientHeight + 1; - var sWidth = measure.nativeBarWidth; - if (needsV) { - this.vert.style.display = "block"; - this.vert.style.bottom = needsH ? sWidth + "px" : "0"; - var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); - // A bug in IE8 can cause this value to be negative, so guard it. - this.vert.firstChild.style.height = Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; - } else { - this.vert.style.display = ""; - this.vert.firstChild.style.height = "0"; - } - if (needsH) { - this.horiz.style.display = "block"; - this.horiz.style.right = needsV ? sWidth + "px" : "0"; - this.horiz.style.left = measure.barLeft + "px"; - var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); - this.horiz.firstChild.style.width = Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; - } else { - this.horiz.style.display = ""; - this.horiz.firstChild.style.width = "0"; - } - if (!this.checkedZeroWidth && measure.clientHeight > 0) { - if (sWidth == 0) { - this.zeroWidthHack(); - } - this.checkedZeroWidth = true; - } - return { - right: needsV ? sWidth : 0, - bottom: needsH ? sWidth : 0 - }; - }; - NativeScrollbars.prototype.setScrollLeft = function (pos) { - if (this.horiz.scrollLeft != pos) { - this.horiz.scrollLeft = pos; - } - if (this.disableHoriz) { - this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); - } - }; - NativeScrollbars.prototype.setScrollTop = function (pos) { - if (this.vert.scrollTop != pos) { - this.vert.scrollTop = pos; - } - if (this.disableVert) { - this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); - } - }; - NativeScrollbars.prototype.zeroWidthHack = function () { - var w = mac && !mac_geMountainLion ? "12px" : "18px"; - this.horiz.style.height = this.vert.style.width = w; - this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; - this.disableHoriz = new Delayed(); - this.disableVert = new Delayed(); - }; - NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { - bar.style.pointerEvents = "auto"; - function maybeDisable() { - // To find out whether the scrollbar is still visible, we - // check whether the element under the pixel in the bottom - // right corner of the scrollbar box is the scrollbar box - // itself (when the bar is still visible) or its filler child - // (when the bar is hidden). If it is still visible, we keep - // it enabled, if it's hidden, we disable pointer events. - var box = bar.getBoundingClientRect(); - var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); - if (elt != bar) { - bar.style.pointerEvents = "none"; - } else { - delay.set(1000, maybeDisable); - } - } - delay.set(1000, maybeDisable); - }; - NativeScrollbars.prototype.clear = function () { - var parent = this.horiz.parentNode; - parent.removeChild(this.horiz); - parent.removeChild(this.vert); - }; - var NullScrollbars = function () {}; - NullScrollbars.prototype.update = function () { - return { - bottom: 0, - right: 0 - }; - }; - NullScrollbars.prototype.setScrollLeft = function () {}; - NullScrollbars.prototype.setScrollTop = function () {}; - NullScrollbars.prototype.clear = function () {}; - function updateScrollbars(cm, measure) { - if (!measure) { - measure = measureForScrollbars(cm); - } - var startWidth = cm.display.barWidth, - startHeight = cm.display.barHeight; - updateScrollbarsInner(cm, measure); - for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { - if (startWidth != cm.display.barWidth && cm.options.lineWrapping) { - updateHeightsInViewport(cm); - } - updateScrollbarsInner(cm, measureForScrollbars(cm)); - startWidth = cm.display.barWidth; - startHeight = cm.display.barHeight; - } - } - - // Re-synchronize the fake scrollbars with the actual size of the - // content. - function updateScrollbarsInner(cm, measure) { - var d = cm.display; - var sizes = d.scrollbars.update(measure); - d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; - d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; - d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; - if (sizes.right && sizes.bottom) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = sizes.bottom + "px"; - d.scrollbarFiller.style.width = sizes.right + "px"; - } else { - d.scrollbarFiller.style.display = ""; - } - if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { - d.gutterFiller.style.display = "block"; - d.gutterFiller.style.height = sizes.bottom + "px"; - d.gutterFiller.style.width = measure.gutterWidth + "px"; - } else { - d.gutterFiller.style.display = ""; - } - } - var scrollbarModel = { - "native": NativeScrollbars, - "null": NullScrollbars - }; - function initScrollbars(cm) { - if (cm.display.scrollbars) { - cm.display.scrollbars.clear(); - if (cm.display.scrollbars.addClass) { - rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - } - cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { - cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); - // Prevent clicks in the scrollbars from killing focus - on(node, "mousedown", function () { - if (cm.state.focused) { - setTimeout(function () { - return cm.display.input.focus(); - }, 0); - } - }); - node.setAttribute("cm-not-content", "true"); - }, function (pos, axis) { - if (axis == "horizontal") { - setScrollLeft(cm, pos); - } else { - updateScrollTop(cm, pos); - } - }, cm); - if (cm.display.scrollbars.addClass) { - addClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - } - - // Operations are used to wrap a series of changes to the editor - // state in such a way that each change won't have to update the - // cursor and display (which would be awkward, slow, and - // error-prone). Instead, display updates are batched and then all - // combined and executed at once. - - var nextOpId = 0; - // Start a new operation. - function startOperation(cm) { - cm.curOp = { - cm: cm, - viewChanged: false, - // Flag that indicates that lines might need to be redrawn - startHeight: cm.doc.height, - // Used to detect need to update scrollbar - forceUpdate: false, - // Used to force a redraw - updateInput: null, - // Whether to reset the input textarea - typing: false, - // Whether this reset should be careful to leave existing text (for compositing) - changeObjs: null, - // Accumulated changes, for firing change events - cursorActivityHandlers: null, - // Set of handlers to fire cursorActivity on - cursorActivityCalled: 0, - // Tracks which cursorActivity handlers have been called already - selectionChanged: false, - // Whether the selection needs to be redrawn - updateMaxLine: false, - // Set when the widest line needs to be determined anew - scrollLeft: null, - scrollTop: null, - // Intermediate scroll position, not pushed to DOM yet - scrollToPos: null, - // Used to scroll to a specific position - focus: false, - id: ++nextOpId // Unique ID - }; - pushOperation(cm.curOp); - } - - // Finish an operation, updating the display and signalling delayed events - function endOperation(cm) { - var op = cm.curOp; - finishOperation(op, function (group) { - for (var i = 0; i < group.ops.length; i++) { - group.ops[i].cm.curOp = null; - } - endOperations(group); - }); - } - - // The DOM updates done when an operation finishes are batched so - // that the minimum number of relayouts are required. - function endOperations(group) { - var ops = group.ops; - for (var i = 0; i < ops.length; i++) - // Read DOM - { - endOperation_R1(ops[i]); - } - for (var i$1 = 0; i$1 < ops.length; i$1++) - // Write DOM (maybe) - { - endOperation_W1(ops[i$1]); - } - for (var i$2 = 0; i$2 < ops.length; i$2++) - // Read DOM - { - endOperation_R2(ops[i$2]); - } - for (var i$3 = 0; i$3 < ops.length; i$3++) - // Write DOM (maybe) - { - endOperation_W2(ops[i$3]); - } - for (var i$4 = 0; i$4 < ops.length; i$4++) - // Read DOM - { - endOperation_finish(ops[i$4]); - } - } - function endOperation_R1(op) { - var cm = op.cm, - display = cm.display; - maybeClipScrollbars(cm); - if (op.updateMaxLine) { - findMaxLine(cm); - } - op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || op.scrollToPos.to.line >= display.viewTo) || display.maxLineChanged && cm.options.lineWrapping; - op.update = op.mustUpdate && new DisplayUpdate(cm, op.mustUpdate && { - top: op.scrollTop, - ensure: op.scrollToPos - }, op.forceUpdate); - } - function endOperation_W1(op) { - op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); - } - function endOperation_R2(op) { - var cm = op.cm, - display = cm.display; - if (op.updatedDisplay) { - updateHeightsInViewport(cm); - } - op.barMeasure = measureForScrollbars(cm); - - // If the max line changed since it was last measured, measure it, - // and ensure the document's width matches it. - // updateDisplay_W2 will use these properties to do the actual resizing - if (display.maxLineChanged && !cm.options.lineWrapping) { - op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; - cm.display.sizerWidth = op.adjustWidthTo; - op.barMeasure.scrollWidth = Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); - op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); - } - if (op.updatedDisplay || op.selectionChanged) { - op.preparedSelection = display.input.prepareSelection(op.focus); - } - } - function endOperation_W2(op) { - var cm = op.cm; - if (op.adjustWidthTo != null) { - cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; - if (op.maxScrollLeft < cm.doc.scrollLeft) { - setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); - } - cm.display.maxLineChanged = false; - } - var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()); - if (op.preparedSelection) { - cm.display.input.showSelection(op.preparedSelection, takeFocus); - } - if (op.updatedDisplay || op.startHeight != cm.doc.height) { - updateScrollbars(cm, op.barMeasure); - } - if (op.updatedDisplay) { - setDocumentHeight(cm, op.barMeasure); - } - if (op.selectionChanged) { - restartBlink(cm); - } - if (cm.state.focused && op.updateInput) { - cm.display.input.reset(op.typing); - } - if (takeFocus) { - ensureFocus(op.cm); - } - } - function endOperation_finish(op) { - var cm = op.cm, - display = cm.display, - doc = cm.doc; - if (op.updatedDisplay) { - postUpdateDisplay(cm, op.update); - } - - // Abort mouse wheel delta measurement, when scrolling explicitly - if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) { - display.wheelStartX = display.wheelStartY = null; - } - - // Propagate the scroll position to the actual DOM scroller - if (op.scrollTop != null) { - setScrollTop(cm, op.scrollTop, op.forceScroll); - } - if (op.scrollLeft != null) { - setScrollLeft(cm, op.scrollLeft, true, true); - } - // If we need to scroll a specific position into view, do so. - if (op.scrollToPos) { - var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); - maybeScrollWindow(cm, rect); - } - - // Fire events for markers that are hidden/unidden by editing or - // undoing - var hidden = op.maybeHiddenMarkers, - unhidden = op.maybeUnhiddenMarkers; - if (hidden) { - for (var i = 0; i < hidden.length; ++i) { - if (!hidden[i].lines.length) { - signal(hidden[i], "hide"); - } - } - } - if (unhidden) { - for (var i$1 = 0; i$1 < unhidden.length; ++i$1) { - if (unhidden[i$1].lines.length) { - signal(unhidden[i$1], "unhide"); - } - } - } - if (display.wrapper.offsetHeight) { - doc.scrollTop = cm.display.scroller.scrollTop; - } - - // Fire change events, and delayed event handlers - if (op.changeObjs) { - signal(cm, "changes", cm, op.changeObjs); - } - if (op.update) { - op.update.finish(); - } - } - - // Run the given function in an operation - function runInOp(cm, f) { - if (cm.curOp) { - return f(); - } - startOperation(cm); - try { - return f(); - } finally { - endOperation(cm); - } - } - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm, f) { - return function () { - if (cm.curOp) { - return f.apply(cm, arguments); - } - startOperation(cm); - try { - return f.apply(cm, arguments); - } finally { - endOperation(cm); - } - }; - } - // Used to add methods to editor and doc instances, wrapping them in - // operations. - function methodOp(f) { - return function () { - if (this.curOp) { - return f.apply(this, arguments); - } - startOperation(this); - try { - return f.apply(this, arguments); - } finally { - endOperation(this); - } - }; - } - function docMethodOp(f) { - return function () { - var cm = this.cm; - if (!cm || cm.curOp) { - return f.apply(this, arguments); - } - startOperation(cm); - try { - return f.apply(this, arguments); - } finally { - endOperation(cm); - } - }; - } - - // Updates the display.view data structure for a given change to the - // document. From and to are in pre-change coordinates. Lendiff is - // the amount of lines added or subtracted by the change. This is - // used for changes that span multiple lines, or change the way - // lines are divided into visual lines. regLineChange (below) - // registers single-line changes. - function regChange(cm, from, to, lendiff) { - if (from == null) { - from = cm.doc.first; - } - if (to == null) { - to = cm.doc.first + cm.doc.size; - } - if (!lendiff) { - lendiff = 0; - } - var display = cm.display; - if (lendiff && to < display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers > from)) { - display.updateLineNumbers = from; - } - cm.curOp.viewChanged = true; - if (from >= display.viewTo) { - // Change after - if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) { - resetView(cm); - } - } else if (to <= display.viewFrom) { - // Change before - if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { - resetView(cm); - } else { - display.viewFrom += lendiff; - display.viewTo += lendiff; - } - } else if (from <= display.viewFrom && to >= display.viewTo) { - // Full overlap - resetView(cm); - } else if (from <= display.viewFrom) { - // Top overlap - var cut = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cut) { - display.view = display.view.slice(cut.index); - display.viewFrom = cut.lineN; - display.viewTo += lendiff; - } else { - resetView(cm); - } - } else if (to >= display.viewTo) { - // Bottom overlap - var cut$1 = viewCuttingPoint(cm, from, from, -1); - if (cut$1) { - display.view = display.view.slice(0, cut$1.index); - display.viewTo = cut$1.lineN; - } else { - resetView(cm); - } - } else { - // Gap in the middle - var cutTop = viewCuttingPoint(cm, from, from, -1); - var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cutTop && cutBot) { - display.view = display.view.slice(0, cutTop.index).concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)).concat(display.view.slice(cutBot.index)); - display.viewTo += lendiff; - } else { - resetView(cm); - } - } - var ext = display.externalMeasured; - if (ext) { - if (to < ext.lineN) { - ext.lineN += lendiff; - } else if (from < ext.lineN + ext.size) { - display.externalMeasured = null; - } - } - } - - // Register a change to a single line. Type must be one of "text", - // "gutter", "class", "widget" - function regLineChange(cm, line, type) { - cm.curOp.viewChanged = true; - var display = cm.display, - ext = cm.display.externalMeasured; - if (ext && line >= ext.lineN && line < ext.lineN + ext.size) { - display.externalMeasured = null; - } - if (line < display.viewFrom || line >= display.viewTo) { - return; - } - var lineView = display.view[findViewIndex(cm, line)]; - if (lineView.node == null) { - return; - } - var arr = lineView.changes || (lineView.changes = []); - if (indexOf(arr, type) == -1) { - arr.push(type); - } - } - - // Clear the view. - function resetView(cm) { - cm.display.viewFrom = cm.display.viewTo = cm.doc.first; - cm.display.view = []; - cm.display.viewOffset = 0; - } - function viewCuttingPoint(cm, oldN, newN, dir) { - var index = findViewIndex(cm, oldN), - diff, - view = cm.display.view; - if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) { - return { - index: index, - lineN: newN - }; - } - var n = cm.display.viewFrom; - for (var i = 0; i < index; i++) { - n += view[i].size; - } - if (n != oldN) { - if (dir > 0) { - if (index == view.length - 1) { - return null; - } - diff = n + view[index].size - oldN; - index++; - } else { - diff = n - oldN; - } - oldN += diff; - newN += diff; - } - while (visualLineNo(cm.doc, newN) != newN) { - if (index == (dir < 0 ? 0 : view.length - 1)) { - return null; - } - newN += dir * view[index - (dir < 0 ? 1 : 0)].size; - index += dir; - } - return { - index: index, - lineN: newN - }; - } - - // Force the view to cover a given range, adding empty view element - // or clipping off existing ones as needed. - function adjustView(cm, from, to) { - var display = cm.display, - view = display.view; - if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { - display.view = buildViewArray(cm, from, to); - display.viewFrom = from; - } else { - if (display.viewFrom > from) { - display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); - } else if (display.viewFrom < from) { - display.view = display.view.slice(findViewIndex(cm, from)); - } - display.viewFrom = from; - if (display.viewTo < to) { - display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); - } else if (display.viewTo > to) { - display.view = display.view.slice(0, findViewIndex(cm, to)); - } - } - display.viewTo = to; - } - - // Count the number of lines in the view whose DOM representation is - // out of date (or nonexistent). - function countDirtyView(cm) { - var view = cm.display.view, - dirty = 0; - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (!lineView.hidden && (!lineView.node || lineView.changes)) { - ++dirty; - } - } - return dirty; - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.doc.highlightFrontier < cm.display.viewTo) { - cm.state.highlight.set(time, bind(highlightWorker, cm)); - } - } - function highlightWorker(cm) { - var doc = cm.doc; - if (doc.highlightFrontier >= cm.display.viewTo) { - return; - } - var end = +new Date() + cm.options.workTime; - var context = getContextBefore(cm, doc.highlightFrontier); - var changedLines = []; - doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { - if (context.line >= cm.display.viewFrom) { - // Visible - var oldStyles = line.styles; - var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; - var highlighted = highlightLine(cm, line, context, true); - if (resetState) { - context.state = resetState; - } - line.styles = highlighted.styles; - var oldCls = line.styleClasses, - newCls = highlighted.classes; - if (newCls) { - line.styleClasses = newCls; - } else if (oldCls) { - line.styleClasses = null; - } - var ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); - for (var i = 0; !ischange && i < oldStyles.length; ++i) { - ischange = oldStyles[i] != line.styles[i]; - } - if (ischange) { - changedLines.push(context.line); - } - line.stateAfter = context.save(); - context.nextLine(); - } else { - if (line.text.length <= cm.options.maxHighlightLength) { - processLine(cm, line.text, context); - } - line.stateAfter = context.line % 5 == 0 ? context.save() : null; - context.nextLine(); - } - if (+new Date() > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - doc.highlightFrontier = context.line; - doc.modeFrontier = Math.max(doc.modeFrontier, context.line); - if (changedLines.length) { - runInOp(cm, function () { - for (var i = 0; i < changedLines.length; i++) { - regLineChange(cm, changedLines[i], "text"); - } - }); - } - } - - // DISPLAY DRAWING - - var DisplayUpdate = function (cm, viewport, force) { - var display = cm.display; - this.viewport = viewport; - // Store some values that we'll need later (but don't want to force a relayout for) - this.visible = visibleLines(display, cm.doc, viewport); - this.editorIsHidden = !display.wrapper.offsetWidth; - this.wrapperHeight = display.wrapper.clientHeight; - this.wrapperWidth = display.wrapper.clientWidth; - this.oldDisplayWidth = displayWidth(cm); - this.force = force; - this.dims = getDimensions(cm); - this.events = []; - }; - DisplayUpdate.prototype.signal = function (emitter, type) { - if (hasHandler(emitter, type)) { - this.events.push(arguments); - } - }; - DisplayUpdate.prototype.finish = function () { - var this$1 = this; - for (var i = 0; i < this.events.length; i++) { - signal.apply(null, this$1.events[i]); - } - }; - function maybeClipScrollbars(cm) { - var display = cm.display; - if (!display.scrollbarsClipped && display.scroller.offsetWidth) { - display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; - display.heightForcer.style.height = scrollGap(cm) + "px"; - display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; - display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; - display.scrollbarsClipped = true; - } - } - function selectionSnapshot(cm) { - if (cm.hasFocus()) { - return null; - } - var active = activeElt(); - if (!active || !contains(cm.display.lineDiv, active)) { - return null; - } - var result = { - activeElt: active - }; - if (window.getSelection) { - var sel = window.getSelection(); - if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { - result.anchorNode = sel.anchorNode; - result.anchorOffset = sel.anchorOffset; - result.focusNode = sel.focusNode; - result.focusOffset = sel.focusOffset; - } - } - return result; - } - function restoreSelection(snapshot) { - if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { - return; - } - snapshot.activeElt.focus(); - if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { - var sel = window.getSelection(), - range = document.createRange(); - range.setEnd(snapshot.anchorNode, snapshot.anchorOffset); - range.collapse(false); - sel.removeAllRanges(); - sel.addRange(range); - sel.extend(snapshot.focusNode, snapshot.focusOffset); - } - } - - // Does the actual updating of the line display. Bails out - // (returning false) when there is nothing to be done and forced is - // false. - function updateDisplayIfNeeded(cm, update) { - var display = cm.display, - doc = cm.doc; - if (update.editorIsHidden) { - resetView(cm); - return false; - } - - // Bail out if the visible area is already rendered and nothing changed. - if (!update.force && update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && display.renderedView == display.view && countDirtyView(cm) == 0) { - return false; - } - if (maybeUpdateLineNumberWidth(cm)) { - resetView(cm); - update.dims = getDimensions(cm); - } - - // Compute a suitable new viewport (from & to) - var end = doc.first + doc.size; - var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, update.visible.to + cm.options.viewportMargin); - if (display.viewFrom < from && from - display.viewFrom < 20) { - from = Math.max(doc.first, display.viewFrom); - } - if (display.viewTo > to && display.viewTo - to < 20) { - to = Math.min(end, display.viewTo); - } - if (sawCollapsedSpans) { - from = visualLineNo(cm.doc, from); - to = visualLineEndNo(cm.doc, to); - } - var different = from != display.viewFrom || to != display.viewTo || display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; - adjustView(cm, from, to); - display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); - // Position the mover div to align with the current scroll position - cm.display.mover.style.top = display.viewOffset + "px"; - var toUpdate = countDirtyView(cm); - if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) { - return false; - } - - // For big changes, we hide the enclosing element during the - // update, since that speeds up the operations on most browsers. - var selSnapshot = selectionSnapshot(cm); - if (toUpdate > 4) { - display.lineDiv.style.display = "none"; - } - patchDisplay(cm, display.updateLineNumbers, update.dims); - if (toUpdate > 4) { - display.lineDiv.style.display = ""; - } - display.renderedView = display.view; - // There might have been a widget with a focused element that got - // hidden or updated, if so re-focus it. - restoreSelection(selSnapshot); - - // Prevent selection and cursors from interfering with the scroll - // width and height. - removeChildren(display.cursorDiv); - removeChildren(display.selectionDiv); - display.gutters.style.height = display.sizer.style.minHeight = 0; - if (different) { - display.lastWrapHeight = update.wrapperHeight; - display.lastWrapWidth = update.wrapperWidth; - startWorker(cm, 400); - } - display.updateLineNumbers = null; - return true; - } - function postUpdateDisplay(cm, update) { - var viewport = update.viewport; - for (var first = true;; first = false) { - if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { - // Clip forced viewport to actual scrollable area. - if (viewport && viewport.top != null) { - viewport = { - top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top) - }; - } - // Updated line heights might result in the drawn area not - // actually covering the viewport. Keep looping until it does. - update.visible = visibleLines(cm.display, cm.doc, viewport); - if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) { - break; - } - } - if (!updateDisplayIfNeeded(cm, update)) { - break; - } - updateHeightsInViewport(cm); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - updateScrollbars(cm, barMeasure); - setDocumentHeight(cm, barMeasure); - update.force = false; - } - update.signal(cm, "update", cm); - if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { - update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); - cm.display.reportedViewFrom = cm.display.viewFrom; - cm.display.reportedViewTo = cm.display.viewTo; - } - } - function updateDisplaySimple(cm, viewport) { - var update = new DisplayUpdate(cm, viewport); - if (updateDisplayIfNeeded(cm, update)) { - updateHeightsInViewport(cm); - postUpdateDisplay(cm, update); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - updateScrollbars(cm, barMeasure); - setDocumentHeight(cm, barMeasure); - update.finish(); - } - } - - // Sync the actual display DOM structure with display.view, removing - // nodes for lines that are no longer in view, and creating the ones - // that are not there yet, and updating the ones that are out of - // date. - function patchDisplay(cm, updateNumbersFrom, dims) { - var display = cm.display, - lineNumbers = cm.options.lineNumbers; - var container = display.lineDiv, - cur = container.firstChild; - function rm(node) { - var next = node.nextSibling; - // Works around a throw-scroll bug in OS X Webkit - if (webkit && mac && cm.display.currentWheelTarget == node) { - node.style.display = "none"; - } else { - node.parentNode.removeChild(node); - } - return next; - } - var view = display.view, - lineN = display.viewFrom; - // Loop over the elements in the view, syncing cur (the DOM nodes - // in display.lineDiv) with the view as we go. - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { - // Not drawn yet - var node = buildLineElement(cm, lineView, lineN, dims); - container.insertBefore(node, cur); - } else { - // Already drawn - while (cur != lineView.node) { - cur = rm(cur); - } - var updateNumber = lineNumbers && updateNumbersFrom != null && updateNumbersFrom <= lineN && lineView.lineNumber; - if (lineView.changes) { - if (indexOf(lineView.changes, "gutter") > -1) { - updateNumber = false; - } - updateLineForChanges(cm, lineView, lineN, dims); - } - if (updateNumber) { - removeChildren(lineView.lineNumber); - lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); - } - cur = lineView.node.nextSibling; - } - lineN += lineView.size; - } - while (cur) { - cur = rm(cur); - } - } - function updateGutterSpace(cm) { - var width = cm.display.gutters.offsetWidth; - cm.display.sizer.style.marginLeft = width + "px"; - } - function setDocumentHeight(cm, measure) { - cm.display.sizer.style.minHeight = measure.docHeight + "px"; - cm.display.heightForcer.style.top = measure.docHeight + "px"; - cm.display.gutters.style.height = measure.docHeight + cm.display.barHeight + scrollGap(cm) + "px"; - } - - // Rebuild the gutter elements, ensure the margin to the left of the - // code matches their width. - function updateGutters(cm) { - var gutters = cm.display.gutters, - specs = cm.options.gutters; - removeChildren(gutters); - var i = 0; - for (; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - updateGutterSpace(cm); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = indexOf(options.gutters, "CodeMirror-linenumbers"); - if (found == -1 && options.lineNumbers) { - options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); - } else if (found > -1 && !options.lineNumbers) { - options.gutters = options.gutters.slice(0); - options.gutters.splice(found, 1); - } - } - var wheelSamples = 0; - var wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) { - wheelPixelsPerUnit = -.53; - } else if (gecko) { - wheelPixelsPerUnit = 15; - } else if (chrome) { - wheelPixelsPerUnit = -.7; - } else if (safari) { - wheelPixelsPerUnit = -1 / 3; - } - function wheelEventDelta(e) { - var dx = e.wheelDeltaX, - dy = e.wheelDeltaY; - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { - dx = e.detail; - } - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { - dy = e.detail; - } else if (dy == null) { - dy = e.wheelDelta; - } - return { - x: dx, - y: dy - }; - } - function wheelEventPixels(e) { - var delta = wheelEventDelta(e); - delta.x *= wheelPixelsPerUnit; - delta.y *= wheelPixelsPerUnit; - return delta; - } - function onScrollWheel(cm, e) { - var delta = wheelEventDelta(e), - dx = delta.x, - dy = delta.y; - var display = cm.display, - scroll = display.scroller; - // Quit if there's nothing to scroll here - var canScrollX = scroll.scrollWidth > scroll.clientWidth; - var canScrollY = scroll.scrollHeight > scroll.clientHeight; - if (!(dx && canScrollX || dy && canScrollY)) { - return; - } - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { - for (var i = 0; i < view.length; i++) { - if (view[i].node == cur) { - cm.display.currentWheelTarget = cur; - break outer; - } - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { - if (dy && canScrollY) { - updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); - } - setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)); - // Only prevent default scrolling if vertical scrolling is - // actually possible. Otherwise, it causes vertical scroll - // jitter on OSX trackpads when deltaX is small and deltaY - // is large (issue #3579) - if (!dy || dy && canScrollY) { - e_preventDefault(e); - } - display.wheelStartX = null; // Abort measurement, if in progress - return; - } - - // 'Project' the visible viewport to cover the area that is being - // scrolled into view (if we know enough to estimate it). - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - var top = cm.doc.scrollTop, - bot = top + display.wrapper.clientHeight; - if (pixels < 0) { - top = Math.max(0, top + pixels - 50); - } else { - bot = Math.min(cm.doc.height, bot + pixels + 50); - } - updateDisplaySimple(cm, { - top: top, - bottom: bot - }); - } - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; - display.wheelStartY = scroll.scrollTop; - display.wheelDX = dx; - display.wheelDY = dy; - setTimeout(function () { - if (display.wheelStartX == null) { - return; - } - var movedX = scroll.scrollLeft - display.wheelStartX; - var movedY = scroll.scrollTop - display.wheelStartY; - var sample = movedY && display.wheelDY && movedY / display.wheelDY || movedX && display.wheelDX && movedX / display.wheelDX; - display.wheelStartX = display.wheelStartY = null; - if (!sample) { - return; - } - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - display.wheelDX += dx; - display.wheelDY += dy; - } - } - } - - // Selection objects are immutable. A new one is created every time - // the selection changes. A selection is one or more non-overlapping - // (and non-touching) ranges, sorted, and an integer that indicates - // which one is the primary selection (the one that's scrolled into - // view, that getCursor returns, etc). - var Selection = function (ranges, primIndex) { - this.ranges = ranges; - this.primIndex = primIndex; - }; - Selection.prototype.primary = function () { - return this.ranges[this.primIndex]; - }; - Selection.prototype.equals = function (other) { - var this$1 = this; - if (other == this) { - return true; - } - if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { - return false; - } - for (var i = 0; i < this.ranges.length; i++) { - var here = this$1.ranges[i], - there = other.ranges[i]; - if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { - return false; - } - } - return true; - }; - Selection.prototype.deepCopy = function () { - var this$1 = this; - var out = []; - for (var i = 0; i < this.ranges.length; i++) { - out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); - } - return new Selection(out, this.primIndex); - }; - Selection.prototype.somethingSelected = function () { - var this$1 = this; - for (var i = 0; i < this.ranges.length; i++) { - if (!this$1.ranges[i].empty()) { - return true; - } - } - return false; - }; - Selection.prototype.contains = function (pos, end) { - var this$1 = this; - if (!end) { - end = pos; - } - for (var i = 0; i < this.ranges.length; i++) { - var range = this$1.ranges[i]; - if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) { - return i; - } - } - return -1; - }; - var Range = function (anchor, head) { - this.anchor = anchor; - this.head = head; - }; - Range.prototype.from = function () { - return minPos(this.anchor, this.head); - }; - Range.prototype.to = function () { - return maxPos(this.anchor, this.head); - }; - Range.prototype.empty = function () { - return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; - }; - - // Take an unsorted, potentially overlapping set of ranges, and - // build a selection out of it. 'Consumes' ranges array (modifying - // it). - function normalizeSelection(ranges, primIndex) { - var prim = ranges[primIndex]; - ranges.sort(function (a, b) { - return cmp(a.from(), b.from()); - }); - primIndex = indexOf(ranges, prim); - for (var i = 1; i < ranges.length; i++) { - var cur = ranges[i], - prev = ranges[i - 1]; - if (cmp(prev.to(), cur.from()) >= 0) { - var from = minPos(prev.from(), cur.from()), - to = maxPos(prev.to(), cur.to()); - var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; - if (i <= primIndex) { - --primIndex; - } - ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); - } - } - return new Selection(ranges, primIndex); - } - function simpleSelection(anchor, head) { - return new Selection([new Range(anchor, head || anchor)], 0); - } - - // Compute the position of the end of a change (its 'to' property - // refers to the pre-change end). - function changeEnd(change) { - if (!change.text) { - return change.to; - } - return Pos(change.from.line + change.text.length - 1, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - } - - // Adjust a position to refer to the post-change position of the - // same text, or the end of the change if the change covers it. - function adjustForChange(pos, change) { - if (cmp(pos, change.from) < 0) { - return pos; - } - if (cmp(pos, change.to) <= 0) { - return changeEnd(change); - } - var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, - ch = pos.ch; - if (pos.line == change.to.line) { - ch += changeEnd(change).ch - change.to.ch; - } - return Pos(line, ch); - } - function computeSelAfterChange(doc, change) { - var out = []; - for (var i = 0; i < doc.sel.ranges.length; i++) { - var range = doc.sel.ranges[i]; - out.push(new Range(adjustForChange(range.anchor, change), adjustForChange(range.head, change))); - } - return normalizeSelection(out, doc.sel.primIndex); - } - function offsetPos(pos, old, nw) { - if (pos.line == old.line) { - return Pos(nw.line, pos.ch - old.ch + nw.ch); - } else { - return Pos(nw.line + (pos.line - old.line), pos.ch); - } - } - - // Used by replaceSelections to allow moving the selection to the - // start or around the replaced test. Hint may be "start" or "around". - function computeReplacedSel(doc, changes, hint) { - var out = []; - var oldPrev = Pos(doc.first, 0), - newPrev = oldPrev; - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var from = offsetPos(change.from, oldPrev, newPrev); - var to = offsetPos(changeEnd(change), oldPrev, newPrev); - oldPrev = change.to; - newPrev = to; - if (hint == "around") { - var range = doc.sel.ranges[i], - inv = cmp(range.head, range.anchor) < 0; - out[i] = new Range(inv ? to : from, inv ? from : to); - } else { - out[i] = new Range(from, from); - } - } - return new Selection(out, doc.sel.primIndex); - } - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - cm.doc.mode = getMode(cm.options, cm.doc.modeOption); - resetModeState(cm); - } - function resetModeState(cm) { - cm.doc.iter(function (line) { - if (line.stateAfter) { - line.stateAfter = null; - } - if (line.styles) { - line.styles = null; - } - }); - cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; - startWorker(cm, 100); - cm.state.modeGen++; - if (cm.curOp) { - regChange(cm); - } - } - - // DOCUMENT DATA STRUCTURE - - // By default, updates that start and end at the beginning of a line - // are treated specially, in order to make the association of line - // widgets and marker elements with the text behave more intuitive. - function isWholeLineUpdate(doc, change) { - return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && (!doc.cm || doc.cm.options.wholeLineUpdateBefore); - } - - // Perform a change on the document data structure. - function updateDoc(doc, change, markedSpans, estimateHeight) { - function spansFor(n) { - return markedSpans ? markedSpans[n] : null; - } - function update(line, text, spans) { - updateLine(line, text, spans, estimateHeight); - signalLater(line, "change", line, change); - } - function linesFor(start, end) { - var result = []; - for (var i = start; i < end; ++i) { - result.push(new Line(text[i], spansFor(i), estimateHeight)); - } - return result; - } - var from = change.from, - to = change.to, - text = change.text; - var firstLine = getLine(doc, from.line), - lastLine = getLine(doc, to.line); - var lastText = lst(text), - lastSpans = spansFor(text.length - 1), - nlines = to.line - from.line; - - // Adjust the line structure - if (change.full) { - doc.insert(0, linesFor(0, text.length)); - doc.remove(text.length, doc.size - text.length); - } else if (isWholeLineUpdate(doc, change)) { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - var added = linesFor(0, text.length - 1); - update(lastLine, lastLine.text, lastSpans); - if (nlines) { - doc.remove(from.line, nlines); - } - if (added.length) { - doc.insert(from.line, added); - } - } else if (firstLine == lastLine) { - if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); - } else { - var added$1 = linesFor(1, text.length - 1); - added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - doc.insert(from.line + 1, added$1); - } - } else if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); - doc.remove(from.line + 1, nlines); - } else { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); - var added$2 = linesFor(1, text.length - 1); - if (nlines > 1) { - doc.remove(from.line + 1, nlines - 1); - } - doc.insert(from.line + 1, added$2); - } - signalLater(doc, "change", doc, change); - } - - // Call f for all linked documents. - function linkedDocs(doc, f, sharedHistOnly) { - function propagate(doc, skip, sharedHist) { - if (doc.linked) { - for (var i = 0; i < doc.linked.length; ++i) { - var rel = doc.linked[i]; - if (rel.doc == skip) { - continue; - } - var shared = sharedHist && rel.sharedHist; - if (sharedHistOnly && !shared) { - continue; - } - f(rel.doc, shared); - propagate(rel.doc, doc, shared); - } - } - } - propagate(doc, null, true); - } - - // Attach a document to an editor. - function attachDoc(cm, doc) { - if (doc.cm) { - throw new Error("This document is already in use."); - } - cm.doc = doc; - doc.cm = cm; - estimateLineHeights(cm); - loadMode(cm); - setDirectionClass(cm); - if (!cm.options.lineWrapping) { - findMaxLine(cm); - } - cm.options.mode = doc.modeOption; - regChange(cm); - } - function setDirectionClass(cm) { - (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); - } - function directionChanged(cm) { - runInOp(cm, function () { - setDirectionClass(cm); - regChange(cm); - }); - } - function History(startGen) { - // Arrays of change events and selections. Doing something adds an - // event to done and clears undo. Undoing moves events from done - // to undone, redoing moves them in the other direction. - this.done = []; - this.undone = []; - this.undoDepth = Infinity; - // Used to track when changes can be merged into a single undo - // event - this.lastModTime = this.lastSelTime = 0; - this.lastOp = this.lastSelOp = null; - this.lastOrigin = this.lastSelOrigin = null; - // Used by the isClean() method - this.generation = this.maxGeneration = startGen || 1; - } - - // Create a history change event from an updateDoc-style change - // object. - function historyChangeFromChange(doc, change) { - var histChange = { - from: copyPos(change.from), - to: changeEnd(change), - text: getBetween(doc, change.from, change.to) - }; - attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - linkedDocs(doc, function (doc) { - return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - }, true); - return histChange; - } - - // Pop all selection events off the end of a history array. Stop at - // a change event. - function clearSelectionEvents(array) { - while (array.length) { - var last = lst(array); - if (last.ranges) { - array.pop(); - } else { - break; - } - } - } - - // Find the top change event in the history. Pop off selection - // events that are in the way. - function lastChangeEvent(hist, force) { - if (force) { - clearSelectionEvents(hist.done); - return lst(hist.done); - } else if (hist.done.length && !lst(hist.done).ranges) { - return lst(hist.done); - } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { - hist.done.pop(); - return lst(hist.done); - } - } - - // Register a change in the history. Merges changes that are within - // a single operation, or are close together with an origin that - // allows merging (starting with "+") into a single event. - function addChangeToHistory(doc, change, selAfter, opId) { - var hist = doc.history; - hist.undone.length = 0; - var time = +new Date(), - cur; - var last; - if ((hist.lastOp == opId || hist.lastOrigin == change.origin && change.origin && (change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay || change.origin.charAt(0) == "*")) && (cur = lastChangeEvent(hist, hist.lastOp == opId))) { - // Merge this change into the last event - last = lst(cur.changes); - if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { - // Optimized case for simple insertion -- don't want to add - // new changesets for every character typed - last.to = changeEnd(change); - } else { - // Add new sub-event - cur.changes.push(historyChangeFromChange(doc, change)); - } - } else { - // Can not be merged, start a new event. - var before = lst(hist.done); - if (!before || !before.ranges) { - pushSelectionToHistory(doc.sel, hist.done); - } - cur = { - changes: [historyChangeFromChange(doc, change)], - generation: hist.generation - }; - hist.done.push(cur); - while (hist.done.length > hist.undoDepth) { - hist.done.shift(); - if (!hist.done[0].ranges) { - hist.done.shift(); - } - } - } - hist.done.push(selAfter); - hist.generation = ++hist.maxGeneration; - hist.lastModTime = hist.lastSelTime = time; - hist.lastOp = hist.lastSelOp = opId; - hist.lastOrigin = hist.lastSelOrigin = change.origin; - if (!last) { - signal(doc, "historyAdded"); - } - } - function selectionEventCanBeMerged(doc, origin, prev, sel) { - var ch = origin.charAt(0); - return ch == "*" || ch == "+" && prev.ranges.length == sel.ranges.length && prev.somethingSelected() == sel.somethingSelected() && new Date() - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); - } - - // Called whenever the selection changes, sets the new selection as - // the pending selection in the history, and pushes the old pending - // selection into the 'done' array when it was significantly - // different (in number of selected ranges, emptiness, or time). - function addSelectionToHistory(doc, sel, opId, options) { - var hist = doc.history, - origin = options && options.origin; - - // A new event is started when the previous origin does not match - // the current, or the origins don't allow matching. Origins - // starting with * are always merged, those starting with + are - // merged when similar and close together in time. - if (opId == hist.lastSelOp || origin && hist.lastSelOrigin == origin && (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))) { - hist.done[hist.done.length - 1] = sel; - } else { - pushSelectionToHistory(sel, hist.done); - } - hist.lastSelTime = +new Date(); - hist.lastSelOrigin = origin; - hist.lastSelOp = opId; - if (options && options.clearRedo !== false) { - clearSelectionEvents(hist.undone); - } - } - function pushSelectionToHistory(sel, dest) { - var top = lst(dest); - if (!(top && top.ranges && top.equals(sel))) { - dest.push(sel); - } - } - - // Used to store marked span information in the history. - function attachLocalSpans(doc, change, from, to) { - var existing = change["spans_" + doc.id], - n = 0; - doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { - if (line.markedSpans) { - (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; - } - ++n; - }); - } - - // When un/re-doing restores text containing marked spans, those - // that have been explicitly cleared should not be restored. - function removeClearedSpans(spans) { - if (!spans) { - return null; - } - var out; - for (var i = 0; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { - if (!out) { - out = spans.slice(0, i); - } - } else if (out) { - out.push(spans[i]); - } - } - return !out ? spans : out.length ? out : null; - } - - // Retrieve and filter the old marked spans stored in a change event. - function getOldSpans(doc, change) { - var found = change["spans_" + doc.id]; - if (!found) { - return null; - } - var nw = []; - for (var i = 0; i < change.text.length; ++i) { - nw.push(removeClearedSpans(found[i])); - } - return nw; - } - - // Used for un/re-doing changes from the history. Combines the - // result of computing the existing spans with the set of spans that - // existed in the history (so that deleting around a span and then - // undoing brings back the span). - function mergeOldSpans(doc, change) { - var old = getOldSpans(doc, change); - var stretched = stretchSpansOverChange(doc, change); - if (!old) { - return stretched; - } - if (!stretched) { - return old; - } - for (var i = 0; i < old.length; ++i) { - var oldCur = old[i], - stretchCur = stretched[i]; - if (oldCur && stretchCur) { - spans: for (var j = 0; j < stretchCur.length; ++j) { - var span = stretchCur[j]; - for (var k = 0; k < oldCur.length; ++k) { - if (oldCur[k].marker == span.marker) { - continue spans; - } - } - oldCur.push(span); - } - } else if (stretchCur) { - old[i] = stretchCur; - } - } - return old; - } - - // Used both to provide a JSON-safe object in .getHistory, and, when - // detaching a document, to split the history in two - function copyHistoryArray(events, newGroup, instantiateSel) { - var copy = []; - for (var i = 0; i < events.length; ++i) { - var event = events[i]; - if (event.ranges) { - copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); - continue; - } - var changes = event.changes, - newChanges = []; - copy.push({ - changes: newChanges - }); - for (var j = 0; j < changes.length; ++j) { - var change = changes[j], - m = void 0; - newChanges.push({ - from: change.from, - to: change.to, - text: change.text - }); - if (newGroup) { - for (var prop in change) { - if (m = prop.match(/^spans_(\d+)$/)) { - if (indexOf(newGroup, Number(m[1])) > -1) { - lst(newChanges)[prop] = change[prop]; - delete change[prop]; - } - } - } - } - } - } - return copy; - } - - // The 'scroll' parameter given to many of these indicated whether - // the new cursor position should be scrolled into view after - // modifying the selection. - - // If shift is held or the extend flag is set, extends a range to - // include a given position (and optionally a second position). - // Otherwise, simply returns the range between the given positions. - // Used for cursor motion and such. - function extendRange(range, head, other, extend) { - if (extend) { - var anchor = range.anchor; - if (other) { - var posBefore = cmp(head, anchor) < 0; - if (posBefore != cmp(other, anchor) < 0) { - anchor = head; - head = other; - } else if (posBefore != cmp(head, other) < 0) { - head = other; - } - } - return new Range(anchor, head); - } else { - return new Range(other || head, head); - } - } - - // Extend the primary selection range, discard the rest. - function extendSelection(doc, head, other, options, extend) { - if (extend == null) { - extend = doc.cm && (doc.cm.display.shift || doc.extend); - } - setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); - } - - // Extend all selections (pos is an array of selections with length - // equal the number of selections) - function extendSelections(doc, heads, options) { - var out = []; - var extend = doc.cm && (doc.cm.display.shift || doc.extend); - for (var i = 0; i < doc.sel.ranges.length; i++) { - out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); - } - var newSel = normalizeSelection(out, doc.sel.primIndex); - setSelection(doc, newSel, options); - } - - // Updates a single range in the selection. - function replaceOneSelection(doc, i, range, options) { - var ranges = doc.sel.ranges.slice(0); - ranges[i] = range; - setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); - } - - // Reset the selection to a single range. - function setSimpleSelection(doc, anchor, head, options) { - setSelection(doc, simpleSelection(anchor, head), options); - } - - // Give beforeSelectionChange handlers a change to influence a - // selection update. - function filterSelectionChange(doc, sel, options) { - var obj = { - ranges: sel.ranges, - update: function (ranges) { - var this$1 = this; - this.ranges = []; - for (var i = 0; i < ranges.length; i++) { - this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), clipPos(doc, ranges[i].head)); - } - }, - origin: options && options.origin - }; - signal(doc, "beforeSelectionChange", doc, obj); - if (doc.cm) { - signal(doc.cm, "beforeSelectionChange", doc.cm, obj); - } - if (obj.ranges != sel.ranges) { - return normalizeSelection(obj.ranges, obj.ranges.length - 1); - } else { - return sel; - } - } - function setSelectionReplaceHistory(doc, sel, options) { - var done = doc.history.done, - last = lst(done); - if (last && last.ranges) { - done[done.length - 1] = sel; - setSelectionNoUndo(doc, sel, options); - } else { - setSelection(doc, sel, options); - } - } - - // Set a new selection. - function setSelection(doc, sel, options) { - setSelectionNoUndo(doc, sel, options); - addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); - } - function setSelectionNoUndo(doc, sel, options) { - if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { - sel = filterSelectionChange(doc, sel, options); - } - var bias = options && options.bias || (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); - setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); - if (!(options && options.scroll === false) && doc.cm) { - ensureCursorVisible(doc.cm); - } - } - function setSelectionInner(doc, sel) { - if (sel.equals(doc.sel)) { - return; - } - doc.sel = sel; - if (doc.cm) { - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; - signalCursorActivity(doc.cm); - } - signalLater(doc, "cursorActivity", doc); - } - - // Verify that the selection does not partially select any atomic - // marked ranges. - function reCheckSelection(doc) { - setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); - } - - // Return a selection that does not partially select any atomic - // ranges. - function skipAtomicInSelection(doc, sel, bias, mayClear) { - var out; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; - var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); - var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); - if (out || newAnchor != range.anchor || newHead != range.head) { - if (!out) { - out = sel.ranges.slice(0, i); - } - out[i] = new Range(newAnchor, newHead); - } - } - return out ? normalizeSelection(out, sel.primIndex) : sel; - } - function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { - var line = getLine(doc, pos.line); - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], - m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) { - break; - } else { - --i; - continue; - } - } - } - if (!m.atomic) { - continue; - } - if (oldPos) { - var near = m.find(dir < 0 ? 1 : -1), - diff = void 0; - if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) { - near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); - } - if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) { - return skipAtomicInner(doc, near, pos, dir, mayClear); - } - } - var far = m.find(dir < 0 ? -1 : 1); - if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) { - far = movePos(doc, far, dir, far.line == pos.line ? line : null); - } - return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; - } - } - } - return pos; - } - - // Ensure a given position is not inside an atomic range. - function skipAtomic(doc, pos, oldPos, bias, mayClear) { - var dir = bias || 1; - var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || !mayClear && skipAtomicInner(doc, pos, oldPos, dir, true) || skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || !mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true); - if (!found) { - doc.cantEdit = true; - return Pos(doc.first, 0); - } - return found; - } - function movePos(doc, pos, dir, line) { - if (dir < 0 && pos.ch == 0) { - if (pos.line > doc.first) { - return clipPos(doc, Pos(pos.line - 1)); - } else { - return null; - } - } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { - if (pos.line < doc.first + doc.size - 1) { - return Pos(pos.line + 1, 0); - } else { - return null; - } - } else { - return new Pos(pos.line, pos.ch + dir); - } - } - function selectAll(cm) { - cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); - } - - // UPDATING - - // Allow "beforeChange" event handlers to influence a change - function filterChange(doc, change, update) { - var obj = { - canceled: false, - from: change.from, - to: change.to, - text: change.text, - origin: change.origin, - cancel: function () { - return obj.canceled = true; - } - }; - if (update) { - obj.update = function (from, to, text, origin) { - if (from) { - obj.from = clipPos(doc, from); - } - if (to) { - obj.to = clipPos(doc, to); - } - if (text) { - obj.text = text; - } - if (origin !== undefined) { - obj.origin = origin; - } - }; - } - signal(doc, "beforeChange", doc, obj); - if (doc.cm) { - signal(doc.cm, "beforeChange", doc.cm, obj); - } - if (obj.canceled) { - return null; - } - return { - from: obj.from, - to: obj.to, - text: obj.text, - origin: obj.origin - }; - } - - // Apply a change to a document, and add it to the document's - // history, and propagating it to all linked documents. - function makeChange(doc, change, ignoreReadOnly) { - if (doc.cm) { - if (!doc.cm.curOp) { - return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); - } - if (doc.cm.state.suppressEdits) { - return; - } - } - if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change, true); - if (!change) { - return; - } - } - - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); - if (split) { - for (var i = split.length - 1; i >= 0; --i) { - makeChangeInner(doc, { - from: split[i].from, - to: split[i].to, - text: i ? [""] : change.text - }); - } - } else { - makeChangeInner(doc, change); - } - } - function makeChangeInner(doc, change) { - if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { - return; - } - var selAfter = computeSelAfterChange(doc, change); - addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); - makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); - var rebased = []; - linkedDocs(doc, function (doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); - }); - } - - // Revert a change stored in a document's history. - function makeChangeFromHistory(doc, type, allowSelectionOnly) { - if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { - return; - } - var hist = doc.history, - event, - selAfter = doc.sel; - var source = type == "undo" ? hist.done : hist.undone, - dest = type == "undo" ? hist.undone : hist.done; - - // Verify that there is a useable event (so that ctrl-z won't - // needlessly clear selection events) - var i = 0; - for (; i < source.length; i++) { - event = source[i]; - if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) { - break; - } - } - if (i == source.length) { - return; - } - hist.lastOrigin = hist.lastSelOrigin = null; - for (;;) { - event = source.pop(); - if (event.ranges) { - pushSelectionToHistory(event, dest); - if (allowSelectionOnly && !event.equals(doc.sel)) { - setSelection(doc, event, { - clearRedo: false - }); - return; - } - selAfter = event; - } else { - break; - } - } - - // Build up a reverse change object to add to the opposite history - // stack (redo when undoing, and vice versa). - var antiChanges = []; - pushSelectionToHistory(selAfter, dest); - dest.push({ - changes: antiChanges, - generation: hist.generation - }); - hist.generation = event.generation || ++hist.maxGeneration; - var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); - var loop = function (i) { - var change = event.changes[i]; - change.origin = type; - if (filter && !filterChange(doc, change, false)) { - source.length = 0; - return {}; - } - antiChanges.push(historyChangeFromChange(doc, change)); - var after = i ? computeSelAfterChange(doc, change) : lst(source); - makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - if (!i && doc.cm) { - doc.cm.scrollIntoView({ - from: change.from, - to: changeEnd(change) - }); - } - var rebased = []; - - // Propagate to the linked documents - linkedDocs(doc, function (doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); - }); - }; - for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { - var returned = loop(i$1); - if (returned) return returned.v; - } - } - - // Sub-views need their line numbers shifted when text is added - // above or below them in the parent document. - function shiftDoc(doc, distance) { - if (distance == 0) { - return; - } - doc.first += distance; - doc.sel = new Selection(map(doc.sel.ranges, function (range) { - return new Range(Pos(range.anchor.line + distance, range.anchor.ch), Pos(range.head.line + distance, range.head.ch)); - }), doc.sel.primIndex); - if (doc.cm) { - regChange(doc.cm, doc.first, doc.first - distance, distance); - for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) { - regLineChange(doc.cm, l, "gutter"); - } - } - } - - // More lower-level change function, handling only a single document - // (not linked ones). - function makeChangeSingleDoc(doc, change, selAfter, spans) { - if (doc.cm && !doc.cm.curOp) { - return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); - } - if (change.to.line < doc.first) { - shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); - return; - } - if (change.from.line > doc.lastLine()) { - return; - } - - // Clip the change to the size of this doc - if (change.from.line < doc.first) { - var shift = change.text.length - 1 - (doc.first - change.from.line); - shiftDoc(doc, shift); - change = { - from: Pos(doc.first, 0), - to: Pos(change.to.line + shift, change.to.ch), - text: [lst(change.text)], - origin: change.origin - }; - } - var last = doc.lastLine(); - if (change.to.line > last) { - change = { - from: change.from, - to: Pos(last, getLine(doc, last).text.length), - text: [change.text[0]], - origin: change.origin - }; - } - change.removed = getBetween(doc, change.from, change.to); - if (!selAfter) { - selAfter = computeSelAfterChange(doc, change); - } - if (doc.cm) { - makeChangeSingleDocInEditor(doc.cm, change, spans); - } else { - updateDoc(doc, change, spans); - } - setSelectionNoUndo(doc, selAfter, sel_dontScroll); - } - - // Handle the interaction of a change to a document with the editor - // that this document is part of. - function makeChangeSingleDocInEditor(cm, change, spans) { - var doc = cm.doc, - display = cm.display, - from = change.from, - to = change.to; - var recomputeMaxLength = false, - checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); - doc.iter(checkWidthStart, to.line + 1, function (line) { - if (line == display.maxLine) { - recomputeMaxLength = true; - return true; - } - }); - } - if (doc.sel.contains(change.from, change.to) > -1) { - signalCursorActivity(cm); - } - updateDoc(doc, change, spans, estimateHeight(cm)); - if (!cm.options.lineWrapping) { - doc.iter(checkWidthStart, from.line + change.text.length, function (line) { - var len = lineLength(line); - if (len > display.maxLineLength) { - display.maxLine = line; - display.maxLineLength = len; - display.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) { - cm.curOp.updateMaxLine = true; - } - } - retreatFrontier(doc, from.line); - startWorker(cm, 400); - var lendiff = change.text.length - (to.line - from.line) - 1; - // Remember that these lines changed, for updating the display - if (change.full) { - regChange(cm); - } else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) { - regLineChange(cm, from.line, "text"); - } else { - regChange(cm, from.line, to.line + 1, lendiff); - } - var changesHandler = hasHandler(cm, "changes"), - changeHandler = hasHandler(cm, "change"); - if (changeHandler || changesHandler) { - var obj = { - from: from, - to: to, - text: change.text, - removed: change.removed, - origin: change.origin - }; - if (changeHandler) { - signalLater(cm, "change", cm, obj); - } - if (changesHandler) { - (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); - } - } - cm.display.selForContextMenu = null; - } - function replaceRange(doc, code, from, to, origin) { - if (!to) { - to = from; - } - if (cmp(to, from) < 0) { - var tmp = to; - to = from; - from = tmp; - } - if (typeof code == "string") { - code = doc.splitLines(code); - } - makeChange(doc, { - from: from, - to: to, - text: code, - origin: origin - }); - } - - // Rebasing/resetting history to deal with externally-sourced changes - - function rebaseHistSelSingle(pos, from, to, diff) { - if (to < pos.line) { - pos.line += diff; - } else if (from < pos.line) { - pos.line = from; - pos.ch = 0; - } - } - - // Tries to rebase an array of history events given a change in the - // document. If the change touches the same lines as the event, the - // event, and everything 'behind' it, is discarded. If the change is - // before the event, the event's positions are updated. Uses a - // copy-on-write scheme for the positions, to avoid having to - // reallocate them all on every rebase, but also avoid problems with - // shared position objects being unsafely updated. - function rebaseHistArray(array, from, to, diff) { - for (var i = 0; i < array.length; ++i) { - var sub = array[i], - ok = true; - if (sub.ranges) { - if (!sub.copied) { - sub = array[i] = sub.deepCopy(); - sub.copied = true; - } - for (var j = 0; j < sub.ranges.length; j++) { - rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); - rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); - } - continue; - } - for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { - var cur = sub.changes[j$1]; - if (to < cur.from.line) { - cur.from = Pos(cur.from.line + diff, cur.from.ch); - cur.to = Pos(cur.to.line + diff, cur.to.ch); - } else if (from <= cur.to.line) { - ok = false; - break; - } - } - if (!ok) { - array.splice(0, i + 1); - i = 0; - } - } - } - function rebaseHist(hist, change) { - var from = change.from.line, - to = change.to.line, - diff = change.text.length - (to - from) - 1; - rebaseHistArray(hist.done, from, to, diff); - rebaseHistArray(hist.undone, from, to, diff); - } - - // Utility for applying a change to a line by handle or number, - // returning the number and optionally registering the line as - // changed. - function changeLine(doc, handle, changeType, op) { - var no = handle, - line = handle; - if (typeof handle == "number") { - line = getLine(doc, clipLine(doc, handle)); - } else { - no = lineNo(handle); - } - if (no == null) { - return null; - } - if (op(line, no) && doc.cm) { - regLineChange(doc.cm, no, changeType); - } - return line; - } - - // The document is represented as a BTree consisting of leaves, with - // chunk of lines in them, and branches, with up to ten leaves or - // other branch nodes below them. The top node is always a branch - // node, and is the document object itself (meaning it has - // additional methods and properties). - // - // All nodes have parent links. The tree is used both to go from - // line numbers to line objects, and to go from objects to numbers. - // It also indexes by height, and is used to convert between height - // and line object, and to find the total height of the document. - // - // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html - - function LeafChunk(lines) { - var this$1 = this; - this.lines = lines; - this.parent = null; - var height = 0; - for (var i = 0; i < lines.length; ++i) { - lines[i].parent = this$1; - height += lines[i].height; - } - this.height = height; - } - LeafChunk.prototype = { - chunkSize: function chunkSize() { - return this.lines.length; - }, - // Remove the n lines at offset 'at'. - removeInner: function removeInner(at, n) { - var this$1 = this; - for (var i = at, e = at + n; i < e; ++i) { - var line = this$1.lines[i]; - this$1.height -= line.height; - cleanUpLine(line); - signalLater(line, "delete"); - } - this.lines.splice(at, n); - }, - // Helper used to collapse a small branch into a single leaf. - collapse: function collapse(lines) { - lines.push.apply(lines, this.lines); - }, - // Insert the given array of lines at offset 'at', count them as - // having the given height. - insertInner: function insertInner(at, lines, height) { - var this$1 = this; - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0; i < lines.length; ++i) { - lines[i].parent = this$1; - } - }, - // Used to iterate over a part of the tree. - iterN: function iterN(at, n, op) { - var this$1 = this; - for (var e = at + n; at < e; ++at) { - if (op(this$1.lines[at])) { - return true; - } - } - } - }; - function BranchChunk(children) { - var this$1 = this; - this.children = children; - var size = 0, - height = 0; - for (var i = 0; i < children.length; ++i) { - var ch = children[i]; - size += ch.chunkSize(); - height += ch.height; - ch.parent = this$1; - } - this.size = size; - this.height = height; - this.parent = null; - } - BranchChunk.prototype = { - chunkSize: function chunkSize() { - return this.size; - }, - removeInner: function removeInner(at, n) { - var this$1 = this; - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], - sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), - oldHeight = child.height; - child.removeInner(at, rm); - this$1.height -= oldHeight - child.height; - if (sz == rm) { - this$1.children.splice(i--, 1); - child.parent = null; - } - if ((n -= rm) == 0) { - break; - } - at = 0; - } else { - at -= sz; - } - } - // If the result is smaller than 25 lines, ensure that it is a - // single leaf node. - if (this.size - n < 25 && (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function collapse(lines) { - var this$1 = this; - for (var i = 0; i < this.children.length; ++i) { - this$1.children[i].collapse(lines); - } - }, - insertInner: function insertInner(at, lines, height) { - var this$1 = this; - this.size += lines.length; - this.height += height; - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], - sz = child.chunkSize(); - if (at <= sz) { - child.insertInner(at, lines, height); - if (child.lines && child.lines.length > 50) { - // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. - // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. - var remaining = child.lines.length % 25 + 25; - for (var pos = remaining; pos < child.lines.length;) { - var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); - child.height -= leaf.height; - this$1.children.splice(++i, 0, leaf); - leaf.parent = this$1; - } - child.lines = child.lines.slice(0, remaining); - this$1.maybeSpill(); - } - break; - } - at -= sz; - } - }, - // When a node has grown, check whether it should be split. - maybeSpill: function maybeSpill() { - if (this.children.length <= 10) { - return; - } - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { - // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iterN: function iterN(at, n, op) { - var this$1 = this; - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], - sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) { - return true; - } - if ((n -= used) == 0) { - break; - } - at = 0; - } else { - at -= sz; - } - } - } - }; - - // Line widgets are block elements displayed above or below a line. - - var LineWidget = function (doc, node, options) { - var this$1 = this; - if (options) { - for (var opt in options) { - if (options.hasOwnProperty(opt)) { - this$1[opt] = options[opt]; - } - } - } - this.doc = doc; - this.node = node; - }; - LineWidget.prototype.clear = function () { - var this$1 = this; - var cm = this.doc.cm, - ws = this.line.widgets, - line = this.line, - no = lineNo(line); - if (no == null || !ws) { - return; - } - for (var i = 0; i < ws.length; ++i) { - if (ws[i] == this$1) { - ws.splice(i--, 1); - } - } - if (!ws.length) { - line.widgets = null; - } - var height = widgetHeight(this); - updateLineHeight(line, Math.max(0, line.height - height)); - if (cm) { - runInOp(cm, function () { - adjustScrollWhenAboveVisible(cm, line, -height); - regLineChange(cm, no, "widget"); - }); - signalLater(cm, "lineWidgetCleared", cm, this, no); - } - }; - LineWidget.prototype.changed = function () { - var this$1 = this; - var oldH = this.height, - cm = this.doc.cm, - line = this.line; - this.height = null; - var diff = widgetHeight(this) - oldH; - if (!diff) { - return; - } - updateLineHeight(line, line.height + diff); - if (cm) { - runInOp(cm, function () { - cm.curOp.forceUpdate = true; - adjustScrollWhenAboveVisible(cm, line, diff); - signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); - }); - } - }; - eventMixin(LineWidget); - function adjustScrollWhenAboveVisible(cm, line, diff) { - if (heightAtLine(line) < (cm.curOp && cm.curOp.scrollTop || cm.doc.scrollTop)) { - addToScrollTop(cm, diff); - } - } - function addLineWidget(doc, handle, node, options) { - var widget = new LineWidget(doc, node, options); - var cm = doc.cm; - if (cm && widget.noHScroll) { - cm.display.alignWidgets = true; - } - changeLine(doc, handle, "widget", function (line) { - var widgets = line.widgets || (line.widgets = []); - if (widget.insertAt == null) { - widgets.push(widget); - } else { - widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); - } - widget.line = line; - if (cm && !lineIsHidden(doc, line)) { - var aboveVisible = heightAtLine(line) < doc.scrollTop; - updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) { - addToScrollTop(cm, widget.height); - } - cm.curOp.forceUpdate = true; - } - return true; - }); - signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); - return widget; - } - - // TEXTMARKERS - - // Created with markText and setBookmark methods. A TextMarker is a - // handle that can be used to clear or find a marked position in the - // document. Line objects hold arrays (markedSpans) containing - // {from, to, marker} object pointing to such marker objects, and - // indicating that such a marker is present on that line. Multiple - // lines may point to the same marker when it spans across lines. - // The spans will have null for their from/to properties when the - // marker continues beyond the start/end of the line. Markers have - // links back to the lines they currently touch. - - // Collapsed markers have unique ids, in order to be able to order - // them, which is needed for uniquely determining an outer marker - // when they overlap (they may nest, but not partially overlap). - var nextMarkerId = 0; - var TextMarker = function (doc, type) { - this.lines = []; - this.type = type; - this.doc = doc; - this.id = ++nextMarkerId; - }; - - // Clear the marker. - TextMarker.prototype.clear = function () { - var this$1 = this; - if (this.explicitlyCleared) { - return; - } - var cm = this.doc.cm, - withOp = cm && !cm.curOp; - if (withOp) { - startOperation(cm); - } - if (hasHandler(this, "clear")) { - var found = this.find(); - if (found) { - signalLater(this, "clear", found.from, found.to); - } - } - var min = null, - max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this$1.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this$1); - if (cm && !this$1.collapsed) { - regLineChange(cm, lineNo(line), "text"); - } else if (cm) { - if (span.to != null) { - max = lineNo(line); - } - if (span.from != null) { - min = lineNo(line); - } - } - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) { - updateLineHeight(line, textHeight(cm.display)); - } - } - if (cm && this.collapsed && !cm.options.lineWrapping) { - for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { - var visual = visualLine(this$1.lines[i$1]), - len = lineLength(visual); - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual; - cm.display.maxLineLength = len; - cm.display.maxLineChanged = true; - } - } - } - if (min != null && cm && this.collapsed) { - regChange(cm, min, max + 1); - } - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false; - if (cm) { - reCheckSelection(cm.doc); - } - } - if (cm) { - signalLater(cm, "markerCleared", cm, this, min, max); - } - if (withOp) { - endOperation(cm); - } - if (this.parent) { - this.parent.clear(); - } - }; - - // Find the position of the marker in the document. Returns a {from, - // to} object by default. Side can be passed to get a specific side - // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the - // Pos objects returned contain a line object, rather than a line - // number (used to prevent looking up the same line twice). - TextMarker.prototype.find = function (side, lineObj) { - var this$1 = this; - if (side == null && this.type == "bookmark") { - side = 1; - } - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this$1.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this$1); - if (span.from != null) { - from = Pos(lineObj ? line : lineNo(line), span.from); - if (side == -1) { - return from; - } - } - if (span.to != null) { - to = Pos(lineObj ? line : lineNo(line), span.to); - if (side == 1) { - return to; - } - } - } - return from && { - from: from, - to: to - }; - }; - - // Signals that the marker's widget changed, and surrounding layout - // should be recomputed. - TextMarker.prototype.changed = function () { - var this$1 = this; - var pos = this.find(-1, true), - widget = this, - cm = this.doc.cm; - if (!pos || !cm) { - return; - } - runInOp(cm, function () { - var line = pos.line, - lineN = lineNo(pos.line); - var view = findViewForLine(cm, lineN); - if (view) { - clearLineMeasurementCacheFor(view); - cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; - } - cm.curOp.updateMaxLine = true; - if (!lineIsHidden(widget.doc, line) && widget.height != null) { - var oldHeight = widget.height; - widget.height = null; - var dHeight = widgetHeight(widget) - oldHeight; - if (dHeight) { - updateLineHeight(line, line.height + dHeight); - } - } - signalLater(cm, "markerChanged", cm, this$1); - }); - }; - TextMarker.prototype.attachLine = function (line) { - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) { - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); - } - } - this.lines.push(line); - }; - TextMarker.prototype.detachLine = function (line) { - this.lines.splice(indexOf(this.lines, line), 1); - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); - } - }; - eventMixin(TextMarker); - - // Create a marker, wire it up to the right lines, and - function markText(doc, from, to, options, type) { - // Shared markers (across linked documents) are handled separately - // (markTextShared will call out to this again, once per - // document). - if (options && options.shared) { - return markTextShared(doc, from, to, options, type); - } - // Ensure we are in an operation. - if (doc.cm && !doc.cm.curOp) { - return operation(doc.cm, markText)(doc, from, to, options, type); - } - var marker = new TextMarker(doc, type), - diff = cmp(from, to); - if (options) { - copyObj(options, marker, false); - } - // Don't connect empty markers unless clearWhenEmpty is false - if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) { - return marker; - } - if (marker.replacedWith) { - // Showing up as a widget implies collapsed (widget replaces text) - marker.collapsed = true; - marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) { - marker.widgetNode.setAttribute("cm-ignore-events", "true"); - } - if (options.insertLeft) { - marker.widgetNode.insertLeft = true; - } - } - if (marker.collapsed) { - if (conflictingCollapsedRange(doc, from.line, from, to, marker) || from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) { - throw new Error("Inserting collapsed marker partially overlapping an existing one"); - } - seeCollapsedSpans(); - } - if (marker.addToHistory) { - addChangeToHistory(doc, { - from: from, - to: to, - origin: "markText" - }, doc.sel, NaN); - } - var curLine = from.line, - cm = doc.cm, - updateMaxLine; - doc.iter(curLine, to.line + 1, function (line) { - if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) { - updateMaxLine = true; - } - if (marker.collapsed && curLine != from.line) { - updateLineHeight(line, 0); - } - addMarkedSpan(line, new MarkedSpan(marker, curLine == from.line ? from.ch : null, curLine == to.line ? to.ch : null)); - ++curLine; - }); - // lineIsHidden depends on the presence of the spans, so needs a second pass - if (marker.collapsed) { - doc.iter(from.line, to.line + 1, function (line) { - if (lineIsHidden(doc, line)) { - updateLineHeight(line, 0); - } - }); - } - if (marker.clearOnEnter) { - on(marker, "beforeCursorEnter", function () { - return marker.clear(); - }); - } - if (marker.readOnly) { - seeReadOnlySpans(); - if (doc.history.done.length || doc.history.undone.length) { - doc.clearHistory(); - } - } - if (marker.collapsed) { - marker.id = ++nextMarkerId; - marker.atomic = true; - } - if (cm) { - // Sync editor state - if (updateMaxLine) { - cm.curOp.updateMaxLine = true; - } - if (marker.collapsed) { - regChange(cm, from.line, to.line + 1); - } else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) { - for (var i = from.line; i <= to.line; i++) { - regLineChange(cm, i, "text"); - } - } - if (marker.atomic) { - reCheckSelection(cm.doc); - } - signalLater(cm, "markerAdded", cm, marker); - } - return marker; - } - - // SHARED TEXTMARKERS - - // A shared marker spans multiple linked documents. It is - // implemented as a meta-marker-object controlling multiple normal - // markers. - var SharedTextMarker = function (markers, primary) { - var this$1 = this; - this.markers = markers; - this.primary = primary; - for (var i = 0; i < markers.length; ++i) { - markers[i].parent = this$1; - } - }; - SharedTextMarker.prototype.clear = function () { - var this$1 = this; - if (this.explicitlyCleared) { - return; - } - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) { - this$1.markers[i].clear(); - } - signalLater(this, "clear"); - }; - SharedTextMarker.prototype.find = function (side, lineObj) { - return this.primary.find(side, lineObj); - }; - eventMixin(SharedTextMarker); - function markTextShared(doc, from, to, options, type) { - options = copyObj(options); - options.shared = false; - var markers = [markText(doc, from, to, options, type)], - primary = markers[0]; - var widget = options.widgetNode; - linkedDocs(doc, function (doc) { - if (widget) { - options.widgetNode = widget.cloneNode(true); - } - markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); - for (var i = 0; i < doc.linked.length; ++i) { - if (doc.linked[i].isParent) { - return; - } - } - primary = lst(markers); - }); - return new SharedTextMarker(markers, primary); - } - function findSharedMarkers(doc) { - return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { - return m.parent; - }); - } - function copySharedMarkers(doc, markers) { - for (var i = 0; i < markers.length; i++) { - var marker = markers[i], - pos = marker.find(); - var mFrom = doc.clipPos(pos.from), - mTo = doc.clipPos(pos.to); - if (cmp(mFrom, mTo)) { - var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); - marker.markers.push(subMark); - subMark.parent = marker; - } - } - } - function detachSharedMarkers(markers) { - var loop = function (i) { - var marker = markers[i], - linked = [marker.primary.doc]; - linkedDocs(marker.primary.doc, function (d) { - return linked.push(d); - }); - for (var j = 0; j < marker.markers.length; j++) { - var subMarker = marker.markers[j]; - if (indexOf(linked, subMarker.doc) == -1) { - subMarker.parent = null; - marker.markers.splice(j--, 1); - } - } - }; - for (var i = 0; i < markers.length; i++) loop(i); - } - var nextDocId = 0; - var Doc = function (text, mode, firstLine, lineSep, direction) { - if (!(this instanceof Doc)) { - return new Doc(text, mode, firstLine, lineSep, direction); - } - if (firstLine == null) { - firstLine = 0; - } - BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); - this.first = firstLine; - this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; - this.cleanGeneration = 1; - this.modeFrontier = this.highlightFrontier = firstLine; - var start = Pos(firstLine, 0); - this.sel = simpleSelection(start); - this.history = new History(null); - this.id = ++nextDocId; - this.modeOption = mode; - this.lineSep = lineSep; - this.direction = direction == "rtl" ? "rtl" : "ltr"; - this.extend = false; - if (typeof text == "string") { - text = this.splitLines(text); - } - updateDoc(this, { - from: start, - to: start, - text: text - }); - setSelection(this, simpleSelection(start), sel_dontScroll); - }; - Doc.prototype = createObj(BranchChunk.prototype, { - constructor: Doc, - // Iterate over the document. Supports two forms -- with only one - // argument, it calls that for each line in the document. With - // three, it iterates over the range given by the first two (with - // the second being non-inclusive). - iter: function (from, to, op) { - if (op) { - this.iterN(from - this.first, to - from, op); - } else { - this.iterN(this.first, this.first + this.size, from); - } - }, - // Non-public interface for adding and removing lines. - insert: function (at, lines) { - var height = 0; - for (var i = 0; i < lines.length; ++i) { - height += lines[i].height; - } - this.insertInner(at - this.first, lines, height); - }, - remove: function (at, n) { - this.removeInner(at - this.first, n); - }, - // From here, the methods are part of the public interface. Most - // are also available from CodeMirror (editor) instances. - - getValue: function (lineSep) { - var lines = getLines(this, this.first, this.first + this.size); - if (lineSep === false) { - return lines; - } - return lines.join(lineSep || this.lineSeparator()); - }, - setValue: docMethodOp(function (code) { - var top = Pos(this.first, 0), - last = this.first + this.size - 1; - makeChange(this, { - from: top, - to: Pos(last, getLine(this, last).text.length), - text: this.splitLines(code), - origin: "setValue", - full: true - }, true); - if (this.cm) { - scrollToCoords(this.cm, 0, 0); - } - setSelection(this, simpleSelection(top), sel_dontScroll); - }), - replaceRange: function (code, from, to, origin) { - from = clipPos(this, from); - to = to ? clipPos(this, to) : from; - replaceRange(this, code, from, to, origin); - }, - getRange: function (from, to, lineSep) { - var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); - if (lineSep === false) { - return lines; - } - return lines.join(lineSep || this.lineSeparator()); - }, - getLine: function (line) { - var l = this.getLineHandle(line); - return l && l.text; - }, - getLineHandle: function (line) { - if (isLine(this, line)) { - return getLine(this, line); - } - }, - getLineNumber: function (line) { - return lineNo(line); - }, - getLineHandleVisualStart: function (line) { - if (typeof line == "number") { - line = getLine(this, line); - } - return visualLine(line); - }, - lineCount: function () { - return this.size; - }, - firstLine: function () { - return this.first; - }, - lastLine: function () { - return this.first + this.size - 1; - }, - clipPos: function (pos) { - return clipPos(this, pos); - }, - getCursor: function (start) { - var range = this.sel.primary(), - pos; - if (start == null || start == "head") { - pos = range.head; - } else if (start == "anchor") { - pos = range.anchor; - } else if (start == "end" || start == "to" || start === false) { - pos = range.to(); - } else { - pos = range.from(); - } - return pos; - }, - listSelections: function () { - return this.sel.ranges; - }, - somethingSelected: function () { - return this.sel.somethingSelected(); - }, - setCursor: docMethodOp(function (line, ch, options) { - setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); - }), - setSelection: docMethodOp(function (anchor, head, options) { - setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); - }), - extendSelection: docMethodOp(function (head, other, options) { - extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); - }), - extendSelections: docMethodOp(function (heads, options) { - extendSelections(this, clipPosArray(this, heads), options); - }), - extendSelectionsBy: docMethodOp(function (f, options) { - var heads = map(this.sel.ranges, f); - extendSelections(this, clipPosArray(this, heads), options); - }), - setSelections: docMethodOp(function (ranges, primary, options) { - var this$1 = this; - if (!ranges.length) { - return; - } - var out = []; - for (var i = 0; i < ranges.length; i++) { - out[i] = new Range(clipPos(this$1, ranges[i].anchor), clipPos(this$1, ranges[i].head)); - } - if (primary == null) { - primary = Math.min(ranges.length - 1, this.sel.primIndex); - } - setSelection(this, normalizeSelection(out, primary), options); - }), - addSelection: docMethodOp(function (anchor, head, options) { - var ranges = this.sel.ranges.slice(0); - ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); - setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); - }), - getSelection: function (lineSep) { - var this$1 = this; - var ranges = this.sel.ranges, - lines; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); - lines = lines ? lines.concat(sel) : sel; - } - if (lineSep === false) { - return lines; - } else { - return lines.join(lineSep || this.lineSeparator()); - } - }, - getSelections: function (lineSep) { - var this$1 = this; - var parts = [], - ranges = this.sel.ranges; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); - if (lineSep !== false) { - sel = sel.join(lineSep || this$1.lineSeparator()); - } - parts[i] = sel; - } - return parts; - }, - replaceSelection: function (code, collapse, origin) { - var dup = []; - for (var i = 0; i < this.sel.ranges.length; i++) { - dup[i] = code; - } - this.replaceSelections(dup, collapse, origin || "+input"); - }, - replaceSelections: docMethodOp(function (code, collapse, origin) { - var this$1 = this; - var changes = [], - sel = this.sel; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - changes[i] = { - from: range.from(), - to: range.to(), - text: this$1.splitLines(code[i]), - origin: origin - }; - } - var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); - for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) { - makeChange(this$1, changes[i$1]); - } - if (newSel) { - setSelectionReplaceHistory(this, newSel); - } else if (this.cm) { - ensureCursorVisible(this.cm); - } - }), - undo: docMethodOp(function () { - makeChangeFromHistory(this, "undo"); - }), - redo: docMethodOp(function () { - makeChangeFromHistory(this, "redo"); - }), - undoSelection: docMethodOp(function () { - makeChangeFromHistory(this, "undo", true); - }), - redoSelection: docMethodOp(function () { - makeChangeFromHistory(this, "redo", true); - }), - setExtending: function (val) { - this.extend = val; - }, - getExtending: function () { - return this.extend; - }, - historySize: function () { - var hist = this.history, - done = 0, - undone = 0; - for (var i = 0; i < hist.done.length; i++) { - if (!hist.done[i].ranges) { - ++done; - } - } - for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { - if (!hist.undone[i$1].ranges) { - ++undone; - } - } - return { - undo: done, - redo: undone - }; - }, - clearHistory: function () { - this.history = new History(this.history.maxGeneration); - }, - markClean: function () { - this.cleanGeneration = this.changeGeneration(true); - }, - changeGeneration: function (forceSplit) { - if (forceSplit) { - this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; - } - return this.history.generation; - }, - isClean: function (gen) { - return this.history.generation == (gen || this.cleanGeneration); - }, - getHistory: function () { - return { - done: copyHistoryArray(this.history.done), - undone: copyHistoryArray(this.history.undone) - }; - }, - setHistory: function (histData) { - var hist = this.history = new History(this.history.maxGeneration); - hist.done = copyHistoryArray(histData.done.slice(0), null, true); - hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); - }, - setGutterMarker: docMethodOp(function (line, gutterID, value) { - return changeLine(this, line, "gutter", function (line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) { - line.gutterMarkers = null; - } - return true; - }); - }), - clearGutter: docMethodOp(function (gutterID) { - var this$1 = this; - this.iter(function (line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - changeLine(this$1, line, "gutter", function () { - line.gutterMarkers[gutterID] = null; - if (isEmpty(line.gutterMarkers)) { - line.gutterMarkers = null; - } - return true; - }); - } - }); - }), - lineInfo: function (line) { - var n; - if (typeof line == "number") { - if (!isLine(this, line)) { - return null; - } - n = line; - line = getLine(this, line); - if (!line) { - return null; - } - } else { - n = lineNo(line); - if (n == null) { - return null; - } - } - return { - line: n, - handle: line, - text: line.text, - gutterMarkers: line.gutterMarkers, - textClass: line.textClass, - bgClass: line.bgClass, - wrapClass: line.wrapClass, - widgets: line.widgets - }; - }, - addLineClass: docMethodOp(function (handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass"; - if (!line[prop]) { - line[prop] = cls; - } else if (classTest(cls).test(line[prop])) { - return false; - } else { - line[prop] += " " + cls; - } - return true; - }); - }), - removeLineClass: docMethodOp(function (handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) { - return false; - } else if (cls == null) { - line[prop] = null; - } else { - var found = cur.match(classTest(cls)); - if (!found) { - return false; - } - var end = found.index + found[0].length; - line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; - } - return true; - }); - }), - addLineWidget: docMethodOp(function (handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - removeLineWidget: function (widget) { - widget.clear(); - }, - markText: function (from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range"); - }, - setBookmark: function (pos, options) { - var realOpts = { - replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft, - clearWhenEmpty: false, - shared: options && options.shared, - handleMouseEvents: options && options.handleMouseEvents - }; - pos = clipPos(this, pos); - return markText(this, pos, pos, realOpts, "bookmark"); - }, - findMarksAt: function (pos) { - pos = clipPos(this, pos); - var markers = [], - spans = getLine(this, pos.line).markedSpans; - if (spans) { - for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && (span.to == null || span.to >= pos.ch)) { - markers.push(span.marker.parent || span.marker); - } - } - } - return markers; - }, - findMarks: function (from, to, filter) { - from = clipPos(this, from); - to = clipPos(this, to); - var found = [], - lineNo = from.line; - this.iter(from.line, to.line + 1, function (line) { - var spans = line.markedSpans; - if (spans) { - for (var i = 0; i < spans.length; i++) { - var span = spans[i]; - if (!(span.to != null && lineNo == from.line && from.ch >= span.to || span.from == null && lineNo != from.line || span.from != null && lineNo == to.line && span.from >= to.ch) && (!filter || filter(span.marker))) { - found.push(span.marker.parent || span.marker); - } - } - } - ++lineNo; - }); - return found; - }, - getAllMarks: function () { - var markers = []; - this.iter(function (line) { - var sps = line.markedSpans; - if (sps) { - for (var i = 0; i < sps.length; ++i) { - if (sps[i].from != null) { - markers.push(sps[i].marker); - } - } - } - }); - return markers; - }, - posFromIndex: function (off) { - var ch, - lineNo = this.first, - sepSize = this.lineSeparator().length; - this.iter(function (line) { - var sz = line.text.length + sepSize; - if (sz > off) { - ch = off; - return true; - } - off -= sz; - ++lineNo; - }); - return clipPos(this, Pos(lineNo, ch)); - }, - indexFromPos: function (coords) { - coords = clipPos(this, coords); - var index = coords.ch; - if (coords.line < this.first || coords.ch < 0) { - return 0; - } - var sepSize = this.lineSeparator().length; - this.iter(this.first, coords.line, function (line) { - // iter aborts when callback returns a truthy value - index += line.text.length + sepSize; - }); - return index; - }, - copy: function (copyHistory) { - var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first, this.lineSep, this.direction); - doc.scrollTop = this.scrollTop; - doc.scrollLeft = this.scrollLeft; - doc.sel = this.sel; - doc.extend = false; - if (copyHistory) { - doc.history.undoDepth = this.history.undoDepth; - doc.setHistory(this.getHistory()); - } - return doc; - }, - linkedDoc: function (options) { - if (!options) { - options = {}; - } - var from = this.first, - to = this.first + this.size; - if (options.from != null && options.from > from) { - from = options.from; - } - if (options.to != null && options.to < to) { - to = options.to; - } - var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); - if (options.sharedHist) { - copy.history = this.history; - } - (this.linked || (this.linked = [])).push({ - doc: copy, - sharedHist: options.sharedHist - }); - copy.linked = [{ - doc: this, - isParent: true, - sharedHist: options.sharedHist - }]; - copySharedMarkers(copy, findSharedMarkers(this)); - return copy; - }, - unlinkDoc: function (other) { - var this$1 = this; - if (other instanceof CodeMirror) { - other = other.doc; - } - if (this.linked) { - for (var i = 0; i < this.linked.length; ++i) { - var link = this$1.linked[i]; - if (link.doc != other) { - continue; - } - this$1.linked.splice(i, 1); - other.unlinkDoc(this$1); - detachSharedMarkers(findSharedMarkers(this$1)); - break; - } - } - // If the histories were shared, split them again - if (other.history == this.history) { - var splitIds = [other.id]; - linkedDocs(other, function (doc) { - return splitIds.push(doc.id); - }, true); - other.history = new History(null); - other.history.done = copyHistoryArray(this.history.done, splitIds); - other.history.undone = copyHistoryArray(this.history.undone, splitIds); - } - }, - iterLinkedDocs: function (f) { - linkedDocs(this, f); - }, - getMode: function () { - return this.mode; - }, - getEditor: function () { - return this.cm; - }, - splitLines: function (str) { - if (this.lineSep) { - return str.split(this.lineSep); - } - return splitLinesAuto(str); - }, - lineSeparator: function () { - return this.lineSep || "\n"; - }, - setDirection: docMethodOp(function (dir) { - if (dir != "rtl") { - dir = "ltr"; - } - if (dir == this.direction) { - return; - } - this.direction = dir; - this.iter(function (line) { - return line.order = null; - }); - if (this.cm) { - directionChanged(this.cm); - } - }) - }); - - // Public alias. - Doc.prototype.eachLine = Doc.prototype.iter; - - // Kludge to work around strange IE behavior where it'll sometimes - // re-fire a series of drag-related events right after the drop (#1551) - var lastDrop = 0; - function onDrop(e) { - var cm = this; - clearDragCursor(cm); - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { - return; - } - e_preventDefault(e); - if (ie) { - lastDrop = +new Date(); - } - var pos = posFromMouse(cm, e, true), - files = e.dataTransfer.files; - if (!pos || cm.isReadOnly()) { - return; - } - // Might be a file drop, in which case we simply extract the text - // and insert it. - if (files && files.length && window.FileReader && window.File) { - var n = files.length, - text = Array(n), - read = 0; - var loadFile = function (file, i) { - if (cm.options.allowDropFileTypes && indexOf(cm.options.allowDropFileTypes, file.type) == -1) { - return; - } - var reader = new FileReader(); - reader.onload = operation(cm, function () { - var content = reader.result; - if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { - content = ""; - } - text[i] = content; - if (++read == n) { - pos = clipPos(cm.doc, pos); - var change = { - from: pos, - to: pos, - text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), - origin: "paste" - }; - makeChange(cm.doc, change); - setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); - } - }); - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) { - loadFile(files[i], i); - } - } else { - // Normal drop - // Don't do a replace if the drop happened inside of the selected text. - if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { - cm.state.draggingText(e); - // Ensure the editor is re-focused - setTimeout(function () { - return cm.display.input.focus(); - }, 20); - return; - } - try { - var text$1 = e.dataTransfer.getData("Text"); - if (text$1) { - var selected; - if (cm.state.draggingText && !cm.state.draggingText.copy) { - selected = cm.listSelections(); - } - setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); - if (selected) { - for (var i$1 = 0; i$1 < selected.length; ++i$1) { - replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); - } - } - cm.replaceSelection(text$1, "around", "paste"); - cm.display.input.focus(); - } - } catch (e) {} - } - } - function onDragStart(cm, e) { - if (ie && (!cm.state.draggingText || +new Date() - lastDrop < 100)) { - e_stop(e); - return; - } - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { - return; - } - e.dataTransfer.setData("Text", cm.getSelection()); - e.dataTransfer.effectAllowed = "copyMove"; - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) { - var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); - img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - if (presto) { - img.width = img.height = 1; - cm.display.wrapper.appendChild(img); - // Force a relayout, or Opera won't use our image for some obscure reason - img._top = img.offsetTop; - } - e.dataTransfer.setDragImage(img, 0, 0); - if (presto) { - img.parentNode.removeChild(img); - } - } - } - function onDragOver(cm, e) { - var pos = posFromMouse(cm, e); - if (!pos) { - return; - } - var frag = document.createDocumentFragment(); - drawSelectionCursor(cm, pos, frag); - if (!cm.display.dragCursor) { - cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); - cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); - } - removeChildrenAndAdd(cm.display.dragCursor, frag); - } - function clearDragCursor(cm) { - if (cm.display.dragCursor) { - cm.display.lineSpace.removeChild(cm.display.dragCursor); - cm.display.dragCursor = null; - } - } - - // These must be handled carefully, because naively registering a - // handler for each editor will cause the editors to never be - // garbage collected. - - function forEachCodeMirror(f) { - if (!document.getElementsByClassName) { - return; - } - var byClass = document.getElementsByClassName("CodeMirror"); - for (var i = 0; i < byClass.length; i++) { - var cm = byClass[i].CodeMirror; - if (cm) { - f(cm); - } - } - } - var globalsRegistered = false; - function ensureGlobalHandlers() { - if (globalsRegistered) { - return; - } - registerGlobalHandlers(); - globalsRegistered = true; - } - function registerGlobalHandlers() { - // When the window resizes, we need to refresh active editors. - var resizeTimer; - on(window, "resize", function () { - if (resizeTimer == null) { - resizeTimer = setTimeout(function () { - resizeTimer = null; - forEachCodeMirror(onResize); - }, 100); - } - }); - // When the window loses focus, we want to show the editor as blurred - on(window, "blur", function () { - return forEachCodeMirror(onBlur); - }); - } - // Called when the window resizes - function onResize(cm) { - var d = cm.display; - if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) { - return; - } - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - d.scrollbarsClipped = false; - cm.setSize(); - } - var keyNames = { - 3: "Enter", - 8: "Backspace", - 9: "Tab", - 13: "Enter", - 16: "Shift", - 17: "Ctrl", - 18: "Alt", - 19: "Pause", - 20: "CapsLock", - 27: "Esc", - 32: "Space", - 33: "PageUp", - 34: "PageDown", - 35: "End", - 36: "Home", - 37: "Left", - 38: "Up", - 39: "Right", - 40: "Down", - 44: "PrintScrn", - 45: "Insert", - 46: "Delete", - 59: ";", - 61: "=", - 91: "Mod", - 92: "Mod", - 93: "Mod", - 106: "*", - 107: "=", - 109: "-", - 110: ".", - 111: "/", - 127: "Delete", - 173: "-", - 186: ";", - 187: "=", - 188: ",", - 189: "-", - 190: ".", - 191: "/", - 192: "`", - 219: "[", - 220: "\\", - 221: "]", - 222: "'", - 63232: "Up", - 63233: "Down", - 63234: "Left", - 63235: "Right", - 63272: "Delete", - 63273: "Home", - 63275: "End", - 63276: "PageUp", - 63277: "PageDown", - 63302: "Insert" - }; - - // Number keys - for (var i = 0; i < 10; i++) { - keyNames[i + 48] = keyNames[i + 96] = String(i); - } - // Alphabetic keys - for (var i$1 = 65; i$1 <= 90; i$1++) { - keyNames[i$1] = String.fromCharCode(i$1); - } - // Function keys - for (var i$2 = 1; i$2 <= 12; i$2++) { - keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; - } - var keyMap = {}; - keyMap.basic = { - "Left": "goCharLeft", - "Right": "goCharRight", - "Up": "goLineUp", - "Down": "goLineDown", - "End": "goLineEnd", - "Home": "goLineStartSmart", - "PageUp": "goPageUp", - "PageDown": "goPageDown", - "Delete": "delCharAfter", - "Backspace": "delCharBefore", - "Shift-Backspace": "delCharBefore", - "Tab": "defaultTab", - "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", - "Insert": "toggleOverwrite", - "Esc": "singleSelection" - }; - // Note that the save and find-related commands aren't defined by - // default. User code or addons can define them. Unknown commands - // are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", - "Ctrl-D": "deleteLine", - "Ctrl-Z": "undo", - "Shift-Ctrl-Z": "redo", - "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", - "Ctrl-End": "goDocEnd", - "Ctrl-Up": "goLineUp", - "Ctrl-Down": "goLineDown", - "Ctrl-Left": "goGroupLeft", - "Ctrl-Right": "goGroupRight", - "Alt-Left": "goLineStart", - "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delGroupBefore", - "Ctrl-Delete": "delGroupAfter", - "Ctrl-S": "save", - "Ctrl-F": "find", - "Ctrl-G": "findNext", - "Shift-Ctrl-G": "findPrev", - "Shift-Ctrl-F": "replace", - "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", - "Ctrl-]": "indentMore", - "Ctrl-U": "undoSelection", - "Shift-Ctrl-U": "redoSelection", - "Alt-U": "redoSelection", - fallthrough: "basic" - }; - // Very basic readline/emacs-style bindings, which are standard on Mac. - keyMap.emacsy = { - "Ctrl-F": "goCharRight", - "Ctrl-B": "goCharLeft", - "Ctrl-P": "goLineUp", - "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", - "Alt-B": "goWordLeft", - "Ctrl-A": "goLineStart", - "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageDown", - "Shift-Ctrl-V": "goPageUp", - "Ctrl-D": "delCharAfter", - "Ctrl-H": "delCharBefore", - "Alt-D": "delWordAfter", - "Alt-Backspace": "delWordBefore", - "Ctrl-K": "killLine", - "Ctrl-T": "transposeChars", - "Ctrl-O": "openLine" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", - "Cmd-D": "deleteLine", - "Cmd-Z": "undo", - "Shift-Cmd-Z": "redo", - "Cmd-Y": "redo", - "Cmd-Home": "goDocStart", - "Cmd-Up": "goDocStart", - "Cmd-End": "goDocEnd", - "Cmd-Down": "goDocEnd", - "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", - "Cmd-Left": "goLineLeft", - "Cmd-Right": "goLineRight", - "Alt-Backspace": "delGroupBefore", - "Ctrl-Alt-Backspace": "delGroupAfter", - "Alt-Delete": "delGroupAfter", - "Cmd-S": "save", - "Cmd-F": "find", - "Cmd-G": "findNext", - "Shift-Cmd-G": "findPrev", - "Cmd-Alt-F": "replace", - "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", - "Cmd-]": "indentMore", - "Cmd-Backspace": "delWrappedLineLeft", - "Cmd-Delete": "delWrappedLineRight", - "Cmd-U": "undoSelection", - "Shift-Cmd-U": "redoSelection", - "Ctrl-Up": "goDocStart", - "Ctrl-Down": "goDocEnd", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - - // KEYMAP DISPATCH - - function normalizeKeyName(name) { - var parts = name.split(/-(?!$)/); - name = parts[parts.length - 1]; - var alt, ctrl, shift, cmd; - for (var i = 0; i < parts.length - 1; i++) { - var mod = parts[i]; - if (/^(cmd|meta|m)$/i.test(mod)) { - cmd = true; - } else if (/^a(lt)?$/i.test(mod)) { - alt = true; - } else if (/^(c|ctrl|control)$/i.test(mod)) { - ctrl = true; - } else if (/^s(hift)?$/i.test(mod)) { - shift = true; - } else { - throw new Error("Unrecognized modifier name: " + mod); - } - } - if (alt) { - name = "Alt-" + name; - } - if (ctrl) { - name = "Ctrl-" + name; - } - if (cmd) { - name = "Cmd-" + name; - } - if (shift) { - name = "Shift-" + name; - } - return name; - } - - // This is a kludge to keep keymaps mostly working as raw objects - // (backwards compatibility) while at the same time support features - // like normalization and multi-stroke key bindings. It compiles a - // new normalized keymap, and then updates the old object to reflect - // this. - function normalizeKeyMap(keymap) { - var copy = {}; - for (var keyname in keymap) { - if (keymap.hasOwnProperty(keyname)) { - var value = keymap[keyname]; - if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { - continue; - } - if (value == "...") { - delete keymap[keyname]; - continue; - } - var keys = map(keyname.split(" "), normalizeKeyName); - for (var i = 0; i < keys.length; i++) { - var val = void 0, - name = void 0; - if (i == keys.length - 1) { - name = keys.join(" "); - val = value; - } else { - name = keys.slice(0, i + 1).join(" "); - val = "..."; - } - var prev = copy[name]; - if (!prev) { - copy[name] = val; - } else if (prev != val) { - throw new Error("Inconsistent bindings for " + name); - } - } - delete keymap[keyname]; - } - } - for (var prop in copy) { - keymap[prop] = copy[prop]; - } - return keymap; - } - function lookupKey(key, map, handle, context) { - map = getKeyMap(map); - var found = map.call ? map.call(key, context) : map[key]; - if (found === false) { - return "nothing"; - } - if (found === "...") { - return "multi"; - } - if (found != null && handle(found)) { - return "handled"; - } - if (map.fallthrough) { - if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") { - return lookupKey(key, map.fallthrough, handle, context); - } - for (var i = 0; i < map.fallthrough.length; i++) { - var result = lookupKey(key, map.fallthrough[i], handle, context); - if (result) { - return result; - } - } - } - } - - // Modifier key presses don't count as 'real' key presses for the - // purpose of keymap fallthrough. - function isModifierKey(value) { - var name = typeof value == "string" ? value : keyNames[value.keyCode]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - } - function addModifierNames(name, event, noShift) { - var base = name; - if (event.altKey && base != "Alt") { - name = "Alt-" + name; - } - if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { - name = "Ctrl-" + name; - } - if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { - name = "Cmd-" + name; - } - if (!noShift && event.shiftKey && base != "Shift") { - name = "Shift-" + name; - } - return name; - } - - // Look up the name of a key as indicated by an event object. - function keyName(event, noShift) { - if (presto && event.keyCode == 34 && event["char"]) { - return false; - } - var name = keyNames[event.keyCode]; - if (name == null || event.altGraphKey) { - return false; - } - return addModifierNames(name, event, noShift); - } - function getKeyMap(val) { - return typeof val == "string" ? keyMap[val] : val; - } - - // Helper for deleting text near the selection(s), used to implement - // backspace, delete, and similar functionality. - function deleteNearSelection(cm, compute) { - var ranges = cm.doc.sel.ranges, - kill = []; - // Build up a set of ranges to kill first, merging overlapping - // ranges. - for (var i = 0; i < ranges.length; i++) { - var toKill = compute(ranges[i]); - while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { - var replaced = kill.pop(); - if (cmp(replaced.from, toKill.from) < 0) { - toKill.from = replaced.from; - break; - } - } - kill.push(toKill); - } - // Next, remove those actual ranges. - runInOp(cm, function () { - for (var i = kill.length - 1; i >= 0; i--) { - replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); - } - ensureCursorVisible(cm); - }); - } - - // Commands are parameter-less actions that can be performed on an - // editor, mostly used for keybindings. - var commands = { - selectAll: selectAll, - singleSelection: function (cm) { - return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); - }, - killLine: function (cm) { - return deleteNearSelection(cm, function (range) { - if (range.empty()) { - var len = getLine(cm.doc, range.head.line).text.length; - if (range.head.ch == len && range.head.line < cm.lastLine()) { - return { - from: range.head, - to: Pos(range.head.line + 1, 0) - }; - } else { - return { - from: range.head, - to: Pos(range.head.line, len) - }; - } - } else { - return { - from: range.from(), - to: range.to() - }; - } - }); - }, - deleteLine: function (cm) { - return deleteNearSelection(cm, function (range) { - return { - from: Pos(range.from().line, 0), - to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) - }; - }); - }, - delLineLeft: function (cm) { - return deleteNearSelection(cm, function (range) { - return { - from: Pos(range.from().line, 0), - to: range.from() - }; - }); - }, - delWrappedLineLeft: function (cm) { - return deleteNearSelection(cm, function (range) { - var top = cm.charCoords(range.head, "div").top + 5; - var leftPos = cm.coordsChar({ - left: 0, - top: top - }, "div"); - return { - from: leftPos, - to: range.from() - }; - }); - }, - delWrappedLineRight: function (cm) { - return deleteNearSelection(cm, function (range) { - var top = cm.charCoords(range.head, "div").top + 5; - var rightPos = cm.coordsChar({ - left: cm.display.lineDiv.offsetWidth + 100, - top: top - }, "div"); - return { - from: range.from(), - to: rightPos - }; - }); - }, - undo: function (cm) { - return cm.undo(); - }, - redo: function (cm) { - return cm.redo(); - }, - undoSelection: function (cm) { - return cm.undoSelection(); - }, - redoSelection: function (cm) { - return cm.redoSelection(); - }, - goDocStart: function (cm) { - return cm.extendSelection(Pos(cm.firstLine(), 0)); - }, - goDocEnd: function (cm) { - return cm.extendSelection(Pos(cm.lastLine())); - }, - goLineStart: function (cm) { - return cm.extendSelectionsBy(function (range) { - return lineStart(cm, range.head.line); - }, { - origin: "+move", - bias: 1 - }); - }, - goLineStartSmart: function (cm) { - return cm.extendSelectionsBy(function (range) { - return lineStartSmart(cm, range.head); - }, { - origin: "+move", - bias: 1 - }); - }, - goLineEnd: function (cm) { - return cm.extendSelectionsBy(function (range) { - return lineEnd(cm, range.head.line); - }, { - origin: "+move", - bias: -1 - }); - }, - goLineRight: function (cm) { - return cm.extendSelectionsBy(function (range) { - var top = cm.cursorCoords(range.head, "div").top + 5; - return cm.coordsChar({ - left: cm.display.lineDiv.offsetWidth + 100, - top: top - }, "div"); - }, sel_move); - }, - goLineLeft: function (cm) { - return cm.extendSelectionsBy(function (range) { - var top = cm.cursorCoords(range.head, "div").top + 5; - return cm.coordsChar({ - left: 0, - top: top - }, "div"); - }, sel_move); - }, - goLineLeftSmart: function (cm) { - return cm.extendSelectionsBy(function (range) { - var top = cm.cursorCoords(range.head, "div").top + 5; - var pos = cm.coordsChar({ - left: 0, - top: top - }, "div"); - if (pos.ch < cm.getLine(pos.line).search(/\S/)) { - return lineStartSmart(cm, range.head); - } - return pos; - }, sel_move); - }, - goLineUp: function (cm) { - return cm.moveV(-1, "line"); - }, - goLineDown: function (cm) { - return cm.moveV(1, "line"); - }, - goPageUp: function (cm) { - return cm.moveV(-1, "page"); - }, - goPageDown: function (cm) { - return cm.moveV(1, "page"); - }, - goCharLeft: function (cm) { - return cm.moveH(-1, "char"); - }, - goCharRight: function (cm) { - return cm.moveH(1, "char"); - }, - goColumnLeft: function (cm) { - return cm.moveH(-1, "column"); - }, - goColumnRight: function (cm) { - return cm.moveH(1, "column"); - }, - goWordLeft: function (cm) { - return cm.moveH(-1, "word"); - }, - goGroupRight: function (cm) { - return cm.moveH(1, "group"); - }, - goGroupLeft: function (cm) { - return cm.moveH(-1, "group"); - }, - goWordRight: function (cm) { - return cm.moveH(1, "word"); - }, - delCharBefore: function (cm) { - return cm.deleteH(-1, "char"); - }, - delCharAfter: function (cm) { - return cm.deleteH(1, "char"); - }, - delWordBefore: function (cm) { - return cm.deleteH(-1, "word"); - }, - delWordAfter: function (cm) { - return cm.deleteH(1, "word"); - }, - delGroupBefore: function (cm) { - return cm.deleteH(-1, "group"); - }, - delGroupAfter: function (cm) { - return cm.deleteH(1, "group"); - }, - indentAuto: function (cm) { - return cm.indentSelection("smart"); - }, - indentMore: function (cm) { - return cm.indentSelection("add"); - }, - indentLess: function (cm) { - return cm.indentSelection("subtract"); - }, - insertTab: function (cm) { - return cm.replaceSelection("\t"); - }, - insertSoftTab: function (cm) { - var spaces = [], - ranges = cm.listSelections(), - tabSize = cm.options.tabSize; - for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].from(); - var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); - spaces.push(spaceStr(tabSize - col % tabSize)); - } - cm.replaceSelections(spaces); - }, - defaultTab: function (cm) { - if (cm.somethingSelected()) { - cm.indentSelection("add"); - } else { - cm.execCommand("insertTab"); - } - }, - // Swap the two chars left and right of each selection's head. - // Move cursor behind the two swapped characters afterwards. - // - // Doesn't consider line feeds a character. - // Doesn't scan more than one line above to find a character. - // Doesn't do anything on an empty line. - // Doesn't do anything with non-empty selections. - transposeChars: function (cm) { - return runInOp(cm, function () { - var ranges = cm.listSelections(), - newSel = []; - for (var i = 0; i < ranges.length; i++) { - if (!ranges[i].empty()) { - continue; - } - var cur = ranges[i].head, - line = getLine(cm.doc, cur.line).text; - if (line) { - if (cur.ch == line.length) { - cur = new Pos(cur.line, cur.ch - 1); - } - if (cur.ch > 0) { - cur = new Pos(cur.line, cur.ch + 1); - cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), Pos(cur.line, cur.ch - 2), cur, "+transpose"); - } else if (cur.line > cm.doc.first) { - var prev = getLine(cm.doc, cur.line - 1).text; - if (prev) { - cur = new Pos(cur.line, 1); - cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + prev.charAt(prev.length - 1), Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); - } - } - } - newSel.push(new Range(cur, cur)); - } - cm.setSelections(newSel); - }); - }, - newlineAndIndent: function (cm) { - return runInOp(cm, function () { - var sels = cm.listSelections(); - for (var i = sels.length - 1; i >= 0; i--) { - cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); - } - sels = cm.listSelections(); - for (var i$1 = 0; i$1 < sels.length; i$1++) { - cm.indentLine(sels[i$1].from().line, null, true); - } - ensureCursorVisible(cm); - }); - }, - openLine: function (cm) { - return cm.replaceSelection("\n", "start"); - }, - toggleOverwrite: function (cm) { - return cm.toggleOverwrite(); - } - }; - function lineStart(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLine(line); - if (visual != line) { - lineN = lineNo(visual); - } - return endOfLine(true, cm, visual, lineN, 1); - } - function lineEnd(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLineEnd(line); - if (visual != line) { - lineN = lineNo(visual); - } - return endOfLine(true, cm, line, lineN, -1); - } - function lineStartSmart(cm, pos) { - var start = lineStart(cm, pos.line); - var line = getLine(cm.doc, start.line); - var order = getOrder(line, cm.doc.direction); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; - return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky); - } - return start; - } - - // Run a handler that was bound to a key. - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) { - return false; - } - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - cm.display.input.ensurePolled(); - var prevShift = cm.display.shift, - done = false; - try { - if (cm.isReadOnly()) { - cm.state.suppressEdits = true; - } - if (dropShift) { - cm.display.shift = false; - } - done = bound(cm) != Pass; - } finally { - cm.display.shift = prevShift; - cm.state.suppressEdits = false; - } - return done; - } - function lookupKeyForEditor(cm, name, handle) { - for (var i = 0; i < cm.state.keyMaps.length; i++) { - var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); - if (result) { - return result; - } - } - return cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm) || lookupKey(name, cm.options.keyMap, handle, cm); - } - - // Note that, despite the name, this function is also used to check - // for bound mouse clicks. - - var stopSeq = new Delayed(); - function dispatchKey(cm, name, e, handle) { - var seq = cm.state.keySeq; - if (seq) { - if (isModifierKey(name)) { - return "handled"; - } - stopSeq.set(50, function () { - if (cm.state.keySeq == seq) { - cm.state.keySeq = null; - cm.display.input.reset(); - } - }); - name = seq + " " + name; - } - var result = lookupKeyForEditor(cm, name, handle); - if (result == "multi") { - cm.state.keySeq = name; - } - if (result == "handled") { - signalLater(cm, "keyHandled", cm, name, e); - } - if (result == "handled" || result == "multi") { - e_preventDefault(e); - restartBlink(cm); - } - if (seq && !result && /\'$/.test(name)) { - e_preventDefault(e); - return true; - } - return !!result; - } - - // Handle a key from the keydown event. - function handleKeyBinding(cm, e) { - var name = keyName(e, true); - if (!name) { - return false; - } - if (e.shiftKey && !cm.state.keySeq) { - // First try to resolve full name (including 'Shift-'). Failing - // that, see if there is a cursor-motion command (starting with - // 'go') bound to the keyname without 'Shift-'. - return dispatchKey(cm, "Shift-" + name, e, function (b) { - return doHandleBinding(cm, b, true); - }) || dispatchKey(cm, name, e, function (b) { - if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) { - return doHandleBinding(cm, b); - } - }); - } else { - return dispatchKey(cm, name, e, function (b) { - return doHandleBinding(cm, b); - }); - } - } - - // Handle a key from the keypress event - function handleCharBinding(cm, e, ch) { - return dispatchKey(cm, "'" + ch + "'", e, function (b) { - return doHandleBinding(cm, b, true); - }); - } - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - cm.curOp.focus = activeElt(); - if (signalDOMEvent(cm, e)) { - return; - } - // IE does strange things with escape. - if (ie && ie_version < 11 && e.keyCode == 27) { - e.returnValue = false; - } - var code = e.keyCode; - cm.display.shift = code == 16 || e.shiftKey; - var handled = handleKeyBinding(cm, e); - if (presto) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) { - cm.replaceSelection("", null, "cut"); - } - } - - // Turn mouse into crosshair when Alt is held on Mac. - if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) { - showCrossHair(cm); - } - } - function showCrossHair(cm) { - var lineDiv = cm.display.lineDiv; - addClass(lineDiv, "CodeMirror-crosshair"); - function up(e) { - if (e.keyCode == 18 || !e.altKey) { - rmClass(lineDiv, "CodeMirror-crosshair"); - off(document, "keyup", up); - off(document, "mouseover", up); - } - } - on(document, "keyup", up); - on(document, "mouseover", up); - } - function onKeyUp(e) { - if (e.keyCode == 16) { - this.doc.sel.shift = false; - } - signalDOMEvent(this, e); - } - function onKeyPress(e) { - var cm = this; - if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { - return; - } - var keyCode = e.keyCode, - charCode = e.charCode; - if (presto && keyCode == lastStoppedKey) { - lastStoppedKey = null; - e_preventDefault(e); - return; - } - if (presto && (!e.which || e.which < 10) && handleKeyBinding(cm, e)) { - return; - } - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - // Some browsers fire keypress events for backspace - if (ch == "\x08") { - return; - } - if (handleCharBinding(cm, e, ch)) { - return; - } - cm.display.input.onKeyPress(e); - } - var DOUBLECLICK_DELAY = 400; - var PastClick = function (time, pos, button) { - this.time = time; - this.pos = pos; - this.button = button; - }; - PastClick.prototype.compare = function (time, pos, button) { - return this.time + DOUBLECLICK_DELAY > time && cmp(pos, this.pos) == 0 && button == this.button; - }; - var lastClick; - var lastDoubleClick; - function clickRepeat(pos, button) { - var now = +new Date(); - if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { - lastClick = lastDoubleClick = null; - return "triple"; - } else if (lastClick && lastClick.compare(now, pos, button)) { - lastDoubleClick = new PastClick(now, pos, button); - lastClick = null; - return "double"; - } else { - lastClick = new PastClick(now, pos, button); - lastDoubleClick = null; - return "single"; - } - } - - // A mouse down can be a single click, double click, triple click, - // start of selection drag, start of text drag, new cursor - // (ctrl-click), rectangle drag (alt-drag), or xwin - // middle-click-paste. Or it might be a click on something we should - // not interfere with, such as a scrollbar or widget. - function onMouseDown(e) { - var cm = this, - display = cm.display; - if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { - return; - } - display.input.ensurePolled(); - display.shift = e.shiftKey; - if (eventInWidget(display, e)) { - if (!webkit) { - // Briefly turn off draggability, to allow widgets to do - // normal dragging things. - display.scroller.draggable = false; - setTimeout(function () { - return display.scroller.draggable = true; - }, 100); - } - return; - } - if (clickInGutter(cm, e)) { - return; - } - var pos = posFromMouse(cm, e), - button = e_button(e), - repeat = pos ? clickRepeat(pos, button) : "single"; - window.focus(); - - // #3261: make sure, that we're not starting a second selection - if (button == 1 && cm.state.selectingText) { - cm.state.selectingText(e); - } - if (pos && handleMappedButton(cm, button, pos, repeat, e)) { - return; - } - if (button == 1) { - if (pos) { - leftButtonDown(cm, pos, repeat, e); - } else if (e_target(e) == display.scroller) { - e_preventDefault(e); - } - } else if (button == 2) { - if (pos) { - extendSelection(cm.doc, pos); - } - setTimeout(function () { - return display.input.focus(); - }, 20); - } else if (button == 3) { - if (captureRightClick) { - onContextMenu(cm, e); - } else { - delayBlurEvent(cm); - } - } - } - function handleMappedButton(cm, button, pos, repeat, event) { - var name = "Click"; - if (repeat == "double") { - name = "Double" + name; - } else if (repeat == "triple") { - name = "Triple" + name; - } - name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; - return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { - if (typeof bound == "string") { - bound = commands[bound]; - } - if (!bound) { - return false; - } - var done = false; - try { - if (cm.isReadOnly()) { - cm.state.suppressEdits = true; - } - done = bound(cm, pos) != Pass; - } finally { - cm.state.suppressEdits = false; - } - return done; - }); - } - function configureMouse(cm, repeat, event) { - var option = cm.getOption("configureMouse"); - var value = option ? option(cm, repeat, event) : {}; - if (value.unit == null) { - var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; - value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; - } - if (value.extend == null || cm.doc.extend) { - value.extend = cm.doc.extend || event.shiftKey; - } - if (value.addNew == null) { - value.addNew = mac ? event.metaKey : event.ctrlKey; - } - if (value.moveOnDrag == null) { - value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); - } - return value; - } - function leftButtonDown(cm, pos, repeat, event) { - if (ie) { - setTimeout(bind(ensureFocus, cm), 0); - } else { - cm.curOp.focus = activeElt(); - } - var behavior = configureMouse(cm, repeat, event); - var sel = cm.doc.sel, - contained; - if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && repeat == "single" && (contained = sel.contains(pos)) > -1 && (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) { - leftButtonStartDrag(cm, event, pos, behavior); - } else { - leftButtonSelect(cm, event, pos, behavior); - } - } - - // Start a text drag. When it ends, see if any dragging actually - // happen, and treat as a click if it didn't. - function leftButtonStartDrag(cm, event, pos, behavior) { - var display = cm.display, - moved = false; - var dragEnd = operation(cm, function (e) { - if (webkit) { - display.scroller.draggable = false; - } - cm.state.draggingText = false; - off(document, "mouseup", dragEnd); - off(document, "mousemove", mouseMove); - off(display.scroller, "dragstart", dragStart); - off(display.scroller, "drop", dragEnd); - if (!moved) { - e_preventDefault(e); - if (!behavior.addNew) { - extendSelection(cm.doc, pos, null, null, behavior.extend); - } - // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) - if (webkit || ie && ie_version == 9) { - setTimeout(function () { - document.body.focus(); - display.input.focus(); - }, 20); - } else { - display.input.focus(); - } - } - }); - var mouseMove = function (e2) { - moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; - }; - var dragStart = function () { - return moved = true; - }; - // Let the drag handler handle this. - if (webkit) { - display.scroller.draggable = true; - } - cm.state.draggingText = dragEnd; - dragEnd.copy = !behavior.moveOnDrag; - // IE's approach to draggable - if (display.scroller.dragDrop) { - display.scroller.dragDrop(); - } - on(document, "mouseup", dragEnd); - on(document, "mousemove", mouseMove); - on(display.scroller, "dragstart", dragStart); - on(display.scroller, "drop", dragEnd); - delayBlurEvent(cm); - setTimeout(function () { - return display.input.focus(); - }, 20); - } - function rangeForUnit(cm, pos, unit) { - if (unit == "char") { - return new Range(pos, pos); - } - if (unit == "word") { - return cm.findWordAt(pos); - } - if (unit == "line") { - return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); - } - var result = unit(cm, pos); - return new Range(result.from, result.to); - } - - // Normal selection, as opposed to text dragging. - function leftButtonSelect(cm, event, start, behavior) { - var display = cm.display, - doc = cm.doc; - e_preventDefault(event); - var ourRange, - ourIndex, - startSel = doc.sel, - ranges = startSel.ranges; - if (behavior.addNew && !behavior.extend) { - ourIndex = doc.sel.contains(start); - if (ourIndex > -1) { - ourRange = ranges[ourIndex]; - } else { - ourRange = new Range(start, start); - } - } else { - ourRange = doc.sel.primary(); - ourIndex = doc.sel.primIndex; - } - if (behavior.unit == "rectangle") { - if (!behavior.addNew) { - ourRange = new Range(start, start); - } - start = posFromMouse(cm, event, true, true); - ourIndex = -1; - } else { - var range = rangeForUnit(cm, start, behavior.unit); - if (behavior.extend) { - ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend); - } else { - ourRange = range; - } - } - if (!behavior.addNew) { - ourIndex = 0; - setSelection(doc, new Selection([ourRange], 0), sel_mouse); - startSel = doc.sel; - } else if (ourIndex == -1) { - ourIndex = ranges.length; - setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), { - scroll: false, - origin: "*mouse" - }); - } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { - setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), { - scroll: false, - origin: "*mouse" - }); - startSel = doc.sel; - } else { - replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); - } - var lastPos = start; - function extendTo(pos) { - if (cmp(lastPos, pos) == 0) { - return; - } - lastPos = pos; - if (behavior.unit == "rectangle") { - var ranges = [], - tabSize = cm.options.tabSize; - var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); - var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); - var left = Math.min(startCol, posCol), - right = Math.max(startCol, posCol); - for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); line <= end; line++) { - var text = getLine(doc, line).text, - leftPos = findColumn(text, left, tabSize); - if (left == right) { - ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); - } else if (text.length > leftPos) { - ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); - } - } - if (!ranges.length) { - ranges.push(new Range(start, start)); - } - setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), { - origin: "*mouse", - scroll: false - }); - cm.scrollIntoView(pos); - } else { - var oldRange = ourRange; - var range = rangeForUnit(cm, pos, behavior.unit); - var anchor = oldRange.anchor, - head; - if (cmp(range.anchor, anchor) > 0) { - head = range.head; - anchor = minPos(oldRange.from(), range.anchor); - } else { - head = range.anchor; - anchor = maxPos(oldRange.to(), range.head); - } - var ranges$1 = startSel.ranges.slice(0); - ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head); - setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse); - } - } - var editorSize = display.wrapper.getBoundingClientRect(); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); - if (!cur) { - return; - } - if (cmp(cur, lastPos) != 0) { - cm.curOp.focus = activeElt(); - extendTo(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) { - setTimeout(operation(cm, function () { - if (counter == curCount) { - extend(e); - } - }), 150); - } - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) { - setTimeout(operation(cm, function () { - if (counter != curCount) { - return; - } - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - } - function done(e) { - cm.state.selectingText = false; - counter = Infinity; - e_preventDefault(e); - display.input.focus(); - off(document, "mousemove", move); - off(document, "mouseup", up); - doc.history.lastSelOrigin = null; - } - var move = operation(cm, function (e) { - if (!e_button(e)) { - done(e); - } else { - extend(e); - } - }); - var up = operation(cm, done); - cm.state.selectingText = up; - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - // Determines whether an event happened in the gutter, and fires the - // handlers for the corresponding event. - function gutterEvent(cm, e, type, prevent) { - var mX, mY; - try { - mX = e.clientX; - mY = e.clientY; - } catch (e) { - return false; - } - if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { - return false; - } - if (prevent) { - e_preventDefault(e); - } - var display = cm.display; - var lineBox = display.lineDiv.getBoundingClientRect(); - if (mY > lineBox.bottom || !hasHandler(cm, type)) { - return e_defaultPrevented(e); - } - mY -= lineBox.top - display.viewOffset; - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && g.getBoundingClientRect().right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signal(cm, type, cm, line, gutter, e); - return e_defaultPrevented(e); - } - } - } - function clickInGutter(cm, e) { - return gutterEvent(cm, e, "gutterClick", true); - } - - // CONTEXT MENU HANDLING - - // To make the context menu work, we need to briefly unhide the - // textarea (making it as unobtrusive as possible) to let the - // right-click take effect on it. - function onContextMenu(cm, e) { - if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { - return; - } - if (signalDOMEvent(cm, e, "contextmenu")) { - return; - } - cm.display.input.onContextMenu(e); - } - function contextMenuInGutter(cm, e) { - if (!hasHandler(cm, "gutterContextMenu")) { - return false; - } - return gutterEvent(cm, e, "gutterContextMenu", false); - } - function themeChanged(cm) { - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - clearCaches(cm); - } - var Init = { - toString: function () { - return "CodeMirror.Init"; - } - }; - var defaults = {}; - var optionHandlers = {}; - function defineOptions(CodeMirror) { - var optionHandlers = CodeMirror.optionHandlers; - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) { - optionHandlers[name] = notOnInit ? function (cm, val, old) { - if (old != Init) { - handle(cm, val, old); - } - } : handle; - } - } - CodeMirror.defineOption = option; - - // Passed to option handlers when there is no old value. - CodeMirror.Init = Init; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function (cm, val) { - return cm.setValue(val); - }, true); - option("mode", null, function (cm, val) { - cm.doc.modeOption = val; - loadMode(cm); - }, true); - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function (cm) { - resetModeState(cm); - clearCaches(cm); - regChange(cm); - }, true); - option("lineSeparator", null, function (cm, val) { - cm.doc.lineSep = val; - if (!val) { - return; - } - var newBreaks = [], - lineNo = cm.doc.first; - cm.doc.iter(function (line) { - for (var pos = 0;;) { - var found = line.text.indexOf(val, pos); - if (found == -1) { - break; - } - pos = found + val.length; - newBreaks.push(Pos(lineNo, found)); - } - lineNo++; - }); - for (var i = newBreaks.length - 1; i >= 0; i--) { - replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); - } - }); - option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { - cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); - if (old != Init) { - cm.refresh(); - } - }); - option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { - return cm.refresh(); - }, true); - option("electricChars", true); - option("inputStyle", mobile ? "contenteditable" : "textarea", function () { - throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME - }, true); - option("spellcheck", false, function (cm, val) { - return cm.getInputField().spellcheck = val; - }, true); - option("rtlMoveVisually", !windows); - option("wholeLineUpdateBefore", true); - option("theme", "default", function (cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", function (cm, val, old) { - var next = getKeyMap(val); - var prev = old != Init && getKeyMap(old); - if (prev && prev.detach) { - prev.detach(cm, next); - } - if (next.attach) { - next.attach(cm, prev || null); - } - }); - option("extraKeys", null); - option("configureMouse", null); - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function (cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("fixedGutter", true, function (cm, val) { - cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; - cm.refresh(); - }, true); - option("coverGutterNextToScrollbar", false, function (cm) { - return updateScrollbars(cm); - }, true); - option("scrollbarStyle", "native", function (cm) { - initScrollbars(cm); - updateScrollbars(cm); - cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); - cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); - }, true); - option("lineNumbers", false, function (cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function (integer) { - return integer; - }, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - option("resetSelectionOnContextMenu", true); - option("lineWiseCopyCut", true); - option("pasteLinesPerSelection", true); - option("readOnly", false, function (cm, val) { - if (val == "nocursor") { - onBlur(cm); - cm.display.input.blur(); - } - cm.display.input.readOnlyChanged(val); - }); - option("disableInput", false, function (cm, val) { - if (!val) { - cm.display.input.reset(); - } - }, true); - option("dragDrop", true, dragDropChanged); - option("allowDropFileTypes", null); - option("cursorBlinkRate", 530); - option("cursorScrollMargin", 0); - option("cursorHeight", 1, updateSelection, true); - option("singleCursorHeightPerLine", true, updateSelection, true); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true, resetModeState, true); - option("addModeClass", false, resetModeState, true); - option("pollInterval", 100); - option("undoDepth", 200, function (cm, val) { - return cm.doc.history.undoDepth = val; - }); - option("historyEventDelay", 1250); - option("viewportMargin", 10, function (cm) { - return cm.refresh(); - }, true); - option("maxHighlightLength", 10000, resetModeState, true); - option("moveInputWithCursor", true, function (cm, val) { - if (!val) { - cm.display.input.resetPosition(); - } - }); - option("tabindex", null, function (cm, val) { - return cm.display.input.getField().tabIndex = val || ""; - }); - option("autofocus", null); - option("direction", "ltr", function (cm, val) { - return cm.doc.setDirection(val); - }, true); - } - function guttersChanged(cm) { - updateGutters(cm); - regChange(cm); - alignHorizontally(cm); - } - function dragDropChanged(cm, value, old) { - var wasOn = old && old != Init; - if (!value != !wasOn) { - var funcs = cm.display.dragFunctions; - var toggle = value ? on : off; - toggle(cm.display.scroller, "dragstart", funcs.start); - toggle(cm.display.scroller, "dragenter", funcs.enter); - toggle(cm.display.scroller, "dragover", funcs.over); - toggle(cm.display.scroller, "dragleave", funcs.leave); - toggle(cm.display.scroller, "drop", funcs.drop); - } - } - function wrappingChanged(cm) { - if (cm.options.lineWrapping) { - addClass(cm.display.wrapper, "CodeMirror-wrap"); - cm.display.sizer.style.minWidth = ""; - cm.display.sizerWidth = null; - } else { - rmClass(cm.display.wrapper, "CodeMirror-wrap"); - findMaxLine(cm); - } - estimateLineHeights(cm); - regChange(cm); - clearCaches(cm); - setTimeout(function () { - return updateScrollbars(cm); - }, 100); - } - - // A CodeMirror instance represents an editor. This is the object - // that user code is usually dealing with. - - function CodeMirror(place, options) { - var this$1 = this; - if (!(this instanceof CodeMirror)) { - return new CodeMirror(place, options); - } - this.options = options = options ? copyObj(options) : {}; - // Determine effective options based on given values and defaults. - copyObj(defaults, options, false); - setGuttersForLineNumbers(options); - var doc = options.value; - if (typeof doc == "string") { - doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); - } - this.doc = doc; - var input = new CodeMirror.inputStyles[options.inputStyle](this); - var display = this.display = new Display(place, doc, input); - display.wrapper.CodeMirror = this; - updateGutters(this); - themeChanged(this); - if (options.lineWrapping) { - this.display.wrapper.className += " CodeMirror-wrap"; - } - initScrollbars(this); - this.state = { - keyMaps: [], - // stores maps added by addKeyMap - overlays: [], - // highlighting overlays, as added by addOverlay - modeGen: 0, - // bumped when mode/overlay changes, used to invalidate highlighting info - overwrite: false, - delayingBlurEvent: false, - focused: false, - suppressEdits: false, - // used to disable editing during key handlers when in readOnly mode - pasteIncoming: false, - cutIncoming: false, - // help recognize paste/cut edits in input.poll - selectingText: false, - draggingText: false, - highlight: new Delayed(), - // stores highlight worker timeout - keySeq: null, - // Unfinished key sequence - specialChars: null - }; - if (options.autofocus && !mobile) { - display.input.focus(); - } - - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie && ie_version < 11) { - setTimeout(function () { - return this$1.display.input.reset(true); - }, 20); - } - registerEventHandlers(this); - ensureGlobalHandlers(); - startOperation(this); - this.curOp.forceUpdate = true; - attachDoc(this, doc); - if (options.autofocus && !mobile || this.hasFocus()) { - setTimeout(bind(onFocus, this), 20); - } else { - onBlur(this); - } - for (var opt in optionHandlers) { - if (optionHandlers.hasOwnProperty(opt)) { - optionHandlers[opt](this$1, options[opt], Init); - } - } - maybeUpdateLineNumberWidth(this); - if (options.finishInit) { - options.finishInit(this); - } - for (var i = 0; i < initHooks.length; ++i) { - initHooks[i](this$1); - } - endOperation(this); - // Suppress optimizelegibility in Webkit, since it breaks text - // measuring on line wrapping boundaries. - if (webkit && options.lineWrapping && getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") { - display.lineDiv.style.textRendering = "auto"; - } - } - - // The default configuration options. - CodeMirror.defaults = defaults; - // Functions to run when options are changed. - CodeMirror.optionHandlers = optionHandlers; - - // Attach the necessary event handlers when initializing the editor - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - // Older IE's will not fire a second mousedown for a double click - if (ie && ie_version < 11) { - on(d.scroller, "dblclick", operation(cm, function (e) { - if (signalDOMEvent(cm, e)) { - return; - } - var pos = posFromMouse(cm, e); - if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { - return; - } - e_preventDefault(e); - var word = cm.findWordAt(pos); - extendSelection(cm.doc, word.anchor, word.head); - })); - } else { - on(d.scroller, "dblclick", function (e) { - return signalDOMEvent(cm, e) || e_preventDefault(e); - }); - } - // Some browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for these browsers. - if (!captureRightClick) { - on(d.scroller, "contextmenu", function (e) { - return onContextMenu(cm, e); - }); - } - - // Used to suppress mouse event handling when a touch happens - var touchFinished, - prevTouch = { - end: 0 - }; - function finishTouch() { - if (d.activeTouch) { - touchFinished = setTimeout(function () { - return d.activeTouch = null; - }, 1000); - prevTouch = d.activeTouch; - prevTouch.end = +new Date(); - } - } - function isMouseLikeTouchEvent(e) { - if (e.touches.length != 1) { - return false; - } - var touch = e.touches[0]; - return touch.radiusX <= 1 && touch.radiusY <= 1; - } - function farAway(touch, other) { - if (other.left == null) { - return true; - } - var dx = other.left - touch.left, - dy = other.top - touch.top; - return dx * dx + dy * dy > 20 * 20; - } - on(d.scroller, "touchstart", function (e) { - if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { - d.input.ensurePolled(); - clearTimeout(touchFinished); - var now = +new Date(); - d.activeTouch = { - start: now, - moved: false, - prev: now - prevTouch.end <= 300 ? prevTouch : null - }; - if (e.touches.length == 1) { - d.activeTouch.left = e.touches[0].pageX; - d.activeTouch.top = e.touches[0].pageY; - } - } - }); - on(d.scroller, "touchmove", function () { - if (d.activeTouch) { - d.activeTouch.moved = true; - } - }); - on(d.scroller, "touchend", function (e) { - var touch = d.activeTouch; - if (touch && !eventInWidget(d, e) && touch.left != null && !touch.moved && new Date() - touch.start < 300) { - var pos = cm.coordsChar(d.activeTouch, "page"), - range; - if (!touch.prev || farAway(touch, touch.prev)) - // Single tap - { - range = new Range(pos, pos); - } else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) - // Double tap - { - range = cm.findWordAt(pos); - } else - // Triple tap - { - range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); - } - cm.setSelection(range.anchor, range.head); - cm.focus(); - e_preventDefault(e); - } - finishTouch(); - }); - on(d.scroller, "touchcancel", finishTouch); - - // Sync scrolling between fake scrollbars and real scrollable - // area, ensure viewport is updated when scrolling. - on(d.scroller, "scroll", function () { - if (d.scroller.clientHeight) { - updateScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); - } - }); - - // Listen to wheel events in order to try and update the viewport on time. - on(d.scroller, "mousewheel", function (e) { - return onScrollWheel(cm, e); - }); - on(d.scroller, "DOMMouseScroll", function (e) { - return onScrollWheel(cm, e); - }); - - // Prevent wrapper from ever scrolling - on(d.wrapper, "scroll", function () { - return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; - }); - d.dragFunctions = { - enter: function (e) { - if (!signalDOMEvent(cm, e)) { - e_stop(e); - } - }, - over: function (e) { - if (!signalDOMEvent(cm, e)) { - onDragOver(cm, e); - e_stop(e); - } - }, - start: function (e) { - return onDragStart(cm, e); - }, - drop: operation(cm, onDrop), - leave: function (e) { - if (!signalDOMEvent(cm, e)) { - clearDragCursor(cm); - } - } - }; - var inp = d.input.getField(); - on(inp, "keyup", function (e) { - return onKeyUp.call(cm, e); - }); - on(inp, "keydown", operation(cm, onKeyDown)); - on(inp, "keypress", operation(cm, onKeyPress)); - on(inp, "focus", function (e) { - return onFocus(cm, e); - }); - on(inp, "blur", function (e) { - return onBlur(cm, e); - }); - } - var initHooks = []; - CodeMirror.defineInitHook = function (f) { - return initHooks.push(f); - }; - - // Indent the given line. The how parameter can be "smart", - // "add"/null, "subtract", or "prev". When aggressive is false - // (typically set to true for forced single-line indents), empty - // lines are not indented, and places where the mode returns Pass - // are left alone. - function indentLine(cm, n, how, aggressive) { - var doc = cm.doc, - state; - if (how == null) { - how = "add"; - } - if (how == "smart") { - // Fall back to "prev" when the mode doesn't have an indentation - // method. - if (!doc.mode.indent) { - how = "prev"; - } else { - state = getContextBefore(cm, n).state; - } - } - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), - curSpace = countColumn(line.text, null, tabSize); - if (line.stateAfter) { - line.stateAfter = null; - } - var curSpaceString = line.text.match(/^\s*/)[0], - indentation; - if (!aggressive && !/\S/.test(line.text)) { - indentation = 0; - how = "not"; - } else if (how == "smart") { - indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass || indentation > 150) { - if (!aggressive) { - return; - } - how = "prev"; - } - } - if (how == "prev") { - if (n > doc.first) { - indentation = countColumn(getLine(doc, n - 1).text, null, tabSize); - } else { - indentation = 0; - } - } else if (how == "add") { - indentation = curSpace + cm.options.indentUnit; - } else if (how == "subtract") { - indentation = curSpace - cm.options.indentUnit; - } else if (typeof how == "number") { - indentation = curSpace + how; - } - indentation = Math.max(0, indentation); - var indentString = "", - pos = 0; - if (cm.options.indentWithTabs) { - for (var i = Math.floor(indentation / tabSize); i; --i) { - pos += tabSize; - indentString += "\t"; - } - } - if (pos < indentation) { - indentString += spaceStr(indentation - pos); - } - if (indentString != curSpaceString) { - replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); - line.stateAfter = null; - return true; - } else { - // Ensure that, if the cursor was in the whitespace at the start - // of the line, it is moved to the end of that space. - for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { - var range = doc.sel.ranges[i$1]; - if (range.head.line == n && range.head.ch < curSpaceString.length) { - var pos$1 = Pos(n, curSpaceString.length); - replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); - break; - } - } - } - } - - // This will be set to a {lineWise: bool, text: [string]} object, so - // that, when pasting, we know what kind of selections the copied - // text was made out of. - var lastCopied = null; - function setLastCopied(newLastCopied) { - lastCopied = newLastCopied; - } - function applyTextInput(cm, inserted, deleted, sel, origin) { - var doc = cm.doc; - cm.display.shift = false; - if (!sel) { - sel = doc.sel; - } - var paste = cm.state.pasteIncoming || origin == "paste"; - var textLines = splitLinesAuto(inserted), - multiPaste = null; - // When pasing N lines into N selections, insert one line per selection - if (paste && sel.ranges.length > 1) { - if (lastCopied && lastCopied.text.join("\n") == inserted) { - if (sel.ranges.length % lastCopied.text.length == 0) { - multiPaste = []; - for (var i = 0; i < lastCopied.text.length; i++) { - multiPaste.push(doc.splitLines(lastCopied.text[i])); - } - } - } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { - multiPaste = map(textLines, function (l) { - return [l]; - }); - } - } - var updateInput; - // Normal behavior is to insert the new text into every selection - for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { - var range = sel.ranges[i$1]; - var from = range.from(), - to = range.to(); - if (range.empty()) { - if (deleted && deleted > 0) - // Handle deletion - { - from = Pos(from.line, from.ch - deleted); - } else if (cm.state.overwrite && !paste) - // Handle overwrite - { - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); - } else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) { - from = to = Pos(from.line, 0); - } - } - updateInput = cm.curOp.updateInput; - var changeEvent = { - from: from, - to: to, - text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, - origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input") - }; - makeChange(cm.doc, changeEvent); - signalLater(cm, "inputRead", cm, changeEvent); - } - if (inserted && !paste) { - triggerElectric(cm, inserted); - } - ensureCursorVisible(cm); - cm.curOp.updateInput = updateInput; - cm.curOp.typing = true; - cm.state.pasteIncoming = cm.state.cutIncoming = false; - } - function handlePaste(e, cm) { - var pasted = e.clipboardData && e.clipboardData.getData("Text"); - if (pasted) { - e.preventDefault(); - if (!cm.isReadOnly() && !cm.options.disableInput) { - runInOp(cm, function () { - return applyTextInput(cm, pasted, 0, null, "paste"); - }); - } - return true; - } - } - function triggerElectric(cm, inserted) { - // When an 'electric' character is inserted, immediately trigger a reindent - if (!cm.options.electricChars || !cm.options.smartIndent) { - return; - } - var sel = cm.doc.sel; - for (var i = sel.ranges.length - 1; i >= 0; i--) { - var range = sel.ranges[i]; - if (range.head.ch > 100 || i && sel.ranges[i - 1].head.line == range.head.line) { - continue; - } - var mode = cm.getModeAt(range.head); - var indented = false; - if (mode.electricChars) { - for (var j = 0; j < mode.electricChars.length; j++) { - if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { - indented = indentLine(cm, range.head.line, "smart"); - break; - } - } - } else if (mode.electricInput) { - if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) { - indented = indentLine(cm, range.head.line, "smart"); - } - } - if (indented) { - signalLater(cm, "electricInput", cm, range.head.line); - } - } - } - function copyableRanges(cm) { - var text = [], - ranges = []; - for (var i = 0; i < cm.doc.sel.ranges.length; i++) { - var line = cm.doc.sel.ranges[i].head.line; - var lineRange = { - anchor: Pos(line, 0), - head: Pos(line + 1, 0) - }; - ranges.push(lineRange); - text.push(cm.getRange(lineRange.anchor, lineRange.head)); - } - return { - text: text, - ranges: ranges - }; - } - function disableBrowserMagic(field, spellcheck) { - field.setAttribute("autocorrect", "off"); - field.setAttribute("autocapitalize", "off"); - field.setAttribute("spellcheck", !!spellcheck); - } - function hiddenTextarea() { - var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); - var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The textarea is kept positioned near the cursor to prevent the - // fact that it'll be scrolled into view on input from scrolling - // our fake cursor out of view. On webkit, when wrap=off, paste is - // very slow. So make the area wide instead. - if (webkit) { - te.style.width = "1000px"; - } else { - te.setAttribute("wrap", "off"); - } - // If border: 0; -- iOS fails to open keyboard (issue #1287) - if (ios) { - te.style.border = "1px solid black"; - } - disableBrowserMagic(te); - return div; - } - - // The publicly visible API. Note that methodOp(f) means - // 'wrap f in an operation, performed on its `this` parameter'. - - // This is not the complete set of editor methods. Most of the - // methods defined on the Doc type are also injected into - // CodeMirror.prototype, for backwards compatibility and - // convenience. - - function addEditorMethods(CodeMirror) { - var optionHandlers = CodeMirror.optionHandlers; - var helpers = CodeMirror.helpers = {}; - CodeMirror.prototype = { - constructor: CodeMirror, - focus: function () { - window.focus(); - this.display.input.focus(); - }, - setOption: function (option, value) { - var options = this.options, - old = options[option]; - if (options[option] == value && option != "mode") { - return; - } - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) { - operation(this, optionHandlers[option])(this, value, old); - } - signal(this, "optionChange", this, option); - }, - getOption: function (option) { - return this.options[option]; - }, - getDoc: function () { - return this.doc; - }, - addKeyMap: function (map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); - }, - removeKeyMap: function (map) { - var maps = this.state.keyMaps; - for (var i = 0; i < maps.length; ++i) { - if (maps[i] == map || maps[i].name == map) { - maps.splice(i, 1); - return true; - } - } - }, - addOverlay: methodOp(function (spec, options) { - var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); - if (mode.startState) { - throw new Error("Overlays may not be stateful."); - } - insertSorted(this.state.overlays, { - mode: mode, - modeSpec: spec, - opaque: options && options.opaque, - priority: options && options.priority || 0 - }, function (overlay) { - return overlay.priority; - }); - this.state.modeGen++; - regChange(this); - }), - removeOverlay: methodOp(function (spec) { - var this$1 = this; - var overlays = this.state.overlays; - for (var i = 0; i < overlays.length; ++i) { - var cur = overlays[i].modeSpec; - if (cur == spec || typeof spec == "string" && cur.name == spec) { - overlays.splice(i, 1); - this$1.state.modeGen++; - regChange(this$1); - return; - } - } - }), - indentLine: methodOp(function (n, dir, aggressive) { - if (typeof dir != "string" && typeof dir != "number") { - if (dir == null) { - dir = this.options.smartIndent ? "smart" : "prev"; - } else { - dir = dir ? "add" : "subtract"; - } - } - if (isLine(this.doc, n)) { - indentLine(this, n, dir, aggressive); - } - }), - indentSelection: methodOp(function (how) { - var this$1 = this; - var ranges = this.doc.sel.ranges, - end = -1; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (!range.empty()) { - var from = range.from(), - to = range.to(); - var start = Math.max(end, from.line); - end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; - for (var j = start; j < end; ++j) { - indentLine(this$1, j, how); - } - var newRanges = this$1.doc.sel.ranges; - if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) { - replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); - } - } else if (range.head.line > end) { - indentLine(this$1, range.head.line, how, true); - end = range.head.line; - if (i == this$1.doc.sel.primIndex) { - ensureCursorVisible(this$1); - } - } - } - }), - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function (pos, precise) { - return takeToken(this, pos, precise); - }, - getLineTokens: function (line, precise) { - return takeToken(this, Pos(line), precise, true); - }, - getTokenTypeAt: function (pos) { - pos = clipPos(this.doc, pos); - var styles = getLineStyles(this, getLine(this.doc, pos.line)); - var before = 0, - after = (styles.length - 1) / 2, - ch = pos.ch; - var type; - if (ch == 0) { - type = styles[2]; - } else { - for (;;) { - var mid = before + after >> 1; - if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { - after = mid; - } else if (styles[mid * 2 + 1] < ch) { - before = mid + 1; - } else { - type = styles[mid * 2 + 2]; - break; - } - } - } - var cut = type ? type.indexOf("overlay ") : -1; - return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); - }, - getModeAt: function (pos) { - var mode = this.doc.mode; - if (!mode.innerMode) { - return mode; - } - return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; - }, - getHelper: function (pos, type) { - return this.getHelpers(pos, type)[0]; - }, - getHelpers: function (pos, type) { - var this$1 = this; - var found = []; - if (!helpers.hasOwnProperty(type)) { - return found; - } - var help = helpers[type], - mode = this.getModeAt(pos); - if (typeof mode[type] == "string") { - if (help[mode[type]]) { - found.push(help[mode[type]]); - } - } else if (mode[type]) { - for (var i = 0; i < mode[type].length; i++) { - var val = help[mode[type][i]]; - if (val) { - found.push(val); - } - } - } else if (mode.helperType && help[mode.helperType]) { - found.push(help[mode.helperType]); - } else if (help[mode.name]) { - found.push(help[mode.name]); - } - for (var i$1 = 0; i$1 < help._global.length; i$1++) { - var cur = help._global[i$1]; - if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) { - found.push(cur.val); - } - } - return found; - }, - getStateAfter: function (line, precise) { - var doc = this.doc; - line = clipLine(doc, line == null ? doc.first + doc.size - 1 : line); - return getContextBefore(this, line + 1, precise).state; - }, - cursorCoords: function (start, mode) { - var pos, - range = this.doc.sel.primary(); - if (start == null) { - pos = range.head; - } else if (typeof start == "object") { - pos = clipPos(this.doc, start); - } else { - pos = start ? range.from() : range.to(); - } - return cursorCoords(this, pos, mode || "page"); - }, - charCoords: function (pos, mode) { - return charCoords(this, clipPos(this.doc, pos), mode || "page"); - }, - coordsChar: function (coords, mode) { - coords = fromCoordSystem(this, coords, mode || "page"); - return coordsChar(this, coords.left, coords.top); - }, - lineAtHeight: function (height, mode) { - height = fromCoordSystem(this, { - top: height, - left: 0 - }, mode || "page").top; - return lineAtHeight(this.doc, height + this.display.viewOffset); - }, - heightAtLine: function (line, mode, includeWidgets) { - var end = false, - lineObj; - if (typeof line == "number") { - var last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) { - line = this.doc.first; - } else if (line > last) { - line = last; - end = true; - } - lineObj = getLine(this.doc, line); - } else { - lineObj = line; - } - return intoCoordSystem(this, lineObj, { - top: 0, - left: 0 - }, mode || "page", includeWidgets || end).top + (end ? this.doc.height - heightAtLine(lineObj) : 0); - }, - defaultTextHeight: function () { - return textHeight(this.display); - }, - defaultCharWidth: function () { - return charWidth(this.display); - }, - getViewport: function () { - return { - from: this.display.viewFrom, - to: this.display.viewTo - }; - }, - addWidget: function (pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.doc, pos)); - var top = pos.bottom, - left = pos.left; - node.style.position = "absolute"; - node.setAttribute("cm-ignore-events", "true"); - this.display.input.setUneditable(node); - display.sizer.appendChild(node); - if (vert == "over") { - top = pos.top; - } else if (vert == "above" || vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - // Default to positioning above (if specified and possible); otherwise default to positioning below - if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) { - top = pos.top - node.offsetHeight; - } else if (pos.bottom + node.offsetHeight <= vspace) { - top = pos.bottom; - } - if (left + node.offsetWidth > hspace) { - left = hspace - node.offsetWidth; - } - } - node.style.top = top + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") { - left = 0; - } else if (horiz == "middle") { - left = (display.sizer.clientWidth - node.offsetWidth) / 2; - } - node.style.left = left + "px"; - } - if (scroll) { - scrollIntoView(this, { - left: left, - top: top, - right: left + node.offsetWidth, - bottom: top + node.offsetHeight - }); - } - }, - triggerOnKeyDown: methodOp(onKeyDown), - triggerOnKeyPress: methodOp(onKeyPress), - triggerOnKeyUp: onKeyUp, - triggerOnMouseDown: methodOp(onMouseDown), - execCommand: function (cmd) { - if (commands.hasOwnProperty(cmd)) { - return commands[cmd].call(null, this); - } - }, - triggerElectric: methodOp(function (text) { - triggerElectric(this, text); - }), - findPosH: function (from, amount, unit, visually) { - var this$1 = this; - var dir = 1; - if (amount < 0) { - dir = -1; - amount = -amount; - } - var cur = clipPos(this.doc, from); - for (var i = 0; i < amount; ++i) { - cur = findPosH(this$1.doc, cur, dir, unit, visually); - if (cur.hitSide) { - break; - } - } - return cur; - }, - moveH: methodOp(function (dir, unit) { - var this$1 = this; - this.extendSelectionsBy(function (range) { - if (this$1.display.shift || this$1.doc.extend || range.empty()) { - return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually); - } else { - return dir < 0 ? range.from() : range.to(); - } - }, sel_move); - }), - deleteH: methodOp(function (dir, unit) { - var sel = this.doc.sel, - doc = this.doc; - if (sel.somethingSelected()) { - doc.replaceSelection("", null, "+delete"); - } else { - deleteNearSelection(this, function (range) { - var other = findPosH(doc, range.head, dir, unit, false); - return dir < 0 ? { - from: other, - to: range.head - } : { - from: range.head, - to: other - }; - }); - } - }), - findPosV: function (from, amount, unit, goalColumn) { - var this$1 = this; - var dir = 1, - x = goalColumn; - if (amount < 0) { - dir = -1; - amount = -amount; - } - var cur = clipPos(this.doc, from); - for (var i = 0; i < amount; ++i) { - var coords = cursorCoords(this$1, cur, "div"); - if (x == null) { - x = coords.left; - } else { - coords.left = x; - } - cur = findPosV(this$1, coords, dir, unit); - if (cur.hitSide) { - break; - } - } - return cur; - }, - moveV: methodOp(function (dir, unit) { - var this$1 = this; - var doc = this.doc, - goals = []; - var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); - doc.extendSelectionsBy(function (range) { - if (collapse) { - return dir < 0 ? range.from() : range.to(); - } - var headPos = cursorCoords(this$1, range.head, "div"); - if (range.goalColumn != null) { - headPos.left = range.goalColumn; - } - goals.push(headPos.left); - var pos = findPosV(this$1, headPos, dir, unit); - if (unit == "page" && range == doc.sel.primary()) { - addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); - } - return pos; - }, sel_move); - if (goals.length) { - for (var i = 0; i < doc.sel.ranges.length; i++) { - doc.sel.ranges[i].goalColumn = goals[i]; - } - } - }), - // Find the word at the given position (as returned by coordsChar). - findWordAt: function (pos) { - var doc = this.doc, - line = getLine(doc, pos.line).text; - var start = pos.ch, - end = pos.ch; - if (line) { - var helper = this.getHelper(pos, "wordChars"); - if ((pos.sticky == "before" || end == line.length) && start) { - --start; - } else { - ++end; - } - var startChar = line.charAt(start); - var check = isWordChar(startChar, helper) ? function (ch) { - return isWordChar(ch, helper); - } : /\s/.test(startChar) ? function (ch) { - return /\s/.test(ch); - } : function (ch) { - return !/\s/.test(ch) && !isWordChar(ch); - }; - while (start > 0 && check(line.charAt(start - 1))) { - --start; - } - while (end < line.length && check(line.charAt(end))) { - ++end; - } - } - return new Range(Pos(pos.line, start), Pos(pos.line, end)); - }, - toggleOverwrite: function (value) { - if (value != null && value == this.state.overwrite) { - return; - } - if (this.state.overwrite = !this.state.overwrite) { - addClass(this.display.cursorDiv, "CodeMirror-overwrite"); - } else { - rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); - } - signal(this, "overwriteToggle", this, this.state.overwrite); - }, - hasFocus: function () { - return this.display.input.getField() == activeElt(); - }, - isReadOnly: function () { - return !!(this.options.readOnly || this.doc.cantEdit); - }, - scrollTo: methodOp(function (x, y) { - scrollToCoords(this, x, y); - }), - getScrollInfo: function () { - var scroller = this.display.scroller; - return { - left: scroller.scrollLeft, - top: scroller.scrollTop, - height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, - width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, - clientHeight: displayHeight(this), - clientWidth: displayWidth(this) - }; - }, - scrollIntoView: methodOp(function (range, margin) { - if (range == null) { - range = { - from: this.doc.sel.primary().head, - to: null - }; - if (margin == null) { - margin = this.options.cursorScrollMargin; - } - } else if (typeof range == "number") { - range = { - from: Pos(range, 0), - to: null - }; - } else if (range.from == null) { - range = { - from: range, - to: null - }; - } - if (!range.to) { - range.to = range.from; - } - range.margin = margin || 0; - if (range.from.line != null) { - scrollToRange(this, range); - } else { - scrollToCoordsRange(this, range.from, range.to, range.margin); - } - }), - setSize: methodOp(function (width, height) { - var this$1 = this; - var interpret = function (val) { - return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; - }; - if (width != null) { - this.display.wrapper.style.width = interpret(width); - } - if (height != null) { - this.display.wrapper.style.height = interpret(height); - } - if (this.options.lineWrapping) { - clearLineMeasurementCache(this); - } - var lineNo = this.display.viewFrom; - this.doc.iter(lineNo, this.display.viewTo, function (line) { - if (line.widgets) { - for (var i = 0; i < line.widgets.length; i++) { - if (line.widgets[i].noHScroll) { - regLineChange(this$1, lineNo, "widget"); - break; - } - } - } - ++lineNo; - }); - this.curOp.forceUpdate = true; - signal(this, "refresh", this); - }), - operation: function (f) { - return runInOp(this, f); - }, - startOperation: function () { - return startOperation(this); - }, - endOperation: function () { - return endOperation(this); - }, - refresh: methodOp(function () { - var oldHeight = this.display.cachedTextHeight; - regChange(this); - this.curOp.forceUpdate = true; - clearCaches(this); - scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); - updateGutterSpace(this); - if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) { - estimateLineHeights(this); - } - signal(this, "refresh", this); - }), - swapDoc: methodOp(function (doc) { - var old = this.doc; - old.cm = null; - attachDoc(this, doc); - clearCaches(this); - this.display.input.reset(); - scrollToCoords(this, doc.scrollLeft, doc.scrollTop); - this.curOp.forceScroll = true; - signalLater(this, "swapDoc", this, old); - return old; - }), - getInputField: function () { - return this.display.input.getField(); - }, - getWrapperElement: function () { - return this.display.wrapper; - }, - getScrollerElement: function () { - return this.display.scroller; - }, - getGutterElement: function () { - return this.display.gutters; - } - }; - eventMixin(CodeMirror); - CodeMirror.registerHelper = function (type, name, value) { - if (!helpers.hasOwnProperty(type)) { - helpers[type] = CodeMirror[type] = { - _global: [] - }; - } - helpers[type][name] = value; - }; - CodeMirror.registerGlobalHelper = function (type, name, predicate, value) { - CodeMirror.registerHelper(type, name, value); - helpers[type]._global.push({ - pred: predicate, - val: value - }); - }; - } - - // Used for horizontal relative motion. Dir is -1 or 1 (left or - // right), unit can be "char", "column" (like char, but doesn't - // cross line boundaries), "word" (across next word), or "group" (to - // the start of next group of word or non-word-non-whitespace - // chars). The visually param controls whether, in right-to-left - // text, direction 1 means to move towards the next index in the - // string, or towards the character to the right of the current - // position. The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosH(doc, pos, dir, unit, visually) { - var oldPos = pos; - var origDir = dir; - var lineObj = getLine(doc, pos.line); - function findNextLine() { - var l = pos.line + dir; - if (l < doc.first || l >= doc.first + doc.size) { - return false; - } - pos = new Pos(l, pos.ch, pos.sticky); - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next; - if (visually) { - next = moveVisually(doc.cm, lineObj, pos, dir); - } else { - next = moveLogically(lineObj, pos, dir); - } - if (next == null) { - if (!boundToLine && findNextLine()) { - pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); - } else { - return false; - } - } else { - pos = next; - } - return true; - } - if (unit == "char") { - moveOnce(); - } else if (unit == "column") { - moveOnce(true); - } else if (unit == "word" || unit == "group") { - var sawType = null, - group = unit == "group"; - var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); - for (var first = true;; first = false) { - if (dir < 0 && !moveOnce(!first)) { - break; - } - var cur = lineObj.text.charAt(pos.ch) || "\n"; - var type = isWordChar(cur, helper) ? "w" : group && cur == "\n" ? "n" : !group || /\s/.test(cur) ? null : "p"; - if (group && !first && !type) { - type = "s"; - } - if (sawType && sawType != type) { - if (dir < 0) { - dir = 1; - moveOnce(); - pos.sticky = "after"; - } - break; - } - if (type) { - sawType = type; - } - if (dir > 0 && !moveOnce(!first)) { - break; - } - } - } - var result = skipAtomic(doc, pos, oldPos, origDir, true); - if (equalCursorPos(oldPos, result)) { - result.hitSide = true; - } - return result; - } - - // For relative vertical movement. Dir may be -1 or 1. Unit can be - // "page" or "line". The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosV(cm, pos, dir, unit) { - var doc = cm.doc, - x = pos.left, - y; - if (unit == "page") { - var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); - y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - var target; - for (;;) { - target = coordsChar(cm, x, y); - if (!target.outside) { - break; - } - if (dir < 0 ? y <= 0 : y >= doc.height) { - target.hitSide = true; - break; - } - y += dir * 5; - } - return target; - } - - // CONTENTEDITABLE INPUT STYLE - - var ContentEditableInput = function (cm) { - this.cm = cm; - this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; - this.polling = new Delayed(); - this.composing = null; - this.gracePeriod = false; - this.readDOMTimeout = null; - }; - ContentEditableInput.prototype.init = function (display) { - var this$1 = this; - var input = this, - cm = input.cm; - var div = input.div = display.lineDiv; - disableBrowserMagic(div, cm.options.spellcheck); - on(div, "paste", function (e) { - if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { - return; - } - // IE doesn't fire input events, so we schedule a read for the pasted content in this way - if (ie_version <= 11) { - setTimeout(operation(cm, function () { - return this$1.updateFromDOM(); - }), 20); - } - }); - on(div, "compositionstart", function (e) { - this$1.composing = { - data: e.data, - done: false - }; - }); - on(div, "compositionupdate", function (e) { - if (!this$1.composing) { - this$1.composing = { - data: e.data, - done: false - }; - } - }); - on(div, "compositionend", function (e) { - if (this$1.composing) { - if (e.data != this$1.composing.data) { - this$1.readFromDOMSoon(); - } - this$1.composing.done = true; - } - }); - on(div, "touchstart", function () { - return input.forceCompositionEnd(); - }); - on(div, "input", function () { - if (!this$1.composing) { - this$1.readFromDOMSoon(); - } - }); - function onCopyCut(e) { - if (signalDOMEvent(cm, e)) { - return; - } - if (cm.somethingSelected()) { - setLastCopied({ - lineWise: false, - text: cm.getSelections() - }); - if (e.type == "cut") { - cm.replaceSelection("", null, "cut"); - } - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - setLastCopied({ - lineWise: true, - text: ranges.text - }); - if (e.type == "cut") { - cm.operation(function () { - cm.setSelections(ranges.ranges, 0, sel_dontScroll); - cm.replaceSelection("", null, "cut"); - }); - } - } - if (e.clipboardData) { - e.clipboardData.clearData(); - var content = lastCopied.text.join("\n"); - // iOS exposes the clipboard API, but seems to discard content inserted into it - e.clipboardData.setData("Text", content); - if (e.clipboardData.getData("Text") == content) { - e.preventDefault(); - return; - } - } - // Old-fashioned briefly-focus-a-textarea hack - var kludge = hiddenTextarea(), - te = kludge.firstChild; - cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); - te.value = lastCopied.text.join("\n"); - var hadFocus = document.activeElement; - selectInput(te); - setTimeout(function () { - cm.display.lineSpace.removeChild(kludge); - hadFocus.focus(); - if (hadFocus == div) { - input.showPrimarySelection(); - } - }, 50); - } - on(div, "copy", onCopyCut); - on(div, "cut", onCopyCut); - }; - ContentEditableInput.prototype.prepareSelection = function () { - var result = prepareSelection(this.cm, false); - result.focus = this.cm.state.focused; - return result; - }; - ContentEditableInput.prototype.showSelection = function (info, takeFocus) { - if (!info || !this.cm.display.view.length) { - return; - } - if (info.focus || takeFocus) { - this.showPrimarySelection(); - } - this.showMultipleSelections(info); - }; - ContentEditableInput.prototype.showPrimarySelection = function () { - var sel = window.getSelection(), - cm = this.cm, - prim = cm.doc.sel.primary(); - var from = prim.from(), - to = prim.to(); - if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { - sel.removeAllRanges(); - return; - } - var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); - var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); - if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && cmp(minPos(curAnchor, curFocus), from) == 0 && cmp(maxPos(curAnchor, curFocus), to) == 0) { - return; - } - var view = cm.display.view; - var start = from.line >= cm.display.viewFrom && posToDOM(cm, from) || { - node: view[0].measure.map[2], - offset: 0 - }; - var end = to.line < cm.display.viewTo && posToDOM(cm, to); - if (!end) { - var measure = view[view.length - 1].measure; - var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; - end = { - node: map[map.length - 1], - offset: map[map.length - 2] - map[map.length - 3] - }; - } - if (!start || !end) { - sel.removeAllRanges(); - return; - } - var old = sel.rangeCount && sel.getRangeAt(0), - rng; - try { - rng = range(start.node, start.offset, end.offset, end.node); - } catch (e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible - if (rng) { - if (!gecko && cm.state.focused) { - sel.collapse(start.node, start.offset); - if (!rng.collapsed) { - sel.removeAllRanges(); - sel.addRange(rng); - } - } else { - sel.removeAllRanges(); - sel.addRange(rng); - } - if (old && sel.anchorNode == null) { - sel.addRange(old); - } else if (gecko) { - this.startGracePeriod(); - } - } - this.rememberSelection(); - }; - ContentEditableInput.prototype.startGracePeriod = function () { - var this$1 = this; - clearTimeout(this.gracePeriod); - this.gracePeriod = setTimeout(function () { - this$1.gracePeriod = false; - if (this$1.selectionChanged()) { - this$1.cm.operation(function () { - return this$1.cm.curOp.selectionChanged = true; - }); - } - }, 20); - }; - ContentEditableInput.prototype.showMultipleSelections = function (info) { - removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); - removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); - }; - ContentEditableInput.prototype.rememberSelection = function () { - var sel = window.getSelection(); - this.lastAnchorNode = sel.anchorNode; - this.lastAnchorOffset = sel.anchorOffset; - this.lastFocusNode = sel.focusNode; - this.lastFocusOffset = sel.focusOffset; - }; - ContentEditableInput.prototype.selectionInEditor = function () { - var sel = window.getSelection(); - if (!sel.rangeCount) { - return false; - } - var node = sel.getRangeAt(0).commonAncestorContainer; - return contains(this.div, node); - }; - ContentEditableInput.prototype.focus = function () { - if (this.cm.options.readOnly != "nocursor") { - if (!this.selectionInEditor()) { - this.showSelection(this.prepareSelection(), true); - } - this.div.focus(); - } - }; - ContentEditableInput.prototype.blur = function () { - this.div.blur(); - }; - ContentEditableInput.prototype.getField = function () { - return this.div; - }; - ContentEditableInput.prototype.supportsTouch = function () { - return true; - }; - ContentEditableInput.prototype.receivedFocus = function () { - var input = this; - if (this.selectionInEditor()) { - this.pollSelection(); - } else { - runInOp(this.cm, function () { - return input.cm.curOp.selectionChanged = true; - }); - } - function poll() { - if (input.cm.state.focused) { - input.pollSelection(); - input.polling.set(input.cm.options.pollInterval, poll); - } - } - this.polling.set(this.cm.options.pollInterval, poll); - }; - ContentEditableInput.prototype.selectionChanged = function () { - var sel = window.getSelection(); - return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; - }; - ContentEditableInput.prototype.pollSelection = function () { - if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { - return; - } - var sel = window.getSelection(), - cm = this.cm; - // On Android Chrome (version 56, at least), backspacing into an - // uneditable block element will put the cursor in that element, - // and then, because it's not editable, hide the virtual keyboard. - // Because Android doesn't allow us to actually detect backspace - // presses in a sane way, this code checks for when that happens - // and simulates a backspace press in this case. - if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { - this.cm.triggerOnKeyDown({ - type: "keydown", - keyCode: 8, - preventDefault: Math.abs - }); - this.blur(); - this.focus(); - return; - } - if (this.composing) { - return; - } - this.rememberSelection(); - var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); - var head = domToPos(cm, sel.focusNode, sel.focusOffset); - if (anchor && head) { - runInOp(cm, function () { - setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); - if (anchor.bad || head.bad) { - cm.curOp.selectionChanged = true; - } - }); - } - }; - ContentEditableInput.prototype.pollContent = function () { - if (this.readDOMTimeout != null) { - clearTimeout(this.readDOMTimeout); - this.readDOMTimeout = null; - } - var cm = this.cm, - display = cm.display, - sel = cm.doc.sel.primary(); - var from = sel.from(), - to = sel.to(); - if (from.ch == 0 && from.line > cm.firstLine()) { - from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); - } - if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) { - to = Pos(to.line + 1, 0); - } - if (from.line < display.viewFrom || to.line > display.viewTo - 1) { - return false; - } - var fromIndex, fromLine, fromNode; - if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { - fromLine = lineNo(display.view[0].line); - fromNode = display.view[0].node; - } else { - fromLine = lineNo(display.view[fromIndex].line); - fromNode = display.view[fromIndex - 1].node.nextSibling; - } - var toIndex = findViewIndex(cm, to.line); - var toLine, toNode; - if (toIndex == display.view.length - 1) { - toLine = display.viewTo - 1; - toNode = display.lineDiv.lastChild; - } else { - toLine = lineNo(display.view[toIndex + 1].line) - 1; - toNode = display.view[toIndex + 1].node.previousSibling; - } - if (!fromNode) { - return false; - } - var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); - var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); - while (newText.length > 1 && oldText.length > 1) { - if (lst(newText) == lst(oldText)) { - newText.pop(); - oldText.pop(); - toLine--; - } else if (newText[0] == oldText[0]) { - newText.shift(); - oldText.shift(); - fromLine++; - } else { - break; - } - } - var cutFront = 0, - cutEnd = 0; - var newTop = newText[0], - oldTop = oldText[0], - maxCutFront = Math.min(newTop.length, oldTop.length); - while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) { - ++cutFront; - } - var newBot = lst(newText), - oldBot = lst(oldText); - var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), oldBot.length - (oldText.length == 1 ? cutFront : 0)); - while (cutEnd < maxCutEnd && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { - ++cutEnd; - } - // Try to move start of change to start of selection if ambiguous - if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { - while (cutFront && cutFront > from.ch && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { - cutFront--; - cutEnd++; - } - } - newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); - newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); - var chFrom = Pos(fromLine, cutFront); - var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); - if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { - replaceRange(cm.doc, newText, chFrom, chTo, "+input"); - return true; - } - }; - ContentEditableInput.prototype.ensurePolled = function () { - this.forceCompositionEnd(); - }; - ContentEditableInput.prototype.reset = function () { - this.forceCompositionEnd(); - }; - ContentEditableInput.prototype.forceCompositionEnd = function () { - if (!this.composing) { - return; - } - clearTimeout(this.readDOMTimeout); - this.composing = null; - this.updateFromDOM(); - this.div.blur(); - this.div.focus(); - }; - ContentEditableInput.prototype.readFromDOMSoon = function () { - var this$1 = this; - if (this.readDOMTimeout != null) { - return; - } - this.readDOMTimeout = setTimeout(function () { - this$1.readDOMTimeout = null; - if (this$1.composing) { - if (this$1.composing.done) { - this$1.composing = null; - } else { - return; - } - } - this$1.updateFromDOM(); - }, 80); - }; - ContentEditableInput.prototype.updateFromDOM = function () { - var this$1 = this; - if (this.cm.isReadOnly() || !this.pollContent()) { - runInOp(this.cm, function () { - return regChange(this$1.cm); - }); - } - }; - ContentEditableInput.prototype.setUneditable = function (node) { - node.contentEditable = "false"; - }; - ContentEditableInput.prototype.onKeyPress = function (e) { - if (e.charCode == 0) { - return; - } - e.preventDefault(); - if (!this.cm.isReadOnly()) { - operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); - } - }; - ContentEditableInput.prototype.readOnlyChanged = function (val) { - this.div.contentEditable = String(val != "nocursor"); - }; - ContentEditableInput.prototype.onContextMenu = function () {}; - ContentEditableInput.prototype.resetPosition = function () {}; - ContentEditableInput.prototype.needsContentAttribute = true; - function posToDOM(cm, pos) { - var view = findViewForLine(cm, pos.line); - if (!view || view.hidden) { - return null; - } - var line = getLine(cm.doc, pos.line); - var info = mapFromLineView(view, line, pos.line); - var order = getOrder(line, cm.doc.direction), - side = "left"; - if (order) { - var partPos = getBidiPartAt(order, pos.ch); - side = partPos % 2 ? "right" : "left"; - } - var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); - result.offset = result.collapse == "right" ? result.end : result.start; - return result; - } - function isInGutter(node) { - for (var scan = node; scan; scan = scan.parentNode) { - if (/CodeMirror-gutter-wrapper/.test(scan.className)) { - return true; - } - } - return false; - } - function badPos(pos, bad) { - if (bad) { - pos.bad = true; - } - return pos; - } - function domTextBetween(cm, from, to, fromLine, toLine) { - var text = "", - closing = false, - lineSep = cm.doc.lineSeparator(); - function recognizeMarker(id) { - return function (marker) { - return marker.id == id; - }; - } - function close() { - if (closing) { - text += lineSep; - closing = false; - } - } - function addText(str) { - if (str) { - close(); - text += str; - } - } - function walk(node) { - if (node.nodeType == 1) { - var cmText = node.getAttribute("cm-text"); - if (cmText != null) { - addText(cmText || node.textContent.replace(/\u200b/g, "")); - return; - } - var markerID = node.getAttribute("cm-marker"), - range; - if (markerID) { - var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); - if (found.length && (range = found[0].find())) { - addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); - } - return; - } - if (node.getAttribute("contenteditable") == "false") { - return; - } - var isBlock = /^(pre|div|p)$/i.test(node.nodeName); - if (isBlock) { - close(); - } - for (var i = 0; i < node.childNodes.length; i++) { - walk(node.childNodes[i]); - } - if (isBlock) { - closing = true; - } - } else if (node.nodeType == 3) { - addText(node.nodeValue); - } - } - for (;;) { - walk(from); - if (from == to) { - break; - } - from = from.nextSibling; - } - return text; - } - function domToPos(cm, node, offset) { - var lineNode; - if (node == cm.display.lineDiv) { - lineNode = cm.display.lineDiv.childNodes[offset]; - if (!lineNode) { - return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); - } - node = null; - offset = 0; - } else { - for (lineNode = node;; lineNode = lineNode.parentNode) { - if (!lineNode || lineNode == cm.display.lineDiv) { - return null; - } - if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { - break; - } - } - } - for (var i = 0; i < cm.display.view.length; i++) { - var lineView = cm.display.view[i]; - if (lineView.node == lineNode) { - return locateNodeInLineView(lineView, node, offset); - } - } - } - function locateNodeInLineView(lineView, node, offset) { - var wrapper = lineView.text.firstChild, - bad = false; - if (!node || !contains(wrapper, node)) { - return badPos(Pos(lineNo(lineView.line), 0), true); - } - if (node == wrapper) { - bad = true; - node = wrapper.childNodes[offset]; - offset = 0; - if (!node) { - var line = lineView.rest ? lst(lineView.rest) : lineView.line; - return badPos(Pos(lineNo(line), line.text.length), bad); - } - } - var textNode = node.nodeType == 3 ? node : null, - topNode = node; - if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { - textNode = node.firstChild; - if (offset) { - offset = textNode.nodeValue.length; - } - } - while (topNode.parentNode != wrapper) { - topNode = topNode.parentNode; - } - var measure = lineView.measure, - maps = measure.maps; - function find(textNode, topNode, offset) { - for (var i = -1; i < (maps ? maps.length : 0); i++) { - var map = i < 0 ? measure.map : maps[i]; - for (var j = 0; j < map.length; j += 3) { - var curNode = map[j + 2]; - if (curNode == textNode || curNode == topNode) { - var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); - var ch = map[j] + offset; - if (offset < 0 || curNode != textNode) { - ch = map[j + (offset ? 1 : 0)]; - } - return Pos(line, ch); - } - } - } - } - var found = find(textNode, topNode, offset); - if (found) { - return badPos(found, bad); - } - - // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems - for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { - found = find(after, after.firstChild, 0); - if (found) { - return badPos(Pos(found.line, found.ch - dist), bad); - } else { - dist += after.textContent.length; - } - } - for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { - found = find(before, before.firstChild, -1); - if (found) { - return badPos(Pos(found.line, found.ch + dist$1), bad); - } else { - dist$1 += before.textContent.length; - } - } - } - - // TEXTAREA INPUT STYLE - - var TextareaInput = function (cm) { - this.cm = cm; - // See input.poll and input.reset - this.prevInput = ""; - - // Flag that indicates whether we expect input to appear real soon - // now (after some event like 'keypress' or 'input') and are - // polling intensively. - this.pollingFast = false; - // Self-resetting timeout for the poller - this.polling = new Delayed(); - // Used to work around IE issue with selection being forgotten when focus moves away from textarea - this.hasSelection = false; - this.composing = null; - }; - TextareaInput.prototype.init = function (display) { - var this$1 = this; - var input = this, - cm = this.cm; - - // Wraps and hides input textarea - var div = this.wrapper = hiddenTextarea(); - // The semihidden textarea that is focused when the editor is - // focused, and receives input. - var te = this.textarea = div.firstChild; - display.wrapper.insertBefore(div, display.wrapper.firstChild); - - // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) - if (ios) { - te.style.width = "0px"; - } - on(te, "input", function () { - if (ie && ie_version >= 9 && this$1.hasSelection) { - this$1.hasSelection = null; - } - input.poll(); - }); - on(te, "paste", function (e) { - if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { - return; - } - cm.state.pasteIncoming = true; - input.fastPoll(); - }); - function prepareCopyCut(e) { - if (signalDOMEvent(cm, e)) { - return; - } - if (cm.somethingSelected()) { - setLastCopied({ - lineWise: false, - text: cm.getSelections() - }); - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - setLastCopied({ - lineWise: true, - text: ranges.text - }); - if (e.type == "cut") { - cm.setSelections(ranges.ranges, null, sel_dontScroll); - } else { - input.prevInput = ""; - te.value = ranges.text.join("\n"); - selectInput(te); - } - } - if (e.type == "cut") { - cm.state.cutIncoming = true; - } - } - on(te, "cut", prepareCopyCut); - on(te, "copy", prepareCopyCut); - on(display.scroller, "paste", function (e) { - if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { - return; - } - cm.state.pasteIncoming = true; - input.focus(); - }); - - // Prevent normal selection in the editor (we handle our own) - on(display.lineSpace, "selectstart", function (e) { - if (!eventInWidget(display, e)) { - e_preventDefault(e); - } - }); - on(te, "compositionstart", function () { - var start = cm.getCursor("from"); - if (input.composing) { - input.composing.range.clear(); - } - input.composing = { - start: start, - range: cm.markText(start, cm.getCursor("to"), { - className: "CodeMirror-composing" - }) - }; - }); - on(te, "compositionend", function () { - if (input.composing) { - input.poll(); - input.composing.range.clear(); - input.composing = null; - } - }); - }; - TextareaInput.prototype.prepareSelection = function () { - // Redraw the selection and/or cursor - var cm = this.cm, - display = cm.display, - doc = cm.doc; - var result = prepareSelection(cm); - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - if (cm.options.moveInputWithCursor) { - var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); - var wrapOff = display.wrapper.getBoundingClientRect(), - lineOff = display.lineDiv.getBoundingClientRect(); - result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, headPos.top + lineOff.top - wrapOff.top)); - result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, headPos.left + lineOff.left - wrapOff.left)); - } - return result; - }; - TextareaInput.prototype.showSelection = function (drawn) { - var cm = this.cm, - display = cm.display; - removeChildrenAndAdd(display.cursorDiv, drawn.cursors); - removeChildrenAndAdd(display.selectionDiv, drawn.selection); - if (drawn.teTop != null) { - this.wrapper.style.top = drawn.teTop + "px"; - this.wrapper.style.left = drawn.teLeft + "px"; - } - }; - - // Reset the input to correspond to the selection (or to be empty, - // when not typing and nothing is selected) - TextareaInput.prototype.reset = function (typing) { - if (this.contextMenuPending || this.composing) { - return; - } - var cm = this.cm; - if (cm.somethingSelected()) { - this.prevInput = ""; - var content = cm.getSelection(); - this.textarea.value = content; - if (cm.state.focused) { - selectInput(this.textarea); - } - if (ie && ie_version >= 9) { - this.hasSelection = content; - } - } else if (!typing) { - this.prevInput = this.textarea.value = ""; - if (ie && ie_version >= 9) { - this.hasSelection = null; - } - } - }; - TextareaInput.prototype.getField = function () { - return this.textarea; - }; - TextareaInput.prototype.supportsTouch = function () { - return false; - }; - TextareaInput.prototype.focus = function () { - if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { - try { - this.textarea.focus(); - } catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM - } - }; - TextareaInput.prototype.blur = function () { - this.textarea.blur(); - }; - TextareaInput.prototype.resetPosition = function () { - this.wrapper.style.top = this.wrapper.style.left = 0; - }; - TextareaInput.prototype.receivedFocus = function () { - this.slowPoll(); - }; - - // Poll for input changes, using the normal rate of polling. This - // runs as long as the editor is focused. - TextareaInput.prototype.slowPoll = function () { - var this$1 = this; - if (this.pollingFast) { - return; - } - this.polling.set(this.cm.options.pollInterval, function () { - this$1.poll(); - if (this$1.cm.state.focused) { - this$1.slowPoll(); - } - }); - }; - - // When an event has just come in that is likely to add or change - // something in the input textarea, we poll faster, to ensure that - // the change appears on the screen quickly. - TextareaInput.prototype.fastPoll = function () { - var missed = false, - input = this; - input.pollingFast = true; - function p() { - var changed = input.poll(); - if (!changed && !missed) { - missed = true; - input.polling.set(60, p); - } else { - input.pollingFast = false; - input.slowPoll(); - } - } - input.polling.set(20, p); - }; - - // Read input from the textarea, and update the document to match. - // When something is selected, it is present in the textarea, and - // selected (unless it is huge, in which case a placeholder is - // used). When nothing is selected, the cursor sits after previously - // seen text (can be empty), which is stored in prevInput (we must - // not reset the textarea when typing, because that breaks IME). - TextareaInput.prototype.poll = function () { - var this$1 = this; - var cm = this.cm, - input = this.textarea, - prevInput = this.prevInput; - // Since this is called a *lot*, try to bail out as cheaply as - // possible when it is clear that nothing happened. hasSelection - // will be the case when there is a lot of text in the textarea, - // in which case reading its value would be expensive. - if (this.contextMenuPending || !cm.state.focused || hasSelection(input) && !prevInput && !this.composing || cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) { - return false; - } - var text = input.value; - // If nothing changed, bail. - if (text == prevInput && !cm.somethingSelected()) { - return false; - } - // Work around nonsensical selection resetting in IE9/10, and - // inexplicable appearance of private area unicode characters on - // some key combos in Mac (#2689). - if (ie && ie_version >= 9 && this.hasSelection === text || mac && /[\uf700-\uf7ff]/.test(text)) { - cm.display.input.reset(); - return false; - } - if (cm.doc.sel == cm.display.selForContextMenu) { - var first = text.charCodeAt(0); - if (first == 0x200b && !prevInput) { - prevInput = "\u200b"; - } - if (first == 0x21da) { - this.reset(); - return this.cm.execCommand("undo"); - } - } - // Find the part of the input that is actually new - var same = 0, - l = Math.min(prevInput.length, text.length); - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { - ++same; - } - runInOp(cm, function () { - applyTextInput(cm, text.slice(same), prevInput.length - same, null, this$1.composing ? "*compose" : null); - - // Don't leave long text in the textarea, since it makes further polling slow - if (text.length > 1000 || text.indexOf("\n") > -1) { - input.value = this$1.prevInput = ""; - } else { - this$1.prevInput = text; - } - if (this$1.composing) { - this$1.composing.range.clear(); - this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), { - className: "CodeMirror-composing" - }); - } - }); - return true; - }; - TextareaInput.prototype.ensurePolled = function () { - if (this.pollingFast && this.poll()) { - this.pollingFast = false; - } - }; - TextareaInput.prototype.onKeyPress = function () { - if (ie && ie_version >= 9) { - this.hasSelection = null; - } - this.fastPoll(); - }; - TextareaInput.prototype.onContextMenu = function (e) { - var input = this, - cm = input.cm, - display = cm.display, - te = input.textarea; - var pos = posFromMouse(cm, e), - scrollPos = display.scroller.scrollTop; - if (!pos || presto) { - return; - } // Opera is difficult. - - // Reset the current text selection only if the click is done outside of the selection - // and 'resetSelectionOnContextMenu' option is true. - var reset = cm.options.resetSelectionOnContextMenu; - if (reset && cm.doc.sel.contains(pos) == -1) { - operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); - } - var oldCSS = te.style.cssText, - oldWrapperCSS = input.wrapper.style.cssText; - input.wrapper.style.cssText = "position: absolute"; - var wrapperBox = input.wrapper.getBoundingClientRect(); - te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - var oldScrollY; - if (webkit) { - oldScrollY = window.scrollY; - } // Work around Chrome issue (#2712) - display.input.focus(); - if (webkit) { - window.scrollTo(null, oldScrollY); - } - display.input.reset(); - // Adds "Select all" to context menu in FF - if (!cm.somethingSelected()) { - te.value = input.prevInput = " "; - } - input.contextMenuPending = true; - display.selForContextMenu = cm.doc.sel; - clearTimeout(display.detectingSelectAll); - - // Select-all will be greyed out if there's nothing to select, so - // this adds a zero-width space so that we can later check whether - // it got selected. - function prepareSelectAllHack() { - if (te.selectionStart != null) { - var selected = cm.somethingSelected(); - var extval = "\u200b" + (selected ? te.value : ""); - te.value = "\u21da"; // Used to catch context-menu undo - te.value = extval; - input.prevInput = selected ? "" : "\u200b"; - te.selectionStart = 1; - te.selectionEnd = extval.length; - // Re-set this, in case some other handler touched the - // selection in the meantime. - display.selForContextMenu = cm.doc.sel; - } - } - function rehide() { - input.contextMenuPending = false; - input.wrapper.style.cssText = oldWrapperCSS; - te.style.cssText = oldCSS; - if (ie && ie_version < 9) { - display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); - } - - // Try to detect the user choosing select-all - if (te.selectionStart != null) { - if (!ie || ie && ie_version < 9) { - prepareSelectAllHack(); - } - var i = 0, - poll = function () { - if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && te.selectionEnd > 0 && input.prevInput == "\u200b") { - operation(cm, selectAll)(cm); - } else if (i++ < 10) { - display.detectingSelectAll = setTimeout(poll, 500); - } else { - display.selForContextMenu = null; - display.input.reset(); - } - }; - display.detectingSelectAll = setTimeout(poll, 200); - } - } - if (ie && ie_version >= 9) { - prepareSelectAllHack(); - } - if (captureRightClick) { - e_stop(e); - var mouseup = function () { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }; - on(window, "mouseup", mouseup); - } else { - setTimeout(rehide, 50); - } - }; - TextareaInput.prototype.readOnlyChanged = function (val) { - if (!val) { - this.reset(); - } - this.textarea.disabled = val == "nocursor"; - }; - TextareaInput.prototype.setUneditable = function () {}; - TextareaInput.prototype.needsContentAttribute = false; - function fromTextArea(textarea, options) { - options = options ? copyObj(options) : {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabIndex) { - options.tabindex = textarea.tabIndex; - } - if (!options.placeholder && textarea.placeholder) { - options.placeholder = textarea.placeholder; - } - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = activeElt(); - options.autofocus = hasFocus == textarea || textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - function save() { - textarea.value = cm.getValue(); - } - var realSubmit; - if (textarea.form) { - on(textarea.form, "submit", save); - // Deplorable hack to make the submit method do the right thing. - if (!options.leaveSubmitMethodAlone) { - var form = textarea.form; - realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function () { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch (e) {} - } - } - options.finishInit = function (cm) { - cm.save = save; - cm.getTextArea = function () { - return textarea; - }; - cm.toTextArea = function () { - cm.toTextArea = isNaN; // Prevent this from being ran twice - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") { - textarea.form.submit = realSubmit; - } - } - }; - }; - textarea.style.display = "none"; - var cm = CodeMirror(function (node) { - return textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - return cm; - } - function addLegacyProps(CodeMirror) { - CodeMirror.off = off; - CodeMirror.on = on; - CodeMirror.wheelEventPixels = wheelEventPixels; - CodeMirror.Doc = Doc; - CodeMirror.splitLines = splitLinesAuto; - CodeMirror.countColumn = countColumn; - CodeMirror.findColumn = findColumn; - CodeMirror.isWordChar = isWordCharBasic; - CodeMirror.Pass = Pass; - CodeMirror.signal = signal; - CodeMirror.Line = Line; - CodeMirror.changeEnd = changeEnd; - CodeMirror.scrollbarModel = scrollbarModel; - CodeMirror.Pos = Pos; - CodeMirror.cmpPos = cmp; - CodeMirror.modes = modes; - CodeMirror.mimeModes = mimeModes; - CodeMirror.resolveMode = resolveMode; - CodeMirror.getMode = getMode; - CodeMirror.modeExtensions = modeExtensions; - CodeMirror.extendMode = extendMode; - CodeMirror.copyState = copyState; - CodeMirror.startState = startState; - CodeMirror.innerMode = innerMode; - CodeMirror.commands = commands; - CodeMirror.keyMap = keyMap; - CodeMirror.keyName = keyName; - CodeMirror.isModifierKey = isModifierKey; - CodeMirror.lookupKey = lookupKey; - CodeMirror.normalizeKeyMap = normalizeKeyMap; - CodeMirror.StringStream = StringStream; - CodeMirror.SharedTextMarker = SharedTextMarker; - CodeMirror.TextMarker = TextMarker; - CodeMirror.LineWidget = LineWidget; - CodeMirror.e_preventDefault = e_preventDefault; - CodeMirror.e_stopPropagation = e_stopPropagation; - CodeMirror.e_stop = e_stop; - CodeMirror.addClass = addClass; - CodeMirror.contains = contains; - CodeMirror.rmClass = rmClass; - CodeMirror.keyNames = keyNames; - } - - // EDITOR CONSTRUCTOR - - defineOptions(CodeMirror); - addEditorMethods(CodeMirror); - - // Set up methods on CodeMirror's prototype to redirect to the editor's document. - var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); - for (var prop in Doc.prototype) { - if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) { - CodeMirror.prototype[prop] = function (method) { - return function () { - return method.apply(this.doc, arguments); - }; - }(Doc.prototype[prop]); - } - } - eventMixin(Doc); - - // INPUT HANDLING - - CodeMirror.inputStyles = { - "textarea": TextareaInput, - "contenteditable": ContentEditableInput - }; - - // MODE DEFINITION AND QUERYING - - // Extra arguments are stored as the mode's dependencies, which is - // used by (legacy) mechanisms like loadmode.js to automatically - // load a mode. (Preferred mechanism is the require/define calls.) - CodeMirror.defineMode = function (name /*, mode, …*/) { - if (!CodeMirror.defaults.mode && name != "null") { - CodeMirror.defaults.mode = name; - } - defineMode.apply(this, arguments); - }; - CodeMirror.defineMIME = defineMIME; - - // Minimal default mode. - CodeMirror.defineMode("null", function () { - return { - token: function (stream) { - return stream.skipToEnd(); - } - }; - }); - CodeMirror.defineMIME("text/plain", "null"); - - // EXTENSIONS - - CodeMirror.defineExtension = function (name, func) { - CodeMirror.prototype[name] = func; - }; - CodeMirror.defineDocExtension = function (name, func) { - Doc.prototype[name] = func; - }; - CodeMirror.fromTextArea = fromTextArea; - addLegacyProps(CodeMirror); - CodeMirror.version = "5.28.0"; - return CodeMirror; - }); - - /*! - * clipboard.js v1.7.1 - * https://zenorocha.github.io/clipboard.js - * - * Licensed MIT © Zeno Rocha - */ - (function (f) { - if (typeof exports === "object" && typeof module !== "undefined") { - module.exports = f(); - } else if (typeof define === "function" && define.amd) { - define([], f); - } else { - var g; - if (typeof window !== "undefined") { - g = window; - } else if (typeof global !== "undefined") { - g = global; - } else if (typeof self !== "undefined") { - g = self; - } else { - g = this; - } - g.Clipboard = f(); - } - })(function () { - return function e(t, n, r) { - function s(o, u) { - if (!n[o]) { - if (!t[o]) { - var a = typeof require == "function" && require; - if (!u && a) return a(o, !0); - if (i) return i(o, !0); - var f = new Error("Cannot find module '" + o + "'"); - throw f.code = "MODULE_NOT_FOUND", f; - } - var l = n[o] = { - exports: {} - }; - t[o][0].call(l.exports, function (e) { - var n = t[o][1][e]; - return s(n ? n : e); - }, l, l.exports, e, t, n, r); - } - return n[o].exports; - } - var i = typeof require == "function" && require; - for (var o = 0; o < r.length; o++) s(r[o]); - return s; - }({ - 1: [function (require, module, exports) { - var DOCUMENT_NODE_TYPE = 9; - - /** - * A polyfill for Element.matches() - */ - if (typeof Element !== 'undefined' && !Element.prototype.matches) { - var proto = Element.prototype; - proto.matches = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector; - } - - /** - * Finds the closest parent that matches a selector. - * - * @param {Element} element - * @param {String} selector - * @return {Function} - */ - function closest(element, selector) { - while (element && element.nodeType !== DOCUMENT_NODE_TYPE) { - if (typeof element.matches === 'function' && element.matches(selector)) { - return element; - } - element = element.parentNode; - } - } - module.exports = closest; - }, {}], - 2: [function (require, module, exports) { - var closest = require('./closest'); - - /** - * Delegates event to a selector. - * - * @param {Element} element - * @param {String} selector - * @param {String} type - * @param {Function} callback - * @param {Boolean} useCapture - * @return {Object} - */ - function delegate(element, selector, type, callback, useCapture) { - var listenerFn = listener.apply(this, arguments); - element.addEventListener(type, listenerFn, useCapture); - return { - destroy: function () { - element.removeEventListener(type, listenerFn, useCapture); - } - }; - } - - /** - * Finds closest match and invokes callback. - * - * @param {Element} element - * @param {String} selector - * @param {String} type - * @param {Function} callback - * @return {Function} - */ - function listener(element, selector, type, callback) { - return function (e) { - e.delegateTarget = closest(e.target, selector); - if (e.delegateTarget) { - callback.call(element, e); - } - }; - } - module.exports = delegate; - }, { - "./closest": 1 - }], - 3: [function (require, module, exports) { - /** - * Check if argument is a HTML element. - * - * @param {Object} value - * @return {Boolean} - */ - exports.node = function (value) { - return value !== undefined && (value instanceof HTMLElement || value instanceof SVGElement) && value.nodeType === 1; - }; - - /** - * Check if argument is a list of HTML elements. - * - * @param {Object} value - * @return {Boolean} - */ - exports.nodeList = function (value) { - var type = Object.prototype.toString.call(value); - return value !== undefined && (type === '[object NodeList]' || type === '[object HTMLCollection]') && 'length' in value && (value.length === 0 || exports.node(value[0])); - }; - - /** - * Check if argument is a string. - * - * @param {Object} value - * @return {Boolean} - */ - exports.string = function (value) { - return typeof value === 'string' || value instanceof String; - }; - - /** - * Check if argument is a function. - * - * @param {Object} value - * @return {Boolean} - */ - exports.fn = function (value) { - var type = Object.prototype.toString.call(value); - return type === '[object Function]'; - }; - }, {}], - 4: [function (require, module, exports) { - var is = require('./is'); - var delegate = require('delegate'); - - /** - * Validates all params and calls the right - * listener function based on its target type. - * - * @param {String|HTMLElement|HTMLCollection|NodeList} target - * @param {String} type - * @param {Function} callback - * @return {Object} - */ - function listen(target, type, callback) { - if (!target && !type && !callback) { - throw new Error('Missing required arguments'); - } - if (!is.string(type)) { - throw new TypeError('Second argument must be a String'); - } - if (!is.fn(callback)) { - throw new TypeError('Third argument must be a Function'); - } - if (is.node(target)) { - return listenNode(target, type, callback); - } else if (is.nodeList(target)) { - return listenNodeList(target, type, callback); - } else if (is.string(target)) { - return listenSelector(target, type, callback); - } else { - throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList'); - } - } - - /** - * Adds an event listener to a HTML element - * and returns a remove listener function. - * - * @param {HTMLElement} node - * @param {String} type - * @param {Function} callback - * @return {Object} - */ - function listenNode(node, type, callback) { - node.addEventListener(type, callback); - return { - destroy: function () { - node.removeEventListener(type, callback); - } - }; - } - - /** - * Add an event listener to a list of HTML elements - * and returns a remove listener function. - * - * @param {NodeList|HTMLCollection} nodeList - * @param {String} type - * @param {Function} callback - * @return {Object} - */ - function listenNodeList(nodeList, type, callback) { - Array.prototype.forEach.call(nodeList, function (node) { - node.addEventListener(type, callback); - }); - return { - destroy: function () { - Array.prototype.forEach.call(nodeList, function (node) { - node.removeEventListener(type, callback); - }); - } - }; - } - - /** - * Add an event listener to a selector - * and returns a remove listener function. - * - * @param {String} selector - * @param {String} type - * @param {Function} callback - * @return {Object} - */ - function listenSelector(selector, type, callback) { - return delegate(document.body, selector, type, callback); - } - module.exports = listen; - }, { - "./is": 3, - "delegate": 2 - }], - 5: [function (require, module, exports) { - function select(element) { - var selectedText; - if (element.nodeName === 'SELECT') { - element.focus(); - selectedText = element.value; - } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { - var isReadOnly = element.hasAttribute('readonly'); - if (!isReadOnly) { - element.setAttribute('readonly', ''); - } - element.select(); - element.setSelectionRange(0, element.value.length); - if (!isReadOnly) { - element.removeAttribute('readonly'); - } - selectedText = element.value; - } else { - if (element.hasAttribute('contenteditable')) { - element.focus(); - } - var selection = window.getSelection(); - var range = document.createRange(); - range.selectNodeContents(element); - selection.removeAllRanges(); - selection.addRange(range); - selectedText = selection.toString(); - } - return selectedText; - } - module.exports = select; - }, {}], - 6: [function (require, module, exports) { - function E() { - // Keep this empty so it's easier to inherit from - // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) - } - E.prototype = { - on: function (name, callback, ctx) { - var e = this.e || (this.e = {}); - (e[name] || (e[name] = [])).push({ - fn: callback, - ctx: ctx - }); - return this; - }, - once: function (name, callback, ctx) { - var self = this; - function listener() { - self.off(name, listener); - callback.apply(ctx, arguments); - } - listener._ = callback; - return this.on(name, listener, ctx); - }, - emit: function (name) { - var data = [].slice.call(arguments, 1); - var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); - var i = 0; - var len = evtArr.length; - for (i; i < len; i++) { - evtArr[i].fn.apply(evtArr[i].ctx, data); - } - return this; - }, - off: function (name, callback) { - var e = this.e || (this.e = {}); - var evts = e[name]; - var liveEvents = []; - if (evts && callback) { - for (var i = 0, len = evts.length; i < len; i++) { - if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]); - } - } - - // Remove event from queue to prevent memory leak - // Suggested by https://github.com/lazd - // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 - - liveEvents.length ? e[name] = liveEvents : delete e[name]; - return this; - } - }; - module.exports = E; - }, {}], - 7: [function (require, module, exports) { - (function (global, factory) { - if (typeof exports !== "undefined") { - factory(module, require('select')); - } else { - var mod = { - exports: {} - }; - factory(mod, global.select); - global.clipboardAction = mod.exports; - } - })(this, function (module, _select) { - - var _select2 = _interopRequireDefault(_select); - function _interopRequireDefault(obj) { - return obj && obj.__esModule ? obj : { - default: obj - }; - } - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - var _createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - var ClipboardAction = function () { - /** - * @param {Object} options - */ - function ClipboardAction(options) { - _classCallCheck(this, ClipboardAction); - this.resolveOptions(options); - this.initSelection(); - } - - /** - * Defines base properties passed from constructor. - * @param {Object} options - */ - - _createClass(ClipboardAction, [{ - key: 'resolveOptions', - value: function resolveOptions() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - this.action = options.action; - this.container = options.container; - this.emitter = options.emitter; - this.target = options.target; - this.text = options.text; - this.trigger = options.trigger; - this.selectedText = ''; - } - }, { - key: 'initSelection', - value: function initSelection() { - if (this.text) { - this.selectFake(); - } else if (this.target) { - this.selectTarget(); - } - } - }, { - key: 'selectFake', - value: function selectFake() { - var _this = this; - var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; - this.removeFake(); - this.fakeHandlerCallback = function () { - return _this.removeFake(); - }; - this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; - this.fakeElem = document.createElement('textarea'); - // Prevent zooming on iOS - this.fakeElem.style.fontSize = '12pt'; - // Reset box model - this.fakeElem.style.border = '0'; - this.fakeElem.style.padding = '0'; - this.fakeElem.style.margin = '0'; - // Move element out of screen horizontally - this.fakeElem.style.position = 'absolute'; - this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; - // Move element to the same position vertically - var yPosition = window.pageYOffset || document.documentElement.scrollTop; - this.fakeElem.style.top = yPosition + 'px'; - this.fakeElem.setAttribute('readonly', ''); - this.fakeElem.value = this.text; - this.container.appendChild(this.fakeElem); - this.selectedText = (0, _select2.default)(this.fakeElem); - this.copyText(); - } - }, { - key: 'removeFake', - value: function removeFake() { - if (this.fakeHandler) { - this.container.removeEventListener('click', this.fakeHandlerCallback); - this.fakeHandler = null; - this.fakeHandlerCallback = null; - } - if (this.fakeElem) { - this.container.removeChild(this.fakeElem); - this.fakeElem = null; - } - } - }, { - key: 'selectTarget', - value: function selectTarget() { - this.selectedText = (0, _select2.default)(this.target); - this.copyText(); - } - }, { - key: 'copyText', - value: function copyText() { - var succeeded = void 0; - try { - succeeded = document.execCommand(this.action); - } catch (err) { - succeeded = false; - } - this.handleResult(succeeded); - } - }, { - key: 'handleResult', - value: function handleResult(succeeded) { - this.emitter.emit(succeeded ? 'success' : 'error', { - action: this.action, - text: this.selectedText, - trigger: this.trigger, - clearSelection: this.clearSelection.bind(this) - }); - } - }, { - key: 'clearSelection', - value: function clearSelection() { - if (this.trigger) { - this.trigger.focus(); - } - window.getSelection().removeAllRanges(); - } - }, { - key: 'destroy', - value: function destroy() { - this.removeFake(); - } - }, { - key: 'action', - set: function set() { - var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; - this._action = action; - if (this._action !== 'copy' && this._action !== 'cut') { - throw new Error('Invalid "action" value, use either "copy" or "cut"'); - } - }, - get: function get() { - return this._action; - } - }, { - key: 'target', - set: function set(target) { - if (target !== undefined) { - if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) { - if (this.action === 'copy' && target.hasAttribute('disabled')) { - throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); - } - if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { - throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); - } - this._target = target; - } else { - throw new Error('Invalid "target" value, use a valid Element'); - } - } - }, - get: function get() { - return this._target; - } - }]); - return ClipboardAction; - }(); - module.exports = ClipboardAction; - }); - }, { - "select": 5 - }], - 8: [function (require, module, exports) { - (function (global, factory) { - if (typeof exports !== "undefined") { - factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener')); - } else { - var mod = { - exports: {} - }; - factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener); - global.clipboard = mod.exports; - } - })(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) { - - var _clipboardAction2 = _interopRequireDefault(_clipboardAction); - var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter); - var _goodListener2 = _interopRequireDefault(_goodListener); - function _interopRequireDefault(obj) { - return obj && obj.__esModule ? obj : { - default: obj - }; - } - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - var _createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - function _possibleConstructorReturn(self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - return call && (typeof call === "object" || typeof call === "function") ? call : self; - } - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - } - var Clipboard = function (_Emitter) { - _inherits(Clipboard, _Emitter); - - /** - * @param {String|HTMLElement|HTMLCollection|NodeList} trigger - * @param {Object} options - */ - function Clipboard(trigger, options) { - _classCallCheck(this, Clipboard); - var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this)); - _this.resolveOptions(options); - _this.listenClick(trigger); - return _this; - } - - /** - * Defines if attributes would be resolved using internal setter functions - * or custom functions that were passed in the constructor. - * @param {Object} options - */ - - _createClass(Clipboard, [{ - key: 'resolveOptions', - value: function resolveOptions() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - this.action = typeof options.action === 'function' ? options.action : this.defaultAction; - this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; - this.text = typeof options.text === 'function' ? options.text : this.defaultText; - this.container = _typeof(options.container) === 'object' ? options.container : document.body; - } - }, { - key: 'listenClick', - value: function listenClick(trigger) { - var _this2 = this; - this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) { - return _this2.onClick(e); - }); - } - }, { - key: 'onClick', - value: function onClick(e) { - var trigger = e.delegateTarget || e.currentTarget; - if (this.clipboardAction) { - this.clipboardAction = null; - } - this.clipboardAction = new _clipboardAction2.default({ - action: this.action(trigger), - target: this.target(trigger), - text: this.text(trigger), - container: this.container, - trigger: trigger, - emitter: this - }); - } - }, { - key: 'defaultAction', - value: function defaultAction(trigger) { - return getAttributeValue('action', trigger); - } - }, { - key: 'defaultTarget', - value: function defaultTarget(trigger) { - var selector = getAttributeValue('target', trigger); - if (selector) { - return document.querySelector(selector); - } - } - }, { - key: 'defaultText', - value: function defaultText(trigger) { - return getAttributeValue('text', trigger); - } - }, { - key: 'destroy', - value: function destroy() { - this.listener.destroy(); - if (this.clipboardAction) { - this.clipboardAction.destroy(); - this.clipboardAction = null; - } - } - }], [{ - key: 'isSupported', - value: function isSupported() { - var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; - var actions = typeof action === 'string' ? [action] : action; - var support = !!document.queryCommandSupported; - actions.forEach(function (action) { - support = support && !!document.queryCommandSupported(action); - }); - return support; - } - }]); - return Clipboard; - }(_tinyEmitter2.default); - - /** - * Helper function to retrieve attribute value. - * @param {String} suffix - * @param {Element} element - */ - function getAttributeValue(suffix, element) { - var attribute = 'data-clipboard-' + suffix; - if (!element.hasAttribute(attribute)) { - return; - } - return element.getAttribute(attribute); - } - module.exports = Clipboard; - }); - }, { - "./clipboard-action": 7, - "good-listener": 4, - "tiny-emitter": 6 - }] - }, {}, [8])(8); - }); - - window._native = function () { - var _options = {}; - var _construct = function _construct(e) { - var defaultOptions = { - carbonZoneKey: '', - fallback: '', - ignore: 'false', - placement: '', - prefix: 'native', - targetClass: 'native-ad' - }; - if (typeof e === 'undefined') return defaultOptions; - Object.keys(defaultOptions).forEach(function (key, index) { - if (typeof e[key] === 'undefined') { - e[key] = defaultOptions[key]; - } - }); - return e; - }; - var init = function init(zone, options) { - _options = _construct(options); - var jsonUrl = 'https://srv.buysellads.com/ads/' + zone + '.json?callback=_native_go'; - if (_options['placement'] !== '') { - jsonUrl += '&segment=placement:' + _options['placement']; - } - if (_options['ignore'] === 'true') { - jsonUrl += '&ignore=yes'; - } - var srv = document.createElement('script'); - srv.src = jsonUrl; - document.getElementsByTagName('head')[0].appendChild(srv); - }; - var carbon = function carbon(e) { - var srv = document.createElement('script'); - srv.src = '//cdn.carbonads.com/carbon.js?serve=' + e['carbonZoneKey'] + '&placement=' + e['placement']; - srv.id = '_carbonads_js'; - return srv; - }; - var sanitize = function sanitize(ads) { - return ads.filter(function (ad) { - return Object.keys(ad).length > 0; - }).filter(function (ad) { - return ad.hasOwnProperty('statlink'); - }); - }; - var pixel = function pixel(p, timestamp) { - var c = ''; - if (p) { - p.split('||').forEach(function (pixel, index) { - c += '
'; - }); - } - return c; - }; - var options = function options() { - return _options; - }; - return { - carbon: carbon, - init: init, - options: options, - pixel: pixel, - sanitize: sanitize - }; - }(); - window._native_go = function (json) { - var options = _native.options(); - var ads = _native.sanitize(json['ads']); - var selectedClass = document.querySelectorAll('.' + options['targetClass']); - if (ads.length < 1) { - selectedClass.forEach(function (className, index) { - var selectedTarget = document.getElementsByClassName(options['targetClass'])[index]; - if (options['fallback'] !== '' || options['carbonZoneKey'] !== '') selectedTarget.setAttribute('data-state', 'visible'); - selectedTarget.innerHTML = options['fallback']; - if (options['carbonZoneKey'] !== '') selectedTarget.appendChild(_native.carbon(options)); - }); - - // End at this line if no ads are found, avoiding unnecessary steps - return; - } - selectedClass.forEach(function (className, index) { - var selectedTarget = document.getElementsByClassName(options['targetClass'])[index]; - var adElement = selectedTarget.innerHTML || ""; - var prefix = options['prefix']; - var ad = ads[index]; - if (ad && className) { - var adInnerHtml = adElement.replace(new RegExp('#' + prefix + '_bg_color#', 'g'), ad['backgroundColor']).replace(new RegExp('#' + prefix + '_bg_color_hover#', 'g'), ad['backgroundHoverColor']).replace(new RegExp('#' + prefix + '_company#', 'g'), ad['company']).replace(new RegExp('#' + prefix + '_cta#', 'g'), ad['callToAction']).replace(new RegExp('#' + prefix + '_cta_bg_color#', 'g'), ad['ctaBackgroundColor']).replace(new RegExp('#' + prefix + '_cta_bg_color_hover#', 'g'), ad['ctaBackgroundHoverColor']).replace(new RegExp('#' + prefix + '_cta_color#', 'g'), ad['ctaTextColor']).replace(new RegExp('#' + prefix + '_cta_color_hover#', 'g'), ad['ctaTextColorHover']).replace(new RegExp('#' + prefix + '_desc#', 'g'), ad['description']).replace(new RegExp('#' + prefix + '_index#', 'g'), prefix + '-' + ad['i']).replace(new RegExp('#' + prefix + '_img#', 'g'), ad['image']).replace(new RegExp('#' + prefix + '_small_img#', 'g'), ad['smallImage']).replace(new RegExp('#' + prefix + '_link#', 'g'), ad['statlink']).replace(new RegExp('#' + prefix + '_logo#', 'g'), ad['logo']).replace(new RegExp('#' + prefix + '_color#', 'g'), ad['textColor']).replace(new RegExp('#' + prefix + '_color_hover#', 'g'), ad['textColorHover']).replace(new RegExp('#' + prefix + '_title#', 'g'), ad['title']); - selectedTarget.innerHTML = adInnerHtml + _native.pixel(ad['pixel'], ad['timestamp']); - selectedTarget.setAttribute('data-state', 'visible'); - } else { - selectedTarget.innerHTML = ""; - } - }); - }; - - /* - * Event - * Visit http://createjs.com/ for documentation, updates and examples. - * - * Copyright (c) 2010 gskinner.com, inc. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - /** - * A collection of Classes that are shared across all the CreateJS libraries. The classes are included in the minified - * files of each library and are available on the createjs namespace directly. - * - *The syntax \\o{FFF}
is also supported.
There are multiple syntaxes for this feature: \\1
\\g1
\\g{1}
.
The latter syntaxes support relative values preceded by +
or -
. For example \\g-1
would match the group preceding the reference.
This behaviour is reversed by the ungreedy (U
) flag/modifier.
Requires the u
flag.
For a list of values, see this MDN page.
" - } - // notunicodecat, unicodescript, notunicodescript are copied from unicodecat below. - } - }; - javascript.docs.notunicodecat = javascript.docs.unicodescript = javascript.docs.notunicodescript = javascript.docs.unicodecat; - - /* - RegExr: Learn, Build, & Test RegEx - Copyright (C) 2017 gskinner.com, inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see" + content + "
";
- if (!token.open) {
- content += app.reference.tipForToken(token);
- } else {
- content += " ";
- }
- let div = DOMUtils.create("div", null, content, el);
- if (token.close) {
- className = groupClasses[token.clss || token.type];
- if (className) {
- className = className.replace("%depth%", Math.min(4, token.depth));
- DOMUtils.addClass(div, className);
- }
- if (token.depth > 3) {
- div.innerHTML = "So... you wanted to see what would happen if you just kept nesting groups, eh? Well, this is it." + " I was going to reward your curiosity with a RegEx joke, but a quick search on google reveals that not even" + " the collective wisdom of the internet can make regular expressions funny. Well, except the whole 'now you've got two problems'" + " shtick, but you've probably heard that one already. Wasn't really worth the effort, was it?";
- token = token.close.prv;
- this._divs.push(div);
- el = div;
- continue;
- }
- el = div;
- }
- div.token = token;
- if (token.open) {
- DOMUtils.addClass(div, "close");
- div.proxy = el;
- el = el.parentNode;
- }
- if (token.error) {
- DOMUtils.addClass(div, "error");
- if (token.error.warning) {
- DOMUtils.addClass(div, "warning");
- }
- }
- if (!token.open) {
- div.addEventListener("mouseover", this._handleMouseEvent);
- div.addEventListener("mouseout", this._handleMouseEvent);
- div.addEventListener("click", this._handleMouseEvent);
- }
- if (token.clss === "quant" || token.type === "lazy" || token.type === "possessive") {
- this._insertApplied(div);
- } else {
- this._divs.push(div);
- }
- }
- }
- _insertApplied(div) {
- let divs = this._divs,
- prv = div.token.prv,
- d,
- i = divs.length;
- while ((d = divs[--i]) && d.token !== prv) {} // search backwards for efficiency
- d = d.proxy || d;
- divs.splice(i, 0, div);
- d.insertAdjacentElement("afterend", div);
- DOMUtils.addClass(div, "applied");
- }
- _handleHoverChange() {
- let token = app.expression.highlighter.hoverToken;
- DOMUtils.removeClass(DOMUtils.queryAll("div.selected", this.el), "selected");
- DOMUtils.removeClass(DOMUtils.queryAll("div.related", this.el), "related");
- if (!token) {
- return;
- }
- let div = this._findDiv(token);
- DOMUtils.addClass(div, "selected");
- if (token.related) {
- for (let i = 0, l = token.related.length; i < l; i++) {
- DOMUtils.addClass(this._findDiv(token.related[i]), "related");
- }
- }
- }
- _findDiv(token) {
- return Utils.find(this._divs, div => div.token === token);
- }
- _handleMouseEvent(evt) {
- let type = evt.type,
- token = evt.currentTarget.token;
- if (type == "click") {
- app.sidebar.showToken(token);
- } else {
- app.expression.highlighter.hoverToken = type === "mouseout" ? null : token;
- }
- evt.stopPropagation();
- }
- _handleEvent(evt) {
- if (evt.type === "change") {
- this._update();
- } else if (evt.type === "hover") {
- this._handleHoverChange();
- }
- }
- }
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see Group
row to highlight it in the Expression.", this.el);
- this._addMatch(app.text.selectedMatch, app.text.value);
- }
- _addMatch(match, textVal) {
- if (!match) {
- return;
- }
- let groups = match.groups,
- l = groups && groups.length,
- ext = l && groups[0].i != null,
- matchVal = this._getMatchVal(match, textVal),
- extStr = "",
- me = match.i + match.l;
- let groupTokens = app.expression.lexer.captureGroups;
- let tableEl = DOMUtils.create("table", null, null, this.el);
- let matchEl = DOMUtils.create("tr", "match", "To edit a pattern you created, click its URL or double-click it in the list to load it, then open Save / Share to edit and save.
", - search: true, - kids: [] - }, { - label: "Cheatsheet", - id: "cheatsheet", - el: "#cheatsheet" - }, { - // injected from Reference - id: "reference" - }, { - label: "Community Patterns", - id: "community", - desc: "Welcome to Community Patterns, a searchable database of patterns submitted by users like you." + "After selecting a pattern, click its URL or double-click it in the list to load the full pattern. Or use the right arrow icon to load just the expression or text.
" + "Help make the Community better by rating patterns, and submitting your own via Search & Share in the menu.
", - search: true, - kids: [] - }, { - label: "Help", - id: "help", - desc: "Help for the RegExr application. See the RegEx Reference for help with Regular Expressions.", - kids: [{ - label: "About", - desc: "RegExr v3.8.0 (May 10, 2024)." + "Created by Grant Skinner and the gskinner team, using the CreateJS & CodeMirror libraries.
" + "You can provide feedback or log bugs on GitHub.
" - }, { - label: "Getting started", - desc: "RegExr provides real-time visual results, syntax highlighting, tooltips, and undo/redo ({{getCtrlKey()}}-Z / Y) so it's easy and fun to explore Regular Expressions." + "Browse through the RegEx Reference and test different tokens to see what they do, then check out Community Patterns to see examples.
" + "You can also Save your patterns for later reference, or to share with others. Sign In to ensure you don't lose your patterns.
" + "Modify your pattern's details, share it with the Community, or make it private, or delete it in Pattern Settings
", - kids: [{ - label: "Expression panel", - desc: "This is where you enter a regular expression to test. The results in the Text and Tools panel will update as you type." + "Roll over the expression for information on each token." + "The buttons to the right allow you to switch RegEx engines, or edit the expression flags.
" - }, { - label: "Text panel", - desc: "This is where you enter text to test your expression against. Drag & drop a text file to load its contents." + "Matches will be highlighted as you type. Roll over a match for information on the match and its capture groups. The match count and execution time are shown in the title bar.
" + "Lighter colored caps at the start or end of a line indicate the match continues between lines.
" - }, { - label: "Tests panel", - desc: "This allows you to create a suite of tests that you can use to validate your Expression in real time." + "A test can check if the pattern matches anything, matches everything, or matches nothing in the test text.
" + "Access this panel with the toggle at the top of the Text panel. When you save, both your Text and Tests will be saved.
" - }, { - label: "Tools panel", - desc: "Click the Tools title bar below the Text panel to show or hide the Tools panel." + "Tools provide different ways of working with or exploring your results.
", - kids: [{ - label: "Replace", - id: "replace", - desc: "The Replace tool replaces matches with a specified string or pattern." + "Matches in the Text panel are replaced by the substitution string & displayed as you type.
" + "Substitution tokens and escaped characters are supported, such as \\n
, \\t
& \\u0009
.
Roll over tokens for information, and check out the RegEx Reference for more information.
" - }, { - label: "List", - id: "list", - desc: "The List tool lists all found matches." + "You can specify either a simple delimiter (ex. ,
or \\n
), or use substitution tokens to generate more advanced reports. For example, $1\\n
would list all group 1 results (in the JavaScript engine).
Escaped characters are supported, such as \\n
, \\t
& \\u0009
.
Roll over tokens for information.
" - }, { - label: "Details", - id: "details", - desc: "The Details tool displays the full text of a match and its capture groups." + "Click on a highlighted match in the Text panel to display the details for that match.
" + "Roll over a group row to highlight that group in your Expression.
" - }, { - label: "Explain", - id: "explain", - desc: "The Explain tool displays a detailed breakdown of the Expression." + "Mouse over the explanation to highlight the related tokens in the Expression panel and vice versa.
" + "Click an item in the explanation to show more info in the RegEx Reference.
" - }] - }, { - label: "Menu", - desc: "The Menu (this panel) includes Help, a full RegEx Reference, a Cheatsheet, and Pattern Settings features." + "Double-click a selected item in the RegEx Reference to insert it into your Expression. Click the arrow beside an example to load it.
" + "The menu also includes searchable Community Patterns, and patterns you've created or favorited in My Patterns.
" - }] - }, { - label: "Signing in", - id: "signin", - desc: "Before you sign in, RegExr creates a temporary account which relies on a browser cookie. This means you can't access your patterns on other computers, and that you could lose your patterns if your cookies are deleted or expire." + "Signing in creates a permanent account, so you can access your patterns anywhere, anytime.
" + "Your existing patterns & favorites will automatically be assigned to the new account.
" + "We don't use your info for anything other than signing you into your RegExr account.
" - }, { - id: "engine", - label: "RegEx engines", - desc: "While the core feature set of regular expressions is fairly consistent, different implementations (ex. Perl vs Java) may have different features or behaviours." + "RegExr currently supports JavaScript RegExp executed in your browser and PCRE via PHP.
" + "You can switch engines using the dropdown in Expression.
", - kids: [{ - label: "JavaScript", - desc: "Your browser's JavaScript engine is used to execute RegEx in an asynchronous worker usingRegExp.exec()
." + "Note that while implementations are mostly consistent, there are small variations between browsers. Here is a short list of known differences:
The following query string params are recognized:
engine
- the RegEx engine to use (js
or pcre
)expression
- populates the Expression area. It is recommended to pass a full expression with flags (/.*/ig
) not just the pattern (.*
)text
- populates the Text areatool
- sets the tool (replace, list, details, or explain)input
- populates the tool input field" + Utils.htmlSafe(exp) + "
";
- if (txt && regex) {
- let over = Math.max(0, txt.length - 160),
- s = txt;
- if (over) {
- s = Utils.htmlSafe(s.substr(0, 159));
- }
- if (regex) {
- s = s.replace(regex, "$&");
- }
- // TODO: this won't match on html elements:
- str += "" + s + (over ? "\u2026" : "") + "
";
- }
- }
- this.el.innerHTML = str;
- if (exp) {
- DOMUtils.query("code.expression > .load", this.el).addEventListener("click", () => {
- // TODO: this will need to be updated when we support other delimiters:
- app.expression.value = exp[0] === "/" ? exp : "/" + exp + "/g";
- });
- }
- if (txt) {
- DOMUtils.query("code.text > .load", this.el).addEventListener("click", () => app.text.value = txt);
- }
- }
- }
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see " + token.on + "
\"." : "";
- if (token.off) {
- str += " Disable \"" + token.off + "
\".";
- }
- return str;
- }
- getInsensitive(token) {
- if (token.code) {
- let chr = String.fromCharCode(token.code);
- if (chr.toLowerCase() === chr.toUpperCase()) {
- return "";
- }
- }
- return token.modes ? `Case ${token.modes.i ? "in" : ""}sensitive.` : "";
- }
- getDotAll(token) {
- return (token.modes.s ? "including" : "except") + " line breaks";
- }
- getLabel(token) {
- let node = this.getNodeForToken(token);
- return node ? node.label || node.id || "" : token.type;
- }
- getDesc(token) {
- return this.getVal(this.getNodeForToken(token), "desc");
- }
- getLazy(token) {
- return token.modes.U ? "greedy" : "lazy";
- }
- getLazyFew(token) {
- return token.modes.U ? "many" : "few";
- }
- getPHPVersion() {
- return this._config.PHPVersion;
- }
- getPCREVersion() {
- return this._config.PCREVersion;
- }
- getCtrlKey() {
- return Utils.getCtrlKey();
- }
- getEscChars() {
- let o = this._flavor.profile.escChars,
- str = "";
- for (let n in o) {
- str += n;
- }
- return str;
- }
-
- /*
- Searches for tags in the string in the format:
- `{{prop.prop}}` or `{{method(prop.prop)}}`
-
- The first format will inject the specified property of the data object.
- For example, `{{a.b}}` would inject the value of `data.a.b`.
-
- The second will inject the results of calling the specified function on the functs object with a property of the data object as it's parameter (or the data object itself if empty).
- For example, `{{myMethod(a.b)}}` would inject the return value of `functs.myMethod(data.a.b)`.
-
- Currently only supports a single param.
- */
- fillTags(str, data, functs, maxLength = 20, htmlSafe = true) {
- let match;
- while (match = str.match(/{{~?[\w.()]+}}/)) {
- let val,
- f,
- safe = false;
- val = match[0].substring(2, match[0].length - 2);
- if (val[0] === "~") {
- val = val.substr(1);
- safe = true;
- }
- let match2 = val.match(/\([\w.]*\)/);
- if (match2) {
- f = val.substr(0, match2.index);
- val = match2[0].substring(1, match2[0].length - 1);
- } else {
- f = null;
- }
- let o = data,
- arr = val.split(".");
- for (let i = 0; i < arr.length; i++) {
- let prop = arr[i];
- if (prop && o) {
- o = o[prop];
- }
- }
- val = o;
- if (f) {
- if (functs[f]) {
- val = functs[f](val);
- } else {
- val = " [" + f + "] ";
- }
- }
- if (!safe && (maxLength || htmlSafe)) {
- val = Utils.shorten(val, maxLength, htmlSafe, "i");
- }
- str = str.replace(match[0], val);
- }
- return str;
- }
-
- // returns doc props from the profile or reference as appropriate (ex. tip, desc, ext)
- getVal(node, prop) {
- if (!node) {
- return "";
- }
- let pDocs = this._flavor.getDocs(node.id),
- pRef = pDocs && pDocs[prop];
- if (pRef != null && pRef[0] !== "+") {
- return pRef;
- }
- let ref = node && node[prop] || "";
- return pRef != null ? ref + pRef.substr(1) : ref;
- }
- getNodeForToken(token) {
- let id = this.idForToken(token),
- clss = token.clss;
-
- // Special cases:
- if (clss === "quant") {
- id = clss;
- }
- if (clss === "esc" && token.type !== "escsequence") {
- id = "escchar";
- }
- return this.getNode(id);
- }
- getNode(id) {
- let map = this._idMap,
- node = map[id];
- while (node && node.proxy) {
- node = map[node.proxy];
- }
- return node;
- }
- getError(error, token) {
- let errId = error && error.id;
- let str = this._content.errors[errId] || "no docs for error='" + errId + "'";
- if (token) {
- str = this.fillTags(str, token, this, 20);
- }
- return str;
- }
- tipForToken(token) {
- if (!token) {
- return null;
- }
- let node = this.getNodeForToken(token),
- label,
- tip;
- if (token.error && !token.error.warning) {
- label = "ERROR: ";
- tip = this.getError(token.error, token);
- } else {
- label = node ? node.label || node.id || "" : token.type;
- tip = this.getVal(node, "tip") || this.getVal(node, "desc");
- tip = this.fillTags(tip, token, this, 20);
- if (token.type === "group") {
- label += " #" + token.num;
- }
- label = "" + label[0].toUpperCase() + label.substr(1) + ". ";
- if (token.error) {
- tip += "WARNING: " + this.getError(token.error, token) + "";
- }
- }
- return tip ? label + tip : "no docs for id='" + this.idForToken(token) + "'";
- }
- getContent(id) {
- let node = this.getNode(id);
- return this.fillTags(this.getVal(node, "desc") + this.getVal(node, "ext"), node, this);
- }
-
- // TODO: this isn't necessarily the most ideal place for this method (has nothing to do with Reference). Maybe move into Text?
- tipForMatch(match, text) {
- if (!match) {
- return null;
- }
- let more = match.l > 150;
- let str = "match: " + Utils.shorten(text.substr(match.i, match.l), 150, true, "i") + "" + match.i + "-" + (match.i + match.l - 1) + "
";
- let groups = match.groups,
- l = groups && groups.length;
- for (let i = 0; i < l; i++) {
- if (i > 3 && l > 5) {
- more = false;
- str += "Double-click an item in the list to insert it into your Expression.
-Click the arrow beside an example to load it.
`; - o.kids = [{ - label: "Character classes", - id: "charclasses", - desc: "Character classes match a character from a specific set. There are a number of predefined character classes and you can also define your own sets.", - kids: [{ - id: "set", - label: "character set", - desc: "Match any character in the set.", - example: ["[aeiou]", "glib jocks vex dwarves!"], - token: "[ABC]" - }, { - id: "setnot", - label: "negated set", - desc: "Match any character that is not in the set.", - example: ["[^aeiou]", "glib jocks vex dwarves!"], - token: "[^ABC]" - }, { - id: "range", - tip: "Matches a character in the range {{getChar(prev)}} to {{getChar(next)}} (char code {{prev.code}} to {{next.code}}). {{getInsensitive()}}", - example: ["[g-s]", "abcdefghijklmnopqrstuvwxyz"], - desc: "Matches a character having a character code between the two specified characters inclusive.", - token: "[A-Z]" - }, { - id: "posixcharclass", - tip: "Matches any character in the '{{value}}' POSIX class.", - label: "POSIX class", - desc: "Matches any character in the specified POSIX class. Must be in a character set. For example,[[:alnum:]$]
will match alphanumeric characters and $
.",
- ext: "For a list of classes, see the PCRE spec.
", - token: "[:alnum:]" - }, { - id: "dot", - tip: "Matches any character {{getDotAll()}}.", - desc: "Matches any character except linebreaks.", - ext: " Equivalent to[^\\n\\r]
.",
- example: [".", "glib jocks vex dwarves!"],
- token: "."
- }, {
- id: "matchanyset",
- label: "match any",
- desc: "A character set that can be used to match any character, including line breaks, without the dotall flag (s
)." + "An alternative is [^]
, but it is not supported in all browsers.
[A-Za-z0-9_]
",
- example: ["\\w", "bonjour, mon fr\u00E8re"],
- token: "\\w"
- }, {
- id: "notword",
- label: "not word",
- desc: "Matches any character that is not a word character (alphanumeric & underscore).",
- ext: " Equivalent to [^A-Za-z0-9_]
",
- example: ["\\W", "bonjour, mon fr\u00E8re"],
- token: "\\W"
- }, {
- id: "digit",
- desc: "Matches any digit character (0-9).",
- ext: " Equivalent to [0-9]
.",
- example: ["\\d", "+1-(444)-555-1234"],
- token: "\\d"
- }, {
- id: "notdigit",
- label: "not digit",
- desc: "Matches any character that is not a digit character (0-9).",
- ext: " Equivalent to [^0-9]
.",
- example: ["\\D", "+1-(444)-555-1234"],
- token: "\\D"
- }, {
- id: "whitespace",
- desc: "Matches any whitespace character (spaces, tabs, line breaks).",
- example: ["\\s", "glib jocks vex dwarves!"],
- token: "\\s"
- }, {
- id: "notwhitespace",
- label: "not whitespace",
- desc: "Matches any character that is not a whitespace character (spaces, tabs, line breaks).",
- example: ["\\S", "glib jocks vex dwarves!"],
- token: "\\S"
- }, {
- id: "hwhitespace",
- label: "horizontal whitespace",
- desc: "Matches any horizontal whitespace character (spaces, tabs).",
- token: "\\h"
- }, {
- id: "nothwhitespace",
- label: "not horizontal whitespace",
- desc: "Matches any character that is not a horizontal whitespace character (spaces, tabs).",
- token: "\\H"
- }, {
- id: "vwhitespace",
- label: "vertical whitespace",
- desc: "Matches any vertical whitespace character (line breaks).",
- token: "\\v"
- }, {
- id: "notvwhitespace",
- label: "not vertical whitespace",
- desc: "Matches any character that is not a vertical whitespace character (line breaks).",
- token: "\\V"
- }, {
- id: "linebreak",
- label: "line break",
- desc: "Matches any line break character, including the CRLF pair, and CR / LF individually.",
- token: "\\R"
- }, {
- id: "notlinebreak",
- label: "not line break",
- desc: "Matches any character that is not a line break.",
- ext: " Similar to dot (.
) but is unaffected by the dotall flag (s
).",
- token: "\\N"
- }, {
- id: "unicodecat",
- tip: "Matches any character in the '{{getUniCat()}}' unicode category.",
- label: "unicode category",
- desc: "Matches a character in the specified unicode category. For example, \\p{Ll}
will match any lowercase letter.",
- ext: "For a list of categories, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\p{L}
\\pL
For a list of categories, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\P{L}
\\p{^L}
\\PL
\\p{Arabic}
will match characters in the Arabic script.",
- ext: "For a list of scripts, see the PCRE spec.
", - token: "\\p{Han}" - }, { - id: "notunicodescript", - tip: "Matches any character that is not in the '{{value}}' unicode script.", - label: "not unicode script", - desc: "Matches any character that is not in the specified unicode script.", - ext: "For a list of scripts, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\P{Han}
\\p{^Han}
",
- token: "\\P{Han}"
- }]
- }, {
- label: "Anchors",
- id: "anchors",
- desc: "Anchors are unique in that they match a position within a string, not a character.",
- kids: [{
- id: "bos",
- label: "beginning of string",
- desc: "Matches the beginning of the string.",
- ext: " Unlike ^
, this is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\A"
- }, {
- id: "eos",
- label: "end of string",
- desc: "Matches the end of the string.",
- ext: " Unlike $
, this is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\Z"
- }, {
- id: "abseos",
- label: "strict end of string",
- desc: "Matches the end of the string. Unlike $
or \\Z
, it does not allow for a trailing newline.",
- ext: " This is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\z"
- }, {
- id: "bof",
- label: "beginning",
- desc: "Matches the beginning of the string, or the beginning of a line if the multiline flag (m
) is enabled.",
- ext: " This matches a position, not a character.",
- example: ["^\\w+", "she sells seashells"],
- token: "^"
- }, {
- id: "eof",
- label: "end",
- desc: "Matches the end of the string, or the end of a line if the multiline flag (m
) is enabled.",
- ext: " This matches a position, not a character.",
- example: ["\\w+$", "she sells seashells"],
- token: "$"
- }, {
- id: "wordboundary",
- label: "word boundary",
- desc: "Matches a word boundary position between a word character and non-word character or position (start / end of string).",
- ext: " See the word character class (\w
) for more info.",
- example: ["s\\b", "she sells seashells"],
- token: "\\b"
- }, {
- id: "notwordboundary",
- label: "not word boundary",
- desc: "Matches any position that is not a word boundary.",
- ext: " This matches a position, not a character.",
- example: ["s\\B", "she sells seashells"],
- token: "\\B"
- }, {
- id: "prevmatchend",
- label: "previous match end",
- desc: "Matches the end position of the previous match.",
- ext: " This matches a position, not a character.",
- token: "\\G"
- }]
- }, {
- label: "Escaped characters",
- id: "escchars",
- desc: "Escape sequences can be used to insert reserved, special, and unicode characters. All escaped characters begin with the \\
character.",
- kids: [{
- id: "reservedchar",
- label: "reserved characters",
- desc: "The following character have special meaning, and should be preceded by a \\
(backslash) to represent a literal character:" + "
{{getEscChars()}}
Within a character set, only \\
, -
, and ]
need to be escaped.
\\000
.",
- ext: " Value must be less than 255 (\\377
).",
- // PCRE profile adds to ext.
- example: ["\\251", "RegExr is \u00A92014"],
- token: "\\000"
- }, {
- id: "eschexadecimal",
- label: "hexadecimal escape",
- desc: "Hexadecimal escaped character in the form \\xFF
.",
- example: ["\\xA9", "RegExr is \u00A92014"],
- token: "\\xFF"
- }, {
- id: "escunicodeu",
- label: "unicode escape",
- desc: "Unicode escaped character in the form \\uFFFF
",
- example: ["\\u00A9", "RegExr is \u00A92014"],
- token: "\\uFFFF"
- }, {
- id: "escunicodeub",
- label: "extended unicode escape",
- desc: "Unicode escaped character in the form \\u{FFFF}
.",
- ext: " Supports a full range of unicode point escapes with any number of hex digits. Requires the unicode flag (u
).
\\x{FF}
.",
- token: "\\x{FF}"
- }, {
- id: "esccontrolchar",
- label: "control character escape",
- desc: "Escaped control character in the form \\cZ
.",
- ext: " This can range from \\cA
(SOH, char code 1) to \\cZ
(SUB, char code 26). \\cI
matches TAB (char code 9).",
- token: "\\cI"
- }, {
- id: "escsequence",
- label: "escape sequence",
- tip: "Matches the literal string '{{value}}'.",
- desc: "All characters between the \\Q
and the \\E
are interpreted as a literal string. If \\E
is omitted, it continues to the end of the expression.",
- ext: " For example, the expression /\\Q(?.)\\E/
will match the string (?.)
.",
- token: "\\Q...\\E"
- }]
- }, {
- label: "Groups & References",
- id: "groups",
- desc: "Groups allow you to combine a sequence of tokens to operate on them together. Capture groups can be referenced by a backreference and accessed separately in the results.",
- kids: [{
- id: "group",
- label: "capturing group",
- desc: "Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference.",
- example: ["(ha)+", "hahaha haa hah!"],
- token: "(ABC)"
- }, {
- id: "namedgroup",
- label: "named capturing group",
- tip: "Creates a capturing group named '{{name}}'.",
- desc: "Creates a capturing group that can be referenced via the specified name.",
- ext: "There are multiple syntaxes for this feature:
(?'name'ABC)
(?P<name>ABC)
(?<name>ABC)
There are multiple syntaxes for this feature:
\\k'name'
\\k<name>
\\k{name}
\\g{name}
(?P=name)
\\1
matches the results of the first capture group & \\3
matches the third.",
- // PCRE adds relative and alternate syntaxes in ext
- example: ["(\\w)a\\1", "hah dad bad dab gag gab"],
- token: "\\1"
- }, {
- id: "branchreset",
- label: "branch reset group",
- desc: "Define alternative groups that share the same group numbers.",
- ext: "For example, in (?|(a)|(b))
both groups (a and b) would be counted as group #1.",
- token: "(?|(a)|(b))"
- }, {
- id: "noncapgroup",
- label: "non-capturing group",
- desc: "Groups multiple tokens together without creating a capture group.",
- example: ["(?:ha)+", "hahaha haa hah!"],
- token: "(?:ABC)"
- }, {
- id: "atomic",
- label: "atomic group",
- desc: "Non-capturing group that discards backtracking positions once matched.",
- ext: "
For example, /(?>ab|a)b/
will match abb
but not ab
because once the ab
option has matched, the atomic group prevents backtracking to retry with the a
option.
For example, /A(?(DEFINE)(?'foo'Z))B\\g'foo'/
will match ABZ
, because the define group is ignored in the match except to define the foo
subroutine that is referenced later with \\g'foo'
.
/(a|b)\\g'1'/
can match ab
, because the expression a|b
is evaluated again.",
- ext: "There are multiple syntaxes for this feature: \\g<1>
\\g'1'
(?1)
.
Relative values preceded by +
or -
are also supported. For example \\g<-1>
would match the group preceding the reference.
There are multiple syntaxes for this feature: \\g<name>
\\g'name'
(?&name)
(?P>name)
.
Negative lookarounds specify a group that can NOT match before or after the pattern.
", - kids: [{ - id: "poslookahead", - label: "positive lookahead", - desc: "Matches a group after the main expression without including it in the result.", - example: ["\\d(?=px)", "1pt 2px 3em 4px"], - token: "(?=ABC)" - }, { - id: "neglookahead", - label: "negative lookahead", - desc: "Specifies a group that can not match after the main expression (if it matches, the result is discarded).", - example: ["\\d(?!px)", "1pt 2px 3em 4px"], - token: "(?!ABC)" - }, { - id: "poslookbehind", - label: "positive lookbehind", - desc: "Matches a group before the main expression without including it in the result.", - token: "(?<=ABC)" - }, { - id: "neglookbehind", - label: "negative lookbehind", - desc: "Specifies a group that can not match before the main expression (if it matches, the result is discarded).", - token: "(?/o\\Kbar/ will matchbar
within the string foobar
",
- token: "\\K"
- }]
- }, {
- label: "Quantifiers & Alternation",
- id: "quants",
- desc: "Quantifiers indicate that the preceding token must be matched a certain number of times. By default, quantifiers are greedy, and will match as many characters as possible." + "{1,3}
will match 1 to 3. " + "{3}
will match exactly 3. " + "{3,}
will match 3 or more. ",
- example: ["b\\w{2,3}", "b be bee beer beers"],
- token: "{1,3}"
- }, {
- id: "opt",
- label: "optional",
- desc: "Matches 0 or 1 of the preceding token, effectively making it optional.",
- example: ["colou?r", "color colour"],
- token: "?"
- }, {
- id: "lazy",
- tip: "Makes the preceding quantifier {{getLazy()}}, causing it to match as {{getLazyFew()}} characters as possible.",
- desc: "Makes the preceding quantifier lazy, causing it to match as few characters as possible.",
- ext: " By default, quantifiers are greedy, and will match as many characters as possible.",
- example: ["b\\w+?", "b be bee beer beers"],
- token: "?"
- }, {
- id: "possessive",
- desc: "Makes the preceding quantifier possessive. It will match as many characters as possible, and will not release them to match subsequent tokens.",
- ext: "For example /.*a/
would match aaa
, but /.*+a/
would not, because the repeating dot would match and not release the last character to match a
.
|
.",
- ext: "It can operate within a group, or on a whole expression. The patterns will be tested in order.
", - example: ["b(a|e|i)d", "bad bud bod bed bid"], - token: "|" - }] - }, { - label: "Special", - id: "other", - desc: "Tokens that don't quite fit anywhere else.", - kids: [{ - id: "comment", - desc: "Allows you to insert a comment into your expression that is ignored when finding a match.", - token: "(?#foo)" - }, { - id: "conditional", - desc: "Conditionally matches one of two options based on whether a lookaround is matched.", - ext: "For example, /(?(?=a)ab|..)/
will match ab
and zx
but not ax
, because if the first character matches the condition a
then it evaluates the pattern ab
.
Any lookaround can be used as the condition. A lookahead will start the subsequent match at the start of the condition, a lookbehind will start it after.
", - token: "(?(?=A)B|C)" - }, { - id: "conditionalgroup", - label: "group conditional", - desc: "Conditionally matches one of two options based on whether group '{{name}}' matched.", - ext: "For example, /(z)?(?(1)a|b)/
will match za
because the first capture group matches z
successfully, which causes the conditional to match the first option a
.
The same pattern will also match b
on its own, because group 1 doesn't match, so it instead tries to match the second option b
.
You can reference a group by name, number, or relative position (ex. -1
).
For example, /a(?R)?b/
will match any number of a
followed by the same number of z
: the full text of az
or aaaazzzz
, but not azzz
.
There are multiple syntaxes for this feature:
(?R)
(?0)
\\g<0>
\\g'0'
(?i)
would enable case insensitivity for the remainder of the expression." + "Multiple modifiers can be specified, and any modifiers that follow -
are disabled. For example (?im-s)
would enable case insensitivity & multiline modes, and disable dotall.
Supported modifiers are: i
- case insensitive, s
- dotall, m
- multiline, x
- free spacing, J
- allow duplicate names, U
- ungreedy.
There are multiple syntaxes for this feature:
$0
\\0
\\{0}
$3
would insert the third capture group.",
- // NOTE: javascript profile overrides this:
- ext: "There are multiple syntaxes for this feature:
$1
\\1
\\{1}
\\n
, \\r
, \\t
, \\\\
, and unicode escapes \\uFFFF
. This may vary in your deploy environment."
- }]
- }, {
- id: "flags",
- label: "Flags",
- tooltip: "Expression flags change how the expression is interpreted. Click to edit.",
- desc: "Expression flags change how the expression is interpreted. Flags follow the closing forward slash of the expression (ex. /.+/igm
).",
- target: "flags",
- kids: [{
- id: "caseinsensitive",
- label: "ignore case",
- desc: "Makes the whole expression case-insensitive.",
- ext: " For example, /aBc/i
would match AbC
.",
- token: "i"
- }, {
- id: "global",
- label: "global search",
- tip: "Retain the index of the last match, allowing iterative searches.",
- desc: "Retain the index of the last match, allowing subsequent searches to start from the end of the previous match." + "Without the global flag, subsequent searches will return the same match.
^
and $
) will match the start and end of a line, instead of the start and end of the whole string." + "Note that patterns such as /^[\\s\\S]+$/m
may return matches that span multiple lines because the anchors will match the start/end of any line.
\\x{FFFFF}
unicode escapes.",
- desc: "When the unicode flag is enabled, you can use extended unicode escapes in the form \\x{FFFFF}
." + "It also makes other escapes stricter, causing unrecognized escapes (ex. \\j
) to throw an error.
g
) flag if set.",
- ext: " Because each search in RegExr is discrete, this flag has no further impact on the displayed results.",
- token: "y"
- }, {
- id: "dotall",
- desc: "Dot (.
) will match any character, including newline.",
- token: "s"
- }, {
- id: "extended",
- desc: "Literal whitespace characters are ignored, except in character sets.",
- token: "x"
- }, {
- id: "ungreedy",
- tip: "Makes quantifiers ungreedy (lazy) by default.",
- desc: "Makes quantifiers ungreedy (lazy) by default. Quantifiers followed by ?
will become greedy.",
- token: "U"
- }]
- }];
-
- // content that isn't included in the Reference menu item:
- o.misc = {
- kids: [{
- id: "ignorews",
- label: "ignored whitespace",
- tip: "Whitespace character ignored due to the extended flag or mode."
- }, {
- id: "extnumref",
- // alternative syntaxes.
- proxy: "numref"
- }, {
- id: "char",
- label: "character",
- tip: "Matches a {{getChar()}} character (char code {{code}}). {{getInsensitive()}}"
- }, {
- id: "escchar",
- label: "escaped character",
- tip: "Matches a {{getChar()}} character (char code {{code}})."
- }, {
- id: "open",
- tip: "Indicates the start of a regular expression."
- }, {
- id: "close",
- tip: "Indicates the end of a regular expression and the start of expression flags."
- }, {
- id: "condition",
- tip: "The lookaround to match in resolving the enclosing conditional statement. See 'conditional' in the Reference for info."
- }, {
- id: "conditionalelse",
- label: "conditional else",
- tip: "Delimits the 'else' portion of the conditional."
- }, {
- id: "ERROR",
- tip: "Errors in the expression are underlined in red. Roll over errors for more info."
- }, {
- id: "PREG_INTERNAL_ERROR",
- tip: "Internal PCRE error"
- }, {
- id: "PREG_BACKTRACK_LIMIT_ERROR",
- tip: "Backtrack limit was exhausted."
- }, {
- id: "PREG_RECURSION_LIMIT_ERROR",
- tip: "Recursion limit was exhausted"
- }, {
- id: "PREG_BAD_UTF8_ERROR",
- tip: "Malformed UTF-8 data"
- }, {
- id: "PREG_BAD_UTF8_OFFSET_ERROR",
- tip: "Malformed UTF-8 data"
- }]
- };
- o.errors = {
- groupopen: "Unmatched opening parenthesis.",
- groupclose: "Unmatched closing parenthesis.",
- setopen: "Unmatched opening square bracket.",
- rangerev: "Range values reversed. Start char code is greater than end char code.",
- quanttarg: "Invalid target for quantifier.",
- quantrev: "Quantifier minimum is greater than maximum.",
- esccharopen: "Dangling backslash.",
- esccharbad: "Unrecognized or malformed escape character.",
- unicodebad: "Unrecognized unicode category or script.",
- posixcharclassbad: "Unrecognized POSIX character class.",
- posixcharclassnoset: "POSIX character class must be in a character set.",
- notsupported: "The \"{{~getLabel()}}\" feature is not supported in this flavor of RegEx.",
- fwdslash: "Unescaped forward slash. This may cause issues if copying/pasting this expression into code.",
- esccharbad: "Invalid escape sequence.",
- servercomm: "An error occurred while communicating with the server.",
- extraelse: "Extra else in conditional group.",
- unmatchedref: "Reference to non-existent group \"{{name}}\".",
- modebad: "Unrecognized mode flag \"{{errmode}}
\".",
- badname: "Group name can not start with a digit.",
- dupname: "Duplicate group name.",
- branchreseterr: "Branch Reset. Results will be ok, but RegExr's parser does not number branch reset groups correctly. Coming soon!",
- timeout: "The expression took longer than 250ms to execute.",
- // TODO: can we couple this to the help content somehow?
-
- // warnings:
- jsfuture: "The \"{{~getLabel()}}\" feature may not be supported in all browsers.",
- infinite: "The expression can return empty matches, and may match infinitely in some use cases." // TODO: can we couple this to the help content somehow?
- };
-
- /*
- classes:
- quant
- set
- special
- ref
- esc
- anchor
- charclass
- group
- comment
- */
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see The syntax \\\\o{FFF}
is also supported.
There are multiple syntaxes for this feature: \\\\1
\\\\g1
\\\\g{1}
.
The latter syntaxes support relative values preceded by +
or -
. For example \\\\g-1
would match the group preceding the reference.
This behaviour is reversed by the ungreedy (U
) flag/modifier.
Requires the u
flag.
For a list of values, see this MDN page.
\"\n\t\t},\n\t\t// notunicodecat, unicodescript, notunicodescript are copied from unicodecat below.\n\t}\n};\n\njavascript.docs.notunicodecat = javascript.docs.unicodescript = javascript.docs.notunicodescript = javascript.docs.unicodecat;\n\nexport default javascript;\n","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see\"+content+\"
\";\n\t\t\tif (!token.open) { content += app.reference.tipForToken(token); }\n\t\t\telse { content += \" \"; }\n\t\t\tlet div = $.create(\"div\", null, content, el);\n\t\t\t\n\t\t\tif (token.close) {\n\t\t\t\tclassName = groupClasses[token.clss || token.type];\n\t\t\t\tif (className) {\n\t\t\t\t\tclassName = className.replace(\"%depth%\", Math.min(4,token.depth));\n\t\t\t\t\t$.addClass(div, className);\n\t\t\t\t}\n\t\t\t\tif (token.depth > 3) {\n\t\t\t\t\tdiv.innerHTML = \"So... you wanted to see what would happen if you just kept nesting groups, eh? Well, this is it.\"+\n\t\t\t\t\t\t\" I was going to reward your curiosity with a RegEx joke, but a quick search on google reveals that not even\"+\n\t\t\t\t\t\t\" the collective wisdom of the internet can make regular expressions funny. Well, except the whole 'now you've got two problems'\"+\n\t\t\t\t\t\t\" shtick, but you've probably heard that one already. Wasn't really worth the effort, was it?\";\n\t\t\t\t\ttoken = token.close.prv;\n\t\t\t\t\tthis._divs.push(div);\n\t\t\t\t\tel = div;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tel = div;\n\t\t\t}\n\t\t\t\n\t\t\tdiv.token = token;\n\t\n\t\t\tif (token.open) {\n\t\t\t\t$.addClass(div, \"close\");\n\t\t\t\tdiv.proxy = el;\n\t\t\t\tel = el.parentNode;\n\t\t\t}\n\t\n\t\t\tif (token.error) {\n\t\t\t\t$.addClass(div, \"error\");\n\t\t\t\tif (token.error.warning) { $.addClass(div, \"warning\"); }\n\t\t\t}\n\t\n\t\t\tif (!token.open) {\n\t\t\t\tdiv.addEventListener(\"mouseover\", this._handleMouseEvent);\n\t\t\t\tdiv.addEventListener(\"mouseout\", this._handleMouseEvent);\n\t\t\t\tdiv.addEventListener(\"click\", this._handleMouseEvent);\n\t\t\t}\n\t\t\t\n\t\t\tif (token.clss === \"quant\" || token.type === \"lazy\" || token.type === \"possessive\") {\n\t\t\t\tthis._insertApplied(div);\n\t\t\t} else {\n\t\t\t\tthis._divs.push(div);\n\t\t\t}\n\t\t}\n\t}\n\n\t_insertApplied(div) {\n\t\tlet divs = this._divs, prv = div.token.prv, d, i=divs.length;\n\t\twhile ((d = divs[--i]) && d.token !== prv) {} // search backwards for efficiency\n\t\td = d.proxy||d;\n\t\tdivs.splice(i, 0, div);\n\t\td.insertAdjacentElement(\"afterend\", div);\n\t\t$.addClass(div, \"applied\");\n\t}\n\n\t_handleHoverChange() {\n\t\tlet token = app.expression.highlighter.hoverToken;\n\t\t$.removeClass($.queryAll(\"div.selected\", this.el), \"selected\");\n\t\t$.removeClass($.queryAll(\"div.related\", this.el), \"related\");\n\t\tif (!token) { return; }\n\t\t\n\t\tlet div = this._findDiv(token);\n\t\t$.addClass(div, \"selected\");\n\t\tif (token.related) {\n\t\t\tfor (let i = 0, l=token.related.length; i < l; i++) {\n\t\t\t\t$.addClass(this._findDiv(token.related[i]), \"related\");\n\t\t\t}\n\t\t}\n\t}\n\t\n\t_findDiv(token) {\n\t\treturn Utils.find(this._divs, (div) => div.token === token);\n\t}\n\n\t_handleMouseEvent(evt) {\n\t\tlet type = evt.type, token = evt.currentTarget.token;\n\t\tif (type == \"click\") { app.sidebar.showToken(token); }\n\t\telse { app.expression.highlighter.hoverToken = type === \"mouseout\" ? null : token; }\n\t\tevt.stopPropagation();\n\t}\n\t\n\t_handleEvent(evt) {\n\t\tif (evt.type === \"change\") { this._update(); }\n\t\telse if (evt.type === \"hover\") { this._handleHoverChange(); }\n\t}\n}","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see Group
row to highlight it in the Expression.\", this.el);\n\t\tthis._addMatch(app.text.selectedMatch, app.text.value);\n\t}\n\t\n\t_addMatch(match, textVal) {\n\t\tif (!match) { return; }\n\t\tlet groups = match.groups, l=groups&&groups.length, ext=l&&(groups[0].i != null), matchVal=this._getMatchVal(match, textVal), extStr=\"\", me = match.i+match.l;\n\t\tlet groupTokens = app.expression.lexer.captureGroups;\n\t\t\n\t\tlet tableEl = $.create(\"table\", null, null, this.el);\n\t\tlet matchEl = $.create(\"tr\", \"match\", \"To edit a pattern you created, click its URL or double-click it in the list to load it, then open Save / Share to edit and save.
\",\n\tsearch: true,\n\tkids: []\n\t},\n\n\t{\n\tlabel: \"Cheatsheet\",\n\tid:\"cheatsheet\",\n\tel: \"#cheatsheet\"\n\t},\n\n\t{ // injected from Reference\n\tid:\"reference\"\n\t},\n\n\t{\n\tlabel: \"Community Patterns\",\n\tid: \"community\",\n\tdesc: \"Welcome to Community Patterns, a searchable database of patterns submitted by users like you.\"+\n\t\t\"After selecting a pattern, click its URL or double-click it in the list to load the full pattern. Or use the right arrow icon to load just the expression or text.
\"+\n\t\t\"Help make the Community better by rating patterns, and submitting your own via Search & Share in the menu.
\",\n\tsearch: true,\n\tkids: []\n\t},\n\n\t{\n\tlabel: \"Help\",\n\tid: \"help\",\n\tdesc: \"Help for the RegExr application. See the RegEx Reference for help with Regular Expressions.\",\n\tkids: [\n\n\t\t{\n\t\tlabel:\"About\",\n\t\tdesc:\"RegExr v<%= build_version %> (<%= build_date %>).\"+\n\t\t\t\"Created by Grant Skinner and the gskinner team, using the CreateJS & CodeMirror libraries.
\"+\n\t\t\t\"You can provide feedback or log bugs on GitHub.
\"\n\t\t},\n\t\t{\n\t\tlabel:\"Getting started\",\n\t\tdesc:\"RegExr provides real-time visual results, syntax highlighting, tooltips, and undo/redo ({{getCtrlKey()}}-Z / Y) so it's easy and fun to explore Regular Expressions.\"+\n\t\t\t\"Browse through the RegEx Reference and test different tokens to see what they do, then check out Community Patterns to see examples.
\"+\n\t\t\t\"You can also Save your patterns for later reference, or to share with others. Sign In to ensure you don't lose your patterns.
\"+\n\t\t\t\"Modify your pattern's details, share it with the Community, or make it private, or delete it in Pattern Settings
\",\n\t\tkids: [\n\t\t\t{\n\t\t\tlabel:\"Expression panel\",\n\t\t\tdesc:\"This is where you enter a regular expression to test. The results in the Text and Tools panel will update as you type.\"+\n\t\t\t\t\"Roll over the expression for information on each token.\"+\n\t\t\t\t\"The buttons to the right allow you to switch RegEx engines, or edit the expression flags.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Text panel\",\n\t\t\tdesc:\"This is where you enter text to test your expression against. Drag & drop a text file to load its contents.\"+\n\t\t\t\t\"Matches will be highlighted as you type. Roll over a match for information on the match and its capture groups. The match count and execution time are shown in the title bar.
\"+\n\t\t\t\t\"Lighter colored caps at the start or end of a line indicate the match continues between lines.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Tests panel\",\n\t\t\tdesc:\"This allows you to create a suite of tests that you can use to validate your Expression in real time.\"+\n\t\t\t\t\"A test can check if the pattern matches anything, matches everything, or matches nothing in the test text.
\"+\n\t\t\t\t\"Access this panel with the toggle at the top of the Text panel. When you save, both your Text and Tests will be saved.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Tools panel\",\n\t\t\tdesc:\"Click the Tools title bar below the Text panel to show or hide the Tools panel.\"+\n\t\t\t\t\"Tools provide different ways of working with or exploring your results.
\",\n\t\t\tkids: [\n\t\t\t\t{\n\t\t\t\tlabel:\"Replace\",\n\t\t\t\tid: \"replace\",\n\t\t\t\tdesc:\"The Replace tool replaces matches with a specified string or pattern.\"+\n\t\t\t\t\t\"Matches in the Text panel are replaced by the substitution string & displayed as you type.
\"+\n\t\t\t\t\t\"Substitution tokens and escaped characters are supported, such as \\\\n
, \\\\t
& \\\\u0009
.
Roll over tokens for information, and check out the RegEx Reference for more information.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"List\",\n\t\t\t\tid: \"list\",\n\t\t\t\tdesc:\"The List tool lists all found matches.\"+\n\t\t\t\t\t\"You can specify either a simple delimiter (ex. ,
or \\\\n
), or use substitution tokens to generate more advanced reports. For example, $1\\\\n
would list all group 1 results (in the JavaScript engine).
Escaped characters are supported, such as \\\\n
, \\\\t
& \\\\u0009
.
Roll over tokens for information.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"Details\",\n\t\t\t\tid: \"details\",\n\t\t\t\tdesc:\"The Details tool displays the full text of a match and its capture groups.\"+\n\t\t\t\t\t\"Click on a highlighted match in the Text panel to display the details for that match.
\"+\n\t\t\t\t\t\"Roll over a group row to highlight that group in your Expression.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"Explain\",\n\t\t\t\tid: \"explain\",\n\t\t\t\tdesc:\"The Explain tool displays a detailed breakdown of the Expression.\"+\n\t\t\t\t\t\"Mouse over the explanation to highlight the related tokens in the Expression panel and vice versa.
\"+\n\t\t\t\t\t\"Click an item in the explanation to show more info in the RegEx Reference.
\"\n\t\t\t\t}\n\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Menu\",\n\t\t\tdesc:\"The Menu (this panel) includes Help, a full RegEx Reference, a Cheatsheet, and Pattern Settings features.\"+\n\t\t\t\t\"Double-click a selected item in the RegEx Reference to insert it into your Expression. Click the arrow beside an example to load it.
\"+\n\t\t\t\t\"The menu also includes searchable Community Patterns, and patterns you've created or favorited in My Patterns.
\"\n\t\t\t}\n\t\t]\n\t\t},\n\t\t{\n\t\tlabel:\"Signing in\",\n\t\tid: \"signin\",\n\t\tdesc:\"Before you sign in, RegExr creates a temporary account which relies on a browser cookie. This means you can't access your patterns on other computers, and that you could lose your patterns if your cookies are deleted or expire.\"+\n\t\t\t\"Signing in creates a permanent account, so you can access your patterns anywhere, anytime.
\"+\n\t\t\t\"Your existing patterns & favorites will automatically be assigned to the new account.
\"+\n\t\t\t\"We don't use your info for anything other than signing you into your RegExr account.
\"\n\t\t},\n\t\t{\n\t\tid: \"engine\",\n\t\tlabel:\"RegEx engines\",\n\t\tdesc:\"While the core feature set of regular expressions is fairly consistent, different implementations (ex. Perl vs Java) may have different features or behaviours.\"+\n\t\t\t \"RegExr currently supports JavaScript RegExp executed in your browser and PCRE via PHP.
\"+\n\t\t\t \"You can switch engines using the dropdown in Expression.
\",\n\t\tkids: [\n\t\t\t{\n\t\t\tlabel:\"JavaScript\",\n\t\t\tdesc:\"Your browser's JavaScript engine is used to execute RegEx in an asynchronous worker usingRegExp.exec()
.\"+\n\t\t\t\t\"Note that while implementations are mostly consistent, there are small variations between browsers. Here is a short list of known differences:
The following query string params are recognized:
engine
- the RegEx engine to use (js
or pcre
)expression
- populates the Expression area. It is recommended to pass a full expression with flags (/.*/ig
) not just the pattern (.*
)text
- populates the Text areatool
- sets the tool (replace, list, details, or explain)input
- populates the tool input field\" + Utils.htmlSafe(exp) + \"
\";\n\t\t\tif (txt && regex) {\n\t\t\t\tlet over=Math.max(0, txt.length-160), s=txt;\n\t\t\t\tif (over) { s = Utils.htmlSafe(s.substr(0,159)); }\n\t\t\t\tif (regex) { s = s.replace(regex, \"$&\"); }\n\t\t\t\t// TODO: this won't match on html elements:\n\t\t\t\tstr += \"\" + s + (over?\"\\u2026\" : \"\") + \"
\";\n\t\t\t}\n\t\t}\n\t\tthis.el.innerHTML = str;\n\t\tif (exp) {\n\t\t\t$.query(\"code.expression > .load\", this.el).addEventListener(\"click\", ()=> {\n\t\t\t\t// TODO: this will need to be updated when we support other delimiters:\n\t\t\t\tapp.expression.value = exp[0] === \"/\" ? exp : \"/\"+exp+\"/g\";\n\t\t\t});\n\t\t\t\n\t\t}\n\t\tif (txt) { $.query(\"code.text > .load\", this.el).addEventListener(\"click\", ()=> app.text.value = txt); }\n\t}\n\t\n\n}","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see \"+token.on+\"
\\\".\" : \"\")\n\t\tif (token.off) { str += \" Disable \\\"\"+token.off+\"
\\\".\"; }\n\t\treturn str;\n\t}\n\n\tgetInsensitive(token) {\n\t\tif (token.code) {\n\t\t\tlet chr = String.fromCharCode(token.code);\n\t\t\tif (chr.toLowerCase() === chr.toUpperCase()) { return \"\"; }\n\t\t}\n\t\treturn token.modes ? `Case ${token.modes.i ? \"in\" : \"\"}sensitive.` : \"\";\n\t}\n\n\tgetDotAll(token) {\n\t\treturn (token.modes.s ? \"including\" : \"except\") + \" line breaks\";\n\t}\n\n\tgetLabel(token) {\n\t\tlet node = this.getNodeForToken(token);\n\t\treturn node ? node.label || node.id || \"\" : token.type;\n\t}\n\n\tgetDesc(token) {\n\t\treturn this.getVal(this.getNodeForToken(token), \"desc\");\n\t}\n\n\tgetLazy(token) {\n\t\treturn token.modes.U ? \"greedy\" : \"lazy\";\n\t}\n\n\tgetLazyFew(token) {\n\t\treturn token.modes.U ? \"many\" : \"few\";\n\t}\n\n\tgetPHPVersion() {\n\t\treturn this._config.PHPVersion;\n\t}\n\n\tgetPCREVersion() {\n\t\treturn this._config.PCREVersion;\n\t}\n\n\tgetCtrlKey() {\n\t\treturn Utils.getCtrlKey();\n\t}\n\n\tgetEscChars() {\n\t\tlet o = this._flavor.profile.escChars, str=\"\";\n\t\tfor (let n in o) { str += n; }\n\t\treturn str;\n\t}\n\n\t/*\n\tSearches for tags in the string in the format:\n\t`{{prop.prop}}` or `{{method(prop.prop)}}`\n\t\n\tThe first format will inject the specified property of the data object.\n\tFor example, `{{a.b}}` would inject the value of `data.a.b`.\n\t\n\tThe second will inject the results of calling the specified function on the functs object with a property of the data object as it's parameter (or the data object itself if empty).\n\tFor example, `{{myMethod(a.b)}}` would inject the return value of `functs.myMethod(data.a.b)`.\n\t\n\tCurrently only supports a single param.\n\t */\n\tfillTags(str, data, functs, maxLength=20, htmlSafe=true) {\n\t\tlet match;\n\t\twhile (match = str.match(/{{~?[\\w.()]+}}/)) {\n\t\t\tlet val, f, safe=false;\n\t\t\tval = match[0].substring(2, match[0].length - 2);\n\t\t\tif (val[0] === \"~\") {\n\t\t\t\tval = val.substr(1);\n\t\t\t\tsafe = true;\n\t\t\t}\n\t\t\tlet match2 = val.match(/\\([\\w.]*\\)/);\n\t\t\tif (match2) {\n\t\t\t\tf = val.substr(0, match2.index);\n\t\t\t\tval = match2[0].substring(1, match2[0].length - 1);\n\t\t\t} else {\n\t\t\t\tf = null;\n\t\t\t}\n\t\t\tlet o = data, arr = val.split(\".\");\n\t\t\tfor (let i = 0; i < arr.length; i++) {\n\t\t\t\tlet prop = arr[i];\n\t\t\t\tif (prop && o) { o = o[prop]; }\n\t\t\t}\n\t\t\tval = o;\n\t\t\tif (f) {\n\t\t\t\tif (functs[f]) { val = functs[f](val); }\n\t\t\t\telse { val = \" [\"+f+\"] \"; }\n\t\t\t}\n\t\t\tif (!safe && (maxLength || htmlSafe)) { val = Utils.shorten(val, maxLength, htmlSafe, \"i\"); }\n\t\t\tstr = str.replace(match[0], val);\n\t\t}\n\t\treturn str;\n\t}\n\t\n\t// returns doc props from the profile or reference as appropriate (ex. tip, desc, ext)\n\tgetVal(node, prop) {\n\t\tif (!node) { return \"\"; }\n\t\tlet pDocs = this._flavor.getDocs(node.id), pRef = (pDocs && pDocs[prop]);\n\t\tif (pRef != null && pRef[0] !== \"+\") { return pRef; }\n\t\tlet ref=(node&&node[prop])||\"\";\n\t\treturn pRef != null ? ref+pRef.substr(1) : ref;\n\t}\n\t\n\tgetNodeForToken(token) {\n\t\tlet id=this.idForToken(token), clss = token.clss;\n\t\t\n\t\t// Special cases:\n\t\tif (clss === \"quant\") { id = clss; }\n\t\tif (clss === \"esc\" && token.type !== \"escsequence\") { id = \"escchar\"; }\n\n\t\treturn this.getNode(id);\n\t}\n\n\tgetNode(id) {\n\t\tlet map=this._idMap, node = map[id]\n\t\twhile (node&&node.proxy) { node = map[node.proxy]; }\n\t\treturn node;\n\t}\n\t\n\tgetError(error, token) {\n\t\tlet errId = error && error.id;\n\t\tlet str = this._content.errors[errId] || \"no docs for error='\" + errId + \"'\";\n\t\tif (token) { str = this.fillTags(str, token, this, 20); }\n\t\treturn str;\n\t}\n\t\n\ttipForToken(token) {\n\t\tif (!token) { return null; }\n\n\t\tlet node = this.getNodeForToken(token), label, tip;\n\n\t\tif (token.error && !token.error.warning) {\n\t\t\tlabel = \"ERROR: \";\n\t\t\ttip = this.getError(token.error, token);\n\t\t} else {\n\t\t\tlabel = node ? node.label || node.id || \"\" : token.type;\n\t\t\ttip = this.getVal(node, \"tip\") || this.getVal(node, \"desc\");\n\t\t\ttip = this.fillTags(tip, token, this, 20);\n\t\t\tif (token.type === \"group\") { label += \" #\" + token.num; }\n\t\t\tlabel = \"\" + label[0].toUpperCase() + label.substr(1) + \". \";\n\n\t\t\tif (token.error) {\n\t\t\t\ttip += \"WARNING: \" + this.getError(token.error, token) + \"\";\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn tip ? label + tip : \"no docs for id='\" + this.idForToken(token) + \"'\";\n\t}\n\t\n\tgetContent(id) {\n\t\tlet node = this.getNode(id);\n\t\treturn this.fillTags(this.getVal(node, \"desc\") + this.getVal(node, \"ext\"), node, this);\n\t}\n\t\n\t// TODO: this isn't necessarily the most ideal place for this method (has nothing to do with Reference). Maybe move into Text?\n\ttipForMatch(match, text) {\n\t\tif (!match) { return null; }\n\t\tlet more = match.l > 150;\n\t\tlet str = \"match: \" + Utils.shorten(text.substr(match.i, match.l), 150, true, \"i\") +\n\t\t\t\t \"\" + match.i + \"-\" + (match.i+match.l-1)+ \"
\";\n\t\t\n\t\tlet groups = match.groups, l = groups && groups.length;\n\t\tfor (let i = 0; i < l; i++) {\n\t\t\tif (i > 3 && l > 5) {\n\t\t\t\tmore = false;\n\t\t\t\tstr += \"Double-click an item in the list to insert it into your Expression.
\n\tClick the arrow beside an example to load it.
`;\n\no.kids = [\n\t{\n\tlabel: \"Character classes\",\n\tid: \"charclasses\",\n\tdesc: \"Character classes match a character from a specific set. There are a number of predefined character classes and you can also define your own sets.\",\n\tkids: [\n\t\t\n\t\t{\n\t\tid:\"set\",\n\t\tlabel: \"character set\",\n\t\tdesc:\"Match any character in the set.\",\n\t\texample:[\"[aeiou]\",\"glib jocks vex dwarves!\"],\n\t\ttoken:\"[ABC]\"\n\t\t},\n\t\t{\n\t\tid:\"setnot\",\n\t\tlabel: \"negated set\",\n\t\tdesc:\"Match any character that is not in the set.\",\n\t\texample:[\"[^aeiou]\",\"glib jocks vex dwarves!\"],\n\t\ttoken:\"[^ABC]\"\n\t\t},\n\t\t{\n\t\tid:\"range\",\n\t\ttip:\"Matches a character in the range {{getChar(prev)}} to {{getChar(next)}} (char code {{prev.code}} to {{next.code}}). {{getInsensitive()}}\",\n\t\texample:[\"[g-s]\",\"abcdefghijklmnopqrstuvwxyz\"],\n\t\tdesc: \"Matches a character having a character code between the two specified characters inclusive.\",\n\t\ttoken:\"[A-Z]\"\n\t\t},\n\t\t{\n\t\tid:\"posixcharclass\",\n\t\ttip:\"Matches any character in the '{{value}}' POSIX class.\",\n\t\tlabel:\"POSIX class\",\n\t\tdesc:\"Matches any character in the specified POSIX class. Must be in a character set. For example,[[:alnum:]$]
will match alphanumeric characters and $
.\",\n\t\text:\"For a list of classes, see the PCRE spec.
\",\n\t\ttoken:\"[:alnum:]\"\n\t\t},\n\t\t{\n\t\tid:\"dot\",\n\t\ttip:\"Matches any character {{getDotAll()}}.\",\n\t\tdesc:\"Matches any character except linebreaks.\",\n\t\text:\" Equivalent to[^\\\\n\\\\r]
.\",\n\t\texample:[\".\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\".\"\n\t\t},\n\t\t{\n\t\tid:\"matchanyset\",\n\t\tlabel:\"match any\",\n\t\tdesc:\"A character set that can be used to match any character, including line breaks, without the dotall flag (s
).\"+\n\t\t\t\"An alternative is [^]
, but it is not supported in all browsers.
[A-Za-z0-9_]
\",\n\t\texample:[\"\\\\w\",\"bonjour, mon fr\\u00E8re\"],\n\t\ttoken:\"\\\\w\"\n\t\t},\n\t\t{\n\t\tid:\"notword\",\n\t\tlabel: \"not word\",\n\t\tdesc:\"Matches any character that is not a word character (alphanumeric & underscore).\",\n\t\text:\" Equivalent to [^A-Za-z0-9_]
\",\n\t\texample:[\"\\\\W\",\"bonjour, mon fr\\u00E8re\"],\n\t\ttoken:\"\\\\W\"\n\t\t},\n\t\t{\n\t\tid:\"digit\",\n\t\tdesc:\"Matches any digit character (0-9).\",\n\t\text:\" Equivalent to [0-9]
.\",\n\t\texample:[\"\\\\d\",\"+1-(444)-555-1234\"],\n\t\ttoken:\"\\\\d\"\n\t\t},\n\t\t{\n\t\tid:\"notdigit\",\n\t\tlabel: \"not digit\",\n\t\tdesc:\"Matches any character that is not a digit character (0-9).\",\n\t\text:\" Equivalent to [^0-9]
.\",\n\t\texample:[\"\\\\D\",\"+1-(444)-555-1234\"],\n\t\ttoken:\"\\\\D\"\n\t\t},\n\t\t{\n\t\tid:\"whitespace\",\n\t\tdesc:\"Matches any whitespace character (spaces, tabs, line breaks).\",\n\t\texample:[\"\\\\s\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\"\\\\s\"\n\t\t},\n\t\t{\n\t\tid:\"notwhitespace\",\n\t\tlabel: \"not whitespace\",\n\t\tdesc:\"Matches any character that is not a whitespace character (spaces, tabs, line breaks).\",\n\t\texample:[\"\\\\S\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\"\\\\S\"\n\t\t},\n\t\t{\n\t\tid:\"hwhitespace\",\n\t\tlabel:\"horizontal whitespace\",\n\t\tdesc:\"Matches any horizontal whitespace character (spaces, tabs).\",\n\t\ttoken:\"\\\\h\"\n\t\t},\n\t\t{\n\t\tid:\"nothwhitespace\",\n\t\tlabel: \"not horizontal whitespace\",\n\t\tdesc:\"Matches any character that is not a horizontal whitespace character (spaces, tabs).\",\n\t\ttoken:\"\\\\H\"\n\t\t},\n\t\t{\n\t\tid:\"vwhitespace\",\n\t\tlabel:\"vertical whitespace\",\n\t\tdesc:\"Matches any vertical whitespace character (line breaks).\",\n\t\ttoken:\"\\\\v\"\n\t\t},\n\t\t{\n\t\tid:\"notvwhitespace\",\n\t\tlabel: \"not vertical whitespace\",\n\t\tdesc:\"Matches any character that is not a vertical whitespace character (line breaks).\",\n\t\ttoken:\"\\\\V\"\n\t\t},\n\t\t{\n\t\tid:\"linebreak\",\n\t\tlabel:\"line break\",\n\t\tdesc:\"Matches any line break character, including the CRLF pair, and CR / LF individually.\",\n\t\ttoken:\"\\\\R\"\n\t\t},\n\t\t{\n\t\tid:\"notlinebreak\",\n\t\tlabel:\"not line break\",\n\t\tdesc:\"Matches any character that is not a line break.\",\n\t\text:\" Similar to dot (.
) but is unaffected by the dotall flag (s
).\",\n\t\ttoken:\"\\\\N\"\n\t\t},\n\t\t{\n\t\tid:\"unicodecat\",\n\t\ttip:\"Matches any character in the '{{getUniCat()}}' unicode category.\",\n\t\tlabel:\"unicode category\",\n\t\tdesc:\"Matches a character in the specified unicode category. For example, \\\\p{Ll}
will match any lowercase letter.\",\n\t\text:\"For a list of categories, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\p{L}
\\\\pL
For a list of categories, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\P{L}
\\\\p{^L}
\\\\PL
\\\\p{Arabic}
will match characters in the Arabic script.\",\n\t\text:\"For a list of scripts, see the PCRE spec.
\",\n\t\ttoken:\"\\\\p{Han}\"\n\t\t},\n\t\t{\n\t\tid:\"notunicodescript\",\n\t\ttip:\"Matches any character that is not in the '{{value}}' unicode script.\",\n\t\tlabel:\"not unicode script\",\n\t\tdesc:\"Matches any character that is not in the specified unicode script.\",\n\t\text:\"For a list of scripts, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\P{Han}
\\\\p{^Han}
\",\n\t\ttoken:\"\\\\P{Han}\"\n\t\t}\n\t]\n\t},\n\n\t{\n\tlabel:\"Anchors\",\n\tid:\"anchors\",\n\tdesc:\"Anchors are unique in that they match a position within a string, not a character.\",\n\tkids:[\n\t\t{\n\t\tid:\"bos\",\n\t\tlabel:\"beginning of string\",\n\t\tdesc:\"Matches the beginning of the string.\",\n\t\text:\" Unlike ^
, this is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\A\"\n\t\t},\n\t\t{\n\t\tid:\"eos\",\n\t\tlabel:\"end of string\",\n\t\tdesc:\"Matches the end of the string.\",\n\t\text:\" Unlike $
, this is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\Z\"\n\t\t},\n\t\t{\n\t\tid:\"abseos\",\n\t\tlabel:\"strict end of string\",\n\t\tdesc:\"Matches the end of the string. Unlike $
or \\\\Z
, it does not allow for a trailing newline.\",\n\t\text:\" This is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\z\"\n\t\t},\n\t\t{\n\t\tid:\"bof\",\n\t\tlabel:\"beginning\",\n\t\tdesc:\"Matches the beginning of the string, or the beginning of a line if the multiline flag (m
) is enabled.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"^\\\\w+\",\"she sells seashells\"],\n\t\ttoken:\"^\"\n\t\t},\n\t\t{\n\t\tid:\"eof\",\n\t\tlabel:\"end\",\n\t\tdesc:\"Matches the end of the string, or the end of a line if the multiline flag (m
) is enabled.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"\\\\w+$\",\"she sells seashells\"],\n\t\ttoken:\"$\"\n\t\t},\n\t\t{\n\t\tid:\"wordboundary\",\n\t\tlabel:\"word boundary\",\n\t\tdesc:\"Matches a word boundary position between a word character and non-word character or position (start / end of string).\",\n\t\text:\" See the word character class (\\w
) for more info.\",\n\t\texample:[\"s\\\\b\",\"she sells seashells\"],\n\t\ttoken:\"\\\\b\"\n\t\t},\n\t\t{\n\t\tid:\"notwordboundary\",\n\t\tlabel: \"not word boundary\",\n\t\tdesc:\"Matches any position that is not a word boundary.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"s\\\\B\",\"she sells seashells\"],\n\t\ttoken:\"\\\\B\"\n\t\t},\n\t\t{\n\t\tid:\"prevmatchend\",\n\t\tlabel: \"previous match end\",\n\t\tdesc:\"Matches the end position of the previous match.\",\n\t\text:\" This matches a position, not a character.\",\n\t\ttoken:\"\\\\G\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Escaped characters\",\n\tid:\"escchars\",\n\tdesc: \"Escape sequences can be used to insert reserved, special, and unicode characters. All escaped characters begin with the \\\\
character.\",\n\tkids: [\n\t\t{\n\t\tid:\"reservedchar\",\n\t\tlabel:\"reserved characters\",\n\t\tdesc:\"The following character have special meaning, and should be preceded by a \\\\
(backslash) to represent a literal character:\"+\n\t\t\t\"
{{getEscChars()}}
Within a character set, only \\\\
, -
, and ]
need to be escaped.
\\\\000
.\",\n\t\text:\" Value must be less than 255 (\\\\377
).\", // PCRE profile adds to ext.\n\t\texample:[\"\\\\251\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\000\"\n\t\t},\n\t\t{\n\t\tid:\"eschexadecimal\",\n\t\tlabel:\"hexadecimal escape\",\n\t\tdesc:\"Hexadecimal escaped character in the form \\\\xFF
.\",\n\t\texample:[\"\\\\xA9\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\xFF\"\n\t\t},\n\t\t{\n\t\tid:\"escunicodeu\",\n\t\tlabel:\"unicode escape\",\n\t\tdesc:\"Unicode escaped character in the form \\\\uFFFF
\",\n\t\texample:[\"\\\\u00A9\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\uFFFF\"\n\t\t},\n\t\t{\n\t\tid:\"escunicodeub\",\n\t\tlabel:\"extended unicode escape\",\n\t\tdesc:\"Unicode escaped character in the form \\\\u{FFFF}
.\",\n\t\text:\" Supports a full range of unicode point escapes with any number of hex digits. Requires the unicode flag (u
).
\\\\x{FF}
.\",\n\t\ttoken:\"\\\\x{FF}\"\n\t\t},\n\t\t{\n\t\tid:\"esccontrolchar\",\n\t\tlabel:\"control character escape\",\n\t\tdesc:\"Escaped control character in the form \\\\cZ
.\",\n\t\text:\" This can range from \\\\cA
(SOH, char code 1) to \\\\cZ
(SUB, char code 26). \\\\cI
matches TAB (char code 9).\",\n\t\ttoken:\"\\\\cI\"\n\t\t},\n\t\t{\n\t\tid:\"escsequence\",\n\t\tlabel:\"escape sequence\",\n\t\ttip: \"Matches the literal string '{{value}}'.\",\n\t\tdesc:\"All characters between the \\\\Q
and the \\\\E
are interpreted as a literal string. If \\\\E
is omitted, it continues to the end of the expression.\",\n\t\text:\" For example, the expression /\\\\Q(?.)\\\\E/
will match the string (?.)
.\",\n\t\ttoken:\"\\\\Q...\\\\E\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Groups & References\",\n\tid:\"groups\",\n\tdesc: \"Groups allow you to combine a sequence of tokens to operate on them together. Capture groups can be referenced by a backreference and accessed separately in the results.\",\n\tkids: [\n\t\t{\n\t\tid:\"group\",\n\t\tlabel: \"capturing group\",\n\t\tdesc: \"Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference.\",\n\t\texample:[\"(ha)+\",\"hahaha haa hah!\"],\n\t\ttoken:\"(ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"namedgroup\",\n\t\tlabel: \"named capturing group\",\n\t\ttip:\"Creates a capturing group named '{{name}}'.\",\n\t\tdesc:\"Creates a capturing group that can be referenced via the specified name.\",\n\t\text:\"There are multiple syntaxes for this feature:
(?'name'ABC)
(?P<name>ABC)
(?<name>ABC)
There are multiple syntaxes for this feature:
\\\\k'name'
\\\\k<name>
\\\\k{name}
\\\\g{name}
(?P=name)
\\\\1
matches the results of the first capture group & \\\\3
matches the third.\",\n\t\t// PCRE adds relative and alternate syntaxes in ext\n\t\texample:[\"(\\\\w)a\\\\1\",\"hah dad bad dab gag gab\"],\n\t\ttoken:\"\\\\1\"\n\t\t},\n\t\t{\n\t\tid:\"branchreset\",\n\t\tlabel: \"branch reset group\",\n\t\tdesc:\"Define alternative groups that share the same group numbers.\",\n\t\text: \"For example, in (?|(a)|(b))
both groups (a and b) would be counted as group #1.\",\n\t\ttoken:\"(?|(a)|(b))\"\n\t\t},\n\t\t{\n\t\tid:\"noncapgroup\",\n\t\tlabel: \"non-capturing group\",\n\t\tdesc:\"Groups multiple tokens together without creating a capture group.\",\n\t\texample:[\"(?:ha)+\",\"hahaha haa hah!\"],\n\t\ttoken:\"(?:ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"atomic\",\n\t\tlabel:\"atomic group\",\n\t\tdesc:\"Non-capturing group that discards backtracking positions once matched.\",\n\t\text:\"
For example, /(?>ab|a)b/
will match abb
but not ab
because once the ab
option has matched, the atomic group prevents backtracking to retry with the a
option.
For example, /A(?(DEFINE)(?'foo'Z))B\\\\g'foo'/
will match ABZ
, because the define group is ignored in the match except to define the foo
subroutine that is referenced later with \\\\g'foo'
.
/(a|b)\\\\g'1'/
can match ab
, because the expression a|b
is evaluated again.\",\n\t\text:\"There are multiple syntaxes for this feature: \\\\g<1>
\\\\g'1'
(?1)
.
Relative values preceded by +
or -
are also supported. For example \\\\g<-1>
would match the group preceding the reference.
There are multiple syntaxes for this feature: \\\\g<name>
\\\\g'name'
(?&name)
(?P>name)
.
Negative lookarounds specify a group that can NOT match before or after the pattern.
\",\n\tkids: [\n\t\t{\n\t\tid:\"poslookahead\",\n\t\tlabel: \"positive lookahead\",\n\t\tdesc:\"Matches a group after the main expression without including it in the result.\",\n\t\texample:[\"\\\\d(?=px)\",\"1pt 2px 3em 4px\"],\n\t\ttoken:\"(?=ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"neglookahead\",\n\t\tlabel: \"negative lookahead\",\n\t\tdesc:\"Specifies a group that can not match after the main expression (if it matches, the result is discarded).\",\n\t\texample:[\"\\\\d(?!px)\",\"1pt 2px 3em 4px\"],\n\t\ttoken:\"(?!ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"poslookbehind\",\n\t\tlabel: \"positive lookbehind\",\n\t\tdesc:\"Matches a group before the main expression without including it in the result.\",\n\t\ttoken:\"(?<=ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"neglookbehind\",\n\t\tlabel: \"negative lookbehind\",\n\t\tdesc:\"Specifies a group that can not match before the main expression (if it matches, the result is discarded).\",\n\t\ttoken:\"(?/o\\\\Kbar/ will matchbar
within the string foobar
\",\n\t\ttoken:\"\\\\K\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Quantifiers & Alternation\",\n\tid:\"quants\",\n\tdesc: \"Quantifiers indicate that the preceding token must be matched a certain number of times. By default, quantifiers are greedy, and will match as many characters as possible.\"+\n\t\t\"{1,3}
will match 1 to 3. \"+\n\t\t\t\"{3}
will match exactly 3. \"+\n\t\t\t\"{3,}
will match 3 or more. \",\n\t\texample:[\"b\\\\w{2,3}\",\"b be bee beer beers\"],\n\t\ttoken:\"{1,3}\"\n\t\t},\n\t\t{\n\t\tid:\"opt\",\n\t\tlabel:\"optional\",\n\t\tdesc:\"Matches 0 or 1 of the preceding token, effectively making it optional.\",\n\t\texample: [\"colou?r\", \"color colour\"],\n\t\ttoken:\"?\"\n\t\t},\n\t\t{\n\t\tid:\"lazy\",\n\t\ttip:\"Makes the preceding quantifier {{getLazy()}}, causing it to match as {{getLazyFew()}} characters as possible.\",\n\t\tdesc:\"Makes the preceding quantifier lazy, causing it to match as few characters as possible.\",\n\t\text:\" By default, quantifiers are greedy, and will match as many characters as possible.\",\n\t\texample:[\"b\\\\w+?\",\"b be bee beer beers\"],\n\t\ttoken:\"?\"\n\t\t},\n\t\t{\n\t\tid:\"possessive\",\n\t\tdesc:\"Makes the preceding quantifier possessive. It will match as many characters as possible, and will not release them to match subsequent tokens.\",\n\t\text:\"For example /.*a/
would match aaa
, but /.*+a/
would not, because the repeating dot would match and not release the last character to match a
.
|
.\",\n\t\text:\"It can operate within a group, or on a whole expression. The patterns will be tested in order.
\",\n\t\texample:[\"b(a|e|i)d\",\"bad bud bod bed bid\"],\n\t\ttoken:\"|\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Special\",\n\tid:\"other\",\n\tdesc: \"Tokens that don't quite fit anywhere else.\",\n\tkids: [\n\t\t{\n\t\tid:\"comment\",\n\t\tdesc:\"Allows you to insert a comment into your expression that is ignored when finding a match.\",\n\t\ttoken:\"(?#foo)\"\n\t\t},\n\t\t{\n\t\tid:\"conditional\",\n\t\tdesc:\"Conditionally matches one of two options based on whether a lookaround is matched.\",\n\t\text:\"For example, /(?(?=a)ab|..)/
will match ab
and zx
but not ax
, because if the first character matches the condition a
then it evaluates the pattern ab
.
Any lookaround can be used as the condition. A lookahead will start the subsequent match at the start of the condition, a lookbehind will start it after.
\",\n\t\ttoken:\"(?(?=A)B|C)\"\n\t\t},\n\t\t{\n\t\tid:\"conditionalgroup\",\n\t\tlabel:\"group conditional\",\n\t\tdesc:\"Conditionally matches one of two options based on whether group '{{name}}' matched.\",\n\t\text:\"For example, /(z)?(?(1)a|b)/
will match za
because the first capture group matches z
successfully, which causes the conditional to match the first option a
.
The same pattern will also match b
on its own, because group 1 doesn't match, so it instead tries to match the second option b
.
You can reference a group by name, number, or relative position (ex. -1
).
For example, /a(?R)?b/
will match any number of a
followed by the same number of z
: the full text of az
or aaaazzzz
, but not azzz
.
There are multiple syntaxes for this feature:
(?R)
(?0)
\\\\g<0>
\\\\g'0'
(?i)
would enable case insensitivity for the remainder of the expression.\"+\n\t\t\t\"Multiple modifiers can be specified, and any modifiers that follow -
are disabled. For example (?im-s)
would enable case insensitivity & multiline modes, and disable dotall.
Supported modifiers are: i
- case insensitive, s
- dotall, m
- multiline, x
- free spacing, J
- allow duplicate names, U
- ungreedy.
There are multiple syntaxes for this feature:
$0
\\\\0
\\\\{0}
$3
would insert the third capture group.\",\n\t\t// NOTE: javascript profile overrides this:\n\t\text:\"There are multiple syntaxes for this feature:
$1
\\\\1
\\\\{1}
\\\\n
, \\\\r
, \\\\t
, \\\\\\\\
, and unicode escapes \\\\uFFFF
. This may vary in your deploy environment.\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tid:\"flags\",\n\tlabel:\"Flags\",\n\ttooltip:\"Expression flags change how the expression is interpreted. Click to edit.\",\n\tdesc:\"Expression flags change how the expression is interpreted. Flags follow the closing forward slash of the expression (ex. /.+/igm
).\",\n\ttarget:\"flags\",\n\tkids: [\n\t\t{\n\t\tid:\"caseinsensitive\",\n\t\tlabel: \"ignore case\",\n\t\tdesc:\"Makes the whole expression case-insensitive.\",\n\t\text:\" For example, /aBc/i
would match AbC
.\",\n\t\ttoken:\"i\"\n\t\t},\n\t\t{\n\t\tid:\"global\",\n\t\tlabel: \"global search\",\n\t\ttip: \"Retain the index of the last match, allowing iterative searches.\",\n\t\tdesc:\"Retain the index of the last match, allowing subsequent searches to start from the end of the previous match.\"+\n\t\t\t\"Without the global flag, subsequent searches will return the same match.
^
and $
) will match the start and end of a line, instead of the start and end of the whole string.\"+\n\t\t\t\"Note that patterns such as /^[\\\\s\\\\S]+$/m
may return matches that span multiple lines because the anchors will match the start/end of any line.
\\\\x{FFFFF}
unicode escapes.\",\n\t\tdesc:\"When the unicode flag is enabled, you can use extended unicode escapes in the form \\\\x{FFFFF}
.\"+\n\t\t\t\"It also makes other escapes stricter, causing unrecognized escapes (ex. \\\\j
) to throw an error.
g
) flag if set.\",\n\t\text:\" Because each search in RegExr is discrete, this flag has no further impact on the displayed results.\",\n\t\ttoken:\"y\"\n\t\t},\n\t\t{\n\t\tid:\"dotall\",\n\t\tdesc:\"Dot (.
) will match any character, including newline.\",\n\t\ttoken:\"s\"\n\t\t},\n\t\t{\n\t\tid:\"extended\",\n\t\tdesc:\"Literal whitespace characters are ignored, except in character sets.\",\n\t\ttoken:\"x\"\n\t\t},\n\t\t{\n\t\tid:\"ungreedy\",\n\t\ttip:\"Makes quantifiers ungreedy (lazy) by default.\",\n\t\tdesc:\"Makes quantifiers ungreedy (lazy) by default. Quantifiers followed by ?
will become greedy.\",\n\t\ttoken:\"U\"\n\t\t}\n\t]\n\t}\n];\n\n// content that isn't included in the Reference menu item:\no.misc = {\n\tkids:[\n\t\t{\n\t\tid:\"ignorews\",\n\t\tlabel:\"ignored whitespace\",\n\t\ttip:\"Whitespace character ignored due to the extended flag or mode.\"\n\t\t},\n\t\t{\n\t\tid:\"extnumref\", // alternative syntaxes.\n\t\tproxy:\"numref\"\n\t\t},\n\t\t{\n\t\tid:\"char\",\n\t\tlabel:\"character\",\n\t\ttip:\"Matches a {{getChar()}} character (char code {{code}}). {{getInsensitive()}}\"\n\t\t},\n\t\t{\n\t\tid:\"escchar\",\n\t\tlabel:\"escaped character\",\n\t\ttip:\"Matches a {{getChar()}} character (char code {{code}}).\"\n\t\t},\n\t\t{\n\t\tid:\"open\",\n\t\ttip:\"Indicates the start of a regular expression.\"\n\t\t},\n\t\t{\n\t\tid:\"close\",\n\t\ttip:\"Indicates the end of a regular expression and the start of expression flags.\"\n\t\t},\n\t\t{\n\t\tid:\"condition\",\n\t\ttip:\"The lookaround to match in resolving the enclosing conditional statement. See 'conditional' in the Reference for info.\"\n\t\t},\n\t\t{\n\t\tid:\"conditionalelse\",\n\t\tlabel:\"conditional else\",\n\t\ttip:\"Delimits the 'else' portion of the conditional.\"\n\t\t},\n\t\t{\n\t\tid:\"ERROR\",\n\t\ttip:\"Errors in the expression are underlined in red. Roll over errors for more info.\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_INTERNAL_ERROR\",\n\t\ttip:\"Internal PCRE error\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BACKTRACK_LIMIT_ERROR\",\n\t\ttip:\"Backtrack limit was exhausted.\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_RECURSION_LIMIT_ERROR\",\n\t\ttip:\"Recursion limit was exhausted\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BAD_UTF8_ERROR\",\n\t\ttip:\"Malformed UTF-8 data\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BAD_UTF8_OFFSET_ERROR\",\n\t\ttip:\"Malformed UTF-8 data\"\n\t\t}\n\t]\n};\n\no.errors = {\n\tgroupopen:\"Unmatched opening parenthesis.\",\n\tgroupclose:\"Unmatched closing parenthesis.\",\n\tsetopen:\"Unmatched opening square bracket.\",\n\trangerev:\"Range values reversed. Start char code is greater than end char code.\",\n\tquanttarg:\"Invalid target for quantifier.\",\n\tquantrev:\"Quantifier minimum is greater than maximum.\",\n\tesccharopen:\"Dangling backslash.\",\n\tesccharbad:\"Unrecognized or malformed escape character.\",\n\tunicodebad:\"Unrecognized unicode category or script.\",\n\tposixcharclassbad:\"Unrecognized POSIX character class.\",\n\tposixcharclassnoset:\"POSIX character class must be in a character set.\",\n\tnotsupported:\"The \\\"{{~getLabel()}}\\\" feature is not supported in this flavor of RegEx.\",\n\tfwdslash:\"Unescaped forward slash. This may cause issues if copying/pasting this expression into code.\",\n\tesccharbad:\"Invalid escape sequence.\",\n\tservercomm:\"An error occurred while communicating with the server.\",\n\textraelse:\"Extra else in conditional group.\",\n\tunmatchedref:\"Reference to non-existent group \\\"{{name}}\\\".\",\n\tmodebad:\"Unrecognized mode flag \\\"{{errmode}}
\\\".\",\n\tbadname:\"Group name can not start with a digit.\",\n\tdupname:\"Duplicate group name.\",\n\tbranchreseterr:\"Branch Reset. Results will be ok, but RegExr's parser does not number branch reset groups correctly. Coming soon!\",\n\ttimeout:\"The expression took longer than 250ms to execute.\", // TODO: can we couple this to the help content somehow?\n\n\t// warnings:\n\tjsfuture:\"The \\\"{{~getLabel()}}\\\" feature may not be supported in all browsers.\",\n\tinfinite:\"The expression can return empty matches, and may match infinitely in some use cases.\", // TODO: can we couple this to the help content somehow?\n};\n\n/*\nclasses:\nquant\nset\nspecial\nref\nesc\nanchor\ncharclass\ngroup\ncomment\n */\n","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see The syntax \\o{FFF}
is also supported.
There are multiple syntaxes for this feature: \\1
\\g1
\\g{1}
.
The latter syntaxes support relative values preceded by +
or -
. For example \\g-1
would match the group preceding the reference.
This behaviour is reversed by the ungreedy (U
) flag/modifier.
Requires the u
flag.
For a list of values, see this MDN page.
" - } - // notunicodecat, unicodescript, notunicodescript are copied from unicodecat below. - } - }; - javascript.docs.notunicodecat = javascript.docs.unicodescript = javascript.docs.notunicodescript = javascript.docs.unicodecat; - - /* - RegExr: Learn, Build, & Test RegEx - Copyright (C) 2017 gskinner.com, inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see" + content + "
";
- if (!token.open) {
- content += app.reference.tipForToken(token);
- } else {
- content += " ";
- }
- let div = DOMUtils.create("div", null, content, el);
- if (token.close) {
- className = groupClasses[token.clss || token.type];
- if (className) {
- className = className.replace("%depth%", Math.min(4, token.depth));
- DOMUtils.addClass(div, className);
- }
- if (token.depth > 3) {
- div.innerHTML = "So... you wanted to see what would happen if you just kept nesting groups, eh? Well, this is it." + " I was going to reward your curiosity with a RegEx joke, but a quick search on google reveals that not even" + " the collective wisdom of the internet can make regular expressions funny. Well, except the whole 'now you've got two problems'" + " shtick, but you've probably heard that one already. Wasn't really worth the effort, was it?";
- token = token.close.prv;
- this._divs.push(div);
- el = div;
- continue;
- }
- el = div;
- }
- div.token = token;
- if (token.open) {
- DOMUtils.addClass(div, "close");
- div.proxy = el;
- el = el.parentNode;
- }
- if (token.error) {
- DOMUtils.addClass(div, "error");
- if (token.error.warning) {
- DOMUtils.addClass(div, "warning");
- }
- }
- if (!token.open) {
- div.addEventListener("mouseover", this._handleMouseEvent);
- div.addEventListener("mouseout", this._handleMouseEvent);
- div.addEventListener("click", this._handleMouseEvent);
- }
- if (token.clss === "quant" || token.type === "lazy" || token.type === "possessive") {
- this._insertApplied(div);
- } else {
- this._divs.push(div);
- }
- }
- }
- _insertApplied(div) {
- let divs = this._divs,
- prv = div.token.prv,
- d,
- i = divs.length;
- while ((d = divs[--i]) && d.token !== prv) {} // search backwards for efficiency
- d = d.proxy || d;
- divs.splice(i, 0, div);
- d.insertAdjacentElement("afterend", div);
- DOMUtils.addClass(div, "applied");
- }
- _handleHoverChange() {
- let token = app.expression.highlighter.hoverToken;
- DOMUtils.removeClass(DOMUtils.queryAll("div.selected", this.el), "selected");
- DOMUtils.removeClass(DOMUtils.queryAll("div.related", this.el), "related");
- if (!token) {
- return;
- }
- let div = this._findDiv(token);
- DOMUtils.addClass(div, "selected");
- if (token.related) {
- for (let i = 0, l = token.related.length; i < l; i++) {
- DOMUtils.addClass(this._findDiv(token.related[i]), "related");
- }
- }
- }
- _findDiv(token) {
- return Utils.find(this._divs, div => div.token === token);
- }
- _handleMouseEvent(evt) {
- let type = evt.type,
- token = evt.currentTarget.token;
- if (type == "click") {
- app.sidebar.showToken(token);
- } else {
- app.expression.highlighter.hoverToken = type === "mouseout" ? null : token;
- }
- evt.stopPropagation();
- }
- _handleEvent(evt) {
- if (evt.type === "change") {
- this._update();
- } else if (evt.type === "hover") {
- this._handleHoverChange();
- }
- }
- }
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see Group
row to highlight it in the Expression.", this.el);
- this._addMatch(app.text.selectedMatch, app.text.value);
- }
- _addMatch(match, textVal) {
- if (!match) {
- return;
- }
- let groups = match.groups,
- l = groups && groups.length,
- ext = l && groups[0].i != null,
- matchVal = this._getMatchVal(match, textVal),
- extStr = "",
- me = match.i + match.l;
- let groupTokens = app.expression.lexer.captureGroups;
- let tableEl = DOMUtils.create("table", null, null, this.el);
- let matchEl = DOMUtils.create("tr", "match", "To edit a pattern you created, click its URL or double-click it in the list to load it, then open Save / Share to edit and save.
", - search: true, - kids: [] - }, { - label: "Cheatsheet", - id: "cheatsheet", - el: "#cheatsheet" - }, { - // injected from Reference - id: "reference" - }, { - label: "Community Patterns", - id: "community", - desc: "Welcome to Community Patterns, a searchable database of patterns submitted by users like you." + "After selecting a pattern, click its URL or double-click it in the list to load the full pattern. Or use the right arrow icon to load just the expression or text.
" + "Help make the Community better by rating patterns, and submitting your own via Search & Share in the menu.
", - search: true, - kids: [] - }, { - label: "Help", - id: "help", - desc: "Help for the RegExr application. See the RegEx Reference for help with Regular Expressions.", - kids: [{ - label: "About", - desc: "RegExr v3.8.0 (May 10, 2024)." + "Created by Grant Skinner and the gskinner team, using the CreateJS & CodeMirror libraries.
" + "You can provide feedback or log bugs on GitHub.
" - }, { - label: "Getting started", - desc: "RegExr provides real-time visual results, syntax highlighting, tooltips, and undo/redo ({{getCtrlKey()}}-Z / Y) so it's easy and fun to explore Regular Expressions." + "Browse through the RegEx Reference and test different tokens to see what they do, then check out Community Patterns to see examples.
" + "You can also Save your patterns for later reference, or to share with others. Sign In to ensure you don't lose your patterns.
" + "Modify your pattern's details, share it with the Community, or make it private, or delete it in Pattern Settings
", - kids: [{ - label: "Expression panel", - desc: "This is where you enter a regular expression to test. The results in the Text and Tools panel will update as you type." + "Roll over the expression for information on each token." + "The buttons to the right allow you to switch RegEx engines, or edit the expression flags.
" - }, { - label: "Text panel", - desc: "This is where you enter text to test your expression against. Drag & drop a text file to load its contents." + "Matches will be highlighted as you type. Roll over a match for information on the match and its capture groups. The match count and execution time are shown in the title bar.
" + "Lighter colored caps at the start or end of a line indicate the match continues between lines.
" - }, { - label: "Tests panel", - desc: "This allows you to create a suite of tests that you can use to validate your Expression in real time." + "A test can check if the pattern matches anything, matches everything, or matches nothing in the test text.
" + "Access this panel with the toggle at the top of the Text panel. When you save, both your Text and Tests will be saved.
" - }, { - label: "Tools panel", - desc: "Click the Tools title bar below the Text panel to show or hide the Tools panel." + "Tools provide different ways of working with or exploring your results.
", - kids: [{ - label: "Replace", - id: "replace", - desc: "The Replace tool replaces matches with a specified string or pattern." + "Matches in the Text panel are replaced by the substitution string & displayed as you type.
" + "Substitution tokens and escaped characters are supported, such as \\n
, \\t
& \\u0009
.
Roll over tokens for information, and check out the RegEx Reference for more information.
" - }, { - label: "List", - id: "list", - desc: "The List tool lists all found matches." + "You can specify either a simple delimiter (ex. ,
or \\n
), or use substitution tokens to generate more advanced reports. For example, $1\\n
would list all group 1 results (in the JavaScript engine).
Escaped characters are supported, such as \\n
, \\t
& \\u0009
.
Roll over tokens for information.
" - }, { - label: "Details", - id: "details", - desc: "The Details tool displays the full text of a match and its capture groups." + "Click on a highlighted match in the Text panel to display the details for that match.
" + "Roll over a group row to highlight that group in your Expression.
" - }, { - label: "Explain", - id: "explain", - desc: "The Explain tool displays a detailed breakdown of the Expression." + "Mouse over the explanation to highlight the related tokens in the Expression panel and vice versa.
" + "Click an item in the explanation to show more info in the RegEx Reference.
" - }] - }, { - label: "Menu", - desc: "The Menu (this panel) includes Help, a full RegEx Reference, a Cheatsheet, and Pattern Settings features." + "Double-click a selected item in the RegEx Reference to insert it into your Expression. Click the arrow beside an example to load it.
" + "The menu also includes searchable Community Patterns, and patterns you've created or favorited in My Patterns.
" - }] - }, { - label: "Signing in", - id: "signin", - desc: "Before you sign in, RegExr creates a temporary account which relies on a browser cookie. This means you can't access your patterns on other computers, and that you could lose your patterns if your cookies are deleted or expire." + "Signing in creates a permanent account, so you can access your patterns anywhere, anytime.
" + "Your existing patterns & favorites will automatically be assigned to the new account.
" + "We don't use your info for anything other than signing you into your RegExr account.
" - }, { - id: "engine", - label: "RegEx engines", - desc: "While the core feature set of regular expressions is fairly consistent, different implementations (ex. Perl vs Java) may have different features or behaviours." + "RegExr currently supports JavaScript RegExp executed in your browser and PCRE via PHP.
" + "You can switch engines using the dropdown in Expression.
", - kids: [{ - label: "JavaScript", - desc: "Your browser's JavaScript engine is used to execute RegEx in an asynchronous worker usingRegExp.exec()
." + "Note that while implementations are mostly consistent, there are small variations between browsers. Here is a short list of known differences:
The following query string params are recognized:
engine
- the RegEx engine to use (js
or pcre
)expression
- populates the Expression area. It is recommended to pass a full expression with flags (/.*/ig
) not just the pattern (.*
)text
- populates the Text areatool
- sets the tool (replace, list, details, or explain)input
- populates the tool input field" + Utils.htmlSafe(exp) + "
";
- if (txt && regex) {
- let over = Math.max(0, txt.length - 160),
- s = txt;
- if (over) {
- s = Utils.htmlSafe(s.substr(0, 159));
- }
- if (regex) {
- s = s.replace(regex, "$&");
- }
- // TODO: this won't match on html elements:
- str += "" + s + (over ? "\u2026" : "") + "
";
- }
- }
- this.el.innerHTML = str;
- if (exp) {
- DOMUtils.query("code.expression > .load", this.el).addEventListener("click", () => {
- // TODO: this will need to be updated when we support other delimiters:
- app.expression.value = exp[0] === "/" ? exp : "/" + exp + "/g";
- });
- }
- if (txt) {
- DOMUtils.query("code.text > .load", this.el).addEventListener("click", () => app.text.value = txt);
- }
- }
- }
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see " + token.on + "
\"." : "";
- if (token.off) {
- str += " Disable \"" + token.off + "
\".";
- }
- return str;
- }
- getInsensitive(token) {
- if (token.code) {
- let chr = String.fromCharCode(token.code);
- if (chr.toLowerCase() === chr.toUpperCase()) {
- return "";
- }
- }
- return token.modes ? `Case ${token.modes.i ? "in" : ""}sensitive.` : "";
- }
- getDotAll(token) {
- return (token.modes.s ? "including" : "except") + " line breaks";
- }
- getLabel(token) {
- let node = this.getNodeForToken(token);
- return node ? node.label || node.id || "" : token.type;
- }
- getDesc(token) {
- return this.getVal(this.getNodeForToken(token), "desc");
- }
- getLazy(token) {
- return token.modes.U ? "greedy" : "lazy";
- }
- getLazyFew(token) {
- return token.modes.U ? "many" : "few";
- }
- getPHPVersion() {
- return this._config.PHPVersion;
- }
- getPCREVersion() {
- return this._config.PCREVersion;
- }
- getCtrlKey() {
- return Utils.getCtrlKey();
- }
- getEscChars() {
- let o = this._flavor.profile.escChars,
- str = "";
- for (let n in o) {
- str += n;
- }
- return str;
- }
-
- /*
- Searches for tags in the string in the format:
- `{{prop.prop}}` or `{{method(prop.prop)}}`
-
- The first format will inject the specified property of the data object.
- For example, `{{a.b}}` would inject the value of `data.a.b`.
-
- The second will inject the results of calling the specified function on the functs object with a property of the data object as it's parameter (or the data object itself if empty).
- For example, `{{myMethod(a.b)}}` would inject the return value of `functs.myMethod(data.a.b)`.
-
- Currently only supports a single param.
- */
- fillTags(str, data, functs, maxLength = 20, htmlSafe = true) {
- let match;
- while (match = str.match(/{{~?[\w.()]+}}/)) {
- let val,
- f,
- safe = false;
- val = match[0].substring(2, match[0].length - 2);
- if (val[0] === "~") {
- val = val.substr(1);
- safe = true;
- }
- let match2 = val.match(/\([\w.]*\)/);
- if (match2) {
- f = val.substr(0, match2.index);
- val = match2[0].substring(1, match2[0].length - 1);
- } else {
- f = null;
- }
- let o = data,
- arr = val.split(".");
- for (let i = 0; i < arr.length; i++) {
- let prop = arr[i];
- if (prop && o) {
- o = o[prop];
- }
- }
- val = o;
- if (f) {
- if (functs[f]) {
- val = functs[f](val);
- } else {
- val = " [" + f + "] ";
- }
- }
- if (!safe && (maxLength || htmlSafe)) {
- val = Utils.shorten(val, maxLength, htmlSafe, "i");
- }
- str = str.replace(match[0], val);
- }
- return str;
- }
-
- // returns doc props from the profile or reference as appropriate (ex. tip, desc, ext)
- getVal(node, prop) {
- if (!node) {
- return "";
- }
- let pDocs = this._flavor.getDocs(node.id),
- pRef = pDocs && pDocs[prop];
- if (pRef != null && pRef[0] !== "+") {
- return pRef;
- }
- let ref = node && node[prop] || "";
- return pRef != null ? ref + pRef.substr(1) : ref;
- }
- getNodeForToken(token) {
- let id = this.idForToken(token),
- clss = token.clss;
-
- // Special cases:
- if (clss === "quant") {
- id = clss;
- }
- if (clss === "esc" && token.type !== "escsequence") {
- id = "escchar";
- }
- return this.getNode(id);
- }
- getNode(id) {
- let map = this._idMap,
- node = map[id];
- while (node && node.proxy) {
- node = map[node.proxy];
- }
- return node;
- }
- getError(error, token) {
- let errId = error && error.id;
- let str = this._content.errors[errId] || "no docs for error='" + errId + "'";
- if (token) {
- str = this.fillTags(str, token, this, 20);
- }
- return str;
- }
- tipForToken(token) {
- if (!token) {
- return null;
- }
- let node = this.getNodeForToken(token),
- label,
- tip;
- if (token.error && !token.error.warning) {
- label = "ERROR: ";
- tip = this.getError(token.error, token);
- } else {
- label = node ? node.label || node.id || "" : token.type;
- tip = this.getVal(node, "tip") || this.getVal(node, "desc");
- tip = this.fillTags(tip, token, this, 20);
- if (token.type === "group") {
- label += " #" + token.num;
- }
- label = "" + label[0].toUpperCase() + label.substr(1) + ". ";
- if (token.error) {
- tip += "WARNING: " + this.getError(token.error, token) + "";
- }
- }
- return tip ? label + tip : "no docs for id='" + this.idForToken(token) + "'";
- }
- getContent(id) {
- let node = this.getNode(id);
- return this.fillTags(this.getVal(node, "desc") + this.getVal(node, "ext"), node, this);
- }
-
- // TODO: this isn't necessarily the most ideal place for this method (has nothing to do with Reference). Maybe move into Text?
- tipForMatch(match, text) {
- if (!match) {
- return null;
- }
- let more = match.l > 150;
- let str = "match: " + Utils.shorten(text.substr(match.i, match.l), 150, true, "i") + "" + match.i + "-" + (match.i + match.l - 1) + "
";
- let groups = match.groups,
- l = groups && groups.length;
- for (let i = 0; i < l; i++) {
- if (i > 3 && l > 5) {
- more = false;
- str += "Double-click an item in the list to insert it into your Expression.
-Click the arrow beside an example to load it.
`; - o.kids = [{ - label: "Character classes", - id: "charclasses", - desc: "Character classes match a character from a specific set. There are a number of predefined character classes and you can also define your own sets.", - kids: [{ - id: "set", - label: "character set", - desc: "Match any character in the set.", - example: ["[aeiou]", "glib jocks vex dwarves!"], - token: "[ABC]" - }, { - id: "setnot", - label: "negated set", - desc: "Match any character that is not in the set.", - example: ["[^aeiou]", "glib jocks vex dwarves!"], - token: "[^ABC]" - }, { - id: "range", - tip: "Matches a character in the range {{getChar(prev)}} to {{getChar(next)}} (char code {{prev.code}} to {{next.code}}). {{getInsensitive()}}", - example: ["[g-s]", "abcdefghijklmnopqrstuvwxyz"], - desc: "Matches a character having a character code between the two specified characters inclusive.", - token: "[A-Z]" - }, { - id: "posixcharclass", - tip: "Matches any character in the '{{value}}' POSIX class.", - label: "POSIX class", - desc: "Matches any character in the specified POSIX class. Must be in a character set. For example,[[:alnum:]$]
will match alphanumeric characters and $
.",
- ext: "For a list of classes, see the PCRE spec.
", - token: "[:alnum:]" - }, { - id: "dot", - tip: "Matches any character {{getDotAll()}}.", - desc: "Matches any character except linebreaks.", - ext: " Equivalent to[^\\n\\r]
.",
- example: [".", "glib jocks vex dwarves!"],
- token: "."
- }, {
- id: "matchanyset",
- label: "match any",
- desc: "A character set that can be used to match any character, including line breaks, without the dotall flag (s
)." + "An alternative is [^]
, but it is not supported in all browsers.
[A-Za-z0-9_]
",
- example: ["\\w", "bonjour, mon fr\u00E8re"],
- token: "\\w"
- }, {
- id: "notword",
- label: "not word",
- desc: "Matches any character that is not a word character (alphanumeric & underscore).",
- ext: " Equivalent to [^A-Za-z0-9_]
",
- example: ["\\W", "bonjour, mon fr\u00E8re"],
- token: "\\W"
- }, {
- id: "digit",
- desc: "Matches any digit character (0-9).",
- ext: " Equivalent to [0-9]
.",
- example: ["\\d", "+1-(444)-555-1234"],
- token: "\\d"
- }, {
- id: "notdigit",
- label: "not digit",
- desc: "Matches any character that is not a digit character (0-9).",
- ext: " Equivalent to [^0-9]
.",
- example: ["\\D", "+1-(444)-555-1234"],
- token: "\\D"
- }, {
- id: "whitespace",
- desc: "Matches any whitespace character (spaces, tabs, line breaks).",
- example: ["\\s", "glib jocks vex dwarves!"],
- token: "\\s"
- }, {
- id: "notwhitespace",
- label: "not whitespace",
- desc: "Matches any character that is not a whitespace character (spaces, tabs, line breaks).",
- example: ["\\S", "glib jocks vex dwarves!"],
- token: "\\S"
- }, {
- id: "hwhitespace",
- label: "horizontal whitespace",
- desc: "Matches any horizontal whitespace character (spaces, tabs).",
- token: "\\h"
- }, {
- id: "nothwhitespace",
- label: "not horizontal whitespace",
- desc: "Matches any character that is not a horizontal whitespace character (spaces, tabs).",
- token: "\\H"
- }, {
- id: "vwhitespace",
- label: "vertical whitespace",
- desc: "Matches any vertical whitespace character (line breaks).",
- token: "\\v"
- }, {
- id: "notvwhitespace",
- label: "not vertical whitespace",
- desc: "Matches any character that is not a vertical whitespace character (line breaks).",
- token: "\\V"
- }, {
- id: "linebreak",
- label: "line break",
- desc: "Matches any line break character, including the CRLF pair, and CR / LF individually.",
- token: "\\R"
- }, {
- id: "notlinebreak",
- label: "not line break",
- desc: "Matches any character that is not a line break.",
- ext: " Similar to dot (.
) but is unaffected by the dotall flag (s
).",
- token: "\\N"
- }, {
- id: "unicodecat",
- tip: "Matches any character in the '{{getUniCat()}}' unicode category.",
- label: "unicode category",
- desc: "Matches a character in the specified unicode category. For example, \\p{Ll}
will match any lowercase letter.",
- ext: "For a list of categories, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\p{L}
\\pL
For a list of categories, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\P{L}
\\p{^L}
\\PL
\\p{Arabic}
will match characters in the Arabic script.",
- ext: "For a list of scripts, see the PCRE spec.
", - token: "\\p{Han}" - }, { - id: "notunicodescript", - tip: "Matches any character that is not in the '{{value}}' unicode script.", - label: "not unicode script", - desc: "Matches any character that is not in the specified unicode script.", - ext: "For a list of scripts, see the PCRE spec.
" + "There are multiple syntaxes for this feature:
\\P{Han}
\\p{^Han}
",
- token: "\\P{Han}"
- }]
- }, {
- label: "Anchors",
- id: "anchors",
- desc: "Anchors are unique in that they match a position within a string, not a character.",
- kids: [{
- id: "bos",
- label: "beginning of string",
- desc: "Matches the beginning of the string.",
- ext: " Unlike ^
, this is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\A"
- }, {
- id: "eos",
- label: "end of string",
- desc: "Matches the end of the string.",
- ext: " Unlike $
, this is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\Z"
- }, {
- id: "abseos",
- label: "strict end of string",
- desc: "Matches the end of the string. Unlike $
or \\Z
, it does not allow for a trailing newline.",
- ext: " This is unaffected by the multiline flag (m
). This matches a position, not a character.",
- token: "\\z"
- }, {
- id: "bof",
- label: "beginning",
- desc: "Matches the beginning of the string, or the beginning of a line if the multiline flag (m
) is enabled.",
- ext: " This matches a position, not a character.",
- example: ["^\\w+", "she sells seashells"],
- token: "^"
- }, {
- id: "eof",
- label: "end",
- desc: "Matches the end of the string, or the end of a line if the multiline flag (m
) is enabled.",
- ext: " This matches a position, not a character.",
- example: ["\\w+$", "she sells seashells"],
- token: "$"
- }, {
- id: "wordboundary",
- label: "word boundary",
- desc: "Matches a word boundary position between a word character and non-word character or position (start / end of string).",
- ext: " See the word character class (\w
) for more info.",
- example: ["s\\b", "she sells seashells"],
- token: "\\b"
- }, {
- id: "notwordboundary",
- label: "not word boundary",
- desc: "Matches any position that is not a word boundary.",
- ext: " This matches a position, not a character.",
- example: ["s\\B", "she sells seashells"],
- token: "\\B"
- }, {
- id: "prevmatchend",
- label: "previous match end",
- desc: "Matches the end position of the previous match.",
- ext: " This matches a position, not a character.",
- token: "\\G"
- }]
- }, {
- label: "Escaped characters",
- id: "escchars",
- desc: "Escape sequences can be used to insert reserved, special, and unicode characters. All escaped characters begin with the \\
character.",
- kids: [{
- id: "reservedchar",
- label: "reserved characters",
- desc: "The following character have special meaning, and should be preceded by a \\
(backslash) to represent a literal character:" + "
{{getEscChars()}}
Within a character set, only \\
, -
, and ]
need to be escaped.
\\000
.",
- ext: " Value must be less than 255 (\\377
).",
- // PCRE profile adds to ext.
- example: ["\\251", "RegExr is \u00A92014"],
- token: "\\000"
- }, {
- id: "eschexadecimal",
- label: "hexadecimal escape",
- desc: "Hexadecimal escaped character in the form \\xFF
.",
- example: ["\\xA9", "RegExr is \u00A92014"],
- token: "\\xFF"
- }, {
- id: "escunicodeu",
- label: "unicode escape",
- desc: "Unicode escaped character in the form \\uFFFF
",
- example: ["\\u00A9", "RegExr is \u00A92014"],
- token: "\\uFFFF"
- }, {
- id: "escunicodeub",
- label: "extended unicode escape",
- desc: "Unicode escaped character in the form \\u{FFFF}
.",
- ext: " Supports a full range of unicode point escapes with any number of hex digits. Requires the unicode flag (u
).
\\x{FF}
.",
- token: "\\x{FF}"
- }, {
- id: "esccontrolchar",
- label: "control character escape",
- desc: "Escaped control character in the form \\cZ
.",
- ext: " This can range from \\cA
(SOH, char code 1) to \\cZ
(SUB, char code 26). \\cI
matches TAB (char code 9).",
- token: "\\cI"
- }, {
- id: "escsequence",
- label: "escape sequence",
- tip: "Matches the literal string '{{value}}'.",
- desc: "All characters between the \\Q
and the \\E
are interpreted as a literal string. If \\E
is omitted, it continues to the end of the expression.",
- ext: " For example, the expression /\\Q(?.)\\E/
will match the string (?.)
.",
- token: "\\Q...\\E"
- }]
- }, {
- label: "Groups & References",
- id: "groups",
- desc: "Groups allow you to combine a sequence of tokens to operate on them together. Capture groups can be referenced by a backreference and accessed separately in the results.",
- kids: [{
- id: "group",
- label: "capturing group",
- desc: "Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference.",
- example: ["(ha)+", "hahaha haa hah!"],
- token: "(ABC)"
- }, {
- id: "namedgroup",
- label: "named capturing group",
- tip: "Creates a capturing group named '{{name}}'.",
- desc: "Creates a capturing group that can be referenced via the specified name.",
- ext: "There are multiple syntaxes for this feature:
(?'name'ABC)
(?P<name>ABC)
(?<name>ABC)
There are multiple syntaxes for this feature:
\\k'name'
\\k<name>
\\k{name}
\\g{name}
(?P=name)
\\1
matches the results of the first capture group & \\3
matches the third.",
- // PCRE adds relative and alternate syntaxes in ext
- example: ["(\\w)a\\1", "hah dad bad dab gag gab"],
- token: "\\1"
- }, {
- id: "branchreset",
- label: "branch reset group",
- desc: "Define alternative groups that share the same group numbers.",
- ext: "For example, in (?|(a)|(b))
both groups (a and b) would be counted as group #1.",
- token: "(?|(a)|(b))"
- }, {
- id: "noncapgroup",
- label: "non-capturing group",
- desc: "Groups multiple tokens together without creating a capture group.",
- example: ["(?:ha)+", "hahaha haa hah!"],
- token: "(?:ABC)"
- }, {
- id: "atomic",
- label: "atomic group",
- desc: "Non-capturing group that discards backtracking positions once matched.",
- ext: "
For example, /(?>ab|a)b/
will match abb
but not ab
because once the ab
option has matched, the atomic group prevents backtracking to retry with the a
option.
For example, /A(?(DEFINE)(?'foo'Z))B\\g'foo'/
will match ABZ
, because the define group is ignored in the match except to define the foo
subroutine that is referenced later with \\g'foo'
.
/(a|b)\\g'1'/
can match ab
, because the expression a|b
is evaluated again.",
- ext: "There are multiple syntaxes for this feature: \\g<1>
\\g'1'
(?1)
.
Relative values preceded by +
or -
are also supported. For example \\g<-1>
would match the group preceding the reference.
There are multiple syntaxes for this feature: \\g<name>
\\g'name'
(?&name)
(?P>name)
.
Negative lookarounds specify a group that can NOT match before or after the pattern.
", - kids: [{ - id: "poslookahead", - label: "positive lookahead", - desc: "Matches a group after the main expression without including it in the result.", - example: ["\\d(?=px)", "1pt 2px 3em 4px"], - token: "(?=ABC)" - }, { - id: "neglookahead", - label: "negative lookahead", - desc: "Specifies a group that can not match after the main expression (if it matches, the result is discarded).", - example: ["\\d(?!px)", "1pt 2px 3em 4px"], - token: "(?!ABC)" - }, { - id: "poslookbehind", - label: "positive lookbehind", - desc: "Matches a group before the main expression without including it in the result.", - token: "(?<=ABC)" - }, { - id: "neglookbehind", - label: "negative lookbehind", - desc: "Specifies a group that can not match before the main expression (if it matches, the result is discarded).", - token: "(?/o\\Kbar/ will matchbar
within the string foobar
",
- token: "\\K"
- }]
- }, {
- label: "Quantifiers & Alternation",
- id: "quants",
- desc: "Quantifiers indicate that the preceding token must be matched a certain number of times. By default, quantifiers are greedy, and will match as many characters as possible." + "{1,3}
will match 1 to 3. " + "{3}
will match exactly 3. " + "{3,}
will match 3 or more. ",
- example: ["b\\w{2,3}", "b be bee beer beers"],
- token: "{1,3}"
- }, {
- id: "opt",
- label: "optional",
- desc: "Matches 0 or 1 of the preceding token, effectively making it optional.",
- example: ["colou?r", "color colour"],
- token: "?"
- }, {
- id: "lazy",
- tip: "Makes the preceding quantifier {{getLazy()}}, causing it to match as {{getLazyFew()}} characters as possible.",
- desc: "Makes the preceding quantifier lazy, causing it to match as few characters as possible.",
- ext: " By default, quantifiers are greedy, and will match as many characters as possible.",
- example: ["b\\w+?", "b be bee beer beers"],
- token: "?"
- }, {
- id: "possessive",
- desc: "Makes the preceding quantifier possessive. It will match as many characters as possible, and will not release them to match subsequent tokens.",
- ext: "For example /.*a/
would match aaa
, but /.*+a/
would not, because the repeating dot would match and not release the last character to match a
.
|
.",
- ext: "It can operate within a group, or on a whole expression. The patterns will be tested in order.
", - example: ["b(a|e|i)d", "bad bud bod bed bid"], - token: "|" - }] - }, { - label: "Special", - id: "other", - desc: "Tokens that don't quite fit anywhere else.", - kids: [{ - id: "comment", - desc: "Allows you to insert a comment into your expression that is ignored when finding a match.", - token: "(?#foo)" - }, { - id: "conditional", - desc: "Conditionally matches one of two options based on whether a lookaround is matched.", - ext: "For example, /(?(?=a)ab|..)/
will match ab
and zx
but not ax
, because if the first character matches the condition a
then it evaluates the pattern ab
.
Any lookaround can be used as the condition. A lookahead will start the subsequent match at the start of the condition, a lookbehind will start it after.
", - token: "(?(?=A)B|C)" - }, { - id: "conditionalgroup", - label: "group conditional", - desc: "Conditionally matches one of two options based on whether group '{{name}}' matched.", - ext: "For example, /(z)?(?(1)a|b)/
will match za
because the first capture group matches z
successfully, which causes the conditional to match the first option a
.
The same pattern will also match b
on its own, because group 1 doesn't match, so it instead tries to match the second option b
.
You can reference a group by name, number, or relative position (ex. -1
).
For example, /a(?R)?b/
will match any number of a
followed by the same number of z
: the full text of az
or aaaazzzz
, but not azzz
.
There are multiple syntaxes for this feature:
(?R)
(?0)
\\g<0>
\\g'0'
(?i)
would enable case insensitivity for the remainder of the expression." + "Multiple modifiers can be specified, and any modifiers that follow -
are disabled. For example (?im-s)
would enable case insensitivity & multiline modes, and disable dotall.
Supported modifiers are: i
- case insensitive, s
- dotall, m
- multiline, x
- free spacing, J
- allow duplicate names, U
- ungreedy.
There are multiple syntaxes for this feature:
$0
\\0
\\{0}
$3
would insert the third capture group.",
- // NOTE: javascript profile overrides this:
- ext: "There are multiple syntaxes for this feature:
$1
\\1
\\{1}
\\n
, \\r
, \\t
, \\\\
, and unicode escapes \\uFFFF
. This may vary in your deploy environment."
- }]
- }, {
- id: "flags",
- label: "Flags",
- tooltip: "Expression flags change how the expression is interpreted. Click to edit.",
- desc: "Expression flags change how the expression is interpreted. Flags follow the closing forward slash of the expression (ex. /.+/igm
).",
- target: "flags",
- kids: [{
- id: "caseinsensitive",
- label: "ignore case",
- desc: "Makes the whole expression case-insensitive.",
- ext: " For example, /aBc/i
would match AbC
.",
- token: "i"
- }, {
- id: "global",
- label: "global search",
- tip: "Retain the index of the last match, allowing iterative searches.",
- desc: "Retain the index of the last match, allowing subsequent searches to start from the end of the previous match." + "Without the global flag, subsequent searches will return the same match.
^
and $
) will match the start and end of a line, instead of the start and end of the whole string." + "Note that patterns such as /^[\\s\\S]+$/m
may return matches that span multiple lines because the anchors will match the start/end of any line.
\\x{FFFFF}
unicode escapes.",
- desc: "When the unicode flag is enabled, you can use extended unicode escapes in the form \\x{FFFFF}
." + "It also makes other escapes stricter, causing unrecognized escapes (ex. \\j
) to throw an error.
g
) flag if set.",
- ext: " Because each search in RegExr is discrete, this flag has no further impact on the displayed results.",
- token: "y"
- }, {
- id: "dotall",
- desc: "Dot (.
) will match any character, including newline.",
- token: "s"
- }, {
- id: "extended",
- desc: "Literal whitespace characters are ignored, except in character sets.",
- token: "x"
- }, {
- id: "ungreedy",
- tip: "Makes quantifiers ungreedy (lazy) by default.",
- desc: "Makes quantifiers ungreedy (lazy) by default. Quantifiers followed by ?
will become greedy.",
- token: "U"
- }]
- }];
-
- // content that isn't included in the Reference menu item:
- o.misc = {
- kids: [{
- id: "ignorews",
- label: "ignored whitespace",
- tip: "Whitespace character ignored due to the extended flag or mode."
- }, {
- id: "extnumref",
- // alternative syntaxes.
- proxy: "numref"
- }, {
- id: "char",
- label: "character",
- tip: "Matches a {{getChar()}} character (char code {{code}}). {{getInsensitive()}}"
- }, {
- id: "escchar",
- label: "escaped character",
- tip: "Matches a {{getChar()}} character (char code {{code}})."
- }, {
- id: "open",
- tip: "Indicates the start of a regular expression."
- }, {
- id: "close",
- tip: "Indicates the end of a regular expression and the start of expression flags."
- }, {
- id: "condition",
- tip: "The lookaround to match in resolving the enclosing conditional statement. See 'conditional' in the Reference for info."
- }, {
- id: "conditionalelse",
- label: "conditional else",
- tip: "Delimits the 'else' portion of the conditional."
- }, {
- id: "ERROR",
- tip: "Errors in the expression are underlined in red. Roll over errors for more info."
- }, {
- id: "PREG_INTERNAL_ERROR",
- tip: "Internal PCRE error"
- }, {
- id: "PREG_BACKTRACK_LIMIT_ERROR",
- tip: "Backtrack limit was exhausted."
- }, {
- id: "PREG_RECURSION_LIMIT_ERROR",
- tip: "Recursion limit was exhausted"
- }, {
- id: "PREG_BAD_UTF8_ERROR",
- tip: "Malformed UTF-8 data"
- }, {
- id: "PREG_BAD_UTF8_OFFSET_ERROR",
- tip: "Malformed UTF-8 data"
- }]
- };
- o.errors = {
- groupopen: "Unmatched opening parenthesis.",
- groupclose: "Unmatched closing parenthesis.",
- setopen: "Unmatched opening square bracket.",
- rangerev: "Range values reversed. Start char code is greater than end char code.",
- quanttarg: "Invalid target for quantifier.",
- quantrev: "Quantifier minimum is greater than maximum.",
- esccharopen: "Dangling backslash.",
- esccharbad: "Unrecognized or malformed escape character.",
- unicodebad: "Unrecognized unicode category or script.",
- posixcharclassbad: "Unrecognized POSIX character class.",
- posixcharclassnoset: "POSIX character class must be in a character set.",
- notsupported: "The \"{{~getLabel()}}\" feature is not supported in this flavor of RegEx.",
- fwdslash: "Unescaped forward slash. This may cause issues if copying/pasting this expression into code.",
- esccharbad: "Invalid escape sequence.",
- servercomm: "An error occurred while communicating with the server.",
- extraelse: "Extra else in conditional group.",
- unmatchedref: "Reference to non-existent group \"{{name}}\".",
- modebad: "Unrecognized mode flag \"{{errmode}}
\".",
- badname: "Group name can not start with a digit.",
- dupname: "Duplicate group name.",
- branchreseterr: "Branch Reset. Results will be ok, but RegExr's parser does not number branch reset groups correctly. Coming soon!",
- timeout: "The expression took longer than 250ms to execute.",
- // TODO: can we couple this to the help content somehow?
-
- // warnings:
- jsfuture: "The \"{{~getLabel()}}\" feature may not be supported in all browsers.",
- infinite: "The expression can return empty matches, and may match infinitely in some use cases." // TODO: can we couple this to the help content somehow?
- };
-
- /*
- classes:
- quant
- set
- special
- ref
- esc
- anchor
- charclass
- group
- comment
- */
-
- /*
- RegExr: Learn, Build, & Test RegEx
- Copyright (C) 2017 gskinner.com, inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see The syntax \\\\o{FFF}
is also supported.
There are multiple syntaxes for this feature: \\\\1
\\\\g1
\\\\g{1}
.
The latter syntaxes support relative values preceded by +
or -
. For example \\\\g-1
would match the group preceding the reference.
This behaviour is reversed by the ungreedy (U
) flag/modifier.
Requires the u
flag.
For a list of values, see this MDN page.
\"\n\t\t},\n\t\t// notunicodecat, unicodescript, notunicodescript are copied from unicodecat below.\n\t}\n};\n\njavascript.docs.notunicodecat = javascript.docs.unicodescript = javascript.docs.notunicodescript = javascript.docs.unicodecat;\n\nexport default javascript;\n","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see\"+content+\"
\";\n\t\t\tif (!token.open) { content += app.reference.tipForToken(token); }\n\t\t\telse { content += \" \"; }\n\t\t\tlet div = $.create(\"div\", null, content, el);\n\t\t\t\n\t\t\tif (token.close) {\n\t\t\t\tclassName = groupClasses[token.clss || token.type];\n\t\t\t\tif (className) {\n\t\t\t\t\tclassName = className.replace(\"%depth%\", Math.min(4,token.depth));\n\t\t\t\t\t$.addClass(div, className);\n\t\t\t\t}\n\t\t\t\tif (token.depth > 3) {\n\t\t\t\t\tdiv.innerHTML = \"So... you wanted to see what would happen if you just kept nesting groups, eh? Well, this is it.\"+\n\t\t\t\t\t\t\" I was going to reward your curiosity with a RegEx joke, but a quick search on google reveals that not even\"+\n\t\t\t\t\t\t\" the collective wisdom of the internet can make regular expressions funny. Well, except the whole 'now you've got two problems'\"+\n\t\t\t\t\t\t\" shtick, but you've probably heard that one already. Wasn't really worth the effort, was it?\";\n\t\t\t\t\ttoken = token.close.prv;\n\t\t\t\t\tthis._divs.push(div);\n\t\t\t\t\tel = div;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tel = div;\n\t\t\t}\n\t\t\t\n\t\t\tdiv.token = token;\n\t\n\t\t\tif (token.open) {\n\t\t\t\t$.addClass(div, \"close\");\n\t\t\t\tdiv.proxy = el;\n\t\t\t\tel = el.parentNode;\n\t\t\t}\n\t\n\t\t\tif (token.error) {\n\t\t\t\t$.addClass(div, \"error\");\n\t\t\t\tif (token.error.warning) { $.addClass(div, \"warning\"); }\n\t\t\t}\n\t\n\t\t\tif (!token.open) {\n\t\t\t\tdiv.addEventListener(\"mouseover\", this._handleMouseEvent);\n\t\t\t\tdiv.addEventListener(\"mouseout\", this._handleMouseEvent);\n\t\t\t\tdiv.addEventListener(\"click\", this._handleMouseEvent);\n\t\t\t}\n\t\t\t\n\t\t\tif (token.clss === \"quant\" || token.type === \"lazy\" || token.type === \"possessive\") {\n\t\t\t\tthis._insertApplied(div);\n\t\t\t} else {\n\t\t\t\tthis._divs.push(div);\n\t\t\t}\n\t\t}\n\t}\n\n\t_insertApplied(div) {\n\t\tlet divs = this._divs, prv = div.token.prv, d, i=divs.length;\n\t\twhile ((d = divs[--i]) && d.token !== prv) {} // search backwards for efficiency\n\t\td = d.proxy||d;\n\t\tdivs.splice(i, 0, div);\n\t\td.insertAdjacentElement(\"afterend\", div);\n\t\t$.addClass(div, \"applied\");\n\t}\n\n\t_handleHoverChange() {\n\t\tlet token = app.expression.highlighter.hoverToken;\n\t\t$.removeClass($.queryAll(\"div.selected\", this.el), \"selected\");\n\t\t$.removeClass($.queryAll(\"div.related\", this.el), \"related\");\n\t\tif (!token) { return; }\n\t\t\n\t\tlet div = this._findDiv(token);\n\t\t$.addClass(div, \"selected\");\n\t\tif (token.related) {\n\t\t\tfor (let i = 0, l=token.related.length; i < l; i++) {\n\t\t\t\t$.addClass(this._findDiv(token.related[i]), \"related\");\n\t\t\t}\n\t\t}\n\t}\n\t\n\t_findDiv(token) {\n\t\treturn Utils.find(this._divs, (div) => div.token === token);\n\t}\n\n\t_handleMouseEvent(evt) {\n\t\tlet type = evt.type, token = evt.currentTarget.token;\n\t\tif (type == \"click\") { app.sidebar.showToken(token); }\n\t\telse { app.expression.highlighter.hoverToken = type === \"mouseout\" ? null : token; }\n\t\tevt.stopPropagation();\n\t}\n\t\n\t_handleEvent(evt) {\n\t\tif (evt.type === \"change\") { this._update(); }\n\t\telse if (evt.type === \"hover\") { this._handleHoverChange(); }\n\t}\n}","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see Group
row to highlight it in the Expression.\", this.el);\n\t\tthis._addMatch(app.text.selectedMatch, app.text.value);\n\t}\n\t\n\t_addMatch(match, textVal) {\n\t\tif (!match) { return; }\n\t\tlet groups = match.groups, l=groups&&groups.length, ext=l&&(groups[0].i != null), matchVal=this._getMatchVal(match, textVal), extStr=\"\", me = match.i+match.l;\n\t\tlet groupTokens = app.expression.lexer.captureGroups;\n\t\t\n\t\tlet tableEl = $.create(\"table\", null, null, this.el);\n\t\tlet matchEl = $.create(\"tr\", \"match\", \"To edit a pattern you created, click its URL or double-click it in the list to load it, then open Save / Share to edit and save.
\",\n\tsearch: true,\n\tkids: []\n\t},\n\n\t{\n\tlabel: \"Cheatsheet\",\n\tid:\"cheatsheet\",\n\tel: \"#cheatsheet\"\n\t},\n\n\t{ // injected from Reference\n\tid:\"reference\"\n\t},\n\n\t{\n\tlabel: \"Community Patterns\",\n\tid: \"community\",\n\tdesc: \"Welcome to Community Patterns, a searchable database of patterns submitted by users like you.\"+\n\t\t\"After selecting a pattern, click its URL or double-click it in the list to load the full pattern. Or use the right arrow icon to load just the expression or text.
\"+\n\t\t\"Help make the Community better by rating patterns, and submitting your own via Search & Share in the menu.
\",\n\tsearch: true,\n\tkids: []\n\t},\n\n\t{\n\tlabel: \"Help\",\n\tid: \"help\",\n\tdesc: \"Help for the RegExr application. See the RegEx Reference for help with Regular Expressions.\",\n\tkids: [\n\n\t\t{\n\t\tlabel:\"About\",\n\t\tdesc:\"RegExr v<%= build_version %> (<%= build_date %>).\"+\n\t\t\t\"Created by Grant Skinner and the gskinner team, using the CreateJS & CodeMirror libraries.
\"+\n\t\t\t\"You can provide feedback or log bugs on GitHub.
\"\n\t\t},\n\t\t{\n\t\tlabel:\"Getting started\",\n\t\tdesc:\"RegExr provides real-time visual results, syntax highlighting, tooltips, and undo/redo ({{getCtrlKey()}}-Z / Y) so it's easy and fun to explore Regular Expressions.\"+\n\t\t\t\"Browse through the RegEx Reference and test different tokens to see what they do, then check out Community Patterns to see examples.
\"+\n\t\t\t\"You can also Save your patterns for later reference, or to share with others. Sign In to ensure you don't lose your patterns.
\"+\n\t\t\t\"Modify your pattern's details, share it with the Community, or make it private, or delete it in Pattern Settings
\",\n\t\tkids: [\n\t\t\t{\n\t\t\tlabel:\"Expression panel\",\n\t\t\tdesc:\"This is where you enter a regular expression to test. The results in the Text and Tools panel will update as you type.\"+\n\t\t\t\t\"Roll over the expression for information on each token.\"+\n\t\t\t\t\"The buttons to the right allow you to switch RegEx engines, or edit the expression flags.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Text panel\",\n\t\t\tdesc:\"This is where you enter text to test your expression against. Drag & drop a text file to load its contents.\"+\n\t\t\t\t\"Matches will be highlighted as you type. Roll over a match for information on the match and its capture groups. The match count and execution time are shown in the title bar.
\"+\n\t\t\t\t\"Lighter colored caps at the start or end of a line indicate the match continues between lines.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Tests panel\",\n\t\t\tdesc:\"This allows you to create a suite of tests that you can use to validate your Expression in real time.\"+\n\t\t\t\t\"A test can check if the pattern matches anything, matches everything, or matches nothing in the test text.
\"+\n\t\t\t\t\"Access this panel with the toggle at the top of the Text panel. When you save, both your Text and Tests will be saved.
\"\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Tools panel\",\n\t\t\tdesc:\"Click the Tools title bar below the Text panel to show or hide the Tools panel.\"+\n\t\t\t\t\"Tools provide different ways of working with or exploring your results.
\",\n\t\t\tkids: [\n\t\t\t\t{\n\t\t\t\tlabel:\"Replace\",\n\t\t\t\tid: \"replace\",\n\t\t\t\tdesc:\"The Replace tool replaces matches with a specified string or pattern.\"+\n\t\t\t\t\t\"Matches in the Text panel are replaced by the substitution string & displayed as you type.
\"+\n\t\t\t\t\t\"Substitution tokens and escaped characters are supported, such as \\\\n
, \\\\t
& \\\\u0009
.
Roll over tokens for information, and check out the RegEx Reference for more information.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"List\",\n\t\t\t\tid: \"list\",\n\t\t\t\tdesc:\"The List tool lists all found matches.\"+\n\t\t\t\t\t\"You can specify either a simple delimiter (ex. ,
or \\\\n
), or use substitution tokens to generate more advanced reports. For example, $1\\\\n
would list all group 1 results (in the JavaScript engine).
Escaped characters are supported, such as \\\\n
, \\\\t
& \\\\u0009
.
Roll over tokens for information.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"Details\",\n\t\t\t\tid: \"details\",\n\t\t\t\tdesc:\"The Details tool displays the full text of a match and its capture groups.\"+\n\t\t\t\t\t\"Click on a highlighted match in the Text panel to display the details for that match.
\"+\n\t\t\t\t\t\"Roll over a group row to highlight that group in your Expression.
\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\tlabel:\"Explain\",\n\t\t\t\tid: \"explain\",\n\t\t\t\tdesc:\"The Explain tool displays a detailed breakdown of the Expression.\"+\n\t\t\t\t\t\"Mouse over the explanation to highlight the related tokens in the Expression panel and vice versa.
\"+\n\t\t\t\t\t\"Click an item in the explanation to show more info in the RegEx Reference.
\"\n\t\t\t\t}\n\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\tlabel:\"Menu\",\n\t\t\tdesc:\"The Menu (this panel) includes Help, a full RegEx Reference, a Cheatsheet, and Pattern Settings features.\"+\n\t\t\t\t\"Double-click a selected item in the RegEx Reference to insert it into your Expression. Click the arrow beside an example to load it.
\"+\n\t\t\t\t\"The menu also includes searchable Community Patterns, and patterns you've created or favorited in My Patterns.
\"\n\t\t\t}\n\t\t]\n\t\t},\n\t\t{\n\t\tlabel:\"Signing in\",\n\t\tid: \"signin\",\n\t\tdesc:\"Before you sign in, RegExr creates a temporary account which relies on a browser cookie. This means you can't access your patterns on other computers, and that you could lose your patterns if your cookies are deleted or expire.\"+\n\t\t\t\"Signing in creates a permanent account, so you can access your patterns anywhere, anytime.
\"+\n\t\t\t\"Your existing patterns & favorites will automatically be assigned to the new account.
\"+\n\t\t\t\"We don't use your info for anything other than signing you into your RegExr account.
\"\n\t\t},\n\t\t{\n\t\tid: \"engine\",\n\t\tlabel:\"RegEx engines\",\n\t\tdesc:\"While the core feature set of regular expressions is fairly consistent, different implementations (ex. Perl vs Java) may have different features or behaviours.\"+\n\t\t\t \"RegExr currently supports JavaScript RegExp executed in your browser and PCRE via PHP.
\"+\n\t\t\t \"You can switch engines using the dropdown in Expression.
\",\n\t\tkids: [\n\t\t\t{\n\t\t\tlabel:\"JavaScript\",\n\t\t\tdesc:\"Your browser's JavaScript engine is used to execute RegEx in an asynchronous worker usingRegExp.exec()
.\"+\n\t\t\t\t\"Note that while implementations are mostly consistent, there are small variations between browsers. Here is a short list of known differences:
The following query string params are recognized:
engine
- the RegEx engine to use (js
or pcre
)expression
- populates the Expression area. It is recommended to pass a full expression with flags (/.*/ig
) not just the pattern (.*
)text
- populates the Text areatool
- sets the tool (replace, list, details, or explain)input
- populates the tool input field\" + Utils.htmlSafe(exp) + \"
\";\n\t\t\tif (txt && regex) {\n\t\t\t\tlet over=Math.max(0, txt.length-160), s=txt;\n\t\t\t\tif (over) { s = Utils.htmlSafe(s.substr(0,159)); }\n\t\t\t\tif (regex) { s = s.replace(regex, \"$&\"); }\n\t\t\t\t// TODO: this won't match on html elements:\n\t\t\t\tstr += \"\" + s + (over?\"\\u2026\" : \"\") + \"
\";\n\t\t\t}\n\t\t}\n\t\tthis.el.innerHTML = str;\n\t\tif (exp) {\n\t\t\t$.query(\"code.expression > .load\", this.el).addEventListener(\"click\", ()=> {\n\t\t\t\t// TODO: this will need to be updated when we support other delimiters:\n\t\t\t\tapp.expression.value = exp[0] === \"/\" ? exp : \"/\"+exp+\"/g\";\n\t\t\t});\n\t\t\t\n\t\t}\n\t\tif (txt) { $.query(\"code.text > .load\", this.el).addEventListener(\"click\", ()=> app.text.value = txt); }\n\t}\n\t\n\n}","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see \"+token.on+\"
\\\".\" : \"\")\n\t\tif (token.off) { str += \" Disable \\\"\"+token.off+\"
\\\".\"; }\n\t\treturn str;\n\t}\n\n\tgetInsensitive(token) {\n\t\tif (token.code) {\n\t\t\tlet chr = String.fromCharCode(token.code);\n\t\t\tif (chr.toLowerCase() === chr.toUpperCase()) { return \"\"; }\n\t\t}\n\t\treturn token.modes ? `Case ${token.modes.i ? \"in\" : \"\"}sensitive.` : \"\";\n\t}\n\n\tgetDotAll(token) {\n\t\treturn (token.modes.s ? \"including\" : \"except\") + \" line breaks\";\n\t}\n\n\tgetLabel(token) {\n\t\tlet node = this.getNodeForToken(token);\n\t\treturn node ? node.label || node.id || \"\" : token.type;\n\t}\n\n\tgetDesc(token) {\n\t\treturn this.getVal(this.getNodeForToken(token), \"desc\");\n\t}\n\n\tgetLazy(token) {\n\t\treturn token.modes.U ? \"greedy\" : \"lazy\";\n\t}\n\n\tgetLazyFew(token) {\n\t\treturn token.modes.U ? \"many\" : \"few\";\n\t}\n\n\tgetPHPVersion() {\n\t\treturn this._config.PHPVersion;\n\t}\n\n\tgetPCREVersion() {\n\t\treturn this._config.PCREVersion;\n\t}\n\n\tgetCtrlKey() {\n\t\treturn Utils.getCtrlKey();\n\t}\n\n\tgetEscChars() {\n\t\tlet o = this._flavor.profile.escChars, str=\"\";\n\t\tfor (let n in o) { str += n; }\n\t\treturn str;\n\t}\n\n\t/*\n\tSearches for tags in the string in the format:\n\t`{{prop.prop}}` or `{{method(prop.prop)}}`\n\t\n\tThe first format will inject the specified property of the data object.\n\tFor example, `{{a.b}}` would inject the value of `data.a.b`.\n\t\n\tThe second will inject the results of calling the specified function on the functs object with a property of the data object as it's parameter (or the data object itself if empty).\n\tFor example, `{{myMethod(a.b)}}` would inject the return value of `functs.myMethod(data.a.b)`.\n\t\n\tCurrently only supports a single param.\n\t */\n\tfillTags(str, data, functs, maxLength=20, htmlSafe=true) {\n\t\tlet match;\n\t\twhile (match = str.match(/{{~?[\\w.()]+}}/)) {\n\t\t\tlet val, f, safe=false;\n\t\t\tval = match[0].substring(2, match[0].length - 2);\n\t\t\tif (val[0] === \"~\") {\n\t\t\t\tval = val.substr(1);\n\t\t\t\tsafe = true;\n\t\t\t}\n\t\t\tlet match2 = val.match(/\\([\\w.]*\\)/);\n\t\t\tif (match2) {\n\t\t\t\tf = val.substr(0, match2.index);\n\t\t\t\tval = match2[0].substring(1, match2[0].length - 1);\n\t\t\t} else {\n\t\t\t\tf = null;\n\t\t\t}\n\t\t\tlet o = data, arr = val.split(\".\");\n\t\t\tfor (let i = 0; i < arr.length; i++) {\n\t\t\t\tlet prop = arr[i];\n\t\t\t\tif (prop && o) { o = o[prop]; }\n\t\t\t}\n\t\t\tval = o;\n\t\t\tif (f) {\n\t\t\t\tif (functs[f]) { val = functs[f](val); }\n\t\t\t\telse { val = \" [\"+f+\"] \"; }\n\t\t\t}\n\t\t\tif (!safe && (maxLength || htmlSafe)) { val = Utils.shorten(val, maxLength, htmlSafe, \"i\"); }\n\t\t\tstr = str.replace(match[0], val);\n\t\t}\n\t\treturn str;\n\t}\n\t\n\t// returns doc props from the profile or reference as appropriate (ex. tip, desc, ext)\n\tgetVal(node, prop) {\n\t\tif (!node) { return \"\"; }\n\t\tlet pDocs = this._flavor.getDocs(node.id), pRef = (pDocs && pDocs[prop]);\n\t\tif (pRef != null && pRef[0] !== \"+\") { return pRef; }\n\t\tlet ref=(node&&node[prop])||\"\";\n\t\treturn pRef != null ? ref+pRef.substr(1) : ref;\n\t}\n\t\n\tgetNodeForToken(token) {\n\t\tlet id=this.idForToken(token), clss = token.clss;\n\t\t\n\t\t// Special cases:\n\t\tif (clss === \"quant\") { id = clss; }\n\t\tif (clss === \"esc\" && token.type !== \"escsequence\") { id = \"escchar\"; }\n\n\t\treturn this.getNode(id);\n\t}\n\n\tgetNode(id) {\n\t\tlet map=this._idMap, node = map[id]\n\t\twhile (node&&node.proxy) { node = map[node.proxy]; }\n\t\treturn node;\n\t}\n\t\n\tgetError(error, token) {\n\t\tlet errId = error && error.id;\n\t\tlet str = this._content.errors[errId] || \"no docs for error='\" + errId + \"'\";\n\t\tif (token) { str = this.fillTags(str, token, this, 20); }\n\t\treturn str;\n\t}\n\t\n\ttipForToken(token) {\n\t\tif (!token) { return null; }\n\n\t\tlet node = this.getNodeForToken(token), label, tip;\n\n\t\tif (token.error && !token.error.warning) {\n\t\t\tlabel = \"ERROR: \";\n\t\t\ttip = this.getError(token.error, token);\n\t\t} else {\n\t\t\tlabel = node ? node.label || node.id || \"\" : token.type;\n\t\t\ttip = this.getVal(node, \"tip\") || this.getVal(node, \"desc\");\n\t\t\ttip = this.fillTags(tip, token, this, 20);\n\t\t\tif (token.type === \"group\") { label += \" #\" + token.num; }\n\t\t\tlabel = \"\" + label[0].toUpperCase() + label.substr(1) + \". \";\n\n\t\t\tif (token.error) {\n\t\t\t\ttip += \"WARNING: \" + this.getError(token.error, token) + \"\";\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn tip ? label + tip : \"no docs for id='\" + this.idForToken(token) + \"'\";\n\t}\n\t\n\tgetContent(id) {\n\t\tlet node = this.getNode(id);\n\t\treturn this.fillTags(this.getVal(node, \"desc\") + this.getVal(node, \"ext\"), node, this);\n\t}\n\t\n\t// TODO: this isn't necessarily the most ideal place for this method (has nothing to do with Reference). Maybe move into Text?\n\ttipForMatch(match, text) {\n\t\tif (!match) { return null; }\n\t\tlet more = match.l > 150;\n\t\tlet str = \"match: \" + Utils.shorten(text.substr(match.i, match.l), 150, true, \"i\") +\n\t\t\t\t \"\" + match.i + \"-\" + (match.i+match.l-1)+ \"
\";\n\t\t\n\t\tlet groups = match.groups, l = groups && groups.length;\n\t\tfor (let i = 0; i < l; i++) {\n\t\t\tif (i > 3 && l > 5) {\n\t\t\t\tmore = false;\n\t\t\t\tstr += \"Double-click an item in the list to insert it into your Expression.
\n\tClick the arrow beside an example to load it.
`;\n\no.kids = [\n\t{\n\tlabel: \"Character classes\",\n\tid: \"charclasses\",\n\tdesc: \"Character classes match a character from a specific set. There are a number of predefined character classes and you can also define your own sets.\",\n\tkids: [\n\t\t\n\t\t{\n\t\tid:\"set\",\n\t\tlabel: \"character set\",\n\t\tdesc:\"Match any character in the set.\",\n\t\texample:[\"[aeiou]\",\"glib jocks vex dwarves!\"],\n\t\ttoken:\"[ABC]\"\n\t\t},\n\t\t{\n\t\tid:\"setnot\",\n\t\tlabel: \"negated set\",\n\t\tdesc:\"Match any character that is not in the set.\",\n\t\texample:[\"[^aeiou]\",\"glib jocks vex dwarves!\"],\n\t\ttoken:\"[^ABC]\"\n\t\t},\n\t\t{\n\t\tid:\"range\",\n\t\ttip:\"Matches a character in the range {{getChar(prev)}} to {{getChar(next)}} (char code {{prev.code}} to {{next.code}}). {{getInsensitive()}}\",\n\t\texample:[\"[g-s]\",\"abcdefghijklmnopqrstuvwxyz\"],\n\t\tdesc: \"Matches a character having a character code between the two specified characters inclusive.\",\n\t\ttoken:\"[A-Z]\"\n\t\t},\n\t\t{\n\t\tid:\"posixcharclass\",\n\t\ttip:\"Matches any character in the '{{value}}' POSIX class.\",\n\t\tlabel:\"POSIX class\",\n\t\tdesc:\"Matches any character in the specified POSIX class. Must be in a character set. For example,[[:alnum:]$]
will match alphanumeric characters and $
.\",\n\t\text:\"For a list of classes, see the PCRE spec.
\",\n\t\ttoken:\"[:alnum:]\"\n\t\t},\n\t\t{\n\t\tid:\"dot\",\n\t\ttip:\"Matches any character {{getDotAll()}}.\",\n\t\tdesc:\"Matches any character except linebreaks.\",\n\t\text:\" Equivalent to[^\\\\n\\\\r]
.\",\n\t\texample:[\".\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\".\"\n\t\t},\n\t\t{\n\t\tid:\"matchanyset\",\n\t\tlabel:\"match any\",\n\t\tdesc:\"A character set that can be used to match any character, including line breaks, without the dotall flag (s
).\"+\n\t\t\t\"An alternative is [^]
, but it is not supported in all browsers.
[A-Za-z0-9_]
\",\n\t\texample:[\"\\\\w\",\"bonjour, mon fr\\u00E8re\"],\n\t\ttoken:\"\\\\w\"\n\t\t},\n\t\t{\n\t\tid:\"notword\",\n\t\tlabel: \"not word\",\n\t\tdesc:\"Matches any character that is not a word character (alphanumeric & underscore).\",\n\t\text:\" Equivalent to [^A-Za-z0-9_]
\",\n\t\texample:[\"\\\\W\",\"bonjour, mon fr\\u00E8re\"],\n\t\ttoken:\"\\\\W\"\n\t\t},\n\t\t{\n\t\tid:\"digit\",\n\t\tdesc:\"Matches any digit character (0-9).\",\n\t\text:\" Equivalent to [0-9]
.\",\n\t\texample:[\"\\\\d\",\"+1-(444)-555-1234\"],\n\t\ttoken:\"\\\\d\"\n\t\t},\n\t\t{\n\t\tid:\"notdigit\",\n\t\tlabel: \"not digit\",\n\t\tdesc:\"Matches any character that is not a digit character (0-9).\",\n\t\text:\" Equivalent to [^0-9]
.\",\n\t\texample:[\"\\\\D\",\"+1-(444)-555-1234\"],\n\t\ttoken:\"\\\\D\"\n\t\t},\n\t\t{\n\t\tid:\"whitespace\",\n\t\tdesc:\"Matches any whitespace character (spaces, tabs, line breaks).\",\n\t\texample:[\"\\\\s\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\"\\\\s\"\n\t\t},\n\t\t{\n\t\tid:\"notwhitespace\",\n\t\tlabel: \"not whitespace\",\n\t\tdesc:\"Matches any character that is not a whitespace character (spaces, tabs, line breaks).\",\n\t\texample:[\"\\\\S\", \"glib jocks vex dwarves!\"],\n\t\ttoken:\"\\\\S\"\n\t\t},\n\t\t{\n\t\tid:\"hwhitespace\",\n\t\tlabel:\"horizontal whitespace\",\n\t\tdesc:\"Matches any horizontal whitespace character (spaces, tabs).\",\n\t\ttoken:\"\\\\h\"\n\t\t},\n\t\t{\n\t\tid:\"nothwhitespace\",\n\t\tlabel: \"not horizontal whitespace\",\n\t\tdesc:\"Matches any character that is not a horizontal whitespace character (spaces, tabs).\",\n\t\ttoken:\"\\\\H\"\n\t\t},\n\t\t{\n\t\tid:\"vwhitespace\",\n\t\tlabel:\"vertical whitespace\",\n\t\tdesc:\"Matches any vertical whitespace character (line breaks).\",\n\t\ttoken:\"\\\\v\"\n\t\t},\n\t\t{\n\t\tid:\"notvwhitespace\",\n\t\tlabel: \"not vertical whitespace\",\n\t\tdesc:\"Matches any character that is not a vertical whitespace character (line breaks).\",\n\t\ttoken:\"\\\\V\"\n\t\t},\n\t\t{\n\t\tid:\"linebreak\",\n\t\tlabel:\"line break\",\n\t\tdesc:\"Matches any line break character, including the CRLF pair, and CR / LF individually.\",\n\t\ttoken:\"\\\\R\"\n\t\t},\n\t\t{\n\t\tid:\"notlinebreak\",\n\t\tlabel:\"not line break\",\n\t\tdesc:\"Matches any character that is not a line break.\",\n\t\text:\" Similar to dot (.
) but is unaffected by the dotall flag (s
).\",\n\t\ttoken:\"\\\\N\"\n\t\t},\n\t\t{\n\t\tid:\"unicodecat\",\n\t\ttip:\"Matches any character in the '{{getUniCat()}}' unicode category.\",\n\t\tlabel:\"unicode category\",\n\t\tdesc:\"Matches a character in the specified unicode category. For example, \\\\p{Ll}
will match any lowercase letter.\",\n\t\text:\"For a list of categories, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\p{L}
\\\\pL
For a list of categories, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\P{L}
\\\\p{^L}
\\\\PL
\\\\p{Arabic}
will match characters in the Arabic script.\",\n\t\text:\"For a list of scripts, see the PCRE spec.
\",\n\t\ttoken:\"\\\\p{Han}\"\n\t\t},\n\t\t{\n\t\tid:\"notunicodescript\",\n\t\ttip:\"Matches any character that is not in the '{{value}}' unicode script.\",\n\t\tlabel:\"not unicode script\",\n\t\tdesc:\"Matches any character that is not in the specified unicode script.\",\n\t\text:\"For a list of scripts, see the PCRE spec.
\"+\n\t\t\t\"There are multiple syntaxes for this feature:
\\\\P{Han}
\\\\p{^Han}
\",\n\t\ttoken:\"\\\\P{Han}\"\n\t\t}\n\t]\n\t},\n\n\t{\n\tlabel:\"Anchors\",\n\tid:\"anchors\",\n\tdesc:\"Anchors are unique in that they match a position within a string, not a character.\",\n\tkids:[\n\t\t{\n\t\tid:\"bos\",\n\t\tlabel:\"beginning of string\",\n\t\tdesc:\"Matches the beginning of the string.\",\n\t\text:\" Unlike ^
, this is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\A\"\n\t\t},\n\t\t{\n\t\tid:\"eos\",\n\t\tlabel:\"end of string\",\n\t\tdesc:\"Matches the end of the string.\",\n\t\text:\" Unlike $
, this is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\Z\"\n\t\t},\n\t\t{\n\t\tid:\"abseos\",\n\t\tlabel:\"strict end of string\",\n\t\tdesc:\"Matches the end of the string. Unlike $
or \\\\Z
, it does not allow for a trailing newline.\",\n\t\text:\" This is unaffected by the multiline flag (m
). This matches a position, not a character.\",\n\t\ttoken:\"\\\\z\"\n\t\t},\n\t\t{\n\t\tid:\"bof\",\n\t\tlabel:\"beginning\",\n\t\tdesc:\"Matches the beginning of the string, or the beginning of a line if the multiline flag (m
) is enabled.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"^\\\\w+\",\"she sells seashells\"],\n\t\ttoken:\"^\"\n\t\t},\n\t\t{\n\t\tid:\"eof\",\n\t\tlabel:\"end\",\n\t\tdesc:\"Matches the end of the string, or the end of a line if the multiline flag (m
) is enabled.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"\\\\w+$\",\"she sells seashells\"],\n\t\ttoken:\"$\"\n\t\t},\n\t\t{\n\t\tid:\"wordboundary\",\n\t\tlabel:\"word boundary\",\n\t\tdesc:\"Matches a word boundary position between a word character and non-word character or position (start / end of string).\",\n\t\text:\" See the word character class (\\w
) for more info.\",\n\t\texample:[\"s\\\\b\",\"she sells seashells\"],\n\t\ttoken:\"\\\\b\"\n\t\t},\n\t\t{\n\t\tid:\"notwordboundary\",\n\t\tlabel: \"not word boundary\",\n\t\tdesc:\"Matches any position that is not a word boundary.\",\n\t\text:\" This matches a position, not a character.\",\n\t\texample:[\"s\\\\B\",\"she sells seashells\"],\n\t\ttoken:\"\\\\B\"\n\t\t},\n\t\t{\n\t\tid:\"prevmatchend\",\n\t\tlabel: \"previous match end\",\n\t\tdesc:\"Matches the end position of the previous match.\",\n\t\text:\" This matches a position, not a character.\",\n\t\ttoken:\"\\\\G\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Escaped characters\",\n\tid:\"escchars\",\n\tdesc: \"Escape sequences can be used to insert reserved, special, and unicode characters. All escaped characters begin with the \\\\
character.\",\n\tkids: [\n\t\t{\n\t\tid:\"reservedchar\",\n\t\tlabel:\"reserved characters\",\n\t\tdesc:\"The following character have special meaning, and should be preceded by a \\\\
(backslash) to represent a literal character:\"+\n\t\t\t\"
{{getEscChars()}}
Within a character set, only \\\\
, -
, and ]
need to be escaped.
\\\\000
.\",\n\t\text:\" Value must be less than 255 (\\\\377
).\", // PCRE profile adds to ext.\n\t\texample:[\"\\\\251\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\000\"\n\t\t},\n\t\t{\n\t\tid:\"eschexadecimal\",\n\t\tlabel:\"hexadecimal escape\",\n\t\tdesc:\"Hexadecimal escaped character in the form \\\\xFF
.\",\n\t\texample:[\"\\\\xA9\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\xFF\"\n\t\t},\n\t\t{\n\t\tid:\"escunicodeu\",\n\t\tlabel:\"unicode escape\",\n\t\tdesc:\"Unicode escaped character in the form \\\\uFFFF
\",\n\t\texample:[\"\\\\u00A9\",\"RegExr is \\u00A92014\"],\n\t\ttoken:\"\\\\uFFFF\"\n\t\t},\n\t\t{\n\t\tid:\"escunicodeub\",\n\t\tlabel:\"extended unicode escape\",\n\t\tdesc:\"Unicode escaped character in the form \\\\u{FFFF}
.\",\n\t\text:\" Supports a full range of unicode point escapes with any number of hex digits. Requires the unicode flag (u
).
\\\\x{FF}
.\",\n\t\ttoken:\"\\\\x{FF}\"\n\t\t},\n\t\t{\n\t\tid:\"esccontrolchar\",\n\t\tlabel:\"control character escape\",\n\t\tdesc:\"Escaped control character in the form \\\\cZ
.\",\n\t\text:\" This can range from \\\\cA
(SOH, char code 1) to \\\\cZ
(SUB, char code 26). \\\\cI
matches TAB (char code 9).\",\n\t\ttoken:\"\\\\cI\"\n\t\t},\n\t\t{\n\t\tid:\"escsequence\",\n\t\tlabel:\"escape sequence\",\n\t\ttip: \"Matches the literal string '{{value}}'.\",\n\t\tdesc:\"All characters between the \\\\Q
and the \\\\E
are interpreted as a literal string. If \\\\E
is omitted, it continues to the end of the expression.\",\n\t\text:\" For example, the expression /\\\\Q(?.)\\\\E/
will match the string (?.)
.\",\n\t\ttoken:\"\\\\Q...\\\\E\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Groups & References\",\n\tid:\"groups\",\n\tdesc: \"Groups allow you to combine a sequence of tokens to operate on them together. Capture groups can be referenced by a backreference and accessed separately in the results.\",\n\tkids: [\n\t\t{\n\t\tid:\"group\",\n\t\tlabel: \"capturing group\",\n\t\tdesc: \"Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference.\",\n\t\texample:[\"(ha)+\",\"hahaha haa hah!\"],\n\t\ttoken:\"(ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"namedgroup\",\n\t\tlabel: \"named capturing group\",\n\t\ttip:\"Creates a capturing group named '{{name}}'.\",\n\t\tdesc:\"Creates a capturing group that can be referenced via the specified name.\",\n\t\text:\"There are multiple syntaxes for this feature:
(?'name'ABC)
(?P<name>ABC)
(?<name>ABC)
There are multiple syntaxes for this feature:
\\\\k'name'
\\\\k<name>
\\\\k{name}
\\\\g{name}
(?P=name)
\\\\1
matches the results of the first capture group & \\\\3
matches the third.\",\n\t\t// PCRE adds relative and alternate syntaxes in ext\n\t\texample:[\"(\\\\w)a\\\\1\",\"hah dad bad dab gag gab\"],\n\t\ttoken:\"\\\\1\"\n\t\t},\n\t\t{\n\t\tid:\"branchreset\",\n\t\tlabel: \"branch reset group\",\n\t\tdesc:\"Define alternative groups that share the same group numbers.\",\n\t\text: \"For example, in (?|(a)|(b))
both groups (a and b) would be counted as group #1.\",\n\t\ttoken:\"(?|(a)|(b))\"\n\t\t},\n\t\t{\n\t\tid:\"noncapgroup\",\n\t\tlabel: \"non-capturing group\",\n\t\tdesc:\"Groups multiple tokens together without creating a capture group.\",\n\t\texample:[\"(?:ha)+\",\"hahaha haa hah!\"],\n\t\ttoken:\"(?:ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"atomic\",\n\t\tlabel:\"atomic group\",\n\t\tdesc:\"Non-capturing group that discards backtracking positions once matched.\",\n\t\text:\"
For example, /(?>ab|a)b/
will match abb
but not ab
because once the ab
option has matched, the atomic group prevents backtracking to retry with the a
option.
For example, /A(?(DEFINE)(?'foo'Z))B\\\\g'foo'/
will match ABZ
, because the define group is ignored in the match except to define the foo
subroutine that is referenced later with \\\\g'foo'
.
/(a|b)\\\\g'1'/
can match ab
, because the expression a|b
is evaluated again.\",\n\t\text:\"There are multiple syntaxes for this feature: \\\\g<1>
\\\\g'1'
(?1)
.
Relative values preceded by +
or -
are also supported. For example \\\\g<-1>
would match the group preceding the reference.
There are multiple syntaxes for this feature: \\\\g<name>
\\\\g'name'
(?&name)
(?P>name)
.
Negative lookarounds specify a group that can NOT match before or after the pattern.
\",\n\tkids: [\n\t\t{\n\t\tid:\"poslookahead\",\n\t\tlabel: \"positive lookahead\",\n\t\tdesc:\"Matches a group after the main expression without including it in the result.\",\n\t\texample:[\"\\\\d(?=px)\",\"1pt 2px 3em 4px\"],\n\t\ttoken:\"(?=ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"neglookahead\",\n\t\tlabel: \"negative lookahead\",\n\t\tdesc:\"Specifies a group that can not match after the main expression (if it matches, the result is discarded).\",\n\t\texample:[\"\\\\d(?!px)\",\"1pt 2px 3em 4px\"],\n\t\ttoken:\"(?!ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"poslookbehind\",\n\t\tlabel: \"positive lookbehind\",\n\t\tdesc:\"Matches a group before the main expression without including it in the result.\",\n\t\ttoken:\"(?<=ABC)\"\n\t\t},\n\t\t{\n\t\tid:\"neglookbehind\",\n\t\tlabel: \"negative lookbehind\",\n\t\tdesc:\"Specifies a group that can not match before the main expression (if it matches, the result is discarded).\",\n\t\ttoken:\"(?/o\\\\Kbar/ will matchbar
within the string foobar
\",\n\t\ttoken:\"\\\\K\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Quantifiers & Alternation\",\n\tid:\"quants\",\n\tdesc: \"Quantifiers indicate that the preceding token must be matched a certain number of times. By default, quantifiers are greedy, and will match as many characters as possible.\"+\n\t\t\"{1,3}
will match 1 to 3. \"+\n\t\t\t\"{3}
will match exactly 3. \"+\n\t\t\t\"{3,}
will match 3 or more. \",\n\t\texample:[\"b\\\\w{2,3}\",\"b be bee beer beers\"],\n\t\ttoken:\"{1,3}\"\n\t\t},\n\t\t{\n\t\tid:\"opt\",\n\t\tlabel:\"optional\",\n\t\tdesc:\"Matches 0 or 1 of the preceding token, effectively making it optional.\",\n\t\texample: [\"colou?r\", \"color colour\"],\n\t\ttoken:\"?\"\n\t\t},\n\t\t{\n\t\tid:\"lazy\",\n\t\ttip:\"Makes the preceding quantifier {{getLazy()}}, causing it to match as {{getLazyFew()}} characters as possible.\",\n\t\tdesc:\"Makes the preceding quantifier lazy, causing it to match as few characters as possible.\",\n\t\text:\" By default, quantifiers are greedy, and will match as many characters as possible.\",\n\t\texample:[\"b\\\\w+?\",\"b be bee beer beers\"],\n\t\ttoken:\"?\"\n\t\t},\n\t\t{\n\t\tid:\"possessive\",\n\t\tdesc:\"Makes the preceding quantifier possessive. It will match as many characters as possible, and will not release them to match subsequent tokens.\",\n\t\text:\"For example /.*a/
would match aaa
, but /.*+a/
would not, because the repeating dot would match and not release the last character to match a
.
|
.\",\n\t\text:\"It can operate within a group, or on a whole expression. The patterns will be tested in order.
\",\n\t\texample:[\"b(a|e|i)d\",\"bad bud bod bed bid\"],\n\t\ttoken:\"|\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tlabel: \"Special\",\n\tid:\"other\",\n\tdesc: \"Tokens that don't quite fit anywhere else.\",\n\tkids: [\n\t\t{\n\t\tid:\"comment\",\n\t\tdesc:\"Allows you to insert a comment into your expression that is ignored when finding a match.\",\n\t\ttoken:\"(?#foo)\"\n\t\t},\n\t\t{\n\t\tid:\"conditional\",\n\t\tdesc:\"Conditionally matches one of two options based on whether a lookaround is matched.\",\n\t\text:\"For example, /(?(?=a)ab|..)/
will match ab
and zx
but not ax
, because if the first character matches the condition a
then it evaluates the pattern ab
.
Any lookaround can be used as the condition. A lookahead will start the subsequent match at the start of the condition, a lookbehind will start it after.
\",\n\t\ttoken:\"(?(?=A)B|C)\"\n\t\t},\n\t\t{\n\t\tid:\"conditionalgroup\",\n\t\tlabel:\"group conditional\",\n\t\tdesc:\"Conditionally matches one of two options based on whether group '{{name}}' matched.\",\n\t\text:\"For example, /(z)?(?(1)a|b)/
will match za
because the first capture group matches z
successfully, which causes the conditional to match the first option a
.
The same pattern will also match b
on its own, because group 1 doesn't match, so it instead tries to match the second option b
.
You can reference a group by name, number, or relative position (ex. -1
).
For example, /a(?R)?b/
will match any number of a
followed by the same number of z
: the full text of az
or aaaazzzz
, but not azzz
.
There are multiple syntaxes for this feature:
(?R)
(?0)
\\\\g<0>
\\\\g'0'
(?i)
would enable case insensitivity for the remainder of the expression.\"+\n\t\t\t\"Multiple modifiers can be specified, and any modifiers that follow -
are disabled. For example (?im-s)
would enable case insensitivity & multiline modes, and disable dotall.
Supported modifiers are: i
- case insensitive, s
- dotall, m
- multiline, x
- free spacing, J
- allow duplicate names, U
- ungreedy.
There are multiple syntaxes for this feature:
$0
\\\\0
\\\\{0}
$3
would insert the third capture group.\",\n\t\t// NOTE: javascript profile overrides this:\n\t\text:\"There are multiple syntaxes for this feature:
$1
\\\\1
\\\\{1}
\\\\n
, \\\\r
, \\\\t
, \\\\\\\\
, and unicode escapes \\\\uFFFF
. This may vary in your deploy environment.\"\n\t\t}\n\t]\n\t},\n\t\n\t{\n\tid:\"flags\",\n\tlabel:\"Flags\",\n\ttooltip:\"Expression flags change how the expression is interpreted. Click to edit.\",\n\tdesc:\"Expression flags change how the expression is interpreted. Flags follow the closing forward slash of the expression (ex. /.+/igm
).\",\n\ttarget:\"flags\",\n\tkids: [\n\t\t{\n\t\tid:\"caseinsensitive\",\n\t\tlabel: \"ignore case\",\n\t\tdesc:\"Makes the whole expression case-insensitive.\",\n\t\text:\" For example, /aBc/i
would match AbC
.\",\n\t\ttoken:\"i\"\n\t\t},\n\t\t{\n\t\tid:\"global\",\n\t\tlabel: \"global search\",\n\t\ttip: \"Retain the index of the last match, allowing iterative searches.\",\n\t\tdesc:\"Retain the index of the last match, allowing subsequent searches to start from the end of the previous match.\"+\n\t\t\t\"Without the global flag, subsequent searches will return the same match.
^
and $
) will match the start and end of a line, instead of the start and end of the whole string.\"+\n\t\t\t\"Note that patterns such as /^[\\\\s\\\\S]+$/m
may return matches that span multiple lines because the anchors will match the start/end of any line.
\\\\x{FFFFF}
unicode escapes.\",\n\t\tdesc:\"When the unicode flag is enabled, you can use extended unicode escapes in the form \\\\x{FFFFF}
.\"+\n\t\t\t\"It also makes other escapes stricter, causing unrecognized escapes (ex. \\\\j
) to throw an error.
g
) flag if set.\",\n\t\text:\" Because each search in RegExr is discrete, this flag has no further impact on the displayed results.\",\n\t\ttoken:\"y\"\n\t\t},\n\t\t{\n\t\tid:\"dotall\",\n\t\tdesc:\"Dot (.
) will match any character, including newline.\",\n\t\ttoken:\"s\"\n\t\t},\n\t\t{\n\t\tid:\"extended\",\n\t\tdesc:\"Literal whitespace characters are ignored, except in character sets.\",\n\t\ttoken:\"x\"\n\t\t},\n\t\t{\n\t\tid:\"ungreedy\",\n\t\ttip:\"Makes quantifiers ungreedy (lazy) by default.\",\n\t\tdesc:\"Makes quantifiers ungreedy (lazy) by default. Quantifiers followed by ?
will become greedy.\",\n\t\ttoken:\"U\"\n\t\t}\n\t]\n\t}\n];\n\n// content that isn't included in the Reference menu item:\no.misc = {\n\tkids:[\n\t\t{\n\t\tid:\"ignorews\",\n\t\tlabel:\"ignored whitespace\",\n\t\ttip:\"Whitespace character ignored due to the extended flag or mode.\"\n\t\t},\n\t\t{\n\t\tid:\"extnumref\", // alternative syntaxes.\n\t\tproxy:\"numref\"\n\t\t},\n\t\t{\n\t\tid:\"char\",\n\t\tlabel:\"character\",\n\t\ttip:\"Matches a {{getChar()}} character (char code {{code}}). {{getInsensitive()}}\"\n\t\t},\n\t\t{\n\t\tid:\"escchar\",\n\t\tlabel:\"escaped character\",\n\t\ttip:\"Matches a {{getChar()}} character (char code {{code}}).\"\n\t\t},\n\t\t{\n\t\tid:\"open\",\n\t\ttip:\"Indicates the start of a regular expression.\"\n\t\t},\n\t\t{\n\t\tid:\"close\",\n\t\ttip:\"Indicates the end of a regular expression and the start of expression flags.\"\n\t\t},\n\t\t{\n\t\tid:\"condition\",\n\t\ttip:\"The lookaround to match in resolving the enclosing conditional statement. See 'conditional' in the Reference for info.\"\n\t\t},\n\t\t{\n\t\tid:\"conditionalelse\",\n\t\tlabel:\"conditional else\",\n\t\ttip:\"Delimits the 'else' portion of the conditional.\"\n\t\t},\n\t\t{\n\t\tid:\"ERROR\",\n\t\ttip:\"Errors in the expression are underlined in red. Roll over errors for more info.\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_INTERNAL_ERROR\",\n\t\ttip:\"Internal PCRE error\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BACKTRACK_LIMIT_ERROR\",\n\t\ttip:\"Backtrack limit was exhausted.\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_RECURSION_LIMIT_ERROR\",\n\t\ttip:\"Recursion limit was exhausted\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BAD_UTF8_ERROR\",\n\t\ttip:\"Malformed UTF-8 data\"\n\t\t},\n\t\t{\n\t\tid:\"PREG_BAD_UTF8_OFFSET_ERROR\",\n\t\ttip:\"Malformed UTF-8 data\"\n\t\t}\n\t]\n};\n\no.errors = {\n\tgroupopen:\"Unmatched opening parenthesis.\",\n\tgroupclose:\"Unmatched closing parenthesis.\",\n\tsetopen:\"Unmatched opening square bracket.\",\n\trangerev:\"Range values reversed. Start char code is greater than end char code.\",\n\tquanttarg:\"Invalid target for quantifier.\",\n\tquantrev:\"Quantifier minimum is greater than maximum.\",\n\tesccharopen:\"Dangling backslash.\",\n\tesccharbad:\"Unrecognized or malformed escape character.\",\n\tunicodebad:\"Unrecognized unicode category or script.\",\n\tposixcharclassbad:\"Unrecognized POSIX character class.\",\n\tposixcharclassnoset:\"POSIX character class must be in a character set.\",\n\tnotsupported:\"The \\\"{{~getLabel()}}\\\" feature is not supported in this flavor of RegEx.\",\n\tfwdslash:\"Unescaped forward slash. This may cause issues if copying/pasting this expression into code.\",\n\tesccharbad:\"Invalid escape sequence.\",\n\tservercomm:\"An error occurred while communicating with the server.\",\n\textraelse:\"Extra else in conditional group.\",\n\tunmatchedref:\"Reference to non-existent group \\\"{{name}}\\\".\",\n\tmodebad:\"Unrecognized mode flag \\\"{{errmode}}
\\\".\",\n\tbadname:\"Group name can not start with a digit.\",\n\tdupname:\"Duplicate group name.\",\n\tbranchreseterr:\"Branch Reset. Results will be ok, but RegExr's parser does not number branch reset groups correctly. Coming soon!\",\n\ttimeout:\"The expression took longer than 250ms to execute.\", // TODO: can we couple this to the help content somehow?\n\n\t// warnings:\n\tjsfuture:\"The \\\"{{~getLabel()}}\\\" feature may not be supported in all browsers.\",\n\tinfinite:\"The expression can return empty matches, and may match infinitely in some use cases.\", // TODO: can we couple this to the help content somehow?\n};\n\n/*\nclasses:\nquant\nset\nspecial\nref\nesc\nanchor\ncharclass\ngroup\ncomment\n */\n","/*\nRegExr: Learn, Build, & Test RegEx\nCopyright (C) 2017 gskinner.com, inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see